1#!/bin/sh 2 3# A Poor (but Free) Man's dtrace 4# 5# Copyright (C) 2014-2021 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 65TR=tr 66NM=@NM_TRANSFORM_NAME@ 67EGREP=egrep 68SED=sed 69CUT=cut 70READELF=@READELF_TRANSFORM_NAME@ 71SORT=sort 72EXPR=expr 73WC=wc 74UNIQ=uniq 75HEAD=head 76SEQ=seq 77AS=@GAS_TRANSFORM_NAME@ 78STRIP=@STRIP_TRANSFORM_NAME@ 79TRUE=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 86dof_hdrsize=64 # sizeof(dtrace_dof_hdr) 87dof_secsize=32 # sizeof(dtrace_dof_sect) 88dof_probesize=48 # sizeof(dtrace_dof_probe) 89dof_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 96dof_sect_type_strtab=8 97dof_sect_type_provider=15 98dof_sect_type_probes=16 99dof_sect_type_prargs=17 100dof_sect_type_proffs=18 101dof_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 111f_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 119f_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 137f_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 149f_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 184probes= 185base_probes= 186providers= 187probes_args= 188 189f_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/") 253EOF 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 261EOF 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 290f_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 329EOF 330} 331 332# Functions to manipulate the global BCOUNT. 333 334BCOUNT=0 335 336f_incr_bcount() 337{ 338 BCOUNT=$((BCOUNT + $1)) 339} 340 341f_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 352asmprogram= 353 354f_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 373f_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 399f_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 503EOF 504 done <<EOF 505$base_probes 506EOF 507 done <<EOF 508$providers 509EOF 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 530EOF 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 551EOF 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 624EOF 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 657EOF 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 700EOF 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") 789EOF 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 804f_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 878EOF 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 899f_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 972test "$#" -eq "0" && f_usage 973 974genelf=0 975genheader=0 976objbits=64 977ofile= 978dfile= 979while 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 995done 996shift $(($OPTIND - 1)) 997 998test "$objbits" -eq "32" || test "$objbits" -eq "64" \ 999 || f_usage 1000 1001test $((genelf + genheader)) -gt 1 && \ 1002 { echo "Please use either -G or -h."; f_usage; } 1003 1004test -n "$dfile" || { echo "Please specify a .d file with -s."; exit 2; } 1005 1006if 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 1022fi 1023 1024if 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 1031fi 1032 1033# pdtrace ends here. 1034