您的位置:首页 > 其它

Kprobe与内核模块

2020-06-04 05:37 1426 查看

一、kprobe:
Dtrace,或者其他调试工具,是如何获取系统调用的栈信息,为什么能统计系统调用的次数?在linux中,这个问题的答案是kprobe

Linux内核调试技术——kprobe使用与实现这篇文章详细介绍了kprobe。抛开各种细节,简单的描述就是:内核提供了一组方法,使用这组方法可以在内核任意一个方法上加一个钩子,每当内核执行到钩子的时候,就可以执行用户自定义的代码。具体的实现原理是:

比如现在要在do_fork上加一个钩子,首先根据名称获取该方法在内核中的代码地址,类似于cat /proc/kallsyms | grep do_fork返回的地址 ffffffff81084950 处的代码,并将其改成一个软中断。当程序执行到这条指令到时候,就会陷入中断处理程序,中断处理程序执行用户指定到代码,这样就实现了hook。

既然作者代码都给了,那就上手试一试:

首先确认系统编译参数中开启了kprobe的支持,具体方法是cat /boot/config-3.10.0-514.el7.x86_64 |grep KPROBES,文章提到,这个默认是开启的。

然后使用内核提供的方法编写代码,并且将代码编译成内核模块,加载到内核中。代码如下:

//kprobe_example.c
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
#include <linux/kprobes.h>

//统计do_fork()总共执行了几次
static int total_count = 0;

//前置方法,这里可以拿到方法入参和栈,每次执行do_fork() total_count++
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
total_count++;
//printk 打印的日志 可以通过dmesg 命令查看
printk(KERN_INFO “累计调用do_fork[%d]次\n”,total_count);
return 0;
}

//后置方法,这里可以拿到方法返回值
static void handler_post(struct kprobe *p, struct pt_regs *regs,
unsigned long flags)
{
}
//方法执行失败的回调函数
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;
}
//通过kprobe这个数据结构,定义要hook的内核方法名称
static struct kprobe kp = {
.symbol_name = “do_fork”,
};
//通过register_kprobe 方法更改内核对应方法的指令
static int 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;

}
//通过unregister_kprobe卸载hook
static void 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”);
编写Makefile文件,并执行make命令,将kprobe_example.c编译成kprobe_example.ko, Makefile内容如下:

// Makefile
obj-m +=kprobe_example.o
CURRENT_PATH:=(shellpwd)LINUXKERNELPATH:=/lib/modules/(shell pwd) LINUX_KERNEL_PATH:=/lib/modules/(shellpwd)LINUXK​ERNELP​ATH:=/lib/modules/(shell uname -r)/build
all:
make -C (LINUXKERNELPATH)M=(LINUX_KERNEL_PATH) M=(LINUXK​ERNELP​ATH)M=(CURRENT_PATH) modules
clean:
make -C (LINUXKERNELPATH)M=(LINUX_KERNEL_PATH) M=(LINUXK​ERNELP​ATH)M=(CURRENT_PATH) clean
然后执行sudo insmod kprobe_example.ko 装载内核模块,然后使用dmesg查看内核日志:

最后记得sudo rmmod kprobe_example.ko 卸载模块。

至此,linux能够获取内核代码执行信息的原理就搞清楚了,使用kprobe,每次只要装载一个内核模块就能进行调试,卸载模块就能停止调试。

在通过make编译内核模块的时候会出现如下的错误:
SSL error:02001002:system library:fopen:No such file or directory: bss_file.c:175
SSL error:2006D080:BIO routines:BIO_new_file:no such file: bss_file.c:182
sign-file: certs/signing_key.pem: No such file or directory

解决办法如下:

  1. Install kernel-devel package
    yum install kernel-devel

  2. Generate another pair for using it with the kernel module you want to sign
    openssl req -new -x509 -newkey rsa:2048 -keyout signing_key.pem -outform DER -out signing_key.x509 -nodes -subj “/CN=Owner/”

  3. Copy the generated keys to /usr/src/kernels/(uname−r)/certs/cpsigningkey.x509signingkey.pem/usr/src/kernels/(uname -r)/certs/ cp signing_key.x509 signing_key.pem /usr/src/kernels/(uname−r)/certs/cpsigningk​ey.x509signingk​ey.pem/usr/src/kernels/(uname -r)/certs/

  4. Compile module sources by following appropriate steps
    make

  5. Do make modules_install https://forums.aws.amazon.com/
    make modules_install

  6. Discard the signing_key.pem or store it in safe place, if needs to be used for future signing.

  7. You may verify if module signature is being appended to the kernel module
    #hexdump -C <module_file_name.ko>

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: