无法从kprobe处理器读取syscall参数问题求助
问题:kprobe监控__x64_sys_kill时获取的PID与信号值异常
我在__x64_sys_kill上注册kprobe,目的是监控调用时的目标PID和信号编号。kprobe成功注册,dmesg能输出日志,但日志中的PID和信号值完全不符合实际,无法解读。
我的代码
#include <linux/module.h> #include <linux/kernel.h> #include <linux/kprobes.h> #include <linux/ptrace.h> #include <linux/signal.h> #include <linux/sched.h> static struct kprobe kp; static int handler_pre(struct kprobe *p, struct pt_regs *regs) { pid_t pid = (pid_t)regs->di; int sig = (int)regs->si; printk(KERN_INFO "kprobe pre_handler: kill syscall - PID: %d, Signal: %d\n", pid, sig); return 0; } static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) { pid_t pid = (pid_t)regs->di; int sig = (int)regs->si; printk(KERN_INFO "kprobe post_handler: kill syscall - PID: %d, Signal: %d\n", pid, sig); } static int __init kprobe_init(void) { kp.symbol_name = "__x64_sys_kill"; kp.pre_handler = handler_pre; kp.post_handler = handler_post; int ret = register_kprobe(&kp); if (ret < 0) { printk(KERN_INFO "register_kprobe failed, returned %d\n", ret); return ret; } printk(KERN_INFO "kprobe registered for __x64_sys_kill\n"); return 0; } static void __exit kprobe_exit(void) { unregister_kprobe(&kp); printk(KERN_INFO "kprobe unregistered\n"); } module_init(kprobe_init) module_exit(kprobe_exit) MODULE_LICENSE("GPL");
输出结果
kernel: [ 2605.007657] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007658] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007660] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007661] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007662] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007663] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007666] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007667] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007668] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007669] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007671] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007672] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007674] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007675] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007676] kprobe pre_handler: kill syscall - PID: 6029144, Signal: 62 kernel: [ 2605.007677] kprobe post_handler: kill syscall - PID: 6029144, Signal: 62
已尝试的方法
- 改用kretprobes,得到完全相同的异常结果
- 尝试通过
regs->orig_ax与__NR_kill匹配来确认是否为kill系统调用,但代码从未进入判断分支:
long syscall_nr = regs->orig_ax; // Check if it is the kill syscall (syscall number 62 on x86_64) if (syscall_nr == __NR_kill) { // NEVER GOT HERE }
我只是为了学习内核编程与Linux内核原理,并非项目开发,特此求助。
问题分析与修复方案
核心问题
- 参数获取方式错误:x86_64架构下,直接访问
regs->di/regs->si获取系统调用参数不可靠——内核的struct pt_regs寄存器布局可能随版本或编译选项变化,且内核内部调用系统调用入口时,寄存器中的值并非用户传递的参数。 - 未过滤内核内部调用:
__x64_sys_kill不仅会被用户空间的kill系统调用触发,还可能被内核其他路径直接调用,这类调用不会设置orig_ax,导致你检查系统调用号的逻辑失效,同时输出无效参数值。
修复后的代码
#include <linux/module.h> #include <linux/kernel.h> #include <linux/kprobes.h> #include <linux/ptrace.h> #include <linux/signal.h> #include <linux/sched.h> #include <linux/syscalls.h> static struct kprobe kp; static int handler_pre(struct kprobe *p, struct pt_regs *regs) { // 仅处理用户空间发起的系统调用 if (user_mode(regs)) { pid_t pid = (pid_t)syscall_get_arg(regs, 0); int sig = (int)syscall_get_arg(regs, 1); printk(KERN_INFO "kprobe pre_handler: user kill syscall - PID: %d, Signal: %d\n", pid, sig); } return 0; } static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) { if (user_mode(regs)) { pid_t pid = (pid_t)syscall_get_arg(regs, 0); int sig = (int)syscall_get_arg(regs, 1); printk(KERN_INFO "kprobe post_handler: user kill syscall - PID: %d, Signal: %d\n", pid, sig); } } static int __init kprobe_init(void) { kp.symbol_name = "__x64_sys_kill"; kp.pre_handler = handler_pre; kp.post_handler = handler_post; int ret = register_kprobe(&kp); if (ret < 0) { printk(KERN_INFO "register_kprobe failed, returned %d\n", ret); return ret; } printk(KERN_INFO "kprobe registered for __x64_sys_kill\n"); return 0; } static void __exit kprobe_exit(void) { unregister_kprobe(&kp); printk(KERN_INFO "kprobe unregistered\n"); } module_init(kprobe_init) module_exit(kprobe_exit) MODULE_LICENSE("GPL");
关键修改点
user_mode(regs):判断当前上下文是否来自用户空间,过滤内核内部对__x64_sys_kill的调用,避免无效输出。syscall_get_arg():使用内核提供的标准接口获取系统调用参数,该接口会根据当前架构和内核版本正确解析寄存器中的参数值,兼容性和可靠性远高于直接访问寄存器字段。- 若需要验证系统调用号,可在
user_mode(regs)判断后添加:if (syscall_get_nr(current, regs) == __NR_kill) { // 处理逻辑 }
内容的提问来源于stack exchange,提问作者Jelal
相关产品推荐
相关产品推荐

