| #!/bin/sh |
| |
| # A Poor (but Free) Man's dtrace |
| # |
| # Copyright (C) 2014-2021 Free Software Foundation, Inc. |
| # |
| # Contributed by Oracle, Inc. |
| # |
| # This file is part of GDB. |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 3 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, but |
| # WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| # General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see |
| # <http://www.gnu.org/licenses/>. |
| |
| # DISCLAIMER DISCLAIMER DISCLAIMER |
| # This script is a test tool. As such it is in no way intended to |
| # replace the "real" dtrace command for any practical purpose, apart |
| # from testing the DTrace USDT probes support in GDB. |
| |
| # that said... |
| # |
| # pdtrace is a limited dtrace program, implementing a subset of its |
| # functionality: |
| # |
| # - The generation of an ELF file containing an embedded dtrace |
| # program. Equivalent to dtrace -G. |
| # |
| # - The generation of a header file with definitions for static |
| # probes. Equivalent to dtrace -h. |
| # |
| # This allows to generate DTrace static probes without having to use |
| # the user-level DTrace components. The generated objects are 100% |
| # compatible with DTrace and can be traced by the dtrace kernel module |
| # like if they were generated by dtrace. |
| # |
| # Some of the known limitations of this implementation are: |
| # - The input d-script must describe one provider, and only one. |
| # - The "probe " directives in the d-file must not include argument |
| # names, just the types. Thus something like `char *' is valid, but |
| # `char *name' is not. |
| # - The command line options must precede other arguments, since the |
| # script uses the (more) portable getopts. |
| # - Each probe header in the d-script must be contained in |
| # a single line. |
| # - strip -K removes the debugging information from the input object |
| # file. |
| # - The supported target platforms are i[3456]86 and x86_64. |
| # |
| # Please keep this code as portable as possible. Restrict yourself to |
| # POSIX sh. |
| |
| # This script uses the following external programs, defined in |
| # variables. Some of them are substituted by autoconf. |
| |
| TR=tr |
| NM=@NM_TRANSFORM_NAME@ |
| EGREP=egrep |
| SED=sed |
| CUT=cut |
| READELF=@READELF_TRANSFORM_NAME@ |
| SORT=sort |
| EXPR=expr |
| WC=wc |
| UNIQ=uniq |
| HEAD=head |
| SEQ=seq |
| AS=@GAS_TRANSFORM_NAME@ |
| STRIP=@STRIP_TRANSFORM_NAME@ |
| TRUE=true |
| |
| # Sizes for several DOF structures, in bytes. |
| # |
| # See linux/dtrace/dof.h for the definition of the referred |
| # structures. |
| |
| dof_hdrsize=64 # sizeof(dtrace_dof_hdr) |
| dof_secsize=32 # sizeof(dtrace_dof_sect) |
| dof_probesize=48 # sizeof(dtrace_dof_probe) |
| dof_providersize=44 # sizeof(dtrace_dof_provider) |
| |
| # Types for the several DOF sections. |
| # |
| # See linux/dtrace/dof_defines.h for a complete list of section types |
| # along with their values. |
| |
| dof_sect_type_strtab=8 |
| dof_sect_type_provider=15 |
| dof_sect_type_probes=16 |
| dof_sect_type_prargs=17 |
| dof_sect_type_proffs=18 |
| dof_sect_type_prenoffs=26 |
| |
| ### Functions |
| |
| # Write a message to the standard error output and exit with an error |
| # status. |
| # |
| # Arguments: |
| # $1 error message. |
| |
| f_panic() |
| { |
| echo "error: $1" 1>&2; exit 1 |
| } |
| |
| # Write a usage message to the standard output and exit with an error |
| # status. |
| |
| f_usage() |
| { |
| printf "Usage: pdtrace [-32|-64] [-GhV] [-o output] [-s script] [ args ... ]\n\n" |
| |
| printf "\t-32 generate 32-bit ELF files\n" |
| printf "\t-64 generate 64-bit ELF files\n\n" |
| |
| printf "\t-G generate an ELF file containing embedded dtrace program\n" |
| printf "\t-h generate a header file with definitions for static probes\n" |
| printf "\t-o set output file\n" |
| printf "\t-s handle probes according to the specified D script\n" |
| printf "\t-V report the DTrace API version implemented by the tool\n" |
| exit 2 |
| } |
| |
| # Write a version message to the standard output and exit with a |
| # successful status. |
| |
| f_version() |
| { |
| echo "pdtrace: Sun D 1.6.3" |
| exit |
| } |
| |
| # Add a new record to a list and return it. |
| # |
| # Arguments: |
| # $1 is the list. |
| # $2 is the new record |
| |
| f_add_record() |
| { |
| rec=$1 |
| test -n "$rec" && \ |
| { rec=$(printf %s\\n "$rec"; echo x); rec=${rec%x}; } |
| printf %s "$rec$2" |
| } |
| |
| # Collect the providers and probes information from the input object |
| # file. |
| # |
| # This function sets the values of the following global variables. |
| # The values are structured in records, each record in a line. The |
| # fields of each record are separated in some cases by white |
| # characters and in other cases by colon (:) characters. |
| # |
| # The type codes in the line format descriptors are: |
| # S: string, D: decimal number |
| # |
| # probes |
| # Regular probes and is-enabled probes. |
| # TYPE(S) PROVIDER(S) NAME(S) OFFSET(D) BASE(D) BASE_SYM(S) |
| # base_probes |
| # Base probes, i.e. probes sharing provider, name and container. |
| # PROVIDER(S) NAME(S) BASE(D) BASE_SYM(S) |
| # providers |
| # List of providers. |
| # PROVIDER(S) |
| # All the offsets are expressed in bytes. |
| # |
| # Input globals: |
| # objfile |
| # Output globals: |
| # probes, base_probes, providers |
| |
| probes= |
| base_probes= |
| providers= |
| probes_args= |
| |
| f_collect_probes() |
| { |
| # Probe points are function calls to undefined functions featuring |
| # distinct names for both normal probes and is-enabled probes. |
| PROBE_REGEX="(__dtrace_([a-zA-Z_]+)___([a-zA-Z_]+))" |
| EPROBE_REGEX="(__dtraceenabled_([a-zA-Z_]+)___([a-zA-Z_]+))" |
| |
| while read type symbol provider name; do |
| test -z "$type" && f_panic "No probe points found in $objfile" |
| |
| provider=$(printf %s $provider | $TR -s _) |
| name=$(printf %s $name | $TR -s _) |
| |
| # Search the object file for relocations defined for the |
| # probe symbols. Then calculate the base address of the |
| # probe (along with the symbol associated with that base |
| # address) and the offset of the probe point. |
| for offset in $($READELF -W -r $objfile | $EGREP $symbol | $CUT -d' ' -f1) |
| do |
| # Figure out the base address for the probe. This is |
| # done finding the function name in the text section of |
| # the object file located above the probed point. But |
| # note that the relocation is for the address operand of |
| # the call instruction, so we have to subtract 1 to find |
| # the real probed point. |
| offset=$((0x$offset - 1)) |
| |
| # The addresses of is-enabled probes must point to the |
| # first NOP instruction in their patched instructions |
| # sequences, so modify them (see f_patch_objfile for the |
| # instruction sequences). |
| if test "$type" = "e"; then |
| if test "$objbits" -eq "32"; then |
| offset=$((offset + 2)) |
| else # 64 bits |
| offset=$((offset + 3)) |
| fi |
| fi |
| |
| # Determine the base address of the probe and its |
| # corresponding function name. |
| funcs=$($NM -td $objfile | $EGREP "^[0-9]+ T " \ |
| | $CUT -d' ' -f1,3 | $SORT -n -r | $TR ' ' :) |
| for fun in $funcs; do |
| func_off=$(printf %s $fun | $CUT -d: -f1) |
| func_sym=$(printf %s $fun | $CUT -d: -f2) |
| # Note that `expr' is used to remove leading zeros |
| # to avoid FUNC_OFF to be interpreted as an octal |
| # number in arithmetic contexts. |
| test "$func_off" -le "$offset" && \ |
| { base=$($EXPR $func_off + 0); break; } |
| done |
| test -n "$base" || \ |
| f_panic "could not find base address for probe at $objfile($o)" |
| |
| # Emit the record for the probe. |
| probes=$(f_add_record "$probes" \ |
| "$type $provider $name $(($offset - $base)) $base $func_sym") |
| done |
| done <<EOF |
| $($NM $objfile | $EGREP " U $PROBE_REGEX" \ |
| | $SED -E -e "s/.*$PROBE_REGEX.*/p \1 \2 \3/"; |
| $NM $objfile | $EGREP " U $EPROBE_REGEX" \ |
| | $SED -E -e "s/.*$EPROBE_REGEX.*/e \1 \2 \3/") |
| EOF |
| |
| # Build the list of providers and of base probes from the probes. |
| while read type provider name offset base base_sym; do |
| providers=$(f_add_record "$providers" "$provider") |
| base_probes=$(f_add_record "$base_probes" "$provider $name $base $base_sym") |
| done <<EOF |
| $probes |
| EOF |
| providers=$(printf %s\\n "$providers" | $SORT | $UNIQ) |
| base_probes=$(printf %s\\n "$base_probes" | $SORT | $UNIQ) |
| } |
| |
| # Collect the argument counts and type strings for all the probes |
| # described in the `probes' global variable. This is done by |
| # inspecting the d-script file provided by the user. |
| # |
| # This function sets the values of the following global variables. |
| # The values are structured in records, each record in a line. The |
| # fields of each record are separated in some cases by white |
| # characters and in other cases by colon (:) characters. |
| # |
| # The type codes in the line format descriptors are: |
| # S: string, D: decimal number |
| # |
| # probes_args |
| # Probes arguments. |
| # PROVIDER(S):NAME(S):NARGS(D):ARG1(S):ARG2(S):...:ARGn(S) |
| # |
| # Input globals: |
| # probes |
| # Output globals: |
| # probes_args |
| # Arguments: |
| # $1 is the d-script file from which to extract the arguments |
| # information. |
| |
| f_collect_probes_args() |
| { |
| dscript=$1 |
| while read type provider name offset base base_sym; do |
| # Process normal probes only. Is-enabled probes are not |
| # described in the d-script file and they don't receive any |
| # argument. |
| test "$type" = "p" || continue |
| |
| # Names are mangled in d-script files to make it possible to |
| # have underscore characters as part of the provider name and |
| # probe name. |
| m_provider=$(printf %s $provider | $SED -e 's/_/__/g') |
| m_name=$(printf %s $name | $SED -e 's/_/__/g') |
| |
| # Ignore this probe if the d-script file does not describe its |
| # provider. |
| $EGREP -q "provider +$m_provider" $dscript || continue |
| |
| # Look for the line containing the description of the probe. |
| # If we can't find it then ignore this probe. |
| line=$($EGREP "^ *probe +$m_name *\(.*\);" $dscript) |
| test -n "$line" || continue |
| |
| # Ok, extract the argument types from the probe prototype. |
| # This is fragile as hell as it requires the prototype to be |
| # in a single line. |
| args=""; nargs=0; line=$(printf %s "$line" | $SED -e 's/.*(\(.*\)).*/\1/') |
| set -f; IFS=, |
| for arg in $line; do |
| args="$args:$arg" |
| nargs=$((nargs + 1)) |
| done |
| set +f; unset IFS |
| |
| # Emit the record for the probe arguments. |
| probes_args=$(f_add_record "$probes_args" "$provider:$name:$nargs$args") |
| done <<EOF |
| $probes |
| EOF |
| } |
| |
| # Functions to manipulate the global BCOUNT. |
| |
| BCOUNT=0 |
| |
| f_incr_bcount() |
| { |
| BCOUNT=$((BCOUNT + $1)) |
| } |
| |
| f_align_bcount() |
| { |
| test $((BCOUNT % $1)) -eq 0 || BCOUNT=$((BCOUNT + ($1 - (BCOUNT % $1)))) |
| } |
| |
| # Generate a line of assembly code and add it to the asmprogram global |
| # variable. |
| # |
| # Arguments: |
| # $1 string to generate in a line. |
| |
| asmprogram= |
| |
| f_gen_asm() |
| { |
| line=$(printf "\t$1") |
| asmprogram=$(f_add_record "$asmprogram" "$line") |
| } |
| |
| # Helper function to generate the assembly code of a DOF section |
| # header. |
| # |
| # This function is used by `f_gen_dof_program'. |
| # |
| # Arguments: |
| # $1 is the name of the described section. |
| # $2 is the type of the described section. |
| # $3 is the alignment of the described section. |
| # $4 is the number of entities stored in the described section. |
| # $5 is the offset in the DOF program of the described section. |
| # $6 is the size of the described section, in bytes. |
| |
| f_gen_dof_sect_header() |
| { |
| f_gen_asm "" |
| f_gen_asm "/* dtrace_dof_sect for the $1 section. */" |
| f_gen_asm ".balign 8" |
| f_gen_asm ".4byte $2\t/* uint32_t dofs_type */" |
| f_gen_asm ".4byte $3\t/* uint32_t dofs_align */" |
| # The DOF_SECF_LOAD flag is 1 => loadable section. |
| f_gen_asm ".4byte 1\t/* uint32_t dofs_flags */" |
| f_gen_asm ".4byte $4\t/* uint32_t dofs_entsize */" |
| f_gen_asm ".8byte $5\t/* uint64_t dofs_offset */" |
| f_gen_asm ".8byte $6\t/* uint64_t dofs_size */" |
| } |
| |
| # Generate a DOF program and assembly it in the output file. |
| # |
| # The DOF program generated by this function has the following |
| # structure: |
| # |
| # HEADER |
| # STRTAB OFFTAB EOFFTAB [PROBES PROVIDER]... |
| # STRTAB_SECT OFFTAB_SECT EOFFTAB_SECT ARGTAB_SECT [PROBES_SECT PROVIDER_SECT]... |
| # |
| # Input globals: |
| # probes, base_probes, providers, probes_args, BCOUNT |
| |
| f_gen_dof_program() |
| { |
| ###### Variables used to cache information needed later. |
| |
| # Number of section headers in the generated DOF program. |
| dof_secnum=0 |
| # Offset of section headers in the generated DOF program, in bytes. |
| dof_secoff=0 |
| |
| # Sizes of the STRTAB, OFFTAB and EOFFTAB sections, in bytes. |
| strtab_size=0 |
| offtab_size=0 |
| eofftab_size=0 |
| |
| # Offsets of the STRTAB, OFFTAB EOFFTAB and PROBES sections in the |
| # generated DOF program. In bytes. |
| strtab_offset=0 |
| offtab_offset=0 |
| eofftab_offset=0 |
| argtab_offset=0 |
| probes_offset=0 |
| |
| # Indexes of the section headers of the STRTAB, OFFTAB, EOFFTAB and |
| # PROBES sections in the sections array. |
| strtab_sect_index=0 |
| offtab_sect_index=0 |
| eofftab_sect_index=0 |
| argtab_sect_index=0 |
| probes_sect_index=0 |
| |
| # First offsets and eoffsets of the base-probes. |
| # Lines: PROVIDER(S) NAME(S) BASE(D) (DOF_OFFSET(D)|DOF_EOFFSET(D)) |
| probes_dof_offsets= |
| probes_dof_eoffsets= |
| |
| # Offsets in the STRTAB section for the first type of base probes. |
| # Record per line: PROVIDER(S) NAME(S) BASE(D) OFFSET(D) |
| probes_dof_types= |
| |
| |
| # Offsets of the provider names in the provider's STRTAB section. |
| # Lines: PROVIDER(S) OFFSET(D) |
| providers_dof_names= |
| |
| # Offsets of the base-probe names in the provider's STRTAB section. |
| # Lines: PROVIDER(S) NAME(S) BASE(D) OFFSET(D) |
| probes_dof_names= |
| |
| # Offsets of the provider sections in the DOF program. |
| # Lines: PROVIDER(S) OFFSET(D) |
| providers_offsets= |
| |
| ###### Generation phase. |
| |
| # The header of the DOF program contains a `struct |
| # dtrace_dof_hdr'. Record its size, but it is written at the end |
| # of the function. |
| f_incr_bcount $dof_hdrsize; f_align_bcount 8 |
| |
| # The STRTAB section immediately follows the header. It contains |
| # the following set of packed null-terminated strings: |
| # |
| # [PROVIDER [BASE_PROBE_NAME [BASE_PROBE_ARG_TYPE...]]...]... |
| strtab_offset=$BCOUNT |
| strtab_sect_index=$dof_secnum |
| dof_secnum=$((dof_secnum + 1)) |
| f_gen_asm "" |
| f_gen_asm "/* The STRTAB section. */" |
| f_gen_asm ".balign 8" |
| # Add the provider names. |
| off=0 |
| while read provider; do |
| strtab_size=$(($strtab_size + ${#prov} + 1)) |
| # Note the funny mangling... |
| f_gen_asm ".asciz \"$(printf %s $provider | $TR _ -)\"" |
| providers_dof_names=$(f_add_record "$providers_dof_names" \ |
| "$provider $off") |
| off=$(($off + ${#provider} + 1)) |
| |
| # Add the base-probe names. |
| while read p_provider name base base_sym; do |
| test "$p_provider" = "$provider" || continue |
| # And yes, more funny mangling... |
| f_gen_asm ".asciz \"$(printf %s $name | $TR _ -)\"" |
| probes_dof_names=$(f_add_record "$probes_dof_names" \ |
| "$p_provider $name $base $off") |
| off=$(($off + ${#name} + 1)) |
| while read args; do |
| a_provider=$(printf %s "$args" | $CUT -d: -f1) |
| a_name=$(printf %s "$args" | $CUT -d: -f2) |
| test "$a_provider" = "$p_provider" \ |
| && test "$a_name" = "$name" \ |
| || continue |
| |
| probes_dof_types=$(f_add_record "$probes_dof_types" \ |
| "$a_provider $name $base $off") |
| nargs=$(printf %s "$args" | $CUT -d: -f3) |
| for n in $($SEQ $nargs); do |
| arg=$(printf %s "$args" | $CUT -d: -f$(($n + 3))) |
| f_gen_asm ".asciz \"${arg}\"" |
| off=$(($off + ${#arg} + 1)) |
| done |
| done <<EOF |
| $probes_args |
| EOF |
| done <<EOF |
| $base_probes |
| EOF |
| done <<EOF |
| $providers |
| EOF |
| strtab_size=$off |
| f_incr_bcount $strtab_size; f_align_bcount 8 |
| |
| # The OFFTAB section contains a set of 32bit words, one per |
| # defined regular probe. |
| offtab_offset=$BCOUNT |
| offtab_sect_index=$dof_secnum |
| dof_secnum=$((dof_secnum + 1)) |
| f_gen_asm "" |
| f_gen_asm "/* The OFFTAB section. */" |
| f_gen_asm ".balign 8" |
| off=0 |
| while read type provider name offset base base_sym; do |
| test "$type" = "p" || continue |
| f_gen_asm ".4byte $offset\t/* probe ${provider}:${name} */" |
| probes_dof_offsets=$(f_add_record "$probes_dof_offsets" \ |
| "$provider $name $base $off") |
| off=$(($off + 4)) |
| done <<EOF |
| $probes |
| EOF |
| offtab_size=$off |
| f_incr_bcount $offtab_size; f_align_bcount 8 |
| |
| # The EOFFTAB section contains a set of 32bit words, one per |
| # defined is-enabled probe. |
| eofftab_offset=$BCOUNT |
| eofftab_sect_index=$dof_secnum |
| dof_secnum=$((dof_secnum + 1)) |
| f_gen_asm "" |
| f_gen_asm "/* The EOFFTAB section. */" |
| f_gen_asm ".balign 8" |
| off=0 |
| while read type provider name offset base base_sym; do |
| test "$type" = "e" || continue |
| f_gen_asm ".4byte $offset\t/* is-enabled probe ${provider}:${name} */" |
| probes_dof_eoffsets=$(f_add_record "$probes_dof_eoffsets" \ |
| "$provider $name $base $off") |
| off=$(($off + 4)) |
| done <<EOF |
| $probes |
| EOF |
| eofftab_size=$off |
| f_incr_bcount $eofftab_size; f_align_bcount 8 |
| |
| # The ARGTAB section is empty, but nonetheless has a section |
| # header, so record its section index here. |
| argtab_offset=0 |
| argtab_sect_index=$dof_secnum |
| dof_secnum=$((dof_secnum + 1)) |
| |
| # Generate a pair of sections PROBES and PROVIDER for each |
| # provider. |
| while read prov; do |
| # The PROBES section contains an array of `struct |
| # dtrace_dof_probe'. |
| # |
| # A `dtrace_dof_probe' entry characterizes the collection of |
| # probes and is-enabled probes sharing the same provider, name and |
| # base address. |
| probes_sect_index=$dof_secnum |
| dof_secnum=$((dof_secnum + 1)) |
| probes_offset=$BCOUNT |
| num_base_probes=$(printf %s\\n "$base_probes" | $WC -l) |
| while read provider name base base_sym; do |
| name_offset=$(printf %s\\n "$probes_dof_names" \ |
| | $EGREP "^$provider $name " | $CUT -d' ' -f4) |
| |
| num_offsets=$(printf %s\\n "$probes_dof_offsets" \ |
| | $EGREP "^$provider $name [0-9]+ " | $WC -l) |
| |
| first_offset=0 |
| test "$num_offsets" -gt 0 && \ |
| first_offset=$(printf %s\\n "$probes_dof_offsets" \ |
| | $EGREP "^$provider $name " | $CUT -d' ' -f4 | $HEAD -1) |
| |
| num_eoffsets=$(printf %s\\n "$probes_dof_eoffsets" \ |
| | $EGREP "^$provider $name [0-9]+ " | $WC -l) |
| first_eoffset=0 |
| test "$num_eoffsets" -gt 0 && \ |
| first_eoffset=$(printf %s "$probes_dof_eoffsets" \ |
| | $EGREP "^$provider $name " | $CUT -d' ' -f4 | $HEAD -1) |
| |
| num_args=$(printf %s "$probes_args" \ |
| | $EGREP "^$provider:$name:" | $CUT -d: -f3 | $HEAD -1) |
| |
| first_type=$(printf %s "$probes_dof_types" \ |
| | $EGREP "^$provider $name $base " | $CUT -d' ' -f4 | $HEAD -1) |
| |
| reloctype=R_X86_64_GLOB_DAT |
| test "$objbits" = "32" && reloctype=R_386_32 |
| |
| f_gen_asm "" |
| f_gen_asm "/* dtrace_dof_probe for ${provider}:${name} at ${base_sym} */" |
| f_gen_asm ".balign 8" |
| f_gen_asm ".reloc ., $reloctype, $base_sym + 0" |
| f_gen_asm ".8byte ${base}\t/* uint64_t dofpr_addr */" |
| f_gen_asm ".4byte 0\t/* uint32_t dofpr_func */" |
| f_gen_asm ".4byte $name_offset\t/* uint32_t dofpr_name */" |
| f_gen_asm ".4byte $first_type\t/* uint32_t dofpr_nargv */" |
| f_gen_asm ".4byte 0\t/* uint32_t dofpr_xargv */" |
| f_gen_asm ".4byte 0\t/* uint32_t dofpr_argidx */" |
| f_gen_asm ".4byte $(($first_offset/4))\t/* uint32_t dofpr_offidx */" |
| f_gen_asm ".byte $num_args\t/* uint8_t dofpr_nargc */" |
| f_gen_asm ".byte 0\t/* uint8_t dofpr_xargc */" |
| f_gen_asm ".2byte $num_offsets\t/* uint16_t dofpr_noffs */" |
| f_gen_asm ".4byte $(($first_eoffset/4))\t/* uint32_t dofpr_enoffidx */" |
| f_gen_asm ".2byte $num_eoffsets\t/* uint16_t dofpr_nenoffs */" |
| f_gen_asm ".2byte 0\t/* uint16_t dofpr_pad1 */" |
| f_gen_asm ".4byte 0\t/* uint16_t dofpr_pad2 */" |
| |
| f_incr_bcount "$dof_probesize" |
| done <<EOF |
| $base_probes |
| EOF |
| |
| # The PROVIDER section contains a `struct dtrace_dof_provider' |
| # instance describing the provider for the probes above. |
| dof_secnum=$((dof_secnum + 1)) |
| providers_offsets=$(f_add_record "$providers_offsets" \ |
| "$prov $BCOUNT") |
| # The dtrace_dof_provider. |
| provider_name_offset=$(printf %s "$providers_dof_names" \ |
| | $EGREP "^$prov " | $CUT -d' ' -f2) |
| |
| f_gen_asm "" |
| f_gen_asm "/* dtrace_dof_provider for $prov */" |
| f_gen_asm ".balign 8" |
| # Links to several DOF sections. |
| f_gen_asm ".4byte $strtab_sect_index\t/* uint32_t dofpv_strtab */" |
| f_gen_asm ".4byte $probes_sect_index\t/* uint32_t dofpv_probes */" |
| f_gen_asm ".4byte $argtab_sect_index\t/* uint32_t dofpv_prargs */" |
| f_gen_asm ".4byte $offtab_sect_index\t/* uint32_t dofpv_proffs */" |
| # Offset of the provider name into the STRTAB section. |
| f_gen_asm ".4byte $provider_name_offset\t/* uint32_t dofpv_name */" |
| # The rest of fields can be 0 for our modest purposes :) |
| f_gen_asm ".4byte 0\t/* uint32_t dofpv_provattr */" |
| f_gen_asm ".4byte 0\t/* uint32_t dofpv_modattr */" |
| f_gen_asm ".4byte 0\t/* uint32_t dofpv_funcattr */" |
| f_gen_asm ".4byte 0\t/* uint32_t dofpv_nameattr */" |
| f_gen_asm ".4byte 0\t/* uint32_t dofpv_argsattr */" |
| # But not this one, of course... |
| f_gen_asm ".4byte $eofftab_sect_index\t/* uint32_t dofpv_prenoffs */" |
| |
| f_incr_bcount $dof_providersize |
| done<<EOF |
| $providers |
| EOF |
| f_align_bcount 8 |
| |
| # The section headers follow, one per section defined above. |
| dof_secoff=$BCOUNT |
| |
| f_gen_dof_sect_header STRTAB \ |
| $dof_sect_type_strtab \ |
| 1 1 $strtab_offset $strtab_size |
| f_incr_bcount $dof_secsize; f_align_bcount 8 |
| |
| f_gen_dof_sect_header OFFTAB \ |
| $dof_sect_type_proffs \ |
| 4 4 $offtab_offset $offtab_size |
| f_incr_bcount $dof_secsize; f_align_bcount 8 |
| |
| f_gen_dof_sect_header EOFFTAB \ |
| $dof_sect_type_prenoffs \ |
| 4 4 $eofftab_offset $eofftab_size |
| f_incr_bcount $dof_secsize; f_align_bcount 8 |
| |
| f_gen_dof_sect_header ARGTAB \ |
| $dof_sect_type_prargs \ |
| 4 1 $argtab_offset 0 |
| f_incr_bcount $dof_secsize; f_align_bcount 8 |
| |
| while read provider; do |
| provider_offset=$(printf %s "$providers_offsets" \ |
| | $EGREP "^$provider " | $CUT -d' ' -f2) |
| num_base_probes=$(printf %s\\n "$base_probes" | $WC -l) |
| |
| f_gen_dof_sect_header "$provider probes" \ |
| $dof_sect_type_probes \ |
| 8 $dof_probesize $probes_offset \ |
| $((num_base_probes * dof_probesize)) |
| f_incr_bcount $dof_secsize; f_align_bcount 8 |
| |
| f_gen_dof_sect_header "$provider provider" \ |
| $dof_sect_type_provider \ |
| 8 1 $provider_offset $dof_providersize |
| f_incr_bcount $dof_secsize; f_align_bcount 8 |
| done <<EOF |
| $providers |
| EOF |
| |
| # Finally, cook the header. |
| asmbody="$asmprogram" |
| asmprogram="" |
| f_gen_asm "/* File generated by pdtrace. */" |
| f_gen_asm "" |
| |
| f_gen_asm ".section .SUNW_dof,\"a\",\"progbits\"" |
| f_gen_asm ".globl __SUNW_dof" |
| f_gen_asm ".hidden __SUNW_dof" |
| f_gen_asm ".size __SUNW_dof, ${BCOUNT}" |
| f_gen_asm ".type __SUNW_dof, @object" |
| f_gen_asm "__SUNW_dof:" |
| |
| f_gen_asm "" |
| f_gen_asm "/* dtrace_dof_hdr */" |
| f_gen_asm ".balign 8" |
| f_gen_asm ".byte 0x7f, 'D, 'O, 'F\t/* dofh_ident[0..3] */" |
| f_gen_asm ".byte 2\t\t/* model: 1=ILP32, 2=LP64 */" |
| f_gen_asm ".byte 1\t\t/* encoding: 1: little-endian, 2: big-endian */" |
| f_gen_asm ".byte 2\t\t/* DOF version: 1 or 2. Latest is 2 */" |
| f_gen_asm ".byte 2\t\t/* DIF version: 1 or 2. Latest is 2 */" |
| f_gen_asm ".byte 8\t\t/* number of DIF integer registers */" |
| f_gen_asm ".byte 8\t\t/* number of DIF tuple registers */" |
| f_gen_asm ".byte 0, 0\t\t/* dofh_ident[10..11] */" |
| f_gen_asm ".4byte 0\t\t/* dofh_ident[12..15] */" |
| f_gen_asm ".4byte 0\t/* uint32_t dofh_flags */" # See Limitations above. |
| f_gen_asm ".4byte ${dof_hdrsize}\t/* uint32_t dofh_hdrsize */" |
| f_gen_asm ".4byte ${dof_secsize}\t/* uint32_t dofh_secsize */" |
| f_gen_asm ".4byte ${dof_secnum}\t/* uint32_t dofh_secnum */" |
| f_gen_asm ".8byte ${dof_secoff}\t/* uint64_t dofh_secoff */" |
| f_gen_asm ".8byte ${BCOUNT}\t/* uint64_t dofh_loadsz */" |
| f_gen_asm ".8byte ${BCOUNT}\t/* uint64_t dofh_filesz */" |
| f_gen_asm ".8byte 0\t/* uint64_t dofh_pad */" |
| f_gen_asm "" |
| |
| # Ok, now assembly the program in OFILE |
| echo "$asmprogram$asmbody" | $AS -$objbits -o $ofile |
| |
| # Next step is to change the sh_type of the ".SUNW_dof" section |
| # headers to 0x6ffffff4 (SHT_SUNW_dof). |
| # |
| # Note that this code relies in the fact that readelf will list |
| # the sections ordered in the same order than the section headers |
| # in the section header table of the file. |
| elfinfo=$($READELF -a $ofile) |
| |
| # Mind the endianness. |
| if printf %s "$elfinfo" | $EGREP -q "little endian"; then |
| sht_sunw_dof=$(printf %s%s%s%s \\364 \\377 \\377 \\157) |
| else |
| sht_sunw_dof=$(printf %s%s%s%s \\157 \\377 \\377 \\364) |
| fi |
| |
| shdr_start=$(printf %s "$elfinfo" \ |
| | $EGREP "^[ \t]*Start of section headers:" \ |
| | $SED -E -e 's/.*headers:[ \t]*([0-9]+).*/\1/') |
| test -n "$shdr_start" \ |
| || f_panic "could not extract the start of shdr from $ofile" |
| |
| shdr_num_entries=$(printf %s "$elfinfo" \ |
| | $EGREP "^[ \t]*Size of section headers:" \ |
| | $SED -E -e 's/.*headers:[ \t]*([0-9]+).*/\1/') |
| test -n "$shdr_num_entries" \ |
| || f_panic "could not extract the number of shdr entries from $ofile" |
| |
| shdr_entry_size=$(printf %s "$elfinfo" \ |
| | $EGREP "^[ \t]*Size of section headers:" \ |
| | $SED -E -e 's/.*headers:[ \t]*([0-9]+).*/\1/') |
| test -n "$shdr_entry_size" \ |
| || f_panic "could not fetch the size of section headers from $ofile" |
| |
| while read line; do |
| data=$(printf %s "$line" \ |
| | $SED -E -e 's/.*\[(.*)\][ \t]+([a-zA-Z_.]+).*/\1:\2/') |
| num=$(printf %s "$data" | $CUT -d: -f1) |
| name=$(printf %s "$data" | $CUT -d: -f2) |
| if test "$name" = ".SUNW_dof"; then |
| # Patch the new sh_type in the proper entry of the section |
| # header table. |
| printf "$sht_sunw_dof" \ |
| | dd of=$ofile conv=notrunc count=4 ibs=1 bs=1 \ |
| seek=$((shdr_start + (shdr_entry_size * num) + 4)) \ |
| 2> /dev/null |
| break |
| fi |
| done <<EOF |
| $(printf %s "$elfinfo" | $EGREP "^[ \t]*\[[0-9 ]+\].*[A-Z]+.*PROGBITS") |
| EOF |
| |
| } |
| |
| # Patch the probed points in the given object file, replacing the |
| # function calls with NOPs. |
| # |
| # The probed points in the input object files are function calls. |
| # This function replaces these function calls by some other |
| # instruction sequences. Which replacement to use depends on several |
| # factors, as documented below. |
| # |
| # Arguments: |
| # $1 is the object file to patch. |
| |
| f_patch_objfile() |
| { |
| objfile=$1 |
| |
| # Several x86_64 instruction opcodes, in octal. |
| x86_op_nop=$(printf \\220) |
| x86_op_ret=$(printf \\303) |
| x86_op_call=$(printf \\350) |
| x86_op_jmp32=$(printf \\351) |
| x86_op_rex_rax=$(printf \\110) |
| x86_op_xor_eax_0=$(printf \\063) |
| x86_op_xor_eax_1=$(printf \\300) |
| |
| # Figure out the file offset of the text section in the object |
| # file. |
| text_off=0x$(objdump -j .text -h $objfile \ |
| | grep \.text | $TR -s ' ' | $CUT -d' ' -f 7) |
| |
| while read type provider name offset base base_sym; do |
| # Calculate the offset of the probed point in the object file. |
| # Note that the `offset' of is-enabled probes is tweaked in |
| # `f_collect_probes" to point ahead the patching point. |
| probe_off=$((text_off + base + offset)) |
| if test "$type" = "e"; then |
| if test "$objbits" -eq "32"; then |
| probe_off=$((probe_off - 2)) |
| else # 64 bits |
| probe_off=$((probe_off - 3)) |
| fi |
| fi |
| |
| # The probed point can be either a CALL instruction or a JMP |
| # instruction (a tail call). This has an impact on the |
| # patching sequence. Fetch the first byte at the probed point |
| # and do the right thing. |
| nopret="$x86_op_nop" |
| byte=$(dd if=$objfile count=1 ibs=1 bs=1 skip=$probe_off 2> /dev/null) |
| test "$byte" = "$x86_op_jmp32" && nopret="$x86_op_ret" |
| |
| # Determine the patching sequence. It depends on the type of |
| # probe at hand (regular or is-enabled) and also if |
| # manipulating a 32bit or 64bit binary. |
| patchseq= |
| case $type in |
| p) patchseq=$(printf %s%s%s%s%s \ |
| "$nopret" \ |
| "$x86_op_nop" \ |
| "$x86_op_nop" \ |
| "$x86_op_nop" \ |
| "$x86_op_nop") |
| ;; |
| e) test "$objbits" -eq 64 && \ |
| patchseq=$(printf %s%s%s%s%s \ |
| "$x86_op_rex_rax" \ |
| "$x86_op_xor_eax_0" \ |
| "$x86_op_xor_eax_1" \ |
| "$nopret" \ |
| "$x86_op_nop") |
| test "$objbits" -eq 32 && \ |
| patchseq=$(printf %s%s%s%s%s \ |
| "$x86_op_xor_eax_0" \ |
| "$x86_op_xor_eax_1" \ |
| "$nopret" \ |
| "$x86_op_nop" \ |
| "$x86_op_nop") |
| ;; |
| *) f_panic "internal error: wrong probe type $type";; |
| esac |
| |
| # Patch! |
| printf %s "$patchseq" \ |
| | dd of=$objfile conv=notrunc count=5 ibs=1 bs=1 seek=$probe_off 2> /dev/null |
| done <<EOF |
| $probes |
| EOF |
| |
| # Finally, we have to remove the __dtrace_* and __dtraceenabled_* |
| # symbols from the object file, along with their respective |
| # relocations. |
| # |
| # Note that the most obvious call: |
| # strip -v -N whatever -w foo.o |
| # will not work: |
| # strip: not stripping symbol `whatever' because it is named in a relocation |
| # |
| # Fortunately using `-K !whatever' instead tricks strip to do the |
| # right thing, but this is black magic and may eventually stop |
| # working... |
| $STRIP -K '!__dtrace_*' -w $objfile |
| $STRIP -K '!__dtraceenabled_*' -w $objfile |
| } |
| |
| # Read the input .d file and print a header file with macros to |
| # invoke the probes defined in it. |
| |
| f_gen_header_file() |
| { |
| guard=$(basename $ofile | $TR - _ | $CUT -d. -f1 | $TR a-z A-Z) |
| printf "/*\n * Generated by pdtrace.\n */\n\n" |
| |
| printf "#ifndef _${guard}_H\n" |
| printf "#define _${guard}_H\n\n" |
| |
| printf "#include <unistd.h>\n" |
| printf "#include <inttypes.h>\n" |
| printf \\n\\n |
| |
| printf "#ifdef __cplusplus\nextern \"C\" {\n#endif\n" |
| |
| printf "#define _DTRACE_VERSION 1\n\n" |
| |
| provider=$(cat $dfile | $EGREP "^ *provider +([a-zA-Z_]+)" \ |
| | $SED -E -e 's/^ *provider +([a-zA-Z]+).*/\1/') |
| test -z "$provider" \ |
| && f_panic "unable to parse the provider name from $dfile." |
| u_provider=$(printf %s "$provider" | $TR a-z A-Z | $TR -s _) |
| |
| cat $dfile | $EGREP "^ *probe +[a-zA-Z_]+ *\(.*\);" | \ |
| while read line; do |
| # Extract the probe name. |
| name=$(printf %s "$line" \ |
| | $SED -E -e 's/^ *probe +([a-zA-Z_]+).*/\1/') |
| u_name=$(printf %s "$name" | $TR a-z A-Z | $TR -s _) |
| |
| # Generate an arg1,arg2,...,argN line for the probe. |
| args=""; nargs=0; aline=$(printf %s "$line" | $SED -e 's/.*(\(.*\)).*/\1/') |
| set -f; IFS=, |
| for arg in $aline; do |
| args="${args}arg${nargs}," |
| nargs=$((nargs + 1)) |
| done |
| set +f; unset IFS |
| args=${args%,} |
| |
| echo "#if _DTRACE_VERSION" |
| echo "" |
| |
| # Emit the macros for the probe. |
| echo "#define ${u_provider}_${u_name}($args) \\" |
| echo " __dtrace_${provider}___${name}($args)" |
| echo "#define ${u_provider}_${u_name}_ENABLED() \\" |
| echo " __dtraceenabled_${provider}___${name}()" |
| |
| # Emit the extern definitions for the probe dummy |
| # functions. |
| echo "" |
| printf %s\\n "$line" \ |
| | $SED -E -e "s/^ *probe +/extern void __dtrace_${provider}___/" |
| echo "extern int __dtraceenabled_${provider}___${name}(void);" |
| |
| |
| printf "\n#else\n" |
| |
| # Emit empty macros for the probe |
| echo "#define ${u_provider}_${u_name}($args)" |
| echo "#define ${u_provider}_${u_name}_ENABLED() (0)" |
| |
| printf "\n#endif /* _DTRACE_VERSION */\n" |
| done |
| |
| printf "#ifdef __cplusplus\n}\n#endif\n\n" |
| printf "#endif /* _${guard}_H */\n" |
| } |
| |
| ### Main program. |
| |
| # Process command line arguments. |
| |
| test "$#" -eq "0" && f_usage |
| |
| genelf=0 |
| genheader=0 |
| objbits=64 |
| ofile= |
| dfile= |
| while getopts VG3264hs:o: name; do |
| case $name in |
| V) f_version;; |
| s) dfile="$OPTARG"; |
| test -f "$dfile" || f_panic "cannot read $dfile";; |
| o) ofile="$OPTARG";; |
| G) genelf=1;; |
| h) genheader=1;; |
| # Note the trick to support -32 |
| 3) objbits=666;; |
| 2) test "$objbits" -eq 666 || f_usage; objbits=32;; |
| # Likewise for -64 |
| 6) objbits=777;; |
| 4) test "$objbits" -eq 777 || f_usage; objbits=64;; |
| ?) f_usage;; |
| esac |
| done |
| shift $(($OPTIND - 1)) |
| |
| test "$objbits" -eq "32" || test "$objbits" -eq "64" \ |
| || f_usage |
| |
| test $((genelf + genheader)) -gt 1 && \ |
| { echo "Please use either -G or -h."; f_usage; } |
| |
| test -n "$dfile" || { echo "Please specify a .d file with -s."; exit 2; } |
| |
| if test "$genelf" -gt 0; then |
| # In this mode there must be a remaining argument: the name of the |
| # object file to inspect for probed points. |
| test "$#" -ne "1" && f_usage |
| test -f "$1" || f_panic "cannot read $1" |
| objfile=$1 |
| |
| # Collect probe information from the input object file and the |
| # d-script. |
| f_collect_probes $objfile |
| f_collect_probes_args $dfile |
| |
| # Generate the assembly code and assemble the DOF program in |
| # OFILE. Then patch OBJFILE to remove the dummy probe calls. |
| f_gen_dof_program |
| f_patch_objfile $objfile |
| fi |
| |
| if test "$genheader" -gt 0; then |
| test -n "$ofile" || { echo "Please specify an output file with -o."; exit 2; } |
| |
| # In this mode no extra arguments shall be present. |
| test "$#" -ne "0" && f_usage |
| |
| f_gen_header_file > $ofile |
| fi |
| |
| # pdtrace ends here. |