基於 Kprobe 的事件追蹤¶
- 作者:
Masami Hiramatsu
概述¶
這些事件與基於 tracepoint 的事件類似。不同之處在於,它基於 kprobes (kprobe 和 kretprobe)。因此,它可以在 kprobes 可以探測的任何地方進行探測(這意味著,除了帶有 __kprobes/nokprobe_inline 註釋和標記為 NOKPROBE_SYMBOL 的函式之外的所有函式)。與基於 tracepoint 的事件不同,它可以動態地、即時地新增和刪除。
要啟用此功能,請使用 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]’ 是可以的。)
Char 型別可用於顯示追蹤引數的字元值。
String 型別是一種特殊型別,它從核心空間獲取一個“以 null 結尾”的字串。這意味著如果字串容器已分頁出去,它將失敗並存儲 NULL。 “ustring” 型別是使用者空間的字串替代方案。有關更多資訊,請參見 使用者記憶體訪問。
字串陣列型別與其他型別略有不同。對於其他基本型別,<base-type>[1] 等於 <base-type>(例如,+0(%di):x32[1] 與 +0(%di):x32 相同。)但 string[1] 不等於 string。 string 型別本身表示“char 陣列”,但字串陣列型別表示“char * 陣列”。因此,例如,+0(%di):string[1] 等於 +0(+0(%di)):string。位域是另一種特殊型別,它接受 3 個引數,位寬、位偏移量和容器大小(通常為 32)。語法是
b<bit-width>@<bit-offset>/<container-size>
Symbol 型別(‘symbol’)是 u32 或 u64 型別的別名(取決於 BITS_PER_LONG),它以“symbol+offset”樣式顯示給定的指標。另一方面,symbol-string 型別 (‘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-event 提供了使用者記憶體訪問語法,但它不會透明地使用它。這意味著如果您對使用者記憶體使用正常的解引用或字串型別,它可能會失敗,並且可能在某些架構上總是失敗。使用者必須仔細檢查目標資料是在核心空間還是使用者空間中。
按探針事件過濾¶
按探針事件過濾功能允許您在每個探針上設定不同的過濾器,併為您提供將在追蹤緩衝區中顯示哪些引數。如果在 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)。