优点:
以内核模块的方式使用kprobes,可以在任意地方插入探测器,执行包括printk在内的各种调试工作,而无须重新构建内核,也无须重启。相比于printk,是调试内核的有效方法之一。
前提:
内核必须支持kprobes、jprobes:
#make menuconfig
General setup --->
[*] Kprobes ------这项要选上
使内核支持kprobes。
kprobes的使用方法:
1、分配一个kprobe结构体供kprobes运行时使用。
2、设置symbol_name成员为"do_execve"(想要探测的函数,可以通过查看system.map查找,或者通过proc文件系统查找:# cat /proc/kallsyms | grep "do_execve")。
3、给kprob结构体的pre_handler成员赋值探测函数exec_pre_handler。
4、调用register_kprobe函数注册探测器函数。
示例:
在每次调用do_execve()函数之前,就会打印当前的进程pid、jiffies值等全局变量信息。
#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\n", __func__);
printk("pt_regs:%p, pid:%d, jiffies:%ld\n", regs, current->tgid, jiffies);
return 0;
}
static __init int kprobes_exec_init(void)
{
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 v2");
编译并insmod上述ko,那么在do_execve()函数执行之前就会先执行kprobes的pre_handler所指向的exec_pre_handler,打印内核的当前进程pid、jiffies等全局变量的值。由于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成员,就可以查看内核内部函数任意位置的信息。