kprobe
kprobe 是 Linux 内核中一种强大的 动态追踪机制,允许开发者在内核任意函数或指令处注册钩子(probe),用于调试、性能分析、故障排查等。下面是对其 原理 的详细分析:
基本概念
- kprobe(Kernel Probe):动态插桩,在运行中的内核中插入断点,回调用户注册的处理函数。
- jprobe(已废弃):在函数开始处插入 probe,用于获取函数参数。
- kretprobe:插入函数返回点,用于捕获返回值。
核心原理分析
工作机制流程
以下是一次 kprobe 的典型处理流程:
- 注册 probe:
- 使用 register_kprobe() 接口注册一个 probe。
- 内核会查找目标地址对应的指令,并将其替换为 int3(x86 中断指令 0xCC)或架构相关的 trap 指令 (如,arm64 brk指令)。
- 执行到 probe 点:
- 当 CPU 执行到该地址时,发生 #BP(Breakpoint)异常。
- 中断处理程序捕获异常,判断是否为 kprobe 插入的断点。
- 执行预处理函数:
- 如果是 kprobe 插入的断点,内核执行用户提供的 pre_handler()。
- 用户可以读取寄存器、堆栈,甚至篡改指令流。
- 恢复原指令:
- 内核在执行原始指令之前,先将其恢复,然后单步执行。
- 执行完后再恢复为断点(断点重插)。
- 执行后处理函数(可选):
- 在单步完成后,内核会调用 post_handler()。
实现细节(x86 为例)
- 插入断点
- 内核替换目标函数某指令为 int3(0xCC,单字节):
*addr = 0xCC; - 保存原始指令,便于后续恢复。
- 异常处理流程
当 int3 被触发:
- a. CPU 触发 #BP,进入 do_int3():
do_int3() -> kprobe_int3_handler() -> kprobe_handler() - b. 内核检查异常地址是否是注册的 kprobe:
- 如果是,则调用 pre_handler。
- 设置单步标志(EFLAGS.TF = 1),并将原指令写回地址。
- 单步异常触发 do_debug(),执行原指令后触发:
- c. 调用 post_handler。
- 再将指令替换回断点(0xCC)。
数据结构核心
以下是几个关键的数据结构:
struct kprobe {
struct hlist_node hlist;
kprobe_opcode_t *addr; // 插入的地址
kprobe_pre_handler_t pre_handler; // 断点前回调
kprobe_post_handler_t post_handler; // 单步后回调
kprobe_opcode_t opcode; // 原始指令
...
};
注册接口:
int register_kprobe(struct kprobe *kp);
void unregister_kprobe(struct kprobe *kp);
kretprobe 原理(函数返回点)
kretprobe 是在函数返回时触发:
- 内核 hook 掉函数的返回地址(在栈中)。
- 用 trampoline 地址替换返回地址。
- 当函数执行完成返回 trampoline 函数时,执行用户定义的 handler()。
关键结构:
struct kretprobe {
struct kprobe kp;
kretprobe_handler_t handler; // 函数返回时回调
...
};
并发与异常处理
- 每 CPU 栈上维护一个 kprobe_ctlblk 结构体,避免多 probe 嵌套时状态错乱。
- 支持 reentrancy,但嵌套 probe 会有复杂状态切换。
- kprobe 是抢占不可重入的(除非显式开启 KPROBE_FLAG_SHARED)。
限制和注意事项
- 不能插入到中断上下文中可能运行的函数中(风险高)。
- 不能 probe 某些 early boot 函数或 __init 代码。
- 不可插入到 INT3 之后立即是 RET/IRET 的位置。
- kprobe 支持动态加载模块,但注册时要特别小心地址变化。
使用示例(x86)
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
printk("kprobe hit at %p\n", p->addr);
return 0;
}
static struct kprobe kp = {
.symbol_name = "do_fork",
.pre_handler = handler_pre,
};
static int __init kprobe_init(void)
{
register_kprobe(&kp);
return 0;
}
static void __exit kprobe_exit(void)
{
unregister_kprobe(&kp);
}
工具和扩展
- perf、ftrace、bpftrace、systemtap 底层都可用 kprobe 实现。
- eBPF 支持将 probe 直接挂在 kprobe 上运行,无需内核模块(使用 tracefs + kprobe_events)。
深入分析 Linux kprobe 原理
1112

被折叠的 条评论
为什么被折叠?



