zoukankan      html  css  js  c++  java
  • 使用kprobes查看内核内部信息

    前言:
    使用printk打印变量等方法,是调试内核的有效方法之一,但是这种方法必须重新构建并用新内核启动,调试效率比较低。以内核模块的方式使用kprobesjprobes,就可以在任意地址插入侦测器,执行包括printk在内的各种调试工作,而无须重新构建内核,也无须重启。

    一、首先内核必须支持kprobesjprobes

    #make menuconfig

    General setup  --->

    [*] Kprobes

    使内核支持kprobes

    二、kprobes的使用方法:
    1、分配一个kprobe结构体供kprobes运行时使用。
    2、在kprobe结构体的addr成员中设置要插入侦测器的函数地址,可以通过查看system.map查找,或者通过proc文件系统查找

    # cat /proc/kallsyms | grep "do_execve"

    c00bbfb8 T do_execve
    3、如果不想设置addr成员,可以设置symbol_name成员为"do_execve"(想要侦测的函数),需要内核版本在2.6.19以上才支持。

    4、在kprob结构的pre_handler成员设置侦测函数exec_pre_handler

    5、调用register_kprobe函数注册侦测器函数。

    以下是一个用kprobes监测do_execve()函数的例子,在每次调用do_execve()函数之前,就会打印当前的进程pidjiffies值等全局变量信息。

    #include <linux/module.h>
    #include <linux/kprobes.h>
    #include <linux/kallsyms.h>
    
    struct kprobe exec_kp;
    
    static int exec_pre_handler(struct kprobe *p, struct pt_regs *regs)
    {
        printk("Enter into %s
    ", __func__);
        printk("pt_regs:%p, pid:%d, jiffies:%ld
    ", regs, current->tgid, jiffies);
        
        return 0;
    }
    
    static __init int kprobes_exec_init(void)
    {    
    
        /*设置要检测的函数do_execve()的地址*/
        //exec_kp.addr = (kprobe_opcode_t *)0xc00bbfb8;
    
        /*如果不想直接给addr成员设置地址,也可以使用符号名*/
        exec_kp.symbol_name = "do_execve";
    
        exec_kp.pre_handler = exec_pre_handler;
    
        /*注册kprobes*/
        register_kprobe(&exec_kp);
        
        return 0;
    }
    
    
    
    static __exit void kprobes_exec_cleanup(void)
    {
        /*撤销kprobes注册*/
        unregister_kprobe(&exec_kp);
    }
    
    module_init(kprobes_exec_init); 
    module_exit(kprobes_exec_cleanup);
    MODULE_LICENSE("GPL");

    编译并insmod上述ko,那么在do_execve()函数执行之前就会先执行kprobespre_handler所指向的exec_pre_handler,打印内核的当前进程pidjiffies等全局变量的值。由于do_exec函数用于生成进程,因此每次执行ls等命令都会显示一次:

    pt_regs:c52ebf08, pid:110, jiffies:155286

    pt_regs:c5313f08, pid:113, jiffies:159137

    pt_regs:c52ebf08, pid:112, jiffies:159137

    利用kprobes也可以查看内核函数内部任意位置的信息,此时需要把内核函数反汇编,确定想要查看位置相对于函数起始地址的偏移量,在初始化kprobes时,设置kprobe结构体的offset成员,就可以查看内核内部函数任意位置的信息。

    三、jprobes的使用方法

    kprobes可以方便的查看内核内部任意位置的信息,相反,jprobes则是特别为函数开头的侦测准备的,通过jprobes,可以更容易的获取传递给函数的参数。使用jprobes与使用kprobes大体上一样,主要有以下区别:

    1、分配给jprobes的结构体是jprobe结构体,并将指针传递给register_jprobejprobe结构的成员如下所示,只包括kprobeentry两者。

    struct jprobe {
        struct kprobe kp;
        void *entry;    /* probe handling code to jump to */
    };

    struct kprobe kp成员设置的是要侦测函数的符号或地址,entry中为通过JPROBE_ENTRY()宏处理过的侦测器处理程序。

    2、侦测器处理程序的参数应当与需要侦测的函数的参数相同,这样可以方便的打印被侦测函数的形参。使用kprobes时,必须通过寄存器或者栈才能计算出参数的值。而计算方法还依赖于cpu架构,如果使用jprobes,则无须了解架构的详细知识,也能查看参数的值。
    3、侦测器处理函数的尾部必须加上jprobe_return();


    以下是使用jprobes打印do_execve函数的参数的例子,每次调用do_execve之前,都会打印相应的参数:

    #include <linux/module.h>
    #include <linux/kprobes.h>
    #include <linux/kallsyms.h>
    
    struct jprobe exec_jp;
    
    
    int jp_do_execve(const char * filename,
        const char __user *const __user *argv,
        const char __user *const __user *envp,
        struct pt_regs * regs)
    {
        int cnt = 0;
    
        printk("filename = %s
    ", filename);
    
        for(; *argv != NULL;argv++,cnt++)
            printk("argv[%d] = %s
    ", cnt, *argv);
    
        jprobe_return();
        return 0;
    }
    
    
    static __init int jprobes_exec_init(void)
    {    
        exec_jp.kp.symbol_name = "do_execve";
    
        exec_jp.entry = JPROBE_ENTRY(jp_do_execve);
    
        /*注册jprobes*/
        register_jprobe(&exec_jp);
        
        return 0;
    }
    
    
    
    static __exit void jprobes_exec_cleanup(void)
    {
        /*撤销jprobes注册*/
        unregister_jprobe(&exec_jp);
    }
    
    module_init(jprobes_exec_init); 
    module_exit(jprobes_exec_cleanup);
    MODULE_LICENSE("GPL");

    每次执行ls时都会调用do_execve函数,具体的打印如下:

    # ls

    filename = /bin/ls

    argv[0] = ls


    from:http://sunjiangang.blog.chinaunix.net/uid-9543173-id-3571748.html
  • 相关阅读:
    《Head First 设计模式》 第八、九章 模板方法模式、迭代器模式、组合模式
    《Head First 设计模式》 第十、十一章 状态模式、代理模式
    《Head First 设计模式》 第六、七章 命令模式、适配器模式、外观模式
    《Head First 设计模式》 第四、五章 工厂模式、单例模式
    敏捷软件开发 第十章、第十一章、第十二章
    《Head First 设计模式》 第二、三章 观察者模式、装饰者模式
    二叉树-面试题27-二叉树的镜像
    堆栈-155-最小栈
    堆栈-20-有效括号
    java编程基础(六)----泛型
  • 原文地址:https://www.cnblogs.com/hehehaha/p/6332304.html
Copyright © 2011-2022 走看看