内核必须启用 config_kprobes=y,否则kprobe操作静默失败或报-enosys;需通过grep config_kprobes /boot/config-$(uname -r)确认为=y,而非=m或未定义,且须注意符号名变更、函数内联、寄存器架构差异等关键细节。

内核必须启用 CONFIG_KPROBES=y,否则所有 kprobe 操作都会静默失败或直接报错 -ENOSYS。
确认内核是否真正支持 kprobe
很多用户以为装了 linux-headers 就万事大吉,其实关键在运行时内核配置。执行以下命令验证:
grep CONFIG_KPROBES /boot/config-$(uname -r)
输出必须是 CONFIG_KPROBES=y(不是 =m 或未定义)。若为 =m,说明 kprobe 编译为模块,但默认不自动加载,需手动 modprobe kprobes,且部分发行版(如某些嵌入式定制内核)会禁用该选项。
常见误判点:
- 看到
/lib/modules/$(uname -r)/kernel/kernel/kprobes.ko存在 ≠ 功能可用 -
lsmod | grep kprobes为空 ≠ 不支持,可能只是没加载 - 使用
kprobe_events接口时报Permission denied,大概率是内核未启用,而非权限问题
两种主流用法:模块方式 vs debugfs/ftrace 方式
模块方式适合需要自定义逻辑(比如修改寄存器、条件跳过、统计上下文);debugfs 方式适合快速验证、无编译环境或临时排查。
模块方式依赖 register_kprobe(),但要注意:
-
symbol_name必须是内核导出的符号(cat /proc/kallsyms | grep do_fork能查到),do_fork在 5.10+ 内核中已移除,应改用_do_fork或sys_clone - 不能探测黑名单函数(如
do_int3、get_kprobe),register_kprobe()返回负值时要检查dmesg中的kprobe: blacklisted symbol提示 -
pre_handler中禁止调用可能睡眠的函数(如msleep、mutex_lock),否则触发BUG: scheduling while atomic
debugfs 方式更轻量:
echo 'p:_do_fork _do_fork' > /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/events/kprobes/_do_fork/enabled
cat /sys/kernel/debug/tracing/trace_pipe
注意:/sys/kernel/debug 必须已挂载(mount -t debugfs nodev /sys/kernel/debug),且当前用户需有写权限(通常需 root)。
常见错误:探测点注册成功但无输出
这比 outright failure 更难排查。典型原因包括:
- 函数已被 inlining:比如
__do_sys_open在某些配置下被内联进do_syscall_64,此时对__do_sys_open下 kprobe 无效 —— 应改用addr+kallsyms_lookup_name()定位实际地址 - 探测的是旧符号名:例如在 5.15+ 内核中仍用
do_fork,实际符号已改为_do_fork或kernel_clone,register_kprobe()不报错但kp.addr为NULL -
post_handler不触发:因为被探测指令本身是跳转或异常指令(如int 0x80),kprobe 无法安全单步,此时仅pre_handler执行,post_handler被跳过 - ftrace 已接管该函数:当
CONFIG_KPROBES_ON_FTRACE=y且函数在 ftrace 列表中,kprobe 会自动降级为 ftrace 模式,行为略有差异(如regs->ip指向 ftrace stub 而非原函数)
最易被忽略的一点:kprobe 对寄存器状态的读取高度依赖架构和内核版本。x86_64 上 regs->di 是第一个参数,ARM64 上却是 regs->regs[0];同一内核版本下,pt_regs 布局也可能因 CONFIG_FRAME_POINTER 开关而变化。别硬背寄存器名,先 dump_stack() 看上下文,再查对应架构的 arch/x86/include/asm/ptrace.h 或 arch/arm64/include/asm/ptrace.h。











