Commit | Line | Data |
---|---|---|
497c491b JM |
1 | #!/bin/sh |
2 | ||
3 | # A Poor(but Free)'s Man dtrace | |
4 | # | |
5 | # Copyright (C) 2014, 2015 Free Software Foundation, Inc. | |
6 | # | |
7 | # Contributed by Oracle, Inc. | |
8 | # | |
9 | # This file is part of GDB. | |
10 | # | |
11 | # This program is free software; you can redistribute it and/or modify | |
12 | # it under the terms of the GNU General Public License as published by | |
13 | # the Free Software Foundation; either version 3 of the License, or | |
14 | # (at your option) any later version. | |
15 | # | |
16 | # This program is distributed in the hope that it will be useful, but | |
17 | # WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
19 | # General Public License for more details. | |
20 | # | |
21 | # You should have received a copy of the GNU General Public License | |
22 | # along with this program. If not, see | |
23 | # <http://www.gnu.org/licenses/>. | |
24 | ||
25 | # DISCLAIMER DISCLAIMER DISCLAIMER | |
26 | # This script is a test tool. As such it is in no way intended to | |
27 | # replace the "real" dtrace command for any practical purpose, apart | |
28 | # from testing the DTrace USDT probes support in GDB. | |
29 | ||
30 | # that said... | |
31 | # | |
32 | # pdtrace is a limited dtrace program, implementing a subset of its | |
33 | # functionality: | |
34 | # | |
35 | # - The generation of an ELF file containing an embedded dtrace | |
36 | # program. Equivalent to dtrace -G. | |
37 | # | |
38 | # - The generation of a header file with definitions for static | |
39 | # probes. Equivalent to dtrace -h. | |
40 | # | |
41 | # This allows to generate DTrace static probes without having to use | |
42 | # the user-level DTrace components. The generated objects are 100% | |
43 | # compatible with DTrace and can be traced by the dtrace kernel module | |
44 | # like if they were generated by dtrace. | |
45 | # | |
46 | # Some of the known limitations of this implementation are: | |
47 | # - The input d-script must describe one provider, and only one. | |
48 | # - The "probe " directives in the d-file must not include argument | |
49 | # names, just the types. Thus something like `char *' is valid, but | |
50 | # `char *name' is not. | |
51 | # - The command line options must precede other arguments, since the | |
52 | # script uses the (more) portable getopts. | |
53 | # - Each probe header in the d-script must be contained in | |
54 | # a single line. | |
55 | # - strip -K removes the debugging information from the input object | |
56 | # file. | |
57 | # - The supported target platforms are i[3456]86 and x86_64. | |
58 | # | |
59 | # Please keep this code as portable as possible. Restrict yourself to | |
60 | # POSIX sh. | |
61 | ||
62 | # This script uses the following external programs, defined in | |
63 | # variables. Some of them are substituted by autoconf. | |
64 | ||
65 | TR=tr | |
66 | NM=@NM_TRANSFORM_NAME@ | |
67 | EGREP=egrep | |
68 | SED=sed | |
69 | CUT=cut | |
70 | READELF=@READELF_TRANSFORM_NAME@ | |
71 | SORT=sort | |
72 | EXPR=expr | |
73 | WC=wc | |
74 | UNIQ=uniq | |
75 | HEAD=head | |
76 | SEQ=seq | |
77 | AS=@GAS_TRANSFORM_NAME@ | |
78 | STRIP=@STRIP_TRANSFORM_NAME@ | |
79 | TRUE=true | |
80 | ||
81 | # Sizes for several DOF structures, in bytes. | |
82 | # | |
83 | # See linux/dtrace/dof.h for the definition of the referred | |
84 | # structures. | |
85 | ||
86 | dof_hdrsize=64 # sizeof(dtrace_dof_hdr) | |
87 | dof_secsize=32 # sizeof(dtrace_dof_sect) | |
88 | dof_probesize=48 # sizeof(dtrace_dof_probe) | |
89 | dof_providersize=44 # sizeof(dtrace_dof_provider) | |
90 | ||
91 | # Types for the several DOF sections. | |
92 | # | |
93 | # See linux/dtrace/dof_defines.h for a complete list of section types | |
94 | # along with their values. | |
95 | ||
96 | dof_sect_type_strtab=8 | |
97 | dof_sect_type_provider=15 | |
98 | dof_sect_type_probes=16 | |
99 | dof_sect_type_prargs=17 | |
100 | dof_sect_type_proffs=18 | |
101 | dof_sect_type_prenoffs=26 | |
102 | ||
103 | ### Functions | |
104 | ||
105 | # Write a message to the standard error output and exit with an error | |
106 | # status. | |
107 | # | |
108 | # Arguments: | |
109 | # $1 error message. | |
110 | ||
111 | f_panic() | |
112 | { | |
113 | echo "error: $1" 1>&2; exit 1 | |
114 | } | |
115 | ||
116 | # Write a usage message to the standard output and exit with an error | |
117 | # status. | |
118 | ||
119 | f_usage() | |
120 | { | |
121 | printf "Usage: pdtrace [-32|-64] [-GhV] [-o output] [-s script] [ args ... ]\n\n" | |
122 | ||
123 | printf "\t-32 generate 32-bit ELF files\n" | |
124 | printf "\t-64 generate 64-bit ELF files\n\n" | |
125 | ||
126 | printf "\t-G generate an ELF file containing embedded dtrace program\n" | |
127 | printf "\t-h generate a header file with definitions for static probes\n" | |
128 | printf "\t-o set output file\n" | |
129 | printf "\t-s handle probes according to the specified D script\n" | |
130 | printf "\t-V report the DTrace API version implemented by the tool\n" | |
131 | exit 2 | |
132 | } | |
133 | ||
134 | # Write a version message to the standard output and exit with a | |
135 | # successful status. | |
136 | ||
137 | f_version() | |
138 | { | |
139 | echo "pdtrace: Sun D 1.6.3" | |
140 | exit | |
141 | } | |
142 | ||
143 | # Add a new record to a list and return it. | |
144 | # | |
145 | # Arguments: | |
146 | # $1 is the list. | |
147 | # $2 is the new record | |
148 | ||
149 | f_add_record() | |
150 | { | |
151 | rec=$1 | |
152 | test -n "$rec" && \ | |
153 | { rec=$(printf %s\\n "$rec"; echo x); rec=${rec%x}; } | |
154 | printf %s "$rec$2" | |
155 | } | |
156 | ||
157 | # Collect the providers and probes information from the input object | |
158 | # file. | |
159 | # | |
160 | # This function sets the values of the following global variables. | |
161 | # The values are structured in records, each record in a line. The | |
162 | # fields of each record are separated in some cases by white | |
163 | # characters and in other cases by colon (:) characters. | |
164 | # | |
165 | # The type codes in the line format descriptors are: | |
166 | # S: string, D: decimal number | |
167 | # | |
168 | # probes | |
169 | # Regular probes and is-enabled probes. | |
170 | # TYPE(S) PROVIDER(S) NAME(S) OFFSET(D) BASE(D) BASE_SYM(S) | |
171 | # base_probes | |
172 | # Base probes, i.e. probes sharing provider, name and container. | |
173 | # PROVIDER(S) NAME(S) BASE(D) BASE_SYM(S) | |
174 | # providers | |
175 | # List of providers. | |
176 | # PROVIDER(S) | |
177 | # All the offsets are expressed in bytes. | |
178 | # | |
179 | # Input globals: | |
180 | # objfile | |
181 | # Output globals: | |
182 | # probes, base_probes, providers | |
183 | ||
184 | probes= | |
185 | base_probes= | |
186 | providers= | |
187 | probes_args= | |
188 | ||
189 | f_collect_probes() | |
190 | { | |
191 | # Probe points are function calls to undefined functions featuring | |
192 | # distinct names for both normal probes and is-enabled probes. | |
193 | PROBE_REGEX="(__dtrace_([a-zA-Z_]+)___([a-zA-Z_]+))" | |
194 | EPROBE_REGEX="(__dtraceenabled_([a-zA-Z_]+)___([a-zA-Z_]+))" | |
195 | ||
196 | while read type symbol provider name; do | |
197 | test -z "$type" && f_panic "No probe points found in $objfile" | |
198 | ||
199 | provider=$(printf %s $provider | $TR -s _) | |
200 | name=$(printf %s $name | $TR -s _) | |
201 | ||
202 | # Search the object file for relocations defined for the | |
203 | # probe symbols. Then calculate the base address of the | |
204 | # probe (along with the symbol associated with that base | |
205 | # address) and the offset of the probe point. | |
206 | for offset in $($READELF -W -r $objfile | $EGREP $symbol | $CUT -d' ' -f1) | |
207 | do | |
208 | # Figure out the base address for the probe. This is | |
209 | # done finding the function name in the text section of | |
210 | # the object file located above the probed point. But | |
211 | # note that the relocation is for the address operand of | |
212 | # the call instruction, so we have to subtract 1 to find | |
213 | # the real probed point. | |
214 | offset=$((0x$offset - 1)) | |
215 | ||
216 | # The addresses of is-enabled probes must point to the | |
217 | # first NOP instruction in their patched instructions | |
218 | # sequences, so modify them (see f_patch_objfile for the | |
219 | # instruction sequences). | |
220 | if test "$type" = "e"; then | |
221 | if test "$objbits" -eq "32"; then | |
222 | offset=$((offset + 2)) | |
223 | else # 64 bits | |
224 | offset=$((offset + 3)) | |
225 | fi | |
226 | fi | |
227 | ||
228 | # Determine the base address of the probe and its | |
229 | # corresponding function name. | |
230 | funcs=$($NM -td $objfile | $EGREP "^[0-9]+ T " \ | |
231 | | $CUT -d' ' -f1,3 | $SORT -n -r | $TR ' ' :) | |
232 | for fun in $funcs; do | |
233 | func_off=$(printf %s $fun | $CUT -d: -f1) | |
234 | func_sym=$(printf %s $fun | $CUT -d: -f2) | |
235 | # Note that `expr' is used to remove leading zeros | |
236 | # to avoid FUNC_OFF to be interpreted as an octal | |
237 | # number in arithmetic contexts. | |
238 | test "$func_off" -le "$offset" && \ | |
239 | { base=$($EXPR $func_off + 0); break; } | |
240 | done | |
241 | test -n "$base" || \ | |
242 | f_panic "could not find base address for probe at $objfile($o)" | |
243 | ||
244 | # Emit the record for the probe. | |
245 | probes=$(f_add_record "$probes" \ | |
246 | "$type $provider $name $(($offset - $base)) $base $func_sym") | |
247 | done | |
248 | done <<EOF | |
249 | $($NM $objfile | $EGREP " U $PROBE_REGEX" \ | |
250 | | $SED -E -e "s/.*$PROBE_REGEX.*/p \1 \2 \3/"; | |
251 | $NM $objfile | $EGREP " U $EPROBE_REGEX" \ | |
252 | | $SED -E -e "s/.*$EPROBE_REGEX.*/e \1 \2 \3/") | |
253 | EOF | |
254 | ||
255 | # Build the list of providers and of base probes from the probes. | |
256 | while read type provider name offset base base_sym; do | |
257 | providers=$(f_add_record "$providers" "$provider") | |
258 | base_probes=$(f_add_record "$base_probes" "$provider $name $base $base_sym") | |
259 | done <<EOF | |
260 | $probes | |
261 | EOF | |
262 | providers=$(printf %s\\n "$providers" | $SORT | $UNIQ) | |
263 | base_probes=$(printf %s\\n "$base_probes" | $SORT | $UNIQ) | |
264 | } | |
265 | ||
266 | # Collect the argument counts and type strings for all the probes | |
267 | # described in the `probes' global variable. This is done by | |
268 | # inspecting the d-script file provided by the user. | |
269 | # | |
270 | # This function sets the values of the following global variables. | |
271 | # The values are structured in records, each record in a line. The | |
272 | # fields of each record are separated in some cases by white | |
273 | # characters and in other cases by colon (:) characters. | |
274 | # | |
275 | # The type codes in the line format descriptors are: | |
276 | # S: string, D: decimal number | |
277 | # | |
278 | # probes_args | |
279 | # Probes arguments. | |
280 | # PROVIDER(S):NAME(S):NARGS(D):ARG1(S):ARG2(S):...:ARGn(S) | |
281 | # | |
282 | # Input globals: | |
283 | # probes | |
284 | # Output globals: | |
285 | # probes_args | |
286 | # Arguments: | |
287 | # $1 is the d-script file from which to extract the arguments | |
288 | # information. | |
289 | ||
290 | f_collect_probes_args() | |
291 | { | |
292 | dscript=$1 | |
293 | while read type provider name offset base base_sym; do | |
294 | # Process normal probes only. Is-enabled probes are not | |
295 | # described in the d-script file and they don't receive any | |
296 | # argument. | |
297 | test "$type" = "p" || continue | |
298 | ||
299 | # Names are mangled in d-script files to make it possible to | |
300 | # have underscore characters as part of the provider name and | |
301 | # probe name. | |
302 | m_provider=$(printf %s $provider | $SED -e 's/_/__/g') | |
303 | m_name=$(printf %s $name | $SED -e 's/_/__/g') | |
304 | ||
305 | # Ignore this probe if the d-script file does not describe its | |
306 | # provider. | |
307 | $EGREP -q "provider +$m_provider" $dscript || continue | |
308 | ||
309 | # Look for the line containing the description of the probe. | |
310 | # If we can't find it then ignore this probe. | |
311 | line=$($EGREP "^ *probe +$m_name *\(.*\);" $dscript) | |
312 | test -n "$line" || continue | |
313 | ||
314 | # Ok, extract the argument types from the probe prototype. | |
315 | # This is fragile as hell as it requires the prototype to be | |
316 | # in a single line. | |
317 | args=""; nargs=0; line=$(printf %s "$line" | $SED -e 's/.*(\(.*\)).*/\1/') | |
318 | set -f; IFS=, | |
319 | for arg in $line; do | |
320 | args="$args:$arg" | |
321 | nargs=$((nargs + 1)) | |
322 | done | |
323 | set +f; unset IFS | |
324 | ||
325 | # Emit the record for the probe arguments. | |
326 | probes_args=$(f_add_record "$probes_args" "$provider:$name:$nargs$args") | |
327 | done <<EOF | |
328 | $probes | |
329 | EOF | |
330 | } | |
331 | ||
332 | # Functions to manipulate the global BCOUNT. | |
333 | ||
334 | BCOUNT=0 | |
335 | ||
336 | f_incr_bcount() | |
337 | { | |
338 | BCOUNT=$((BCOUNT + $1)) | |
339 | } | |
340 | ||
341 | f_align_bcount() | |
342 | { | |
343 | test $((BCOUNT % $1)) -eq 0 || BCOUNT=$((BCOUNT + ($1 - (BCOUNT % $1)))) | |
344 | } | |
345 | ||
346 | # Generate a line of assembly code and add it to the asmprogram global | |
347 | # variable. | |
348 | # | |
349 | # Arguments: | |
350 | # $1 string to generate in a line. | |
351 | ||
352 | asmprogram= | |
353 | ||
354 | f_gen_asm() | |
355 | { | |
356 | line=$(printf "\t$1") | |
357 | asmprogram=$(f_add_record "$asmprogram" "$line") | |
358 | } | |
359 | ||
360 | # Helper function to generate the assembly code of a DOF section | |
361 | # header. | |
362 | # | |
363 | # This function is used by `f_gen_dof_program'. | |
364 | # | |
365 | # Arguments: | |
366 | # $1 is the name of the described section. | |
367 | # $2 is the type of the described section. | |
368 | # $3 is the alignment of the described section. | |
369 | # $4 is the number of entities stored in the described section. | |
370 | # $5 is the offset in the DOF program of the described section. | |
371 | # $6 is the size of the described section, in bytes. | |
372 | ||
373 | f_gen_dof_sect_header() | |
374 | { | |
375 | f_gen_asm "" | |
376 | f_gen_asm "/* dtrace_dof_sect for the $1 section. */" | |
377 | f_gen_asm ".balign 8" | |
378 | f_gen_asm ".4byte $2\t/* uint32_t dofs_type */" | |
379 | f_gen_asm ".4byte $3\t/* uint32_t dofs_align */" | |
380 | # The DOF_SECF_LOAD flag is 1 => loadable section. | |
381 | f_gen_asm ".4byte 1\t/* uint32_t dofs_flags */" | |
382 | f_gen_asm ".4byte $4\t/* uint32_t dofs_entsize */" | |
383 | f_gen_asm ".8byte $5\t/* uint64_t dofs_offset */" | |
384 | f_gen_asm ".8byte $6\t/* uint64_t dofs_size */" | |
385 | } | |
386 | ||
387 | # Generate a DOF program and assembly it in the output file. | |
388 | # | |
389 | # The DOF program generated by this function has the following | |
390 | # structure: | |
391 | # | |
392 | # HEADER | |
393 | # STRTAB OFFTAB EOFFTAB [PROBES PROVIDER]... | |
394 | # STRTAB_SECT OFFTAB_SECT EOFFTAB_SECT ARGTAB_SECT [PROBES_SECT PROVIDER_SECT]... | |
395 | # | |
396 | # Input globals: | |
397 | # probes, base_probes, providers, probes_args, BCOUNT | |
398 | ||
399 | f_gen_dof_program() | |
400 | { | |
401 | ###### Variables used to cache information needed later. | |
402 | ||
403 | # Number of section headers in the generated DOF program. | |
404 | dof_secnum=0 | |
405 | # Offset of section headers in the generated DOF program, in bytes. | |
406 | dof_secoff=0 | |
407 | ||
408 | # Sizes of the STRTAB, OFFTAB and EOFFTAB sections, in bytes. | |
409 | strtab_size=0 | |
410 | offtab_size=0 | |
411 | eofftab_size=0 | |
412 | ||
413 | # Offsets of the STRTAB, OFFTAB EOFFTAB and PROBES sections in the | |
414 | # generated DOF program. In bytes. | |
415 | strtab_offset=0 | |
416 | offtab_offset=0 | |
417 | eofftab_offset=0 | |
418 | argtab_offset=0 | |
419 | probes_offset=0 | |
420 | ||
421 | # Indexes of the section headers of the STRTAB, OFFTAB, EOFFTAB and | |
422 | # PROBES sections in the sections array. | |
423 | strtab_sect_index=0 | |
424 | offtab_sect_index=0 | |
425 | eofftab_sect_index=0 | |
426 | argtab_sect_index=0 | |
427 | probes_sect_index=0 | |
428 | ||
429 | # First offsets and eoffsets of the base-probes. | |
430 | # Lines: PROVIDER(S) NAME(S) BASE(D) (DOF_OFFSET(D)|DOF_EOFFSET(D)) | |
431 | probes_dof_offsets= | |
432 | probes_dof_eoffsets= | |
433 | ||
434 | # Offsets in the STRTAB section for the first type of base probes. | |
435 | # Record per line: PROVIDER(S) NAME(S) BASE(D) OFFSET(D) | |
436 | probes_dof_types= | |
437 | ||
438 | ||
439 | # Offsets of the provider names in the provider's STRTAB section. | |
440 | # Lines: PROVIDER(S) OFFSET(D) | |
441 | providers_dof_names= | |
442 | ||
443 | # Offsets of the base-probe names in the provider's STRTAB section. | |
444 | # Lines: PROVIDER(S) NAME(S) BASE(D) OFFSET(D) | |
445 | probes_dof_names= | |
446 | ||
447 | # Offsets of the provider sections in the DOF program. | |
448 | # Lines: PROVIDER(S) OFFSET(D) | |
449 | providers_offsets= | |
450 | ||
451 | ###### Generation phase. | |
452 | ||
453 | # The header of the DOF program contains a `struct | |
454 | # dtrace_dof_hdr'. Record its size, but it is written at the end | |
455 | # of the function. | |
456 | f_incr_bcount $dof_hdrsize; f_align_bcount 8 | |
457 | ||
458 | # The STRTAB section immediately follows the header. It contains | |
459 | # the following set of packed null-terminated strings: | |
460 | # | |
461 | # [PROVIDER [BASE_PROBE_NAME [BASE_PROBE_ARG_TYPE...]]...]... | |
462 | strtab_offset=$BCOUNT | |
463 | strtab_sect_index=$dof_secnum | |
464 | dof_secnum=$((dof_secnum + 1)) | |
465 | f_gen_asm "" | |
466 | f_gen_asm "/* The STRTAB section. */" | |
467 | f_gen_asm ".balign 8" | |
468 | # Add the provider names. | |
469 | off=0 | |
470 | while read provider; do | |
471 | strtab_size=$(($strtab_size + ${#prov} + 1)) | |
472 | # Note the funny mangling... | |
473 | f_gen_asm ".asciz \"$(printf %s $provider | $TR _ -)\"" | |
474 | providers_dof_names=$(f_add_record "$providers_dof_names" \ | |
475 | "$provider $off") | |
476 | off=$(($off + ${#provider} + 1)) | |
477 | ||
478 | # Add the base-probe names. | |
479 | while read p_provider name base base_sym; do | |
480 | test "$p_provider" = "$provider" || continue | |
481 | # And yes, more funny mangling... | |
482 | f_gen_asm ".asciz \"$(printf %s $name | $TR _ -)\"" | |
483 | probes_dof_names=$(f_add_record "$probes_dof_names" \ | |
484 | "$p_provider $name $base $off") | |
485 | off=$(($off + ${#name} + 1)) | |
486 | while read args; do | |
487 | a_provider=$(printf %s "$args" | $CUT -d: -f1) | |
488 | a_name=$(printf %s "$args" | $CUT -d: -f2) | |
489 | test "$a_provider" = "$p_provider" \ | |
490 | && test "$a_name" = "$name" \ | |
491 | || continue | |
492 | ||
493 | probes_dof_types=$(f_add_record "$probes_dof_types" \ | |
494 | "$a_provider $name $base $off") | |
495 | nargs=$(printf %s "$args" | $CUT -d: -f3) | |
496 | for n in $($SEQ $nargs); do | |
497 | arg=$(printf %s "$args" | $CUT -d: -f$(($n + 3))) | |
498 | f_gen_asm ".asciz \"${arg}\"" | |
499 | off=$(($off + ${#arg} + 1)) | |
500 | done | |
501 | done <<EOF | |
502 | $probes_args | |
503 | EOF | |
504 | done <<EOF | |
505 | $base_probes | |
506 | EOF | |
507 | done <<EOF | |
508 | $providers | |
509 | EOF | |
510 | strtab_size=$off | |
511 | f_incr_bcount $strtab_size; f_align_bcount 8 | |
512 | ||
513 | # The OFFTAB section contains a set of 32bit words, one per | |
514 | # defined regular probe. | |
515 | offtab_offset=$BCOUNT | |
516 | offtab_sect_index=$dof_secnum | |
517 | dof_secnum=$((dof_secnum + 1)) | |
518 | f_gen_asm "" | |
519 | f_gen_asm "/* The OFFTAB section. */" | |
520 | f_gen_asm ".balign 8" | |
521 | off=0 | |
522 | while read type provider name offset base base_sym; do | |
523 | test "$type" = "p" || continue | |
524 | f_gen_asm ".4byte $offset\t/* probe ${provider}:${name} */" | |
525 | probes_dof_offsets=$(f_add_record "$probes_dof_offsets" \ | |
526 | "$provider $name $base $off") | |
527 | off=$(($off + 4)) | |
528 | done <<EOF | |
529 | $probes | |
530 | EOF | |
531 | offtab_size=$off | |
532 | f_incr_bcount $offtab_size; f_align_bcount 8 | |
533 | ||
534 | # The EOFFTAB section contains a set of 32bit words, one per | |
535 | # defined is-enabled probe. | |
536 | eofftab_offset=$BCOUNT | |
537 | eofftab_sect_index=$dof_secnum | |
538 | dof_secnum=$((dof_secnum + 1)) | |
539 | f_gen_asm "" | |
540 | f_gen_asm "/* The EOFFTAB section. */" | |
541 | f_gen_asm ".balign 8" | |
542 | off=0 | |
543 | while read type provider name offset base base_sym; do | |
544 | test "$type" = "e" || continue | |
545 | f_gen_asm ".4byte $offset\t/* is-enabled probe ${provider}:${name} */" | |
546 | probes_dof_eoffsets=$(f_add_record "$probes_dof_eoffsets" \ | |
547 | "$provider $name $base $off") | |
548 | off=$(($off + 4)) | |
549 | done <<EOF | |
550 | $probes | |
551 | EOF | |
552 | eofftab_size=$off | |
553 | f_incr_bcount $eofftab_size; f_align_bcount 8 | |
554 | ||
555 | # The ARGTAB section is empty, but nonetheless has a section | |
556 | # header, so record its section index here. | |
557 | argtab_offset=0 | |
558 | argtab_sect_index=$dof_secnum | |
559 | dof_secnum=$((dof_secnum + 1)) | |
560 | ||
561 | # Generate a pair of sections PROBES and PROVIDER for each | |
562 | # provider. | |
563 | while read prov; do | |
564 | # The PROBES section contains an array of `struct | |
565 | # dtrace_dof_probe'. | |
566 | # | |
567 | # A `dtrace_dof_probe' entry characterizes the collection of | |
568 | # probes and is-enabled probes sharing the same provider, name and | |
569 | # base address. | |
570 | probes_sect_index=$dof_secnum | |
571 | dof_secnum=$((dof_secnum + 1)) | |
572 | probes_offset=$BCOUNT | |
573 | num_base_probes=$(printf %s\\n "$base_probes" | $WC -l) | |
574 | while read provider name base base_sym; do | |
575 | name_offset=$(printf %s\\n "$probes_dof_names" \ | |
576 | | $EGREP "^$provider $name " | $CUT -d' ' -f4) | |
577 | ||
578 | num_offsets=$(printf %s\\n "$probes_dof_offsets" \ | |
579 | | $EGREP "^$provider $name [0-9]+ " | $WC -l) | |
580 | ||
581 | first_offset=0 | |
582 | test "$num_offsets" -gt 0 && \ | |
583 | first_offset=$(printf %s\\n "$probes_dof_offsets" \ | |
584 | | $EGREP "^$provider $name " | $CUT -d' ' -f4 | $HEAD -1) | |
585 | ||
586 | num_eoffsets=$(printf %s\\n "$probes_dof_eoffsets" \ | |
587 | | $EGREP "^$provider $name [0-9]+ " | $WC -l) | |
588 | first_eoffset=0 | |
589 | test "$num_eoffsets" -gt 0 && \ | |
590 | first_eoffset=$(printf %s "$probes_dof_eoffsets" \ | |
591 | | $EGREP "^$provider $name " | $CUT -d' ' -f4 | $HEAD -1) | |
592 | ||
593 | num_args=$(printf %s "$probes_args" \ | |
594 | | $EGREP "^$provider:$name:" | $CUT -d: -f3 | $HEAD -1) | |
595 | ||
596 | first_type=$(printf %s "$probes_dof_types" \ | |
597 | | $EGREP "^$provider $name $base " | $CUT -d' ' -f4 | $HEAD -1) | |
598 | ||
599 | reloctype=R_X86_64_GLOB_DAT | |
600 | test "$objbits" = "32" && reloctype=R_386_32 | |
601 | ||
602 | f_gen_asm "" | |
603 | f_gen_asm "/* dtrace_dof_probe for ${provider}:${name} at ${base_sym} */" | |
604 | f_gen_asm ".balign 8" | |
605 | f_gen_asm ".reloc ., $reloctype, $base_sym + 0" | |
606 | f_gen_asm ".8byte ${base}\t/* uint64_t dofpr_addr */" | |
607 | f_gen_asm ".4byte 0\t/* uint32_t dofpr_func */" | |
608 | f_gen_asm ".4byte $name_offset\t/* uint32_t dofpr_name */" | |
609 | f_gen_asm ".4byte $first_type\t/* uint32_t dofpr_nargv */" | |
610 | f_gen_asm ".4byte 0\t/* uint32_t dofpr_xargv */" | |
611 | f_gen_asm ".4byte 0\t/* uint32_t dofpr_argidx */" | |
612 | f_gen_asm ".4byte $(($first_offset/4))\t/* uint32_t dofpr_offidx */" | |
613 | f_gen_asm ".byte $num_args\t/* uint8_t dofpr_nargc */" | |
614 | f_gen_asm ".byte 0\t/* uint8_t dofpr_xargc */" | |
615 | f_gen_asm ".2byte $num_offsets\t/* uint16_t dofpr_noffs */" | |
616 | f_gen_asm ".4byte $(($first_eoffset/4))\t/* uint32_t dofpr_enoffidx */" | |
617 | f_gen_asm ".2byte $num_eoffsets\t/* uint16_t dofpr_nenoffs */" | |
618 | f_gen_asm ".2byte 0\t/* uint16_t dofpr_pad1 */" | |
619 | f_gen_asm ".4byte 0\t/* uint16_t dofpr_pad2 */" | |
620 | ||
621 | f_incr_bcount "$dof_probesize" | |
622 | done <<EOF | |
623 | $base_probes | |
624 | EOF | |
625 | ||
626 | # The PROVIDER section contains a `struct dtrace_dof_provider' | |
627 | # instance describing the provider for the probes above. | |
628 | dof_secnum=$((dof_secnum + 1)) | |
629 | providers_offsets=$(f_add_record "$providers_offsets" \ | |
630 | "$prov $BCOUNT") | |
631 | # The dtrace_dof_provider. | |
632 | provider_name_offset=$(printf %s "$providers_dof_names" \ | |
633 | | $EGREP "^$prov " | $CUT -d' ' -f2) | |
634 | ||
635 | f_gen_asm "" | |
636 | f_gen_asm "/* dtrace_dof_provider for $prov */" | |
637 | f_gen_asm ".balign 8" | |
638 | # Links to several DOF sections. | |
639 | f_gen_asm ".4byte $strtab_sect_index\t/* uint32_t dofpv_strtab */" | |
640 | f_gen_asm ".4byte $probes_sect_index\t/* uint32_t dofpv_probes */" | |
641 | f_gen_asm ".4byte $argtab_sect_index\t/* uint32_t dofpv_prargs */" | |
642 | f_gen_asm ".4byte $offtab_sect_index\t/* uint32_t dofpv_proffs */" | |
643 | # Offset of the provider name into the STRTAB section. | |
644 | f_gen_asm ".4byte $provider_name_offset\t/* uint32_t dofpv_name */" | |
645 | # The rest of fields can be 0 for our modest purposes :) | |
646 | f_gen_asm ".4byte 0\t/* uint32_t dofpv_provattr */" | |
647 | f_gen_asm ".4byte 0\t/* uint32_t dofpv_modattr */" | |
648 | f_gen_asm ".4byte 0\t/* uint32_t dofpv_funcattr */" | |
649 | f_gen_asm ".4byte 0\t/* uint32_t dofpv_nameattr */" | |
650 | f_gen_asm ".4byte 0\t/* uint32_t dofpv_argsattr */" | |
651 | # But not this one, of course... | |
652 | f_gen_asm ".4byte $eofftab_sect_index\t/* uint32_t dofpv_prenoffs */" | |
653 | ||
654 | f_incr_bcount $dof_providersize | |
655 | done<<EOF | |
656 | $providers | |
657 | EOF | |
658 | f_align_bcount 8 | |
659 | ||
660 | # The section headers follow, one per section defined above. | |
661 | dof_secoff=$BCOUNT | |
662 | ||
663 | f_gen_dof_sect_header STRTAB \ | |
664 | $dof_sect_type_strtab \ | |
665 | 1 1 $strtab_offset $strtab_size | |
666 | f_incr_bcount $dof_secsize; f_align_bcount 8 | |
667 | ||
668 | f_gen_dof_sect_header OFFTAB \ | |
669 | $dof_sect_type_proffs \ | |
670 | 4 4 $offtab_offset $offtab_size | |
671 | f_incr_bcount $dof_secsize; f_align_bcount 8 | |
672 | ||
673 | f_gen_dof_sect_header EOFFTAB \ | |
674 | $dof_sect_type_prenoffs \ | |
675 | 4 4 $eofftab_offset $eofftab_size | |
676 | f_incr_bcount $dof_secsize; f_align_bcount 8 | |
677 | ||
678 | f_gen_dof_sect_header ARGTAB \ | |
679 | $dof_sect_type_prargs \ | |
680 | 4 1 $argtab_offset 0 | |
681 | f_incr_bcount $dof_secsize; f_align_bcount 8 | |
682 | ||
683 | while read provider; do | |
684 | provider_offset=$(printf %s "$providers_offsets" \ | |
685 | | $EGREP "^$provider " | $CUT -d' ' -f2) | |
686 | num_base_probes=$(printf %s\\n "$base_probes" | $WC -l) | |
687 | ||
688 | f_gen_dof_sect_header "$provider probes" \ | |
689 | $dof_sect_type_probes \ | |
690 | 8 $dof_probesize $probes_offset \ | |
691 | $((num_base_probes * dof_probesize)) | |
692 | f_incr_bcount $dof_secsize; f_align_bcount 8 | |
693 | ||
694 | f_gen_dof_sect_header "$provider provider" \ | |
695 | $dof_sect_type_provider \ | |
696 | 8 1 $provider_offset $dof_providersize | |
697 | f_incr_bcount $dof_secsize; f_align_bcount 8 | |
698 | done <<EOF | |
699 | $providers | |
700 | EOF | |
701 | ||
702 | # Finally, cook the header. | |
703 | asmbody="$asmprogram" | |
704 | asmprogram="" | |
705 | f_gen_asm "/* File generated by pdtrace. */" | |
706 | f_gen_asm "" | |
707 | ||
708 | f_gen_asm ".section .SUNW_dof,\"a\",\"progbits\"" | |
709 | f_gen_asm ".globl __SUNW_dof" | |
710 | f_gen_asm ".hidden __SUNW_dof" | |
711 | f_gen_asm ".size __SUNW_dof, ${BCOUNT}" | |
712 | f_gen_asm ".type __SUNW_dof, @object" | |
713 | f_gen_asm "__SUNW_dof:" | |
714 | ||
715 | f_gen_asm "" | |
716 | f_gen_asm "/* dtrace_dof_hdr */" | |
717 | f_gen_asm ".balign 8" | |
718 | f_gen_asm ".byte 0x7f, 'D, 'O, 'F\t/* dofh_ident[0..3] */" | |
719 | f_gen_asm ".byte 2\t\t/* model: 1=ILP32, 2=LP64 */" | |
720 | f_gen_asm ".byte 1\t\t/* encoding: 1: little-endian, 2: big-endian */" | |
721 | f_gen_asm ".byte 2\t\t/* DOF version: 1 or 2. Latest is 2 */" | |
722 | f_gen_asm ".byte 2\t\t/* DIF version: 1 or 2. Latest is 2 */" | |
723 | f_gen_asm ".byte 8\t\t/* number of DIF integer registers */" | |
724 | f_gen_asm ".byte 8\t\t/* number of DIF tuple registers */" | |
725 | f_gen_asm ".byte 0, 0\t\t/* dofh_ident[10..11] */" | |
726 | f_gen_asm ".4byte 0\t\t/* dofh_ident[12..15] */" | |
727 | f_gen_asm ".4byte 0\t/* uint32_t dofh_flags */" # See Limitations above. | |
728 | f_gen_asm ".4byte ${dof_hdrsize}\t/* uint32_t dofh_hdrsize */" | |
729 | f_gen_asm ".4byte ${dof_secsize}\t/* uint32_t dofh_secsize */" | |
730 | f_gen_asm ".4byte ${dof_secnum}\t/* uint32_t dofh_secnum */" | |
731 | f_gen_asm ".8byte ${dof_secoff}\t/* uint64_t dofh_secoff */" | |
732 | f_gen_asm ".8byte ${BCOUNT}\t/* uint64_t dofh_loadsz */" | |
733 | f_gen_asm ".8byte ${BCOUNT}\t/* uint64_t dofh_filesz */" | |
734 | f_gen_asm ".8byte 0\t/* uint64_t dofh_pad */" | |
735 | f_gen_asm "" | |
736 | ||
737 | # Ok, now assembly the program in OFILE | |
738 | echo "$asmprogram$asmbody" | $AS -$objbits -o $ofile | |
739 | ||
740 | # Next step is to change the sh_type of the ".SUNW_dof" section | |
741 | # headers to 0x6ffffff4 (SHT_SUNW_dof). | |
742 | # | |
743 | # Note that this code relies in the fact that readelf will list | |
744 | # the sections ordered in the same order than the section headers | |
745 | # in the section header table of the file. | |
746 | elfinfo=$($READELF -a $ofile) | |
747 | ||
748 | # Mind the endianness. | |
749 | if printf %s "$elfinfo" | $EGREP -q "little endian"; then | |
750 | sht_sunw_dof=$(printf %s%s%s%s \\364 \\377 \\377 \\157) | |
751 | else | |
752 | sht_sunw_dof=$(printf %s%s%s%s \\157 \\377 \\377 \\364) | |
753 | fi | |
754 | ||
755 | shdr_start=$(printf %s "$elfinfo" \ | |
756 | | $EGREP "^[ \t]*Start of section headers:" \ | |
757 | | $SED -E -e 's/.*headers:[ \t]*([0-9]+).*/\1/') | |
758 | test -n "$shdr_start" \ | |
759 | || f_panic "could not extract the start of shdr from $ofile" | |
760 | ||
761 | shdr_num_entries=$(printf %s "$elfinfo" \ | |
762 | | $EGREP "^[ \t]*Size of section headers:" \ | |
763 | | $SED -E -e 's/.*headers:[ \t]*([0-9]+).*/\1/') | |
764 | test -n "$shdr_num_entries" \ | |
765 | || f_panic "could not extract the number of shdr entries from $ofile" | |
766 | ||
767 | shdr_entry_size=$(printf %s "$elfinfo" \ | |
768 | | $EGREP "^[ \t]*Size of section headers:" \ | |
769 | | $SED -E -e 's/.*headers:[ \t]*([0-9]+).*/\1/') | |
770 | test -n "$shdr_entry_size" \ | |
771 | || f_panic "could not fetch the size of section headers from $ofile" | |
772 | ||
773 | while read line; do | |
774 | data=$(printf %s "$line" \ | |
775 | | $SED -E -e 's/.*\[(.*)\][ \t]+([a-zA-Z_.]+).*/\1:\2/') | |
776 | num=$(printf %s "$data" | $CUT -d: -f1) | |
777 | name=$(printf %s "$data" | $CUT -d: -f2) | |
778 | if test "$name" = ".SUNW_dof"; then | |
779 | # Patch the new sh_type in the proper entry of the section | |
780 | # header table. | |
781 | printf "$sht_sunw_dof" \ | |
782 | | dd of=$ofile conv=notrunc count=4 ibs=1 bs=1 \ | |
783 | seek=$((shdr_start + (shdr_entry_size * num) + 4)) \ | |
784 | 2> /dev/null | |
785 | break | |
786 | fi | |
787 | done <<EOF | |
788 | $(printf %s "$elfinfo" | $EGREP "^[ \t]*\[[0-9 ]+\].*[A-Z]+.*PROGBITS") | |
789 | EOF | |
790 | ||
791 | } | |
792 | ||
793 | # Patch the probed points in the given object file, replacing the | |
794 | # function calls with NOPs. | |
795 | # | |
796 | # The probed points in the input object files are function calls. | |
797 | # This function replaces these function calls by some other | |
798 | # instruction sequences. Which replacement to use depends on several | |
799 | # factors, as documented below. | |
800 | # | |
801 | # Arguments: | |
802 | # $1 is the object file to patch. | |
803 | ||
804 | f_patch_objfile() | |
805 | { | |
806 | objfile=$1 | |
807 | ||
808 | # Several x86_64 instruction opcodes, in octal. | |
809 | x86_op_nop=$(printf \\220) | |
810 | x86_op_ret=$(printf \\303) | |
811 | x86_op_call=$(printf \\350) | |
812 | x86_op_jmp32=$(printf \\351) | |
813 | x86_op_rex_rax=$(printf \\110) | |
814 | x86_op_xor_eax_0=$(printf \\063) | |
815 | x86_op_xor_eax_1=$(printf \\300) | |
816 | ||
817 | # Figure out the file offset of the text section in the object | |
818 | # file. | |
819 | text_off=0x$(objdump -j .text -h $objfile \ | |
820 | | grep \.text | $TR -s ' ' | $CUT -d' ' -f 7) | |
821 | ||
822 | while read type provider name offset base base_sym; do | |
823 | # Calculate the offset of the probed point in the object file. | |
824 | # Note that the `offset' of is-enabled probes is tweaked in | |
825 | # `f_collect_probes" to point ahead the patching point. | |
826 | probe_off=$((text_off + base + offset)) | |
827 | if test "$type" = "e"; then | |
828 | if test "$objbits" -eq "32"; then | |
829 | probe_off=$((probe_off - 2)) | |
830 | else # 64 bits | |
831 | probe_off=$((probe_off - 3)) | |
832 | fi | |
833 | fi | |
834 | ||
835 | # The probed point can be either a CALL instruction or a JMP | |
836 | # instruction (a tail call). This has an impact on the | |
837 | # patching sequence. Fetch the first byte at the probed point | |
838 | # and do the right thing. | |
839 | nopret="$x86_op_nop" | |
840 | byte=$(dd if=$objfile count=1 ibs=1 bs=1 skip=$probe_off 2> /dev/null) | |
841 | test "$byte" = "$x86_op_jmp32" && nopret="$x86_op_ret" | |
842 | ||
843 | # Determine the patching sequence. It depends on the type of | |
844 | # probe at hand (regular or is-enabled) and also if | |
845 | # manipulating a 32bit or 64bit binary. | |
846 | patchseq= | |
847 | case $type in | |
848 | p) patchseq=$(printf %s%s%s%s%s \ | |
849 | "$nopret" \ | |
850 | "$x86_op_nop" \ | |
851 | "$x86_op_nop" \ | |
852 | "$x86_op_nop" \ | |
853 | "$x86_op_nop") | |
854 | ;; | |
855 | e) test "$objbits" -eq 64 && \ | |
856 | patchseq=$(printf %s%s%s%s%s \ | |
857 | "$x86_op_rex_rax" \ | |
858 | "$x86_op_xor_eax_0" \ | |
859 | "$x86_op_xor_eax_1" \ | |
860 | "$nopret" \ | |
861 | "$x86_op_nop") | |
862 | test "$objbits" -eq 32 && \ | |
863 | patchseq=$(printf %s%s%s%s%s \ | |
864 | "$x86_op_xor_eax_0" \ | |
865 | "$x86_op_xor_eax_1" \ | |
866 | "$nopret" \ | |
867 | "$x86_op_nop" \ | |
868 | "$x86_op_nop") | |
869 | ;; | |
870 | *) f_panic "internal error: wrong probe type $type";; | |
871 | esac | |
872 | ||
873 | # Patch! | |
874 | printf %s "$patchseq" \ | |
875 | | dd of=$objfile conv=notrunc count=5 ibs=1 bs=1 seek=$probe_off 2> /dev/null | |
876 | done <<EOF | |
877 | $probes | |
878 | EOF | |
879 | ||
880 | # Finally, we have to remove the __dtrace_* and __dtraceenabled_* | |
881 | # symbols from the object file, along with their respective | |
882 | # relocations. | |
883 | # | |
884 | # Note that the most obvious call: | |
885 | # strip -v -N whatever -w foo.o | |
886 | # will not work: | |
887 | # strip: not stripping symbol `whatever' because it is named in a relocation | |
888 | # | |
889 | # Fortunately using `-K !whatever' instead tricks strip to do the | |
890 | # right thing, but this is black magic and may eventually stop | |
891 | # working... | |
892 | $STRIP -K '!__dtrace_*' -w $objfile | |
893 | $STRIP -K '!__dtraceenabled_*' -w $objfile | |
894 | } | |
895 | ||
896 | # Read the input .d file and print a header file with macros to | |
897 | # invoke the probes defined in it. | |
898 | ||
899 | f_gen_header_file() | |
900 | { | |
901 | guard=$(basename $ofile | $TR - _ | $CUT -d. -f1 | $TR a-z A-Z) | |
902 | printf "/*\n * Generated by pdtrace.\n */\n\n" | |
903 | ||
904 | printf "#ifndef _${guard}_H\n" | |
905 | printf "#define _${guard}_H\n\n" | |
906 | ||
907 | printf "#include <unistd.h>\n" | |
908 | printf "#include <inttypes.h>\n" | |
909 | printf \\n\\n | |
910 | ||
911 | printf "#ifdef __cplusplus\nextern \"C\" {\n#endif\n" | |
912 | ||
913 | printf "#define _DTRACE_VERSION 1\n\n" | |
914 | ||
915 | provider=$(cat $dfile | $EGREP "^ *provider +([a-zA-Z_]+)" \ | |
916 | | $SED -E -e 's/^ *provider +([a-zA-Z]+).*/\1/') | |
917 | test -z "$provider" \ | |
918 | && f_panic "unable to parse the provider name from $dfile." | |
919 | u_provider=$(printf %s "$provider" | $TR a-z A-Z | $TR -s _) | |
920 | ||
921 | cat $dfile | $EGREP "^ *probe +[a-zA-Z_]+ *\(.*\);" | \ | |
922 | while read line; do | |
923 | # Extract the probe name. | |
924 | name=$(printf %s "$line" \ | |
925 | | $SED -E -e 's/^ *probe +([a-zA-Z_]+).*/\1/') | |
926 | u_name=$(printf %s "$name" | $TR a-z A-Z | $TR -s _) | |
927 | ||
928 | # Generate an arg1,arg2,...,argN line for the probe. | |
929 | args=""; nargs=0; aline=$(printf %s "$line" | $SED -e 's/.*(\(.*\)).*/\1/') | |
930 | set -f; IFS=, | |
931 | for arg in $aline; do | |
932 | args="${args}arg${nargs}," | |
933 | nargs=$((nargs + 1)) | |
934 | done | |
935 | set +f; unset IFS | |
936 | args=${args%,} | |
937 | ||
938 | echo "#if _DTRACE_VERSION" | |
939 | echo "" | |
940 | ||
941 | # Emit the macros for the probe. | |
942 | echo "#define ${u_provider}_${u_name}($args) \\" | |
943 | echo " __dtrace_${provider}___${name}($args)" | |
944 | echo "#define ${u_provider}_${u_name}_ENABLED() \\" | |
945 | echo " __dtraceenabled_${provider}___${name}()" | |
946 | ||
947 | # Emit the extern definitions for the probe dummy | |
948 | # functions. | |
949 | echo "" | |
950 | printf %s\\n "$line" \ | |
951 | | $SED -E -e "s/^ *probe +/extern void __dtrace_${provider}___/" | |
952 | echo "extern int __dtraceenabled_${provider}___${name}(void);" | |
953 | ||
954 | ||
955 | printf "\n#else\n" | |
956 | ||
957 | # Emit empty macros for the probe | |
958 | echo "#define ${u_provider}_${u_name}($args)" | |
959 | echo "#define ${u_provider}_${u_name}_ENABLED() (0)" | |
960 | ||
961 | printf "\n#endif /* _DTRACE_VERSION */\n" | |
962 | done | |
963 | ||
964 | printf "#ifdef __cplusplus\n}\n#endif\n\n" | |
965 | printf "#endif /* _${guard}_H */\n" | |
966 | } | |
967 | ||
968 | ### Main program. | |
969 | ||
970 | # Process command line arguments. | |
971 | ||
972 | test "$#" -eq "0" && f_usage | |
973 | ||
974 | genelf=0 | |
975 | genheader=0 | |
976 | objbits=64 | |
977 | ofile= | |
978 | dfile= | |
979 | while getopts VG3264hs:o: name; do | |
980 | case $name in | |
981 | V) f_version;; | |
982 | s) dfile="$OPTARG"; | |
983 | test -f "$dfile" || f_panic "cannot read $dfile";; | |
984 | o) ofile="$OPTARG";; | |
985 | G) genelf=1;; | |
986 | h) genheader=1;; | |
987 | # Note the trick to support -32 | |
988 | 3) objbits=666;; | |
989 | 2) test "$objbits" -eq 666 || f_usage; objbits=32;; | |
990 | # Likewise for -64 | |
991 | 6) objbits=777;; | |
992 | 4) test "$objbits" -eq 777 || f_usage; objbits=64;; | |
993 | ?) f_usage;; | |
994 | esac | |
995 | done | |
996 | shift $(($OPTIND - 1)) | |
997 | ||
998 | test "$objbits" -eq "32" || test "$objbits" -eq "64" \ | |
999 | || f_usage | |
1000 | ||
1001 | test $((genelf + genheader)) -gt 1 && \ | |
1002 | { echo "Please use either -G or -h."; f_usage; } | |
1003 | ||
1004 | test -n "$dfile" || { echo "Please specify a .d file with -s."; exit 2; } | |
1005 | ||
1006 | if test "$genelf" -gt 0; then | |
1007 | # In this mode there must be a remaining argument: the name of the | |
1008 | # object file to inspect for probed points. | |
1009 | test "$#" -ne "1" && f_usage | |
1010 | test -f "$1" || f_panic "cannot read $1" | |
1011 | objfile=$1 | |
1012 | ||
1013 | # Collect probe information from the input object file and the | |
1014 | # d-script. | |
1015 | f_collect_probes $objfile | |
1016 | f_collect_probes_args $dfile | |
1017 | ||
1018 | # Generate the assembly code and assemble the DOF program in | |
1019 | # OFILE. Then patch OBJFILE to remove the dummy probe calls. | |
1020 | f_gen_dof_program | |
1021 | f_patch_objfile $objfile | |
1022 | fi | |
1023 | ||
1024 | if test "$genheader" -gt 0; then | |
1025 | test -n "$ofile" || { echo "Please specify an output file with -o."; exit 2; } | |
1026 | ||
1027 | # In this mode no extra arguments shall be present. | |
1028 | test "$#" -ne "0" && f_usage | |
1029 | ||
1030 | f_gen_header_file > $ofile | |
1031 | fi | |
1032 | ||
1033 | # pdtrace ends here. |