`kprobe`和`kretprobe`同一个函数能不能调注册多个回调?

kprobekretprobe同一个函数能不能调注册多个回调?

答案是:可以

// kretporbe的代码测试代码
/* Here we use the entry_hanlder to timestamp function entry */
static int entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
        struct my_data *data;

        if (!current->mm)
                return 1;       /* Skip kernel threads */

        data = (struct my_data *)ri->data;
        data->entry_stamp = ktime_get();
        printk("22222222222>>>>");

        return 0;
}

/*
 * Return-probe handler: Log the return value and duration. Duration may turn
 * out to be zero consistently, depending upon the granularity of time
 * accounting on the platform.
 */
static int ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
        long retval = regs_return_value(regs);
        struct my_data *data = (struct my_data *)ri->data;
        s64 delta;
        ktime_t now;

        now = ktime_get();
        delta = ktime_to_ns(ktime_sub(now, data->entry_stamp));
        printk("retval = %ld\n", retval);

        return 0;
}

// kprobe的测试代码
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
//      long long stuck_time = 0;
//      show_regs_ptr(regs);
#ifdef CONFIG_X86
        pr_info("1 >>>>>>> <%s> pre_handler: p->addr = 0x%p, ip = %lx, flags = 0x%lx\n",
                p->symbol_name, p->addr, regs->ip, regs->flags);
#endif
}

static void handler_post(struct kprobe *p, struct pt_regs *regs,
                                unsigned long flags)
{
//      dump_stack();
#ifdef CONFIG_X86
        pr_info("1 >>>>>> <%s> post_handler: p->addr = 0x%p, flags = 0x%lx\n",
                p->symbol_name, p->addr, regs->flags);
#endif
}

内核相关信息的打印信息,内核驱动的加载顺序是kprobe,然后kretprobe,从以下的结果来看,先执行了kretprobe entry_handler,然后执行kprobe pre,post,最后执行kret ret_handler

[675254.334490] 22222222222>>>>
[675254.334492] 1 >>>>>>> <show_mountinfo> pre_handler: p->addr = 0x00000000bdb91af1, ip = ffffffffb93dad21, flags = 0x286
[675254.334495] 1 >>>>>> <show_mountinfo> post_handler: p->addr = 0x00000000bdb91af1, flags = 0x286
[675254.334502] retval = 0
[675254.334533] 22222222222>>>>
[675254.334534] 1 >>>>>>> <show_mountinfo> pre_handler: p->addr = 0x00000000bdb91af1, ip = ffffffffb93dad21, flags = 0x282
[675254.334538] 1 >>>>>> <show_mountinfo> post_handler: p->addr = 0x00000000bdb91af1, flags = 0x282
[675254.334544] retval = 0

内核驱动的加载顺序kret kp,从以下结果来看,顺序kp:pre -> kret:entry -> kp:post -> kp:ret

[675698.169300] 1 >>>>>>> <show_mountinfo> pre_handler: p->addr = 0x00000000bdb91af1, ip = ffffffffb93dad21, flags = 0x286
[675698.169303] 22222222222>>>>
[675698.169304] 1 >>>>>> <show_mountinfo> post_handler: p->addr = 0x00000000bdb91af1, flags = 0x286
[675698.169312] retval = 0
[675698.169314] 1 >>>>>>> <show_mountinfo> pre_handler: p->addr = 0x00000000bdb91af1, ip = ffffffffb93dad21, flags = 0x282
[675698.169317] 22222222222>>>>
[675698.169318] 1 >>>>>> <show_mountinfo> post_handler: p->addr = 0x00000000bdb91af1, flags = 0x282
[675698.169324] retval = 0

从以上信息可知,entry会根据前后加载驱动的顺序执行相应的函数,但是ret_handler一定是在最后执行的。

同一个函数可以被kporbe多次的原因分析?挂的是一个钩子,但是函数可以是多个?

kp1 -> kp2

[ 2856.028782] 2 >>>>>>> <show_mountinfo> pre_handler: p->addr = 0x00000000dabf8e0e, ip = ffffffffba5dad21, flags = 0x282
[ 2856.028786] 1 >>>>>>> <show_mountinfo> pre_handler: p->addr = 0x00000000dabf8e0e, ip = ffffffffba5dad21, flags = 0x282
[ 2856.028789] 2 >>>>>> <show_mountinfo> post_handler: p->addr = 0x00000000dabf8e0e, flags = 0x282
[ 2856.028792] 1 >>>>>> <show_mountinfo> post_handler: p->addr = 0x00000000dabf8e0e, flags = 0x282

kp2 -> kp1

[ 3010.391931] 1 >>>>>>> <show_mountinfo> pre_handler: p->addr = 0x00000000dabf8e0e, ip = ffffffffba5dad21, flags = 0x282
[ 3010.391933] 2 >>>>>>> <show_mountinfo> pre_handler: p->addr = 0x00000000dabf8e0e, ip = ffffffffba5dad21, flags = 0x282
[ 3010.391940] 1 >>>>>> <show_mountinfo> post_handler: p->addr = 0x00000000dabf8e0e, flags = 0x282
[ 3010.391942] 2 >>>>>> <show_mountinfo> post_handler: p->addr = 0x00000000dabf8e0e, flags = 0x282

