[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

posted @ 2013-12-29 16:22  jeremyatchina  阅读(521)  评论(0编辑  收藏  举报