使用 kprobe DEBUG 内核
使用 kprobe debug 内核
内核 DEBUG 有很多种方式。其中 kprobe 的好处是:
- 可以在线开关
- 不需要修改或者重新编译内核
- 支持几乎所有的(除了少数之外)内核代码
- 对性能影响较小
使用 kprobe 的两种方式
使用 kprobe 的最直接了当的方式,自然是根据它的 文档 ,编写一个内核模块,并且加载到内核。在内核源码的 samples/kprobes/
下面有示例。
另外一种方法,是使用内核 trace 接口提供的 Kprobe-based Event Tracing 。使用这种方法,可以做到不用编写内核模块,在线增加 kprobe 跟踪,更方便一些,但是灵活性相对就差一些。
kprobe-based event tracing 的写法
官方文档挺好懂的,这里就不重复翻译了。唯一容易引发误解的是 FETCHARGS 的命名。文档是
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), "string", "ustring" and bitfield are supported.
这块写得蛮不清楚的。这个语法有点像汇编,但是又不完全一样,在这里整理一下(以 64 位体系结构为例):
%REG
就是寄存器地址,但是寄存器不使用名称来区分大小。例如栈指针寄存器,只能写成%sp
,不能写成%rsp
,通用正整数寄存器 r9,只能写成%r9
,不能写成%r9d
。变量的实际大小,使用 TYPE 字段来标识- 内存引用则是加上括号,形成行为
+|-[u]OFFS(FETCHARG)
的格式。其中最前面的符号和 OFFS 是不可省略的。例如当前栈的栈顶,需要写成+0(%sp)
,如果栈顶是个指针,希望进一步引用指针地址,就继续嵌套,写成+0(+0(%sp))
。
一个实际的例子
我们以内核回复 syn 包(也就是发送 synack 包时),计算滑动窗口的函数为例。假如我们需要 DEBUG 这个函数。
这里给一个简单的例子,仅仅打印这个函数传递的所有参数。实际上,通过计算内存偏移量,你可以打印出这个函数的所有局部变量,以及各种数据结构的值。这里仅仅以打印参数为例子。
这个函数的定义为:
我们可以看到它有八个参数。我们知道 64 位体系架构下,会把前六个参数分别保存在 %di
%si
%dx
%cx
%r8
%r9
这六个整数寄存器中,剩余的参数则放在栈顶。
在 64 位体系结构下,一个 int 为 4 个字节,一个指针则为 8 个字节。这样我们可以算出所有八个变量的位置。其中所有指针需要加多一个内存引用。栈中的变量如果是指针,则需要两层内存应用,第一层是栈寄存器到内存的栈顶的指针,第二层是内存栈顶指针的解引用。
1# 注意这里用追加,防止覆盖现有的规则
2echo 'p:myprobe tcp_select_initial_window sk=+0(%di):x64 space=%si:s32 mss=%dx:u32 rcv_wnd=+0(%cx):u32 window_clamp=+0(%r8):u32 wscale_ok=%r9:s32 rsv_wscale=+0(+0(%sp)):u8 init_rcv_wnd=+4(%sp):u32' >> /sys/kernel/debug/tracing/kprobe_events
3# 确认规则是否正确
4cat /sys/kernel/debug/tracing/events/kprobes/myprobe/format
5# 启用规则
6echo 1 > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable
7# 查看跟踪的结果
8cat /sys/kernel/debug/tracing/trace
9# 禁用规则
10echo 0 > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable
11# 删除规则
12echo '-:myprobe tcp_select_initial_window' >> /sys/kernel/debug/tracing/kprobe_events
KCupsConnection-1485 [005] ...1 56022.465539: myprobe: (tcp_select_initial_window+0x0/0xf0) sk=0x100007f0100007f space=43690 mss=65495 rcv_wnd=0 window_clamp=0 wscale_ok=1 rsv_wscale=15 init_rcv_wnd=4294967295