基于 Kprobe 的事件跟踪¶
- 作者:
Masami Hiramatsu
概述¶
这些事件类似于基于跟踪点的事件。 不同之处在于,它基于 kprobes(kprobe 和 kretprobe),而不是跟踪点。因此,它可以在 kprobes 可以探测的任何地方进行探测(这意味着,除了带有 __kprobes/nokprobe_inline 注释和标记为 NOKPROBE_SYMBOL 的函数之外的所有函数)。与基于跟踪点的事件不同,它可以在运行时动态地添加和删除。
要启用此功能,请使用 CONFIG_KPROBE_EVENTS=y 构建您的内核。
与事件跟踪器类似,此功能不需要通过 current_tracer 激活。 而是通过 /sys/kernel/tracing/kprobe_events 添加探测点,并通过 /sys/kernel/tracing/events/kprobes/<EVENT>/enable 启用它。
您也可以使用 /sys/kernel/tracing/dynamic_events 代替 kprobe_events。该接口还将提供对其他动态事件的统一访问。
kprobe_events 概要¶
p[:[GRP/][EVENT]] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : Set a probe
r[MAXACTIVE][:[GRP/][EVENT]] [MOD:]SYM[+0] [FETCHARGS] : Set a return probe
p[:[GRP/][EVENT]] [MOD:]SYM[+0]%return [FETCHARGS] : Set a return probe
-:[GRP/][EVENT] : Clear a probe
GRP : Group name. If omitted, use "kprobes" for it.
EVENT : Event name. If omitted, the event name is generated
based on SYM+offs or MEMADDR.
MOD : Module name which has given SYM.
SYM[+offs] : Symbol+offset where the probe is inserted.
SYM%return : Return address of the symbol
MEMADDR : Address where the probe is inserted.
MAXACTIVE : Maximum number of instances of the specified function that
can be probed simultaneously, or 0 for the default value
as defined in Documentation/trace/kprobes.rst section 1.3.1.
FETCHARGS : Arguments. Each probe can have up to 128 args.
%REG : Fetch register REG
@ADDR : Fetch memory at ADDR (ADDR should be in kernel)
@SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol)
$stackN : Fetch Nth entry of stack (N >= 0)
$stack : Fetch stack address.
$argN : Fetch the Nth function argument. (N >= 1) (\*1)
$retval : Fetch return value.(\*2)
$comm : Fetch current task comm.
+|-[u]OFFS(FETCHARG) : Fetch memory at FETCHARG +|- OFFS address.(\*3)(\*4)
\IMM : Store an immediate value to the argument.
NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types
(u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
(x8/x16/x32/x64), VFS layer common type(%pd/%pD), "char",
"string", "ustring", "symbol", "symstr" and bitfield are
supported.
(\*1) only for the probe on function entry (offs == 0). Note, this argument access
is best effort, because depending on the argument type, it may be passed on
the stack. But this only support the arguments via registers.
(\*2) only for return probe. Note that this is also best effort. Depending on the
return value type, it might be passed via a pair of registers. But this only
accesses one register.
(\*3) this is useful for fetching a field of data structures.
(\*4) "u" means user-space dereference. See :ref:`user_mem_access`.
kretprobe 中的函数参数¶
可以使用 $arg<N> fetcharg 在 kretprobe 中访问函数参数。 这对于一次记录函数参数和返回值,并跟踪结构字段的差异非常有用(用于调试函数是否正确更新了给定的数据结构)。有关其工作原理,请参见 fprobe 事件中的示例。
类型¶
fetchargs 支持多种类型。 Kprobe 跟踪器将按给定的类型访问内存。 前缀“s”和“u”分别表示这些类型是有符号和无符号的。 “x”前缀表示它是无符号的。 跟踪的参数以十进制(“s”和“u”)或十六进制(“x”)显示。 如果没有类型转换,则根据体系结构使用“x32”或“x64”(例如,x86-32 使用 x32,x86-64 使用 x64)。
这些值类型可以是数组。 要记录数组数据,您可以将“[N]”(其中 N 是小于 64 的固定数字)添加到基本类型。 例如,“x16[4]”表示一个具有 4 个元素的 x16(2 字节十六进制)数组。请注意,该数组可以应用于内存类型 fetchargs,但不能应用于寄存器/堆栈条目等(例如,“$stack1:x8[8]”是错误的,但“+8($stack):x8[8]”是可以的。)
字符类型可用于显示跟踪参数的字符值。
字符串类型是一种特殊类型,它从内核空间获取一个“以 null 结尾”的字符串。 这意味着如果字符串容器已分页出,它将失败并存储 NULL。 “ustring”类型是用户空间的字符串的替代方案。 有关详细信息,请参见用户内存访问。
字符串数组类型与其他类型略有不同。对于其他基本类型,<base-type>[1] 等于 <base-type>(例如,+0(%di):x32[1] 与 +0(%di):x32 相同。) 但 string[1] 不等于 string。 字符串类型本身表示“字符数组”,但字符串数组类型表示“char * 数组”。 因此,例如,+0(%di):string[1] 等于 +0(+0(%di)):string。 位字段是另一种特殊类型,它采用 3 个参数:位宽、位偏移量和容器大小(通常为 32)。语法为
b<bit-width>@<bit-offset>/<container-size>
符号类型(“symbol”)是 u32 或 u64 类型的别名(取决于 BITS_PER_LONG),它以“symbol+offset”样式显示给定的指针。 另一方面,符号字符串类型(“symstr”)将给定的地址转换为“symbol+offset/symbolsize”样式并将其存储为以 null 结尾的字符串。 通过“symstr”类型,您可以使用符号的通配符模式来过滤事件,并且您无需自己解析符号名称。 对于 $comm,默认类型是“string”;任何其他类型都无效。
VFS 层通用类型(%pd/%pD)是一种特殊类型,它从 struct dentry 的地址或 struct file
的地址获取 dentry 或文件的名称。
用户内存访问¶
Kprobe 事件支持用户空间内存访问。为此,您可以使用用户空间解引用语法或“ustring”类型。
用户空间解引用语法允许您访问用户空间中数据结构的字段。 这是通过将“u”前缀添加到解引用语法来完成的。例如,+u4(%si) 表示它将从寄存器 %si 中的地址读取内存并偏移 4,并且该内存应位于用户空间中。您也可以将此用于字符串,例如,+u0(%si):string 将从寄存器 %si 中的地址读取一个字符串,该地址应位于用户空间中。“ustring”是执行相同任务的快捷方式。也就是说,+0(%si):ustring 等效于 +u0(%si):string。
请注意,kprobe 事件提供了用户内存访问语法,但它并非透明地使用它。这意味着如果您对用户内存使用普通解引用或字符串类型,则可能会失败,并且在某些体系结构上可能总是会失败。用户必须仔细检查目标数据是在内核空间还是用户空间中。
每个探针的事件过滤¶
每个探针的事件过滤功能允许您在每个探针上设置不同的过滤器,并为您提供跟踪缓冲区中将显示的参数。如果在 kprobe_events 中“p:”或“r:”之后立即指定事件名称,则会在 tracing/events/kprobes/<EVENT> 下添加一个事件,在该目录下您可以看到“id”、“enable”、“format”、“filter”和“trigger”。
- enable
您可以通过在其上写入 1 或 0 来启用/禁用探针。
- format
这显示了此探针事件的格式。
- filter
您可以写入此事件的过滤规则。
- id
这显示了此探针事件的 id。
- trigger
这允许安装触发命令,当事件被命中时执行这些命令(有关详细信息,请参见事件跟踪,第 6 节)。
事件分析¶
您可以通过 /sys/kernel/tracing/kprobe_profile 查看探针命中的总数和探针未命中的数量。 第一列是事件名称,第二列是探针命中数,第三列是探针未命中数。
内核启动参数¶
您可以通过“kprobe_event=”参数在启动内核时添加和启用新的 kprobe 事件。 该参数接受以分号分隔的 kprobe 事件,其格式类似于 kprobe_events。不同之处在于探针定义参数是以逗号分隔而不是空格分隔。例如,在 do_sys_open 上添加 myprobe 事件,如下所示
p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)
对于内核启动参数应如下所示(只需将空格替换为逗号)
p:myprobe,do_sys_open,dfd=%ax,filename=%dx,flags=%cx,mode=+4($stack)
使用示例¶
要将探针作为新事件添加,请如下所示将新定义写入 kprobe_events
echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' > /sys/kernel/tracing/kprobe_events
这会在 do_sys_open() 函数的顶部设置一个 kprobe,并将第 1 到第 4 个参数记录为“myprobe”事件。请注意,哪个寄存器/堆栈条目分配给每个函数参数取决于特定于体系结构的 ABI。如果您不确定 ABI,请尝试使用 perf-tools 的 probe 子命令(您可以在 tools/perf/ 下找到它)。正如本示例所示,用户可以为每个参数选择更熟悉的名称。
echo 'r:myretprobe do_sys_open $retval' >> /sys/kernel/tracing/kprobe_events
这会在 do_sys_open() 函数的返回点设置一个 kretprobe,并将返回值记录为“myretprobe”事件。您可以通过 /sys/kernel/tracing/events/kprobes/<EVENT>/format 查看这些事件的格式。
cat /sys/kernel/tracing/events/kprobes/myprobe/format
name: myprobe
ID: 780
format:
field:unsigned short common_type; offset:0; size:2; signed:0;
field:unsigned char common_flags; offset:2; size:1; signed:0;
field:unsigned char common_preempt_count; offset:3; size:1;signed:0;
field:int common_pid; offset:4; size:4; signed:1;
field:unsigned long __probe_ip; offset:12; size:4; signed:0;
field:int __probe_nargs; offset:16; size:4; signed:1;
field:unsigned long dfd; offset:20; size:4; signed:0;
field:unsigned long filename; offset:24; size:4; signed:0;
field:unsigned long flags; offset:28; size:4; signed:0;
field:unsigned long mode; offset:32; size:4; signed:0;
print fmt: "(%lx) dfd=%lx filename=%lx flags=%lx mode=%lx", REC->__probe_ip,
REC->dfd, REC->filename, REC->flags, REC->mode
您可以看到该事件有 4 个参数,如您指定的表达式中所示。
echo > /sys/kernel/tracing/kprobe_events
这将清除所有探测点。
或者,
echo -:myprobe >> kprobe_events
这将有选择地清除探测点。
定义后,默认情况下会禁用每个事件。要跟踪这些事件,您需要启用它。
echo 1 > /sys/kernel/tracing/events/kprobes/myprobe/enable
echo 1 > /sys/kernel/tracing/events/kprobes/myretprobe/enable
使用以下命令在时间间隔内开始跟踪。
# echo 1 > tracing_on
Open something...
# echo 0 > tracing_on
您可以通过 /sys/kernel/tracing/trace 查看跟踪的信息。
cat /sys/kernel/tracing/trace
# tracer: nop
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
<...>-1447 [001] 1038282.286875: myprobe: (do_sys_open+0x0/0xd6) dfd=3 filename=7fffd1ec4440 flags=8000 mode=0
<...>-1447 [001] 1038282.286878: myretprobe: (sys_openat+0xc/0xe <- do_sys_open) $retval=fffffffffffffffe
<...>-1447 [001] 1038282.286885: myprobe: (do_sys_open+0x0/0xd6) dfd=ffffff9c filename=40413c flags=8000 mode=1b6
<...>-1447 [001] 1038282.286915: myretprobe: (sys_open+0x1b/0x1d <- do_sys_open) $retval=3
<...>-1447 [001] 1038282.286969: myprobe: (do_sys_open+0x0/0xd6) dfd=ffffff9c filename=4041c6 flags=98800 mode=10
<...>-1447 [001] 1038282.286976: myretprobe: (sys_open+0x1b/0x1d <- do_sys_open) $retval=3
每行都显示内核何时命中一个事件,<- SYMBOL 表示内核从 SYMBOL 返回(例如,“sys_open+0x1b/0x1d <- do_sys_open”表示内核从 do_sys_open 返回到 sys_open+0x1b)。