kprobe 是一個輕量的 linux kernel 的除錯工具,他可以在不重新編譯核心的情況下追蹤一個函式的呼叫以及回傳,相當方便。kernel 內目前提供了三種類型的 probe,分別是 kprobe、kretprobe 以及 jprobe,三者都是基於類似的原理所實現的。
詳細的技術原理 … 我也沒弄得那麼清楚 😜,有興趣的可以去看核心的技術說明手冊,會比我介紹清楚得多,這邊只簡單介紹好用工具。
Userspace Tool
常見的 kprobe 使用方式是撰寫一個 kernel module,他可以提供最強大的功能,例如在插入點中執行我們想執行的程式碼,但很多時候只是想快速知道函式是否有被呼叫、參數及回傳值為何,重新寫一個 kernel module 就顯得麻煩。這個時候可以使用 krpobe 的 debugfs 介面,他可以在不編譯 kernel module 的情況下達成上述的目的。
不過呢,直接操作 debugfs 介面還是有點麻煩,大神 Brendangregg 已經將這個介面包裝成一個好用的 tool,名字也叫做 kprobe ,是用 shell script 寫成的,可以直接下載使用他。
範例
1. 我想要 trace do_sys_open 被呼叫時的 filename 及 mode 參數
## long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
# kprobe -F 'p:myopen do_sys_open filename=+0(%si):string mode=%cx:u16'
Tracing kprobe myopen. Ctrl-C to end.
<...>-29624 [001] d... 622771.361190: myopen: (do_sys_open+0x0/0x240) filename="/etc/ld.so.cache" mode=0x0
<...>-29624 [001] d... 622771.361195: myopen: (do_sys_open+0x0/0x240) filename="/lib64/tls/haswell/x86_64/libc.so.6"
- p: 代表使用的是 kprobe, do_sys_open 是想追蹤的函式名稱
- myopen, filename, mode 是可以自己隨便定義的名稱,輸出方便看得懂就好
- 印參數可參考 (x86 only)
- 參數順序: di, si, dx, cx, r8, r9
- 參數型別: u8, s8, u16, s16, u32, s32, u64, s64
- 指標字串 (char*): 用 +offset(%REG) 的方式取址,例如 +0(%si):string
2. 我想要 trace do_sys_open 的回傳值
# kprobe -F 'r:myopen do_sys_open ret=$retval'
Tracing kprobe myopen. Ctrl-C to end.
kprobe-2829 [001] d... 673251.101610: myopen: (SyS_openat+0xf/0x20 <- do_sys_open) ret=0x3
- r: 代表使用的是 kretprobe,其他的部份與 kprobe 類似
3. 我想要同時 trace 參數及回傳值
還不知道怎麼辦到這件事,有可能不行
限制
能夠被追蹤的函式必須要有 symbol 才行,如果函式被 inline 展開或是被優化,有可能無法被 probe 到,用前處理器 macro 定義的函式也無法被追蹤。要看有哪些 symbol 可以被追蹤的話可以從 /proc/kallsyms 找到。
Kernel Module
如果 Userspace tool 沒辦法滿足需求,就可能需要撰寫 Kernel Module,範例位於 linux-kernel-source/samples/kprobes/ 底下,可以自行參考編譯,但新一點的 Kernel 可能使用 ebpf 會更快速一點。