list_add_rcu(struct list_head *new, struct list_head *head);

/**
 * list_add_rcu - add a new entry to rcu-protected list
 * @new: new entry to be added
 * @head: list head to add it after
 *
 * Insert a new entry after the specified head.
 * This is good for implementing stacks.
 *
 * The caller must take whatever precautions are necessary
 * (such as holding appropriate locks) to avoid racing
 * with another list-mutation primitive, such as list_add_rcu()
 * or list_del_rcu(), running on this same list.
 * However, it is perfectly legal to run concurrently with
 * the _rcu list-traversal primitives, such as
 * list_for_each_entry_rcu().
 */
static inline void list_add_rcu(struct list_head *new, struct list_head *head)
{
	__list_add_rcu(new, head, head->next);
}

list_add_rcu函数会将新节点的next指针指向链表头节点的下一个节点,然后将链表头节点的next指针指向新节点,从而将新节点插入到链表头节点之后。这样做的好处是,插入操作的时间复杂度为O(1),不需要遍历整个链表,因此效率较高。

|- register_kprobe(struct kprobe *p) // 注册一个kprobe挂载点
    |- kprobe_addr // kprobe结构体合法性检查,在符号表中查找probe地址的合法性
	|- check_kprobe_rereg // 检查kprobe结构体是不是重复注册
    |- INIT_LIST_HEAD(&p->list); // 初始化结构体list,list是用于存放所有回调函数的链表,用于多回调支持
	|- check_kprobe_address_safe // 检查probe地址的合法性
    |- get_kprobe(p->addr) // 在kprobe的所有hlist中查找kprobe->addr有没有被注册过。
        if (old_p) {  // 如果对应地址已经注册过回调
            |- register_aggr_kprobe // 继续往list中添加回调函数
                if (!kprobe_aggrprobe(orig_p)) { // 判断是不是聚合探针
                    |- alloc_aggr_kprobe(orig_p); // 申请一个聚合探针
                    |- init_aggr_kprobe(struct kprobe *ap, struct kprobe *p) // 初始化聚合探针
                        |- ap->pre_handler = aggr_pre_handler;
         }
            |- copy_kprobe(ap, p); // 将原有的insn指令拷贝到新的kprobe地址
            |- add_new_kprobe(ap, p) // 往聚合探针中添加回调函数
        }
struct kprobe {
    struct hlist_node hlist;
    
    /* list of kprobes for multi-handler support */
    struct list_head list;
}

// aggr_pre_handler 回调函数,也就是遍历链表,然后执行相应pre_handler。
/*
 * Aggregate handlers for multiple kprobes support - these handlers
 * take care of invoking the individual kprobe handlers on p->list
 */
static int aggr_pre_handler(struct kprobe *p, struct pt_regs *regs)
{
        struct kprobe *kp;

        list_for_each_entry_rcu(kp, &p->list, list) {
                if (kp->pre_handler && likely(!kprobe_disabled(kp))) {
                        set_kprobe_instance(kp);
                        if (kp->pre_handler(kp, regs))
                                return 1;
                }
                reset_kprobe_instance();
        }
        return 0;
}
NOKPROBE_SYMBOL(aggr_pre_handler);

/*
 * Fill in the required fields of the "manager kprobe". Replace the
 * earlier kprobe in the hlist with the manager kprobe
 */
static void init_aggr_kprobe(struct kprobe *ap, struct kprobe *p)
{
        /* Copy p's insn slot to ap */
        copy_kprobe(p, ap);
        flush_insn_slot(ap);
        ap->addr = p->addr;
        ap->flags = p->flags & ~KPROBE_FLAG_OPTIMIZED;
        ap->pre_handler = aggr_pre_handler;
        ap->fault_handler = aggr_fault_handler;
        /* We don't care the kprobe which has gone. */
        if (p->post_handler && !kprobe_gone(p))
                ap->post_handler = aggr_post_handler;
        if (p->break_handler && !kprobe_gone(p))
                ap->break_handler = aggr_break_handler;

        INIT_LIST_HEAD(&ap->list);
        INIT_HLIST_NODE(&ap->hlist);

        list_add_rcu(&p->list, &ap->list);	// 将需要添加的probe信息添加到aggr,头插法。
        hlist_replace_rcu(&p->hlist, &ap->hlist);
}

/* Add the new probe to ap->list */
static int add_new_kprobe(struct kprobe *ap, struct kprobe *p)
{
	if (p->post_handler)
		unoptimize_kprobe(ap, true);	/* Fall back to normal kprobe */

	list_add_rcu(&p->list, &ap->list);	// 头插进入list
	if (p->post_handler && !ap->post_handler)
		ap->post_handler = aggr_post_handler;

	return 0;
}

"breakpoint trap"是一种在Linux内核中用于调试和跟踪程序执行的机制,它允许开发人员在程序中设置断点,并在断点触发时进入调试模式,以便观察和调试程序的执行过程。

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