Linux kernel debug & trace: Kprobe
kprobe是linux kernel提供的一种动态调试机制,可以在不修改内核源码,不中断系统运行的情况下,插入探针(probe)到内核函数或指令处,以便在该位置执行时获取运行信息,调试或收集性能数据。
·
kprobe是linux kernel提供的一种动态调试机制,可以在不修改内核源码,不中断系统运行的情况下,插入探针(probe)到内核函数或指令处,以便在该位置执行时获取运行信息,调试或收集性能数据。
Kprobe类型
kprobe实现了三种类型的探测点:
- kprobes:可以被插入到内核的任何指令为止的探测点
- jprobes:只能被插入到内核函数的入口,该功能已弃用;
- kretprobes:在指定的内核函数返回时才被执行
Kprobe是如何工作的
kernel通过动态修改被探测函数(目标函数)的指令,将其替换为断点指令,从而将执行流从目标函数强制转向预定义的处理函数(kprobe)。
- 具体执行流程:当Kprobe被注册时,kprobes会复制并验证 被探测对象(目标函数)的第一条指令(第一个字节,复制内容保存在kprobe结构体内私有区域),并将其替换成断点指令(该断点指令关联kprobe实例),当断点命中(执行到目标函数时,触发断点异常int3: do_int3),CPU会暂停当前正常工作流程,并通过notifier_call_chain机制把控制权转交给kprobe,找到对应的kprobe实例,从而执行对应的pre_handler 。执行完成后,kprobe会将CPU指针指向之前复制的目标函数的原始指令地址上,并单步执行(真正执行目标函数),单步执行后,kprobe再次陷入单步异常(int1),int1处理(do_debug) 中会调用post_handler, 并从单步模式恢复到正常模式,然后返回目标函数中断指令之后的位置继续正常执行。
- 断点指令关联kprobe实例:kernel有一个全局的哈希表,它以被探测的地址为键,kprobe注册时,会将kprobe实例插入到这个哈希表中。当执行到目标函数时,由于目标函数首指令被替换成断点指令,会触发断点异常,异常处理程序会检查触发异常的地址,并对照内核kprobe哈希表查找,从而找到kprobe实例。
Kprobes的特性与局限性
- kprobe允许在同一个地址进行多个探测,且存在post_handler的探针点无法被优化。
- 如果为内联函数注册探测点,kprobe无法保证对内联函数的所有实例都注册探测点,因为gcc可能隐式地内联一个函数。
- 若探测处理函数调用了另一个探测点,则该探测点的处理函数将不运行,但其kprobe.nmissed会加1;
- 多个kprobe handler 或同一个函数的多个kprobe实例可以同时运行在不同的CPU上。
- 除了注册和卸载时,kprobe不能使用mutex或分配内存;
code 示例
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
static struct kprobe kp;
// 目标函数执行时会进入这个回调
static int handler_pre(struct kprobe *p, struct pt_regs *regs) {
pr_info("kprobe hit: function=%s, ip=%p\n", p->symbol_name, p->addr);
return 0;
}
static int __init kprobe_init(void) {
kp.symbol_name = "do_fork"; // 目标函数,例如 do_fork
kp.pre_handler = handler_pre;
/* 注册kprobe */
if (register_kprobe(&kp) < 0) {
pr_err("register_kprobe failed\n");
return -1;
}
pr_info("kprobe registered for %s\n", kp.symbol_name);
return 0;
}
static void __exit kprobe_exit(void) {
unregister_kprobe(&kp);
pr_info("kprobe unregistered\n");
}
module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
参考
[monitor] 8. Linux kprobe(内核探针)-CSDN博客
更多推荐


所有评论(0)