kprobe
和kretprobe
同一个函数能不能调注册多个回调?
答案是:可以
// 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内核中用于调试和跟踪程序执行的机制,它允许开发人员在程序中设置断点,并在断点触发时进入调试模式,以便观察和调试程序的执行过程。