#!/bin/bash # # kprobe - trace a given kprobe definition. Kernel dynamic tracing. # Written using Linux ftrace. # # This will create, trace, then destroy a given kprobe definition. See # Documentation/trace/kprobetrace.txt in the Linux kernel source for the # syntax of a kprobe definition, and "kprobe -h" for examples. With this tool, # the probe alias is optional (it will become to kprobe: if not # specified). # # USAGE: ./kprobe [-FhHsv] [-d secs] [-p pid] [-L tid] kprobe_definition [filter] # # Run "kprobe -h" for full usage. # # I wrote this because I kept testing different custom kprobes at the command # line, and wanted a way to automate the steps. # # WARNING: This uses dynamic tracing of kernel functions, and could cause # kernel panics or freezes, depending on the function traced. Test in a lab # environment, and know what you are doing, before use. # # REQUIREMENTS: FTRACE and KPROBE CONFIG, which you may already have on recent # kernel versions. # # From perf-tools: https://github.com/brendangregg/perf-tools # # See the kprobe(8) man page (in perf-tools) for more info. # # COPYRIGHT: Copyright (c) 2014 Brendan Gregg. # # 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 2 # 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, write to the Free Software Foundation, # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # (http://www.gnu.org/copyleft/gpl.html) # # 22-Jul-2014 Brendan Gregg Created this. ### default variables setglobal tracing = '/sys/kernel/debug/tracing' setglobal flock = '/var/tmp/.ftrace-lock'; setglobal wroteflock = '0' setglobal opt_duration = '0'; setglobal duration = ''; setglobal opt_pid = '0'; setglobal pid = ''; setglobal opt_tid = '0'; setglobal tid = '' setglobal opt_filter = '0'; setglobal filter = ''; setglobal opt_view = '0'; setglobal opt_headers = '0'; setglobal opt_stack = '0'; setglobal dmesg = '2' setglobal debug = '0'; setglobal opt_force = '0' trap ':' INT QUIT TERM PIPE HUP # sends execution to end tracing section proc usage { cat << """ >&2 USAGE: kprobe [-FhHsv] [-d secs] [-p PID] [-L TID] kprobe_definition [filter] -F # force. trace despite warnings. -d seconds # trace duration, and use buffers -p PID # PID to match on events -L TID # thread id to match on events -v # view format file (don't trace) -H # include column headers -s # show kernel stack traces -h # this usage message Note that these examples may need modification to match your kernel version's function names and platform's register usage. eg, kprobe p:do_sys_open # trace open() entry kprobe r:do_sys_open # trace open() return kprobe 'r:do_sys_open '$'retval' # trace open() return value kprobe 'r:myopen do_sys_open '$'retval' # use a custom probe name kprobe 'p:myopen do_sys_open mode=%cx:u16' # trace open() file mode kprobe 'p:myopen do_sys_open filename=+0(%si):string' # trace open() with filename kprobe -s 'p:myprobe tcp_retransmit_skb' # show kernel stacks kprobe 'p:do_sys_open file=+0(%si):string' 'file ~ "*stat"' # opened files ending in "stat" See the man page and example file for more info. """ > !2 USAGE: kprobe [-FhHsv] [-d secs] [-p PID] [-L TID] kprobe_definition [filter] -F # force. trace despite warnings. -d seconds # trace duration, and use buffers -p PID # PID to match on events -L TID # thread id to match on events -v # view format file (don't trace) -H # include column headers -s # show kernel stack traces -h # this usage message Note that these examples may need modification to match your kernel version's function names and platform's register usage. eg, kprobe p:do_sys_open # trace open() entry kprobe r:do_sys_open # trace open() return kprobe 'r:do_sys_open \$retval' # trace open() return value kprobe 'r:myopen do_sys_open \$retval' # use a custom probe name kprobe 'p:myopen do_sys_open mode=%cx:u16' # trace open() file mode kprobe 'p:myopen do_sys_open filename=+0(%si):string' # trace open() with filename kprobe -s 'p:myprobe tcp_retransmit_skb' # show kernel stacks kprobe 'p:do_sys_open file=+0(%si):string' 'file ~ "*stat"' # opened files ending in "stat" See the man page and example file for more info. END exit } proc warn { if ! eval @Argv { echo >&2 "WARNING: command failed \"$ifsjoin(Argv)> !2 "WARNING: command failed \"$@\"" } } proc end { # disable tracing echo !2 >/dev/null echo "Ending tracing..." !2 >/dev/null cd $tracing warn "echo 0 > events/kprobes/$kname/enable" if sh-expr ' opt_filter ' { warn "echo 0 > events/kprobes/$kname/filter" } warn "echo -:$kname >> kprobe_events" sh-expr ' opt_stack ' && warn "echo 0 > options/stacktrace" warn "echo > trace" sh-expr ' wroteflock ' && warn "rm $flock" } proc die { echo >&2 @Argv> !2 "$@" exit 1 } proc edie { # die with a quiet end() echo >&2 @Argv> !2 "$@" exec >/dev/null !2 > !1 end exit 1 } ### process options while getopts Fd:hHp:L:sv opt { match $opt { with F setglobal opt_force = '1' with d setglobal opt_duration = '1'; setglobal duration = $OPTARG with p setglobal opt_pid = '1'; setglobal pid = $OPTARG with L setglobal opt_tid = '1'; setglobal tid = $OPTARG with H setglobal opt_headers = '1' with s setglobal opt_stack = '1' with v setglobal opt_view = '1' with h|? usage } } shift $shExpr(' $OPTIND - 1 ') sh-expr ' $# ' || usage setglobal kprobe = $1 shift if sh-expr ' $# ' { setglobal opt_filter = '1' setglobal filter = $1 } ### option logic sh-expr ' opt_pid + opt_filter + opt_tid > 1 ' && \ die "ERROR: use at most one of -p, -L, or filter." sh-expr ' opt_duration && opt_view ' && die "ERROR: use either -d or -v." if sh-expr ' opt_pid ' { # convert to filter setglobal opt_filter = '1' # ftrace common_pid is thread id from user's perspective for tid in [/proc/$pid/task/*] { setglobal filter = ""$filter || common_pid == $(tid##*/)"" } setglobal filter = $(filter:3) # trim leading ' || ' (four characters) } if sh-expr ' opt_tid ' { setglobal opt_filter = '1' setglobal filter = ""common_pid == $tid"" } if [[ "$kprobe" != p:* && "$kprobe" != r:* ]] { echo >&2 "ERROR: invalid kprobe definition (should start with p: or r:)> !2 "ERROR: invalid kprobe definition (should start with p: or r:)" usage } # # parse the following: # r:do_sys_open # r:my_sys_open do_sys_open # r:do_sys_open %ax # r:do_sys_open $retval %ax # r:my_sys_open do_sys_open $retval %ax # r:do_sys_open rval=$retval # r:my_sys_open do_sys_open rval=$retval # r:my_sys_open do_sys_open rval=$retval %ax # ... and examples from USAGE message # setglobal krest = $(kprobe#*:) setglobal kname = $(krest%% *) set -- $krest if [[ $2 == "" || $2 == *[=%\$]* ]] { # if probe name unspecified, default to function name setglobal ktype = $(kprobe%%:*) setglobal kprobe = ""$ktype:$kname $krest"" } if sh-expr ' debug ' { echo "kname: $kname, kprobe: $kprobe" } ### check permissions cd $tracing || die "ERROR: accessing tracing. Root user? Kernel has FTRACE? debugfs mounted? (mount -t debugfs debugfs /sys/kernel/debug)" ## check function set -- $kprobe setglobal fname = $2 if sh-expr ' !opt_force ' && ! grep -w $fname available_filter_functions >/dev/null \ !2 > !1 { echo >&2 "ERROR: func $fname not in $PWD/available_filter_functions.> !2 "ERROR: func $fname not in $PWD/available_filter_functions." printf >&2 "Either it doesn't exist, or, it might be unsafe to kprobe. > !2 "Either it doesn't exist, or, it might be unsafe to kprobe. " echo >&2 "Exiting. Use -F to override.> !2 "Exiting. Use -F to override." exit 1 } if sh-expr ' !opt_view ' { if sh-expr ' opt_duration ' { echo "Tracing kprobe $kname for $duration seconds (buffered)..." } else { echo "Tracing kprobe $kname. Ctrl-C to end." } } ### ftrace lock [[ -e $flock ]] && die "ERROR: ftrace may be in use by PID $[cat $flock] $flock" echo $Pid > $flock || die "ERROR: unable to write $flock." setglobal wroteflock = '1' ### setup and begin tracing echo nop > current_tracer if ! echo $kprobe >> kprobe_events { echo >&2 "ERROR: adding kprobe \"$kprobe\".> !2 "ERROR: adding kprobe \"$kprobe\"." if sh-expr ' dmesg ' { echo >&2 "Last $dmesg dmesg entries (might contain reason):> !2 "Last $dmesg dmesg entries (might contain reason):" dmesg | tail -$dmesg | sed 's/^/ /' } edie "Exiting." } if sh-expr ' opt_view ' { cat events/kprobes/$kname/format edie "" } if sh-expr ' opt_filter ' { if ! echo $filter > events/kprobes/$kname/filter { edie "ERROR: setting filter or -p. Exiting." } } if sh-expr ' opt_stack ' { if ! echo 1 > options/stacktrace { edie "ERROR: enabling stack traces (-s). Exiting" } } if ! echo 1 > events/kprobes/$kname/enable { edie "ERROR: enabling kprobe $kname. Exiting." } ### print trace buffer warn "echo > trace" if sh-expr ' opt_duration ' { sleep $duration if sh-expr ' opt_headers ' { cat trace } else { grep -v '^#' trace } } else { # trace_pipe lack headers, so fetch them from trace sh-expr ' opt_headers ' && cat trace cat trace_pipe } ### end tracing end