Kprobe Hook Linux Kernel taro Posted on Jul 12 2021 ## 介绍 kprobe调试技术是为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术。 利用kprobe技术,可以在内核绝大多数函数中动态插入探测点,收集调试状态所需信息而基本不影响原有执行流程。 kprobe提供三种探测手段:kprobe、jprobe和kretprobe,其中jprobe和kretprobe基于kprobe实现,分别应用于不同探测场景中。 可以通过两种方式使用kprobe:第一种是编写内核模块,向内核注册探测点,探测函数根据需要自行定制,但是使用不方便; 第二种是使用kprobes in ftrace,这种方式结合kprobe和ftrace,可以通过kprobe来优化ftrace跟踪函数。 ## 分类 kprobes技术包括的3种探测手段分别时kprobe、jprobe和kretprobe。 ### kprobe kprobe是最基本的探测方式,是实现其他两种的基础,它可以在任意的位置放置探测点(就连函数内部的某条指令处也可以),它提供了探测点的调用前、调用后和内存访问出错3种回调方式,分别是`pre_handler`、`post_handler`和`fault_handler` - `pre_handler`函数将在被探测指令被执行前回调 - `post_handler`会在被探测指令执行完毕后回调(注意不是被探测函数) - `fault_handler`会在内存访问出错时被调用; ### jprobe `jprobe`基于kprobe实现,它用于获取被探测函数的入参值; ### kretprobe kretprobe从名字种就可以看出其用途了,它同样基于kprobe实现,用于获取被探测函数的返回值。 ## 原理 拿的大佬的图,方便理解  >1. 当用户注册一个探测点后,kprobe首先备份被探测点的对应指令,然后将原始指令的入口点替换为断点指令,该指令是CPU架构相关的,如i386和x86_64是int3,arm是设置一个未定义指令(目前的x86_64架构支持一种跳转优化方案Jump Optimization,内核需开启CONFIG_OPTPROBES选项,该种方案使用跳转指令来代替断点指令); 2. 当CPU流程执行到探测点的断点指令时,就触发了一个trap,在trap处理流程中会保存当前CPU的寄存器信息并调用对 3. 的trap处理函数,该处理函数会设置kprobe的调用状态并调用用户注册的pre_handler回调函数,kprobe会向该函数传递注册的struct kprobe结构地址以及保存的CPU寄存器信息; 4. 随后kprobe单步执行前面所拷贝的被探测指令,具体执行方式各个架构不尽相同,arm会在异常处理流程中使用模拟函数执行,而x86_64架构则会设置单步调试flag并回到异常触发前的流程中执行; 5. 在单步执行完成后,kprobe执行用户注册的post_handler回调函数; 6. 最后,执行流程回到被探测指令之后的正常流程继续执行。 ## DEMO ### struct kprobe结构体 ```c struct kprobe { struct hlist_node hlist;-----------------------------------------------被用于kprobe全局hash,索引值为被探测点的地址。 /* list of kprobes for multi-handler support */ struct list_head list;-------------------------------------------------用于链接同一被探测点的不同探测kprobe。 /*count the number of times this probe was temporarily disarmed */ unsigned long nmissed; /* location of the probe point */ kprobe_opcode_t *addr;-------------------------------------------------被探测点的地址。 /* Allow user to indicate symbol name of the probe point */ const char *symbol_name;-----------------------------------------------被探测函数的名称。 /* Offset into the symbol */ unsigned int offset;---------------------------------------------------被探测点在函数内部的偏移,用于探测函数内核的指令,如果该值为0表示函数的入口。 /* Called before addr is executed. */ kprobe_pre_handler_t pre_handler;--------------------------------------被探测点指令执行之前调用的回调函数。 /* Called after addr is executed, unless... */ kprobe_post_handler_t post_handler;------------------------------------被探测点指令执行之后调用的回调函数。 kprobe_fault_handler_t fault_handler;----------------------------------在执行pre_handler、post_handler或单步执行被探测指令时出现内存异常则会调用该回调函数。 kprobe_break_handler_t break_handler;----------------------------------在执行某一kprobe过程中出发了断点指令后会调用该函数,用于实现jprobe。 kprobe_opcode_t opcode;------------------------------------------------保存的被探测点原始指令。 struct arch_specific_insn ainsn;---------------------------------------被复制的被探测点的原始指令,用于单步执行,架构强相关。 u32 flags;-------------------------------------------------------------状态标记。 }; ``` ### kprobe API函数 ```c int register_kprobe(struct kprobe *p);--------------------------注册kprobe探测点 void unregister_kprobe(struct kprobe *p);-----------------------卸载kprobe探测点 int register_kprobes(struct kprobe **kps, int num);-------------注册多个kprobe探测点 void unregister_kprobes(struct kprobe **kps, int num);----------卸载多个kprobe探测点 int disable_kprobe(struct kprobe *kp);--------------------------暂停指定定kprobe探测点 int enable_kprobe(struct kprobe *kp);---------------------------回复指定kprobe探测点 void dump_kprobe(struct kprobe *kp);----------------------------打印指定kprobe探测点的名称、地址、偏移 ``` 参考资料 - https://www.kernel.org/doc/Documentation/kprobes.txt - https://kernelgo.org/kprobe.html - https://www.cnblogs.com/arnoldlu/p/9752061.html - 告警关联综述学习笔记 Kernel Connector(翻译)