[fw]用Kprobes调试(debug)内核
Kprobes是一种运行时动态调试内核的机制, 你可以用它设置断点并收集调试信息, 甚至改变内核行为.
Kprobes分三种, 普通kprobes以及基于普通kprobes的jprobes和kretprobes. kprobes可以注入某个地址, jprobes用来注入某个函数入口, 而kretprobes则用来注入某个函数返回.
实现原理
Kprobes的实现主要利用了处理器的异常和单步执行特性. 以普通kprobes举例, 注册时它会复制一份被注入的指令, 并加入断点(例如x86的int 3), 当CPU执行到被注入的指令时就会陷入到Kprobes中, 此时Kprobes先运行钩子函数”pre_handler”, 然后单步执行被复制的指令, 并且由于是单步执行模式, 指令执行完毕后会再次触发异常而陷入到Kprobes中, 此时Kprobes会运行钩子函数”post_handler”并最终返回.
使用方法
通常情况下, Kprobes以内核模块的形式工作, 模块的init函数用来注册Kprobes, exit函数相应得用来注销, pre_handler和post_handler分别在单步执行被复制的指令前后运行, fault_handler则在单步执行发生异常时运行.
Kprobes模块写起来很简单, 下面是一个内核源码中的例子, 演示如何在每次do_fork()的时候打印选定寄存器中的内容.
1 #include <linux/kernel.h> 2 #include <linux/module.h> 3 #include <linux/kprobes.h> 4 5 /* For each probe you need to allocate a kprobe structure */ 6 static struct kprobe kp = { 7 .symbol_name = "do_fork", 8 }; 9 10 /* kprobe pre_handler: called just before the probed instruction is executed */ 11 static int handler_pre(struct kprobe *p, struct pt_regs *regs) 12 { 13 printk(KERN_INFO "pre_handler: p->addr = 0x%p, ip = %lx," 14 " flags = 0x%lx\n", 15 p->addr, regs->ip, regs->flags); 16 17 /* A dump_stack() here will give a stack backtrace */ 18 return 0; 19 } 20 21 /* kprobe post_handler: called after the probed instruction is executed */ 22 static void handler_post(struct kprobe *p, struct pt_regs *regs, 23 unsigned long flags) 24 { 25 printk(KERN_INFO "post_handler: p->addr = 0x%p, flags = 0x%lx\n", 26 p->addr, regs->flags); 27 } 28 29 /* 30 * fault_handler: this is called if an exception is generated for any 31 * instruction within the pre- or post-handler, or when Kprobes 32 * single-steps the probed instruction. 33 */ 34 static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr) 35 { 36 printk(KERN_INFO "fault_handler: p->addr = 0x%p, trap #%dn", 37 p->addr, trapnr); 38 /* Return 0 because we don't handle the fault. */ 39 return 0; 40 } 41 42 static int __init kprobe_init(void) 43 { 44 int ret; 45 kp.pre_handler = handler_pre; 46 kp.post_handler = handler_post; 47 kp.fault_handler = handler_fault; 48 49 ret = register_kprobe(&kp); 50 if (ret < 0) { 51 printk(KERN_INFO "register_kprobe failed, returned %d\n", ret); 52 return ret; 53 } 54 printk(KERN_INFO "Planted kprobe at %p\n", kp.addr); 55 return 0; 56 } 57 58 static void __exit kprobe_exit(void) 59 { 60 unregister_kprobe(&kp); 61 printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr); 62 } 63 64 module_init(kprobe_init) 65 module_exit(kprobe_exit) 66 MODULE_LICENSE("GPL");
这只是一个简单的打印示例, Kprobes还可以修改被注入函数的上下文, 如内核数据结构和寄存器, 并且都是在运行时动态修改, 多么美好!
ref:
1, https://lwn.net/Articles/132196/
2, https://www.kernel.org/doc/Documentation/kprobes.txt
3, http://www.ibm.com/developerworks/library/l-kprobes/index.html