原文链接:
Kprobe-based Event Tracing
概述
这些事件类似于基于tracepoint的事件。 但不同于Tracepoint,它基于 kprobes(kprobe 和 kretprobe)。 因此它可以探测 kprobes 可以探测的任何地方(这意味着,除了带有 __kprobes/nokprobe_inline
注释和标记为 NOKPROBE_SYMBOL
的那些函数之外的所有函数)。
与基于 Tracepoint 的事件不同,它可以动态添加和删除。
要启用此功能,请使用 CONFIG_KPROBE_EVENTS=y 构建内核。
与事件tracer类似,它不需要通过 current_tracer 激活。 取而代之的是,通过添加探测点
/sys/kernel/debug/tracing/kprobe_events
,并通过启用它
/sys/kernel/debug/tracing/events/kprobes/<EVENT>/enabled
。
kprobe_events 的概要
p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS] : 设置一个probe
r[MAXACTIVE][:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS] : 设置一个return probe
-:[GRP/]EVENT : 清除probe
GRP:组名。如果省略,请使用“kprobes”。
EVENT:事件名称。如果省略,则根据 SYM+offs 或 MEMADDR 生成事件名称。
MOD : 给出了 SYM 的模块名称。
SYM[+offs] :插入probe的符号+偏移量。
MEMADDR :probe插入的地址。
MAXACTIVE : 可以同时探测的指定函数的最大实例数,或 0 作为默认值,如文档/kprobes.txt 部分 1.3.1 中所定义。
FETCHARGS :参数。每个探针最多可以有 128 个参数。
%REG : 获取寄存器 REG
@ADDR :在 ADDR 处获取内存(ADDR 应该在内核中)
@SYM[+|-offs] :在 SYM +|- offs 处获取内存(SYM 应该是数据符号)
$stackN : 获取栈的第 N 个条目 (N >= 0)
$stack :获取堆栈地址。
$retval : 获取返回值。(*)
$comm :获取当前任务comm。
+|-offs(FETCHARG) : 在 FETCHARG +|- offs 地址获取内存。(**)
NAME=FETCHARG :将 NAME 设置为 FETCHARG 的参数名称。
FETCHARG:TYPE : 将 TYPE 设置为 FETCHARG 的类型。目前,基本类型(u8/u16/u32/u64/s8/s16/s32/s64),十六进制类型(x8/x16/x32/x64),支持“字符串”和位域。
(*) 只针对return probe
(**) 对于获取数据结构的域很有用处
Type
fetch-args 支持多种类型。 Kprobe tracer将按给定类型访问内存。 前缀 ‘s’ 和 ‘u’ 表示这些类型分别是有符号和无符号的。 ‘x’ 前缀意味着它是无符号的。 被trace的参数以十进制(“s”和“u”)或十六进制(“x”)显示。 如果没有类型转换,使用 ‘x32’ 或 ‘x64’ 取决于架构(例如 x86-32 使用 x32,而 x86-64 使用 x64)。
字符串类型是一种特殊类型,它从内核空间获取一个“以空字符结尾的”字符串。 这意味着如果字符串容器已被调出,它将失败并存储 NULL。
位域是另一种特殊类型,它采用 3 个参数,位宽、位偏移和容器大小(通常为 32)。 语法是;
b<bit-width>@<bit-offset>/<container-size>
对于 $comm,默认类型是 “string”; 任何其他类型都是无效的。
Per-Probe Event Filtering
Per-probe事件过滤功能允许您在每个probe上设置不同的过滤器,并向你提供参数,此参数也将在跟踪缓冲区中显示。 如果在 kprobe_events 中的 ‘p:’ 或 ‘r:’ 之后指定了正确的名称,则会在tracing/events/kprobes/<EVENT>
下添加一个事件,在目录中您可以看到 ‘id’, ‘enabled’, ’ 格式”和“过滤器”。
enabled:
您可以通过在其上写入 1 或 0 来启用/禁用探针。
format:
这显示了此探测事件的格式。
filter:
您可以编写此事件的过滤规则。
ID:
这显示了此探测事件的 ID。
Event Profiling
您可以通过以下方式检查probe命中总数和probe未命中总数
/sys/kernel/debug/tracing/kprobe_profile
第一列是事件名称,第二列是探测命中数,第三列是探测未命中数。
Usage examples
要将探测器添加为新事件,请向 kprobe_events 写入新定义
如下。
echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' \
> /sys/kernel/debug/tracing/kprobe_events
(译注:这里的ax dx cx 都是寄存器名字)
这会在 do_sys_open() 函数的顶部设置一个 kprobe,将第 1 到第 4 个参数记录为“myprobe”事件。 请注意,分配给每个函数参数的寄存器/堆栈条目取决于特定于架构的 ABI。 如果您不确定 ABI,请尝试使用 perf-tools 的 probe 子命令(您可以在 tools/perf/ 下找到它)。
如本示例所示,用户可以为每个参数选择更熟悉的名称。
echo 'r:myretprobe do_sys_open $retval' >> /sys/kernel/debug/tracing/kprobe_events
这会在 do_sys_open() 函数的返回点设置一个 kretprobe,并将返回值记录为“myretprobe”事件。
您可以通过以下方式查看这些事件的格式 /sys/kernel/debug/tracing/events/kprobes/<EVENT>/format
.
cat /sys/kernel/debug/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/debug/tracing/kprobe_events
这将清除所有探测点。
或者,
echo -:myprobe >> kprobe_events
这将选择性地清除探测点。
在定义之后,默认情况下每个事件都是禁用的。 要跟踪这些事件,您需要启用它。
echo 1 > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable
echo 1 > /sys/kernel/debug/tracing/events/kprobes/myretprobe/enable
您可以通过 /sys/kernel/debug/tracing/trace
查看跟踪信息。
cat /sys/kernel/debug/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)。