1#!/bin/sh 2#- 3# Copyright (c) 2014-2018 Devin Teske 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions 8# are met: 9# 1. Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# 2. Redistributions in binary form must reproduce the above copyright 12# notice, this list of conditions and the following disclaimer in the 13# documentation and/or other materials provided with the distribution. 14# 15# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25# SUCH DAMAGE. 26# 27############################################################ IDENT(1) 28# 29# $Title: Watch processes as they trigger a particular DTrace probe $ 30# $FreeBSD$ 31# 32############################################################ CONFIGURATION 33 34# 35# DTrace pragma settings 36# 37DTRACE_PRAGMA=" 38 option quiet 39 option dynvarsize=16m 40 option switchrate=10hz 41" # END-QUOTE 42 43# 44# Profiles 45# 46: ${DWATCH_PROFILES_PATH="/usr/libexec/dwatch:/usr/local/libexec/dwatch"} 47 48############################################################ GLOBALS 49 50VERSION='$Version: 1.4 $' # -V 51 52pgm="${0##*/}" # Program basename 53 54# 55# Command-line arguments 56# 57PROBE_ARG= 58 59# 60# Command-line defaults 61# 62_MAX_ARGS=64 # -B num 63_MAX_DEPTH=64 # -K num 64 65# 66# Command-line options 67# 68CONSOLE= # -y 69CONSOLE_FORCE= # -y 70[ -t 1 ] && CONSOLE=1 # -y 71COUNT=0 # -N count 72CUSTOM_DETAILS= # -E code 73CUSTOM_TEST= # -t test 74DEBUG= # -d 75DESTRUCTIVE_ACTIONS= # -w 76DEVELOPER= # -dev 77EXECNAME= # -k name 78EXECREGEX= # -z regex 79EXIT_AFTER_COMPILE= # -e 80FILTER= # -r regex 81PROBE_COALESCE= # -F 82GROUP= # -g group 83JID= # -j jail 84LIST= # -l 85LIST_PROFILES= # -Q 86MAX_ARGS=$_MAX_ARGS # -B num 87MAX_DEPTH=$_MAX_DEPTH # -K num 88ONELINE= # -1 89OUTPUT= # -o file 90OUTPUT_CMD= # -O cmd 91PID= # -p pid 92PROBE_TYPE= # -f -m -n -P 93PROFILE= # -X profile 94PSTREE= # -R 95QUIET= # -q 96TIMEOUT= # -T time 97TRACE= # -x 98USER= # -u user 99USE_PROFILE= # -X profile 100VERBOSE= # -v 101 102# 103# Global exit status 104# 105SUCCESS=0 106FAILURE=1 107 108# 109# Miscellaneous 110# 111ACTIONS= 112EVENT_DETAILS= 113EVENT_TAG='printf("%d.%d %s[%d]: ", 114 this->uid0, this->gid0, execname, this->pid0);' 115EVENT_TEST= 116FILE= 117ID=3 118MODULE_CHECKED= 119PROBE= 120PSARGS=1 121RGID= 122RUID= 123SUDO= 124export SUDO_PROMPT="[sudo] Password:" 125TITLE=\$Title: 126 127############################################################ FUNCTIONS 128 129ansi() { local fmt="$2 $4"; [ "$CONSOLE" ] && fmt="\\033[$1m$2\\033[$3m $4"; 130 shift 4; printf "$fmt\n" "$@"; } 131die() { exec >&2; [ "$*" ] && echo "$pgm:" "$@"; exit $FAILURE; } 132info() { [ "$QUIET" ] || ansi 35 "INFO" 39 "$@" >&2; } 133 134usage() 135{ 136 local optfmt="\t%-10s %s\n" 137 exec >&2 138 [ "$*" ] && printf "%s: %s\n" "$pgm" "$*" 139 printf "Usage: %s [-1defFmnPqRvVwxy] [%s] [%s] [%s] [%s]\n" "$pgm" \ 140 "-B num" "-E code" "-g group" "-j jail" 141 printf "\t [%s] [%s] [%s] [%s] [%s] [%s]\n" \ 142 "-k name" "-K num" "-N count" "-o file" "-O cmd" "-p pid" 143 printf "\t [%s] [%s] [%s] [%s] [%s] [%s]\n" \ 144 "-r regex" "-t test" "-T time" "-u user" "-X profile" \ 145 "-z regex" 146 printf "\t probe[,...] [args ...]\n" 147 printf " %s -l [-fmnPqy] [-r regex] [probe ...]\n" "$pgm" 148 printf " %s -Q [-1qy] [-r regex]\n" "$pgm" 149 printf "\n" 150 printf "$optfmt" "-1" \ 151 "Print one line per process/profile (Default; disables \`-R')." 152 printf "$optfmt" "-B num" \ 153 "Maximum process arguments to display (Default $_MAX_ARGS)." 154 printf "$optfmt" "-d" \ 155 "Debug. Send dtrace(1) script to stdout instead of executing." 156 printf "$optfmt" "-e" \ 157 "Exit after compiling request but prior to enabling probes." 158 printf "$optfmt" "-E code" \ 159 "DTrace code for event details. If \`-', read from stdin." 160 printf "$optfmt" "-f" \ 161 "Enable probe matching the specified function name." 162 printf "$optfmt" "-F" \ 163 "Coalesce trace output by function." 164 printf "$optfmt" "-g group" \ 165 "Group filter. Only show processes matching group name/gid." 166 printf "$optfmt" "-j jail" \ 167 "Jail filter. Only show processes matching jail name/jid." 168 printf "$optfmt" "-k name" \ 169 "Only show processes matching name." 170 printf "$optfmt" "-K num" \ 171 "Maximum directory depth to display (Default $_MAX_DEPTH)." 172 printf "$optfmt" "-l" \ 173 "List available probes on standard output and exit." 174 printf "$optfmt" "-m" \ 175 "Enable probe matching the specified module name." 176 printf "$optfmt" "-n" \ 177 "Enable probe matching the specified probe name." 178 printf "$optfmt" "-N count" \ 179 "Exit after count matching entries (Default 0 for disabled)." 180 printf "$optfmt" "-o file" \ 181 "Set output file. If \`-', the path \`/dev/stdout' is used." 182 printf "$optfmt" "-O cmd" \ 183 "Execute cmd for each event." 184 printf "$optfmt" "-p pid" \ 185 "Process id filter. Only show processes with matching pid." 186 printf "$optfmt" "-P" \ 187 "Enable probe matching the specified provider name." 188 printf "$optfmt" "-q" \ 189 "Quiet. Hide informational messages and all dtrace(1) errors." 190 printf "$optfmt" "-Q" \ 191 "List available profiles in DWATCH_PROFILES_PATH and exit." 192 printf "$optfmt" "-r regex" \ 193 "Filter. Only show blocks matching awk(1) regular expression." 194 printf "$optfmt" "-R" \ 195 "Show parent, grandparent, and ancestor of process." 196 printf "$optfmt" "-t test" \ 197 "Test clause (predicate) to limit events (Default none)." 198 printf "$optfmt" "-T time" \ 199 "Timeout. Format is \`\#[smhd]' or simply \`\#' for seconds." 200 printf "$optfmt" "-u user" \ 201 "User filter. Only show processes matching user name/uid." 202 printf "$optfmt" "-v" \ 203 "Verbose. Show all errors from dtrace(1)." 204 printf "$optfmt" "-V" \ 205 "Report dwatch version on standard output and exit." 206 printf "$optfmt" "-w" \ 207 "Permit destructive actions (copyout*, stop, panic, etc.)." 208 printf "$optfmt" "-x" \ 209 "Trace. Print \`<probe-id>' when a probe is triggered." 210 printf "$optfmt" "-X profile" \ 211 "Load profile name from DWATCH_PROFILES_PATH." 212 printf "$optfmt" "-y" \ 213 "Always treat stdout as console (enable colors/columns/etc.)." 214 printf "$optfmt" "-z regex" \ 215 "Only show processes matching awk(1) regular expression." 216 die 217} 218 219dtrace_cmd() 220{ 221 local status stdout 222 local timeout= 223 224 if [ "$1" = "-t" ]; then 225 shift 226 [ "$TIMEOUT" ] && timeout=1 227 fi 228 229 exec 3>&1 230 stdout=3 231 232 # 233 # Filter dtrace(1) stderr while preserving exit status 234 # 235 status=$( 236 exec 4>&1 237 to_status=4 238 ( trap 'echo $? >&$to_status' EXIT 239 eval $SUDO ${timeout:+timeout \"\$TIMEOUT\"} dtrace \ 240 \"\$@\" 2>&1 ${QUIET:+2> /dev/null} >&$stdout 241 ) | dtrace_stderr_filter >&2 242 ) 243 244 return $status 245} 246 247dtrace_stderr_filter() 248{ 249 if [ "$VERBOSE" ]; then 250 cat 251 return 252 # NOTREACHED 253 fi 254 255 awk ' # Start awk(1) stderr-filter 256 /[[:digit:]]+ drops? on CPU [[:digit:]]+/ { next } 257 /failed to write to <stdout>: No such file or directory/ { next } 258 /failed to write to <stdout>: Broken pipe/ { next } 259 /processing aborted: Broken pipe/ { next } 260 /invalid address \(0x[[:xdigit:]]+\) in action #[[:digit:]]+/ { next } 261 /out of scratch space in action #[[:digit:]]+/ { next } 262 /^Bus error$/ { next } 263 { print; fflush() } 264 ' # END-QUOTE 265} 266 267expand_probe() 268{ 269 local OPTIND=1 OPTARG flag 270 local type= 271 272 while getopts t: flag; do 273 case "$flag" in 274 t) type="$OPTARG" ;; 275 esac 276 done 277 shift $(( $OPTIND - 1 )) 278 279 local probe="$1" 280 case "$probe" in 281 *:*) 282 echo "$probe" 283 return $SUCCESS 284 ;; 285 esac 286 287 dtrace_cmd -l | awk -v probe="$probe" -v type="$type" ' 288 # Start awk(1) processor 289 #################################################### BEGIN 290 BEGIN { getline dtrace_header } 291 #################################################### FUNCTIONS 292 function dump(unused1,unused2) { 293 if (n) { 294 if (NcF[n] == 1) f = N2F[n] 295 if (NcM[n] == 1) m = N2M[n] 296 if (NcP[n] == 1) p = N2P[n] 297 } else if (f) { 298 if (FcM[f] == 1) m = F2M[f] 299 if (FcP[f] == 1) p = F2P[f] 300 if (FcN[f] == 0 && found) n = "entry" 301 } else if (m) { 302 if (McP[m] == 1) p = M2P[m] 303 } 304 printf "%s:%s:%s:%s\n", p, m, f, n 305 exit !found 306 } 307 function inFMP() { return probe in F || probe in M || probe in P } 308 function inNMP() { return probe in N || probe in M || probe in P } 309 function inNFP() { return probe in N || probe in F || probe in P } 310 function inNFM() { return probe in N || probe in F || probe in M } 311 function diva(value, peerA, peerB, peerC) { 312 return value >= peerA && value >= peerB && value >= peerC 313 } 314 #################################################### MAIN 315 type == "name" && $NF != probe { next } 316 type == "function" && NF >=4 && $(NF-1) != probe { next } 317 type == "module" && NF == 5 && $(NF-2) != probe { next } 318 type == "provider" && $2 != probe { next } 319 type || $2 == probe || $3 == probe || $4 == probe || $5 == probe { 320 P[_p = $2]++ 321 M[_m = (NF >= 5 ? $(NF-2) : "")]++ 322 F[_f = (NF >= 4 ? $(NF-1) : "")]++ 323 N[_n = $NF]++ 324 if (N2F[_n] != _f) NcF[_n]++; N2F[_n] = _f 325 if (N2M[_n] != _m) NcM[_n]++; N2M[_n] = _m 326 if (N2P[_n] != _p) NcP[_n]++; N2P[_n] = _p 327 if (_n !~ /entry|return/) { 328 if (F2N[_f] != _n) FcN[_f]++ 329 F2N[_f] = _n 330 } 331 if (F2M[_f] != _m) FcM[_f]++; F2M[_f] = _m 332 if (F2P[_f] != _p) FcP[_f]++; F2P[_f] = _p 333 if (M2P[_m] != _p) McP[_m]++; M2P[_m] = _p 334 } 335 #################################################### END 336 END { 337 if (type == "name") dump(n = probe, found = probe in N) 338 if (type == "function") dump(f = probe, found = probe in F) 339 if (type == "module") dump(m = probe, found = probe in M) 340 if (type == "provider") dump(p = probe, found = probe in P) 341 if (probe in N) { 342 found = 1 343 if (!inFMP()) dump(n = probe) 344 if (diva(F[probe], N[probe], M[probe], P[probe])) 345 dump(f = probe) 346 if (diva(M[probe], N[probe], F[probe], P[probe])) 347 dump(m = probe) 348 if (diva(P[probe], N[probe], F[probe], M[probe])) 349 dump(p = probe) 350 dump(n = probe) # N is the diva 351 } else if (probe in F) { 352 found = 1 353 if (!inNMP()) dump(f = probe) 354 if (diva(N[probe], F[probe], M[probe], P[probe])) 355 dump(n = probe) 356 if (diva(M[probe], F[probe], N[probe], P[probe])) 357 dump(m = probe) 358 if (diva(P[probe], F[probe], N[probe], M[probe])) 359 dump(p = probe) 360 dump(f = probe) # F is the diva 361 } else if (probe in M) { 362 found = 1 363 if (!inNFP()) dump(m = probe) 364 if (diva(N[probe], M[probe], F[probe], P[probe])) 365 dump(n = probe) 366 if (diva(F[probe], M[probe], N[probe], P[probe])) 367 dump(f = probe) 368 if (diva(P[probe], M[probe], N[probe], F[probe])) 369 dump(p = probe) 370 dump(m = probe) # M is the diva 371 } else if (probe in P) { 372 found = 1 373 if (!inNFM()) dump(p = probe) 374 if (diva(N[probe], P[probe], F[probe], M[probe])) 375 dump(n = probe) 376 if (diva(F[probe], P[probe], N[probe], M[probe])) 377 dump(f = probe) 378 if (diva(M[probe], P[probe], N[probe], F[probe])) 379 dump(m = probe) 380 dump(p = probe) # P is the diva 381 } 382 if (!found) print probe 383 exit !found 384 } 385 ' # END-QUOTE 386} 387 388list_probes() 389{ 390 local OPTIND=1 OPTARG flag 391 local column=0 header="PROVIDER:MODULE:FUNCTION:NAME" 392 local filter= quiet= type= 393 394 while getopts f:qt: flag; do 395 case "$flag" in 396 f) filter="$OPTARG" ;; 397 q) quiet=1 ;; 398 t) type="$OPTARG" ;; 399 esac 400 done 401 shift $(( $OPTIND - 1 )) 402 403 if [ $# -eq 0 ]; then 404 case "$type" in 405 provider) column=1 header="PROVIDER" ;; 406 module) column=2 header="MODULE" ;; 407 function) column=3 header="FUNCTION" ;; 408 name) column=4 header="NAME" ;; 409 esac 410 fi 411 412 [ "$quiet" ] || echo "$header" 413 414 local arg probe= 415 for arg in "$@"; do 416 arg=$( expand_probe -t "$type" -- "$arg" ) 417 probe="$probe${probe:+, }$arg" 418 done 419 420 dtrace_cmd -l${probe:+n "$probe"} | awk -v pattern="$( 421 # Prevent backslashes from being lost 422 echo "$filter" | awk 'gsub(/\\/,"&&")||1' 423 )" -v want="$column" -v console="$CONSOLE" ' 424 BEGIN { getline dtrace_header } 425 function ans(seq) { return console ? "\033[" seq "m" : "" } 426 NF > 3 && $(NF-1) ~ /^#/ { next } 427 !_[$0 = column[0] = sprintf("%s:%s:%s:%s", 428 column[1] = $2, 429 column[2] = (NF >= 5 ? $(NF-2) : ""), 430 column[3] = (NF >= 4 ? $(NF-1) : ""), 431 column[4] = $NF)]++ && 432 !__[$0 = column[want]]++ && 433 gsub(pattern, ans("31;1") "&" ans("39;22")) { 434 print | "sort" 435 } 436 END { close("sort") } 437 ' # END-QUOTE 438 439 exit $SUCCESS 440} 441 442list_profiles() 443{ 444 local OPTIND=1 OPTARG flag 445 local filter= oneline= quiet= 446 447 while getopts 1f:q flag; do 448 case "$flag" in 449 1) oneline=1 ;; 450 f) filter="$OPTARG" ;; 451 q) quiet=1 ;; 452 esac 453 done 454 shift $(( $OPTIND - 1 )) 455 456 # Prevent backslashes from being lost 457 filter=$( echo "$filter" | awk 'gsub(/\\/,"&&")||1' ) 458 459 # Build a list of profiles available 460 local profiles 461 profiles=$( { IFS=: 462 for dir in $DWATCH_PROFILES_PATH; do 463 [ -d "$dir" ] || continue 464 for path in $dir/*; do 465 [ -f "$path" ] || continue 466 name="${path##*/}" 467 [ "$name" = "${name%%[!0-9A-Za-z_-]*}" ] || 468 continue 469 echo $name 470 done 471 done 472 } | sort -u ) 473 474 # Get the longest profile name 475 local longest_profile_name 476 longest_profile_name=$( echo "$profiles" | 477 awk -v N=0 '(L = length($0)) > N { N = L } END { print N }' ) 478 479 # Get the width of the terminal 480 local max_size="$( stty size 2> /dev/null )" 481 : ${max_size:=24 80} 482 local max_width="${max_size#*[$IFS]}" 483 484 # Determine how many columns we can display 485 local x=$longest_profile_name ncols=1 486 [ "$QUIET" ] || x=$(( $x + 8 )) # Accommodate leading tab character 487 x=$(( $x + 3 + $longest_profile_name )) # Preload end of next column 488 while [ $x -lt $max_width ]; do 489 ncols=$(( $ncols + 1 )) 490 x=$(( $x + 3 + $longest_profile_name )) 491 done 492 493 # Output single lines if sent to a pipe 494 if [ "$oneline" ]; then 495 echo "$profiles" | awk -v filter="$filter" -v cons="$CONSOLE" ' 496 function ans(s) { return cons ? "\033[" s "m" : "" } 497 gsub(filter, ans("31;1") "&" ans("39;22")) 498 ' # END-QUOTE 499 exit $SUCCESS 500 fi 501 502 [ "$quiet" ] || echo PROFILES: 503 echo "$profiles" | awk \ 504 -v colsize=$longest_profile_name \ 505 -v console="$CONSOLE" \ 506 -v ncols=$ncols \ 507 -v quiet="$quiet" \ 508 -v filter="$filter" \ 509 ' # Begin awk(1) processor 510 function ans(seq) { return console ? "\033[" seq "m" : "" } 511 BEGIN { 512 row_item[1] = "" 513 replace = ans("31;1") "&" ans("39;22") 514 ansi_offset = length(replace) - 1 515 } 516 function print_row() 517 { 518 cs = colsize + ansi_offset * \ 519 gsub(filter, replace, row_item[1]) 520 printf "%s%-*s", quiet ? "" : "\t", cs, row_item[1] 521 for (i = 2; i <= cur_col; i++) { 522 cs = colsize + ansi_offset * \ 523 gsub(filter, replace, row_item[i]) 524 printf " %-*s", cs, row_item[i] 525 } 526 printf "\n" 527 } 528 $0 ~ filter { 529 n++ 530 cur_col = ((n - 1) % ncols) + 1 531 row_item[cur_col] = $0 532 if (cur_col == ncols) print_row() 533 } 534 END { if (cur_col < ncols) print_row() } 535 ' # END-QUOTE 536 537 exit $SUCCESS 538} 539 540shell_escape() 541{ 542 echo "$*" | awk 'gsub(/'\''/, "&\\\\&&")||1' 543} 544 545load_profile() 546{ 547 local profile="$1" 548 549 [ "$profile" ] || 550 die "missing profile argument (\`$pgm -Q' to list profiles)" 551 552 local oldIFS="$IFS" 553 local dir found= 554 local ARGV= 555 556 [ $COUNT -gt 0 ] && ARGV="$ARGV -N $COUNT" 557 [ "$DEBUG" ] && ARGV="$ARGV -d" 558 [ "$DESTRUCTIVE_ACTIONS" ] && ARGV="$ARGV -w" 559 [ "$EXIT_AFTER_COMPILE" ] && ARGV="$ARGV -e" 560 [ "$GROUP" ] && ARGV="$ARGV -g $GROUP" 561 [ "$JID" ] && ARGV="$ARGV -j $JID" 562 [ $MAX_ARGS -ne $_MAX_ARGS ] && ARGV="$ARGV -B $MAX_ARGS" 563 [ $MAX_DEPTH -ne $_MAX_DEPTH ] && ARGV="$ARGV -K $MAX_DEPTH" 564 [ "$ONELINE" ] && ARGV="$ARGV -1" 565 [ "$PID" ] && ARGV="$ARGV -p $PID" 566 [ "$PSTREE" ] && ARGV="$ARGV -R" 567 [ "$QUIET" ] && ARGV="$ARGV -q" 568 [ "$TIMEOUT" ] && ARGV="$ARGV -T $TIMEOUT" 569 [ "$TRACE" ] && ARGV="$ARGV -x" 570 [ "$USER" ] && ARGV="$ARGV -u $USER" 571 [ "$VERBOSE" ] && ARGV="$ARGV -v" 572 573 [ "$FILTER" ] && 574 ARGV="$ARGV -r '$( shell_escape "$FILTER" )'" 575 [ "$EXECREGEX" ] && 576 ARGV="$ARGV -z '$( shell_escape "$EXECREGEX" )'" 577 [ "$CUSTOM_DETAILS" ] && 578 ARGV="$ARGV -E '$( shell_escape "$EVENT_DETAILS" )'" 579 [ "$CUSTOM_TEST" ] && 580 ARGV="$ARGV -t '$( shell_escape "$CUSTOM_TEST" )'" 581 [ "$OUTPUT" ] && 582 ARGV="$ARGV -o '$( shell_escape "$OUTPUT" )'" 583 [ "$OUTPUT_CMD" ] && 584 ARGV="$ARGV -O '$( shell_escape "$OUTPUT_CMD" )'" 585 586 case "$PROBE_TYPE" in 587 provider) ARGV="$ARGV -P" ;; 588 module) ARGV="$ARGV -m" ;; 589 function) ARGV="$ARGV -f" ;; 590 name) ARGV="$ARGV -n" ;; 591 esac 592 593 IFS=: 594 for dir in $DWATCH_PROFILES_PATH; do 595 [ -d "$dir" ] || continue 596 [ -f "$dir/$profile" ] || continue 597 PROFILE="$profile" found=1 598 info "Sourcing $profile profile [found in %s]" "$dir" 599 . "$dir/$profile" 600 break 601 done 602 IFS="$oldIFS" 603 604 [ "$found" ] || 605 die "no module named \`$profile' (\`$pgm -Q' to list profiles)" 606} 607 608pproc() 609{ 610 local OPTIND=1 OPTARG flag 611 local P= N=0 612 613 while getopts P: flag; do 614 case "$flag" in 615 P) P="$OPTARG" ;; 616 esac 617 done 618 shift $(( OPTIND - 1 )) 619 620 local proc=$1 621 if [ ! "$proc" ]; then 622 if [ "$P" = "0" ]; then 623 proc="curthread->td_proc" 624 else 625 proc="this->proc ? this->proc->p_pptr : NULL" 626 fi 627 fi 628 629 awk 'NR > 1 && $0 { $0 = "\t" $0 } 630 gsub(/\\\t/, "\t") || 1 631 ' <<-EOFPREAMBLE 632 this->proc = $proc; 633 this->uid$P = this->proc ? this->proc->p_ucred->cr_uid : -1; 634 this->gid$P = this->proc ? this->proc->p_ucred->cr_rgid : -1; 635 this->pid$P = this->proc ? this->proc->p_pid : -1; 636 this->jid$P = this->proc ? this->proc->p_ucred->cr_prison->pr_id : -1; 637 638 this->p_args = this->proc ? this->proc->p_args : 0; 639 this->ar_length = this->p_args ? this->p_args->ar_length : 0; 640 this->ar_args = (char *)(this->p_args ? this->p_args->ar_args : 0); 641 642 this->args$P = this->arg${P}_$N = this->ar_length > 0 ? 643 \ this->ar_args : stringof(this->proc->p_comm); 644 this->len = this->ar_length > 0 ? strlen(this->ar_args) + 1 : 0; 645 this->ar_args += this->len; 646 this->ar_length -= this->len; 647 648 EOFPREAMBLE 649 650 awk -v P=$P -v MAX_ARGS=$MAX_ARGS ' 651 $0 { $0 = "\t" $0 } 652 buf = buf $0 "\n" { } 653 END { 654 while (++N <= MAX_ARGS) { 655 $0 = buf 656 gsub(/P/, P) 657 gsub(/N/, N) 658 gsub(/\\\t/, "\t") 659 sub(/\n$/, "") 660 print 661 } 662 } 663 ' <<-EOFARGS 664 this->argP_N = this->ar_length > 0 ? this->ar_args : ""; 665 this->argsP = strjoin(this->argsP, 666 \ strjoin(this->argP_N != "" ? " " : "", this->argP_N)); 667 this->len = this->ar_length > 0 ? strlen(this->ar_args) + 1 : 0; 668 this->ar_args += this->len; 669 this->ar_length -= this->len; 670 671 EOFARGS 672 673 N=$(( $MAX_ARGS + 1 )) 674 awk 'sub(/^\\\t/, "\t") || 1, $0 = "\t" $0' <<-EOFPROC 675 this->arg${P}_$N = this->ar_length > 0 ? "..." : ""; 676 this->args$P = strjoin(this->args$P, 677 \ strjoin(this->arg${P}_$N != "" ? " " : "", this->arg${P}_$N)); 678 EOFPROC 679} 680 681pproc_dump() 682{ 683 local OPTIND=1 OPTARG flag 684 local verbose= 685 686 while getopts v flag; do 687 case "$flag" in 688 v) verbose=1 ;; 689 esac 690 done 691 shift $(( $OPTIND - 1 )) 692 693 local P=$1 694 if [ "$verbose" ]; then 695 awk -v P=$P ' 696 BEGIN { printf "\t" } 697 NR > 1 && $0 { $0 = "\t" $0 } 698 buf = buf $0 "\n" { } 699 END { 700 $0 = buf 701 if (P < 3) S = sprintf("%" 7-2*(P+1) "s", "") 702 gsub(/S/, S) 703 gsub(/B/, P < 3 ? "\\" : "") 704 gsub(/\\\t/, "\t") 705 sub(/\n$/, "") 706 print 707 } 708 ' <<-EOFPREAMBLE 709 printf(" SB-+= %05d %d.%d %s\n", 710 \ this->pid$P, this->uid$P, this->gid$P, this->args$P); 711 EOFPREAMBLE 712 else 713 cat <<-EOFPREAMBLE 714 printf("%s", this->args$P); 715 EOFPREAMBLE 716 fi 717} 718 719############################################################ MAIN 720 721# If we're running as root, no need for sudo(8) 722[ "$( id -u )" != 0 ] && type sudo > /dev/null 2>&1 && SUDO=sudo 723 724# 725# Process command-line options 726# 727while getopts 1B:deE:fFg:j:k:K:lmnN:o:O:p:PqQr:Rt:T:u:vVwxX:yz: flag; do 728 case "$flag" in 729 1) ONELINE=1 PSTREE= ;; 730 B) MAX_ARGS="$OPTARG" ;; 731 d) DEBUG=1 ;; 732 e) EXIT_AFTER_COMPILE=1 ;; 733 E) CUSTOM_DETAILS=1 734 EVENT_DETAILS="${EVENT_DETAILS%;}" 735 [ "$EVENT_DETAILS" ] && EVENT_DETAILS="$EVENT_DETAILS; 736 printf(\" \"); 737 " # END-QUOTE 738 # Read event code from stdin if `-' is argument 739 [ "$OPTARG" = "-" ] && OPTARG=$( cat ) 740 EVENT_DETAILS="$EVENT_DETAILS$OPTARG" ;; 741 f) PROBE_TYPE=function ;; 742 F) PROBE_COALESCE=1 ;; 743 g) GROUP="$OPTARG" ;; 744 j) JID="$OPTARG" ;; 745 k) EXECNAME="$EXECNAME${EXECNAME:+ }$OPTARG" 746 case "$OPTARG" in 747 \**\*) name="${OPTARG%\*}" 748 predicate="strstr(execname, \"${name#\*}\") != NULL" ;; 749 \**) name="${OPTARG#\*}" 750 predicate="strstr(execname, \"$name\") == (execname +" 751 predicate="$predicate strlen(execname) - ${#name})" ;; 752 *\*) predicate="strstr(execname, \"${OPTARG%\*}\") == execname" ;; 753 *) predicate="execname == \"$OPTARG\"" 754 esac 755 EVENT_TEST="$predicate${EVENT_TEST:+ || 756 ($EVENT_TEST)}" ;; 757 K) MAX_DEPTH="$OPTARG" ;; 758 l) LIST=1 ;; 759 m) PROBE_TYPE=module ;; 760 n) PROBE_TYPE=name ;; 761 N) COUNT="$OPTARG" ;; 762 o) OUTPUT="$OPTARG" ;; 763 O) OUTPUT_CMD="$OPTARG" ;; 764 p) PID="$OPTARG" ;; 765 P) PROBE_TYPE=provider ;; 766 q) QUIET=1 ;; 767 Q) LIST_PROFILES=1 ;; 768 r) FILTER="$OPTARG" ;; 769 R) PSTREE=1 ;; 770 t) CUSTOM_TEST="${CUSTOM_TEST:+($CUSTOM_TEST) && }$OPTARG" ;; 771 T) TIMEOUT="$OPTARG" ;; 772 u) USER="$OPTARG" ;; 773 v) VERBOSE=1 ;; 774 V) vers="${VERSION#\$*[:\$]}" 775 vers="${vers% \$}" 776 printf "%s: %s\n" "$pgm" "${vers# }" 777 exit ;; 778 w) DESTRUCTIVE_ACTIONS=1 ;; 779 x) TRACE=1 ;; 780 X) USE_PROFILE=1 PROFILE="$OPTARG" ;; 781 y) CONSOLE=1 CONSOLE_FORCE=1 ;; 782 z) EXECREGEX="$OPTARG" ;; 783 *) usage 784 # NOTREACHED 785 esac 786done 787shift $(( $OPTIND - 1 )) 788 789# 790# List probes if `-l' was given 791# 792[ "$LIST" ] && 793 list_probes -f "$FILTER" ${QUIET:+-q} -t "$PROBE_TYPE" -- "$@" 794 # NOTREACHED 795 796# 797# List profiles if `-Q' was given 798# 799[ "$LIST_PROFILES" ] && 800 list_profiles ${ONELINE:+-1} -f "$FILTER" ${QUIET:+-q} 801 # NOTREACHED 802 803# 804# Validate number of arguments 805# 806if [ ! "$PROFILE" ]; then 807 # If not given `-X profile' then a probe argument is required 808 [ $# -gt 0 ] || usage # NOTREACHED 809fi 810 811# 812# Validate `-N count' option argument 813# 814case "$COUNT" in 815"") usage "-N option requires a number argument" ;; # NOTREACHED 816*[!0-9]*) usage "-N argument must be a number" ;; # NOTREACHED 817esac 818 819# 820# Validate `-B num' option argument 821# 822case "$MAX_ARGS" in 823"") usage "-B option requires a number argument" ;; # NOTREACHED 824*[!0-9]*) usage "-B argument must be a number" ;; # NOTREACHED 825esac 826 827# 828# Validate `-K num' option argument 829# 830case "$MAX_DEPTH" in 831"") usage "-K option requires a number argument" ;; # NOTREACHED 832*[!0-9]*) usage "-K argument must be a number" ;; # NOTREACHED 833esac 834 835# 836# Validate `-j jail' option argument 837# 838case "$JID" in 839"") : fall through ;; 840*[!0-9]*) JID=$( jls -j "$JID" jid ) || exit ;; 841esac 842 843# 844# Validate `-u user' option argument 845# 846case "$USER" in 847"") : fall through ;; 848*[![:alnum:]_-]*) RUID="$USER" ;; 849*[!0-9]*) RUID=$( id -u "$USER" 2> /dev/null ) || die "No such user: $USER" ;; 850*) RUID=$USER 851esac 852 853# 854# Validate `-g group' option argument 855# 856case "$GROUP" in 857"") : fall-through ;; 858*[![:alnum:]_-]*) RGID="$GROUP" ;; 859*[!0-9]*) 860 RGID=$( getent group | awk -F: -v group="$GROUP" ' 861 $1 == group { print $3; exit found=1 } 862 END { exit !found } 863 ' ) || die "No such group: $GROUP" ;; 864*) RGID=$GROUP 865esac 866 867# 868# Expand probe argument into probe(s) 869# 870case "$1" in 871-*) : Assume dtrace options such as "-c cmd" or "-p pid" ;; # No probe(s) given 872*) 873 PROBE_ARG="$1" 874 shift 875esac 876if [ "$PROBE_ARG" ]; then 877 oldIFS="$IFS" 878 IFS="$IFS," 879 for arg in $PROBE_ARG; do 880 arg=$( expand_probe -t "$PROBE_TYPE" -- "$arg" ) 881 PROBE="$PROBE${PROBE:+, }$arg" 882 done 883 IFS="$oldIFS" 884fi 885 886# 887# Developer switch 888# 889[ "$DEBUG" -a "$EXIT_AFTER_COMPILE" -a "$VERBOSE" ] && DEVELOPER=1 DEBUG= 890 891# 892# Set default event details if `-E code' was not given 893# 894[ "$CUSTOM_DETAILS" ] || EVENT_DETAILS=$( pproc_dump 0 ) 895 896# 897# Load profile if given `-X profile' 898# 899[ "$USE_PROFILE" ] && load_profile "$PROFILE" 900[ "$PROBE" ] || die "PROBE not defined by profile and none given as argument" 901 902# 903# Show the user what's being watched 904# 905[ "$DEBUG$EXIT_AFTER_COMPILE" ] || info "Watching '$PROBE' ..." 906 907# 908# Header for watched probe entry 909# 910case "$PROBE" in 911*,*) : fall-through ;; 912*:execve:entry|execve:entry) 913 ACTIONS=$( awk 'gsub(/\\\t/, "\t") || 1' <<-EOF 914 $PROBE /* probe ID $ID */ 915 {${TRACE:+ 916 \ printf("<$ID>");} 917 \ this->caller_execname = execname; 918 } 919 EOF 920 ) 921 PROBE="${PROBE%entry}return" 922 ID=$(( $ID + 1 )) 923 EVENT_TEST="execname != this->caller_execname${EVENT_TEST:+ && 924 ($EVENT_TEST)}" 925 EVENT_TAG='printf("%d.%d %s[%d]: ", 926 this->uid1, this->gid1, this->caller_execname, this->pid1);' 927 ;; 928esac 929 930# 931# Jail clause/predicate 932# 933if [ "$JID" ]; then 934 prison_id="curthread->td_proc->p_ucred->cr_prison->pr_id" 935 EVENT_TEST="$prison_id == $JID${EVENT_TEST:+ && 936 ($EVENT_TEST)}" 937fi 938 939# 940# Custom test clause/predicate 941# 942if [ "$CUSTOM_TEST" ]; then 943 case "$EVENT_TEST" in 944 "") EVENT_TEST="$CUSTOM_TEST" ;; 945 *) EVENT_TEST="$EVENT_TEST && 946 ($CUSTOM_TEST)" 947 esac 948fi 949 950# 951# Make sure dynamic code has trailing semi-colons if non-NULL 952# 953EVENT_TAG="${EVENT_TAG%;}${EVENT_TAG:+;}" 954EVENT_DETAILS="${EVENT_DETAILS%;}${EVENT_DETAILS:+;}" 955 956# 957# DTrace script 958# 959# If `-d' is given, script is sent to stdout for debugging 960# If `-c count", `-g group', `-r regex', or `-u user' is given, run script with 961# dtrace and send output to awk(1) post-processor (making sure to preserve the 962# exit code returned by dtrace invocation). Otherwise, simply run script with 963# dtrace and then exit. 964# 965exec 9<<EOF 966$PROBE /* probe ID 2 */ 967{${TRACE:+ 968 printf("<2>"); 969} 970 /* 971 * Examine process, parent process, and grandparent process details 972 */ 973 974 /******************* CURPROC *******************/ 975 976 $( pproc -P0 ) 977 978 /******************* PPARENT *******************/ 979 980 $( if [ "$PSTREE" ]; then pproc -P1; else echo -n \ 981 "this->proc = this->proc ? this->proc->p_pptr : NULL; 982 this->pid1 = this->proc ? this->proc->p_pid : -1; 983 this->uid1 = this->proc ? this->proc->p_ucred->cr_uid : -1; 984 this->gid1 = this->proc ? this->proc->p_ucred->cr_rgid : -1; 985 this->jid1 = this->proc ? this->proc->p_ucred->cr_prison->pr_id : -1;" 986 fi ) 987 988 /******************* GPARENT *******************/ 989 990 $( [ "$PSTREE" ] && pproc -P2 ) 991 992 /******************* APARENT *******************/ 993 994 $( [ "$PSTREE" ] && pproc -P3 ) 995} 996EOF 997PSARGS_ACTION=$( cat <&9 ) 998[ "$OUTPUT" -a ! "$CONSOLE_FORCE" ] && CONSOLE= 999{ 1000 if [ "$DEBUG" ]; then 1001 # Send script to stdout 1002 cat 1003 exit 1004 fi 1005 1006 if [ "$CUSTOM_TEST$EXECNAME$JID$OUTPUT$TIMEOUT$TRACE$VERBOSE" -a \ 1007 ! "$QUIET" ] 1008 then 1009 msg=Setting 1010 [ "$CUSTOM_TEST" ] && msg="$msg test: $CUSTOM_TEST" 1011 [ "$EXECNAME" ] && msg="$msg execname: $EXECNAME" 1012 [ "$JID" ] && msg="$msg jid: $JID" 1013 [ "$OUTPUT" ] && msg="$msg output: $OUTPUT" 1014 [ "$TIMEOUT" ] && msg="$msg timeout: $TIMEOUT" 1015 [ "$TRACE" ] && msg="$msg trace: $TRACE" 1016 [ "$VERBOSE" ] && msg="$msg verbose: $VERBOSE" 1017 info "$msg" 1018 fi 1019 1020 exec 3>&1 1021 console_stdout=3 1022 1023 # 1024 # Developer debugging aide 1025 # 1026 if [ "$DEVELOPER" ]; then 1027 # 1028 # Run, capture the error line, and focus it 1029 # 1030 # Example error text to capture line number from: 1031 # dtrace: failed to compile script /dev/stdin: line 669: ... 1032 # 1033 errline= 1034 stdin_buf=$( cat ) 1035 stderr_buf=$( echo "$stdin_buf" | 1036 dtrace_cmd -t -es /dev/stdin "$@" 2>&1 > /dev/null ) 1037 status=$? 1038 if [ "$stderr_buf" ]; then 1039 errline=$( echo "$stderr_buf" | awk ' 1040 BEGIN { 1041 ti = "\033[31m" 1042 te = "\033[39m" 1043 } 1044 { line = $0 } 1045 sub(/.*: line /, "") && sub(/:.*/, "") { 1046 print # to errline 1047 sub("line " $0, ti "&" te, line) 1048 } 1049 { print line > "/dev/stderr" } 1050 ' 2>&3 ) 1051 fi 1052 if [ "$errline" ]; then 1053 echo "$stdin_buf" | awk -v line="${errline%%[^0-9]*}" ' 1054 BEGIN { 1055 start = line < 10 ? 1 : line - 10 1056 end = line + 10 1057 slen = length(sprintf("%u", start)) 1058 elen = length(sprintf("%u", end)) 1059 N = elen > slen ? elen : slen 1060 ti[line] = "\033[31m" 1061 te[line] = "\033[39m" 1062 fmt = "%s%*u %s%s\n" 1063 } 1064 NR < start { next } 1065 NR == start, NR == end { 1066 printf(fmt, ti[NR], N, NR, $0, te[NR]) 1067 } 1068 NR > end { exit } 1069 ' # END-QUOTE 1070 fi 1071 exit $status 1072 fi 1073 1074 if [ $COUNT -eq 0 -a ! "$EXECREGEX$FILTER$GROUP$OUTPUT_CMD$PID$USER" ] 1075 then 1076 case "$OUTPUT" in 1077 -) output_path=/dev/stdout ;; 1078 *) output_path="$OUTPUT" 1079 esac 1080 1081 # Run script without pipe to awk post-processor 1082 dtrace_cmd -t \ 1083 ${DESTRUCTIVE_ACTIONS:+-w} \ 1084 ${EXIT_AFTER_COMPILE:+-e} \ 1085 ${OUTPUT:+-o "$output_path"} \ 1086 -s /dev/stdin \ 1087 "$@" 1088 exit 1089 fi 1090 1091 # Prevent backslashes from being lost 1092 FILTER=$( echo "$FILTER" | awk 'gsub(/\\/,"&&")||1' ) 1093 EXECREGEX=$( echo "$EXECREGEX" | awk 'gsub(/\\/,"&&")||1' ) 1094 1095 if [ ! "$QUIET" ]; then 1096 msg=Filtering 1097 [ "$EXECREGEX" ] && msg="$msg execregex: $EXECREGEX" 1098 [ "$FILTER" ] && msg="$msg filter: $FILTER" 1099 [ "$GROUP" ] && msg="$msg group: $GROUP" 1100 [ "$OUTPUT_CMD" ] && msg="$msg cmd: $OUTPUT_CMD" 1101 [ "$PID" ] && msg="$msg pid: $PID" 1102 [ "$USER" ] && msg="$msg user: $USER" 1103 [ $COUNT -gt 0 ] && msg="$msg count: $COUNT" 1104 info "$msg" 1105 fi 1106 1107 # 1108 # Send script output to post-processor for filtering 1109 # 1110 status=$( 1111 exec 4>&1 1112 to_status=4 1113 ( exec 5>&1; to_dtrace_stderr_filter=5; ( 1114 trap 'echo $? >&$to_status' EXIT 1115 eval $SUDO ${TIMEOUT:+timeout \"\$TIMEOUT\"} dtrace \ 1116 ${EXIT_AFTER_COMPILE:+-e} \ 1117 ${DESTRUCTIVE_ACTIONS:+-w} \ 1118 -s /dev/stdin \ 1119 \"\$@\" \ 1120 2>&$to_dtrace_stderr_filter \ 1121 ${QUIET:+2> /dev/null} 1122 ) | $SUDO awk \ 1123 -v cmd="$OUTPUT_CMD" \ 1124 -v console="$CONSOLE" \ 1125 -v count=$COUNT \ 1126 -v execregex="$EXECREGEX" \ 1127 -v filter="$FILTER" \ 1128 -v gid="$RGID" \ 1129 -v output="$OUTPUT" \ 1130 -v pid="$PID" \ 1131 -v pstree=$PSTREE \ 1132 -v quiet=$QUIET \ 1133 -v tty=$( ps -o tty= -p $$ ) \ 1134 -v uid="$RUID" \ 1135 ' # Start awk(1) post-processor 1136 ############################################ BEGIN 1137 BEGIN { 1138 true = 1 1139 ansi = "(\\033\\[[[:digit:];]+m)?" 1140 num = year = day = "[[:digit:]]+" 1141 month = "[[:alpha:]]+" 1142 date = year " " month " +" day 1143 time = "[012][0-9]:[0-5][0-9]:[0-5][0-9]" 1144 date_time = ansi date " +" time ansi 1145 name1 = "[^\\[]*" 1146 name2 = "[^\\n]*" 1147 if (output == "-") 1148 output = "/dev/stdout" 1149 1150 # 1151 # Field definitions 1152 # 1153 nexecmatches = 2 1154 execstart[1] = sprintf( \ 1155 "^(%s) (%s)\\.(%s) (%s)\\[(%s)\\]: ", 1156 date_time, num, num, name1, num) 1157 execstart[2] = sprintf( \ 1158 "\\n +\\\\?-\\+= (%s) (%s)\\.(%s) ", 1159 num, num, num) 1160 npidmatches = 2 1161 pidstart[1] = sprintf("^(%s) (%s)\\.(%s) (%s)\\[", 1162 date_time, num, num, name1) 1163 pidstart[2] = "\\n +\\\\?-\\+= " 1164 pidpreen[2] = "^0*" 1165 piddeflt[2] = "0" 1166 ngidmatches = 2 1167 gidstart[1] = sprintf("^(%s) (%s)\\.", date_time, num) 1168 gidstart[2] = sprintf("\\n +\\\\?-\\+= (%s) (%s)\\.", 1169 ansi num ansi, num) 1170 nuidmatches = 2 1171 uidstart[1] = sprintf("^(%s) ", date_time) 1172 uidstart[2] = sprintf("\\n +\\\\?-\\+= (%s) ", 1173 ansi num ansi) 1174 } 1175 ############################################ FUNCTIONS 1176 function strip(s) { gsub(/\033\[[0-9;]*m/, "", s); return s } 1177 function esc(str) { gsub(/'\''/, "&\\\\&&", str); return str } 1178 function arg(str) { return "'\''" esc(str) "'\''" } 1179 function env(var, str) { return var "=" arg(str) " " } 1180 function ans(seq) { return console ? "\033[" seq "m" : "" } 1181 function runcmd() { 1182 return system(sprintf("%s/bin/sh -c %s", 1183 env("TAG", strip(tag)) \ 1184 env("DETAILS", strip(details)), 1185 arg(cmd))) 1186 } 1187 function filter_block() { 1188 if (length(lines) < 1) return 0 1189 block_match = 0 1190 newstr = "" 1191 start = 1 1192 if (match(lines, "^(" date_time ") ")) { 1193 newstr = newstr substr(lines, 1, 1194 RSTART + RLENGTH - 1) 1195 start = RSTART + RLENGTH 1196 } 1197 replace = ans("31;1") "&" ans("39;22") 1198 workstr = substr(lines, start) 1199 if (gsub(filter, replace, workstr)) block_match = 1 1200 lines = newstr workstr 1201 return block_match 1202 } 1203 function filter_field(startre, fieldre, matchre, isword, 1204 preenre, defaultstr) 1205 { 1206 if (length(lines) < 1) return 0 1207 field_match = 0 1208 newstr = "" 1209 start = 1 1210 while ((workstr = substr(lines, start)) && 1211 (workstr ~ (startre fieldre))) 1212 { 1213 match(workstr, startre) 1214 start += end = RSTART + RLENGTH - 1 1215 newstr = newstr substr(workstr, 1, end) 1216 workstr = substr(workstr, end + 1) 1217 match(workstr, fieldre) 1218 start += end = RSTART + RLENGTH - 1 1219 field = matchstr = substr(workstr, 1, end) 1220 sub(preenre, "", matchstr) 1221 if (!matchstr) matchstr = defaultstr 1222 if (isword) { 1223 if (match(matchstr, matchre) && 1224 RSTART == 1 && 1225 RLENGTH == length(matchstr)) { 1226 field_match = 1 1227 field = ans(7) field ans(27) 1228 } 1229 } else { 1230 replace = ans(7) "&" ans(27) 1231 if (gsub(matchre, replace, matchstr)) { 1232 field_match = 1 1233 field = matchstr 1234 } 1235 } 1236 newstr = newstr field 1237 } 1238 lines = newstr workstr 1239 return field_match 1240 } 1241 function dump() { 1242 lines = block 1243 block = "" 1244 found = 0 1245 if (execregex != "") { 1246 for (n = 1; n <= nexecmatches; n++) 1247 if (filter_field(execstart[n], name2, 1248 execregex)) found = 1 1249 if (!found) return 1250 } 1251 if (pid != "") { 1252 for (n = 1; n <= npidmatches; n++) 1253 if (filter_field(pidstart[n], num, pid, 1254 true, pidpreen[n], 1255 piddeflt[n])) found = 1 1256 if (!found) return 1257 } 1258 if (gid != "") { 1259 for (n = 1; n <= ngidmatches; n++) 1260 if (filter_field(gidstart[n], num, 1261 gid, true)) found = 1 1262 if (!found) return 1263 } 1264 if (uid != "") { 1265 for (n = 1; n <= nuidmatches; n++) 1266 if (filter_field(uidstart[n], num, 1267 uid, true)) found = 1 1268 if (!found) return 1269 } 1270 if (filter != "" && !filter_block()) return 1271 if (lines) { 1272 stdout = 1 1273 if (output) { 1274 stdout = 0 1275 if (!console) lines = strip(lines) 1276 print lines > output 1277 } else if (cmd) { 1278 if (!quiet) print lines 1279 tag = details = lines 1280 sub(/: .*/, "", tag) 1281 sub(/.*: /, "", details) 1282 if (!console) tag = strip(tag) 1283 runcmd() 1284 } else print lines 1285 } 1286 fflush() 1287 ++matches 1288 } 1289 ############################################ MAIN 1290 { block = (block ? block "\n" : block) $0 } 1291 !pstree { dump() } 1292 $0 ~ sprintf("^%6s\\\\-\\+= %s ", "", num) { dump() } 1293 count && matches >= count { exit } 1294 ############################################ END 1295 END { 1296 dump() 1297 system(sprintf("pkill -t %s dtrace %s", tty, 1298 quiet ? "2> /dev/null" : "")) 1299 } 1300 ' >&$console_stdout ) | dtrace_stderr_filter >&2 1301 ) # status 1302 exit $status 1303 1304} <<EOF 1305#!/usr/sbin/dtrace -s 1306/* - 1307 * Copyright (c) 2014-2018 Devin Teske <dteske@FreeBSD.org> 1308 * All rights reserved. 1309 * Redistribution and use in source and binary forms, with or without 1310 * modification, are permitted provided that the following conditions 1311 * are met: 1312 * 1. Redistributions of source code must retain the above copyright 1313 * notice, this list of conditions and the following disclaimer. 1314 * 2. Redistributions in binary form must reproduce the above copyright 1315 * notice, this list of conditions and the following disclaimer in the 1316 * documentation and/or other materials provided with the distribution. 1317 * 1318 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS \`\`AS IS'' AND 1319 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 1320 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 1321 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 1322 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 1323 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 1324 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 1325 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 1326 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 1327 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 1328 * SUCH DAMAGE. 1329 * 1330 * $TITLE dtrace(1) script to log process(es) triggering $PROBE $ 1331 * \$FreeBSD$ 1332 */ 1333 1334$( echo "$DTRACE_PRAGMA" | awk ' 1335 !/^[[:space:]]*(#|$)/, sub(/^[[:space:]]*/, "#pragma D ")||1 1336' ) 1337 1338int console; 1339 1340dtrace:::BEGIN { console = ${CONSOLE:-0} } /* probe ID 1 */ 1341 1342/*********************************************************/ 1343 1344${PSARGS:+$PSARGS_ACTION} 1345${ACTIONS:+ 1346/*********************************************************/ 1347 1348$ACTIONS 1349} 1350/*********************************************************/ 1351 1352$PROBE${EVENT_TEST:+ / $EVENT_TEST /} /* probe ID $ID */ 1353{${TRACE:+ 1354 printf("<$ID>"); 1355} 1356 /***********************************************/ 1357 1358 printf("%s%Y%s ", 1359 console ? "\033[32m" : "", 1360 walltimestamp, 1361 console ? "\033[39m" : ""); 1362 1363 /****************** EVENT_TAG ******************/ 1364 1365 ${EVENT_TAG#[[:space:]]} 1366${PROBE_COALESCE:+ 1367 /**************** PROBE_COALESCE ***************/ 1368 1369 printf("%s%s:%s:%s:%s ", probename == "entry" ? "-> " : 1370 probename == "return" ? "<- " : 1371 probename == "start" ? "-> " : 1372 probename == "done" ? "<- " : " | ", 1373 probeprov, probemod, probefunc, probename); 1374} 1375 /**************** EVENT_DETAILS ****************/ 1376 1377 ${EVENT_DETAILS#[[:space:]]} 1378 1379 /***********************************************/ 1380 1381 printf("\\n"); 1382${PSTREE:+ 1383 /* 1384 * Print process, parent, grandparent, and ancestor details 1385 */ 1386$( pproc_dump -v 3 1387 pproc_dump -v 2 1388 pproc_dump -v 1 1389 pproc_dump -v 0 1390)} 1391} 1392EOF 1393# NOTREACHED 1394 1395################################################################################ 1396# END 1397################################################################################ 1398