IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    用Kprobes调试内核

    Adam Lee发表于 2013-06-26 21:00:00
    love 0

    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()的时候打印选定寄存器中的内容.

    #include kernel.h>
    #include <linuxmodule.h>
    #include kprobes.h>
    
    * For each probe you need to allocate a kprobe structure */
    static struct kprobe kp = {
        .symbol_name    = "do_fork",
    };
    
    /* kprobe pre_handler: called just before the probed instruction is executed */
    static int handler_pre(struct kprobe *p, struct pt_regs *regs)
    {
        printk(KERN_INFO "pre_handler: p->addr = 0x%p, ip = %lx,"
                " flags = 0x%lx\n",
            p->addr, regs->ip, regs->flags);
    
        /* A dump_stack() here will give a stack backtrace */
        return 0;
    }
    
    /* kprobe post_handler: called after the probed instruction is executed */
    static void handler_post(struct kprobe *p, struct pt_regs *regs,
                    unsigned long flags)
    {
        printk(KERN_INFO "post_handler: p->addr = 0x%p, flags = 0x%lx\n",
            p->addr, regs->flags);
    }
    
    /*
     * fault_handler: this is called if an exception is generated for any
     * instruction within the pre- or post-handler, or when Kprobes
     * single-steps the probed instruction.
     */
    static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
    {
        printk(KERN_INFO "fault_handler: p->addr = 0x%p, trap #%dn",
            p->addr, trapnr);
        /* Return 0 because we don't handle the fault. */
        return 0;
    }
    
    static int __init kprobe_init(void)
    {
        int ret;
        kp.pre_handler = handler_pre;
        kp.post_handler = handler_post;
        kp.fault_handler = handler_fault;
    
        ret = register_kprobe(&kp;);
        if (ret < 0) {
            printk(KERN_INFO "register_kprobe failed, returned %d\n", ret);
            return ret;
        }
        printk(KERN_INFO "Planted kprobe at %p\n", kp.addr);
        return 0;
    }
    
    static void __exit kprobe_exit(void)
    {
        unregister_kprobe(&kp;);
        printk(KERN_INFO "kprobe at %p unregistered\n", kp.addr);
    }
    
    module_init(kprobe_init)
    module_exit(kprobe_exit)
    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



沪ICP备19023445号-2号
友情链接