kprobe(一)

##Kprobe config

CONFIG_KPROBES=y 
CONFIG_KALLSYMS=y or CONFIG_KALLSYMS_ALL=y

##Kprobe struct

 struct kprobe {
     struct hlist_node hlist;
     /* list of kprobes for multi-handler support */
     struct list_head list;
     /*count the number of times this probe was temporarily disarmed */
     unsigned long nmissed;
     /* location of the probe point */
     kprobe_opcode_t *addr;    //设置的kprobe地址,会根据symbol name和offset计算出来,不需要在register时传入
     /* Allow user to indicate symbol name of the probe point */
     const char *symbol_name;  //设置的kprobe地址所在的symbol name,比如函数名,需要在register时指定
     /* Offset into the symbol */
     unsigned int offset;            //kprobe地址相对于symbol name的偏移地址,需要在register时指定
     /* Called before addr is executed. */
     kprobe_pre_handler_t pre_handler;  //在kprobe地址执行前调用的回调,可以用来printk调试信息
     /* Called after addr is executed, unless... */
     kprobe_post_handler_t post_handler; //在kprobe地址执行后调用的回调,可以用来printk调试信息
     /*
      * ... called if executing addr causes a fault (eg. page fault).
      * Return 1 if it handled fault, otherwise kernel will see it.
      */
     kprobe_fault_handler_t fault_handler;  //如果在执行pre/post之间时发生了fault会调用此回调
     /*
      * ... called if breakpoint trap occurs in probe handler.
      * Return 1 if it handled break, otherwise kernel will see it.
      */
     kprobe_break_handler_t break_handler;

     /* Saved opcode (which has been replaced with breakpoint) */
     kprobe_opcode_t opcode;  //copy并保存的原有kprobe地址处的指令,因为此地址会被断点指令所替代
     /* copy of the original instruction */
     struct arch_specific_insn ainsn; //copy并保存原有指令以便于执行pre和post handler,并且计算和保存执行完断点后的返回地址

     /*
      * Indicates various status flags.
      * Protected by kprobe_mutex after this kprobe is registered.
      */
     u32 flags;

##Kprobe API

int register_kprobe(struct kprobe *p);
void unregister_kprobe(struct kprobe *p);

当我们实现了上面的一个结构体,并且注册到内核中后,内核就会在每次执行到断点前和后执行pre和post handler,在handler中可以获取各种寄存器值或者内存中的数据,便于分析debug问题。kprobe可以注册到内核中的任何地址,包括中断处理程序中。

注册成功后的kprobes可以在debugfs中查看:

/sys/kernel/debug/kprobes

##Kprobe sample

ARM64 platform:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#define MAX_SYMBOL_LEN  64
static char symbol[MAX_SYMBOL_LEN] = "_do_fork";
module_param_string(symbol, symbol, sizeof(symbol), 0644);
/* For each probe you need to allocate a kprobe structure */
static struct kprobe kp = {
    .symbol_name    = symbol,
};
/* kprobe pre_handler: called just before the probed instruction is executed */
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
    pr_info("<%s> pre_handler: p->addr = 0x%p, pc = 0x%lx,"
            " pstate = 0x%lx\n",
        p->symbol_name, p->addr, (long)regs->pc, (long)regs->pstate);
    /* 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)
{
    pr_info("<%s> post_handler: p->addr = 0x%p, pstate = 0x%lx\n",
        p->symbol_name, p->addr, (long)regs->pstate);
}
/*
 * 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)
{
    pr_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) {
         pr_err("register_kprobe failed, returned %d\n", ret);
         return ret;
     }
     pr_info("Planted kprobe at %p\n", kp.addr);
     return 0;
 }
 static void __exit kprobe_exit(void)
 {
     unregister_kprobe(&kp);
     pr_info("kprobe at %p unregistered\n", kp.addr);
 }
 module_init(kprobe_init)
 module_exit(kprobe_exit)
 MODULE_LICENSE("GPL");

上面的例子我们在pre和post handler中仅仅是addr信息以及reg信息,当然我们也可以用来打印kernel中其他的一些信息,甚至是直接修改寄存器。

比如下面我们用post handler来打印系统中所有的task信息,只需要替换post handler的实现即可:


static void handler_post(struct kprobe *p, struct pt_regs *regs,
                    unsigned long flags) {
    struct task_struct *task;
    read_lock(&tasklist_lock);
    for_each_process(task) {
        printk("pid =%x task-info_ptr=%lx\n", task->pid,  task->thread_info);
    }
    read_unlock(&tasklist_lock);
}       	

上面的代码替换掉handler_post可能并不能直接编译过,会报链接错误:

ERROR: "tasklist_lock" [samples/kprobes/kprobe_example.ko] undefined!

这种链接错误要怎么解决,在这里也顺便拓展一下,链接报错一般都是符号表没有找到,对于内核来说符号表的导出,是需要使用如下宏进行声明:


EXPORT_SYMBOL

EXPORT_SYMBOL_GPL

看到这里我想大家应该很常见了吧。这里只需要对上面的tasklist_lock声明一下,既可以编译运行进行测试了,这些sample均在我本地的调试板上运行通过。

另外kernel中也提供了kprobe sample:kernel/samples/kprobes/目录中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值