1033 lines
36 KiB
Bash
Executable file
1033 lines
36 KiB
Bash
Executable file
#!/bin/sh
|
|
|
|
# A Poor (but Free) Man's dtrace
|
|
#
|
|
# Copyright (C) 2014-2022 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.
|