kernel 调试技术之kprobe

kprobe是linux内核提供了一个钩子回调机制,能够让我们轻松的加入回调函数在指定的函数之前调用。方便我们在不更改调试模块代码的情况下,加入回调函数以供调试使用。

 

实验环境

Ubuntu16.04


准备工作

查找想要hook的kernel内核函数。

sudo vim /boot/System.map-$(uname -r)

 

代码实例

这里我们决定对内核函数_do_fork添加回调钩子,也就是说每当_do_fork被调用时,我们注册的回调接口,也会被调用。

kprobe.c

#include 
#include 
#include 
#include 

//定义要Hook的函数,本例中_do_fork
static struct kprobe kp =
{
    .symbol_name = "_do_fork",
};

static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
    printk(KERN_ERR "hook in handler_pre");
    return 0;
}

static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags)
{

    printk(KERN_ERR "hook in handler_post");
}

static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
{
    printk(KERN_ERR "hook in handler_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"); 

编写Makefile

ifneq ($(KERNELRELEASE),)                                                                                                                                                                                                                                              
    obj-m := kprobe.o
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
~                                                                                                                                                                                                                                                                      
~                                                         

编译安装模块

make
sudo ./kprobe.ko

 

执行

 dmesg | tail

 可以看到我们的钩子函数被调用了。



[18186.961947] kprobe: loading out-of-tree module taints kernel.
[18186.961975] kprobe: module verification failed: signature and/or required key missing - tainting kernel
[18186.965653] Planted kprobe at ffffffff8fe86080
[18188.794619] hook in handler_pre
[18188.794622] hook in handler_post
[18188.808006] hook in handler_pre
[18188.808010] hook in handler_post
[18189.567033] hook in handler_pre
[18189.567042] hook in handler_post
[18194.470944] hook in handler_pre
[18194.470953] hook in handler_post
[18195.214975] hook in handler_pre
[18195.214984] hook in handler_post
[18195.790376] hook in handler_pre
[18195.790384] hook in handler_post
[18196.270902] hook in handler_pre

 

配合dump_stack() 使用

配合dump_stack(),我们可以轻松获得_do_fork调用时的堆栈情况。

修改handler_post函数,添加dump_stack();

static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags)
{
    printk(KERN_ERR "hook in handler_post");
    dump_stack();
}

 

执行

 dmesg | tail

可以看到堆栈里函数的调用次序,了解是谁调用了_do_fork。

[19565.981675] hook in handler_post
[19574.126920] hook in handler_pre
[19574.126934] CPU: 2 PID: 15488 Comm: bash Tainted: G           OE   4.13.0-36-generic #40~16.04.1-Ubuntu
[19574.126939] Hardware name: SHINELON Computer P65xRP/P65xRP, BIOS 1.05.03 08/25/2016
[19574.126940] Call Trace:
[19574.126953]  dump_stack+0x63/0x8b
[19574.126962]  handler_post+0xe/0x1c [kprobe]
[19574.126970]  kprobe_ftrace_handler+0x111/0x120
[19574.126976]  ? _do_fork+0x5/0x3f0
[19574.126983]  ftrace_ops_assist_func+0x74/0x110
[19574.126989]  ? security_file_alloc+0x44/0x80
[19574.126995]  0xffffffffc023e0d5
[19574.127000]  ? sys_vfork+0x30/0x30
[19574.127005]  ? _do_fork+0x5/0x3f0
[19574.127009]  _do_fork+0x5/0x3f0
[19574.127014]  SyS_clone+0x19/0x20
[19574.127018]  ? _do_fork+0x5/0x3f0
[19574.127022]  ? SyS_clone+0x19/0x20
[19574.127028]  do_syscall_64+0x61/0xd0
[19574.127035]  entry_SYSCALL64_slow_path+0x25/0x25
[19574.127039] RIP: 0033:0x7eff7b8de41a
[19574.127042] RSP: 002b:00007fff516e25d0 EFLAGS: 00000246 ORIG_RAX: 0000000000000038
[19574.127047] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007eff7b8de41a
[19574.127050] RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000001200011
[19574.127053] RBP: 00007fff516e2600 R08: 0000000000000000 R09: 00007eff7c214700
[19574.127055] R10: 00007eff7c2149d0 R11: 0000000000000246 R12: 0000000000003c80
[19574.127058] R13: 00000000025f9b68 R14: 0000000000000000 R15: 00000000025f8628

另外还有更多灵活的用法等待发掘。

 

 

你可能感兴趣的:(Linux内核)