linux kprobe
###1. kprobes Kprobes 提供了一个强行进入任何(除了Kprobes模块自身的实现代码)内核例程,并从中断处理器无干扰地收集信息的接口。使用 Kprobes 可以轻松地收集处理器寄存器和全局数据结构等调试信息,而无需对Linux内核频繁编译和启动。 它的基本工作机制是:用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点,当内核执行到该探测点时,相应的关联函数被执行,然后继续执行正常的代码路径。 kprobe实现了三种类型的探测点: kprobes, jprobes和kretprobes (也叫返回探测点)。 kprobes是可以被插入到内核的任何指令位置的探测点,jprobes则只能被插入到一个内核函数的入口(一般用于调试函数参数),而kretprobes则是在指定的内核函数返回时才被执行(一般用于调试函数返回值)。 一般,使用kprobe的程序实现作一个内核模块,模块的初始化函数来负责安装探测点,退出函数卸载那些被安装的探测点。kprobe提供了接口函数(APIs)来安装或卸载探测点。目前kprobe支持如下架构:i386、x86_64、ppc64、ia64(不支持对slot1指令的探测)、sparc64 (返回探测还没有实现)。 ###2. kprobes 族的工作原理 当安装一个kprobes探测点时,kprobe首先备份被探测的指令,然后使用断点指令(即在i386和x86_64的int3指令)来取代被探测指令的头一个或几个字节。当CPU执行到探测点时,将因运行断点指令而执行trap操作,那将导致保存CPU的寄存器,调用相应的trap处理函数,而trap处理函数将调用相应的notifier_call_chain(内核中一种异步工作机制)中注册的所有notifier函数,kprobe正是通过向trap对应的notifier_call_chain注册关联到探测点的处理函数来实现探测处理的。当kprobe注册的notifier被执行时,它首先执行关联到探测点的pre_handler函数,并把相应的kprobe struct和保存的寄存器作为该函数的参数,接着,kprobe单步执行被探测指令的备份,最后,kprobe执行post_handler。等所有这些运行完毕后,紧跟在被探测指令后的指令流将被正常执行。 jprobe通过注册kprobes在被探测函数入口的来实现,它能无缝地访问被探测函数的参数。jprobe处理函数应当和被探测函数有同样的原型,而且该处理函数在函数末必须调用kprobe提供的函数jprobe_return()。当执行到该探测点时,kprobe备份CPU寄存器和栈的一些部分,然后修改指令寄存器指向jprobe处理函数,当执行该jprobe处理函数时,寄存器和栈内容与执行真正的被探测函数一模一样,因此它不需要任何特别的处理就能访问函数参数, 在该处理函数执行到最后时,它调用jprobe_return(),那导致寄存器和栈恢复到执行探测点时的状态,因此被探测函数能被正常运行。需要注意,被探测函数的参数可能通过栈传递,也可能通过寄存器传递,但是jprobe对于两种情况都能工作,因为它既备份了栈,又备份了寄存器,当然,前提是jprobe处理函数原型必须与被探测函数完全一样。 kretprobe也使用了kprobes来实现,当用户调用register_kretprobe()时,kprobe在被探测函数的入口建立了一个探测点,当执行到探测点时,kprobe保存了被探测函数的返回地址并取代返回地址为一个trampoline的地址,kprobe在初始化时定义了该trampoline并且为该trampoline注册了一个kprobe,当被探测函数执行它的返回指令时,控制传递到该trampoline,因此kprobe已经注册的对应于trampoline的处理函数将被执行,而该处理函数会调用用户关联到该kretprobe上的处理函数,处理完毕后,设置指令寄存器指向已经备份的函数返回地址,因而原来的函数返回被正常执行。 被探测函数的返回地址保存在类型为kretprobe_instance的变量中,结构kretprobe的maxactive字段指定了被探测函数可以被同时探测的实例数,函数register_kretprobe()将预分配指定数量的kretprobe_instance。如果被探测函数是非递归的并且调用时已经保持了自旋锁(spinlock),那么maxactive为1就足够了; 如果被探测函数是非递归的且运行时是抢占失效的,那么maxactive为NR_CPUS就可以了;如果maxactive被设置为小于等于0, 它被设置到缺省值(如果抢占使能, 即配置了 CONFIG_PREEMPT,缺省值为10和2*NR_CPUS中的最大值,否则缺省值为NR_CPUS)。 如果maxactive被设置的太小了,一些探测点的执行可能被丢失,但是不影响系统的正常运行,在结构kretprobe中nmissed字段将记录被丢失的探测点执行数,它在返回探测点被注册时设置为0,每次当执行探测函数而没有kretprobe_instance可用时,它就加1。 ###3. kprobes的不足 kprobes也有无能为力的时候, 在使用前,请你看看这些。 kprobe允许在同一地址注册多个kprobes,但是不能同时在该地址上有多个jprobes。 通常,用户可以在内核的任何位置注册探测点,特别是可以对中断处理函数注册探测点,但是也有一些例外。如果用户尝试在实现kprobe的代码(包括kernel/kprobes.c和arch//kernel/kprobes.c以及do_page_fault和notifier_call_chain)中注册探测点,register_probe将返回-EINVAL. 如果为一个内联(inline)函数注册探测点,kprobe无法保证对该函数的所有实例都注册探测点,因为gcc可能隐式地内联一个函数。因此,要记住,用户可能看不到预期的探测点的执行。 一个探测点处理函数能够修改被探测函数的上下文,如修改内核数据结构,寄存器等。因此,kprobe可以用来安装bug解决代码或注入一些错误或测试代码。 如果一个探测处理函数调用了另一个探测点,该探测点的处理函数不将运行,但是它的nmissed数将加1。多个探测点处理函数或同一处理函数的多个实例能够在不同的CPU上同时运行。 除了注册和卸载,kprobe不会使用mutexe或分配内存。 探测点处理函数在运行时是失效抢占的,依赖于特定的架构,探测点处理函数运行时也可能是中断失效的。因此,对于任何探测点处理函数,不要使用导致睡眠或进程调度的任何内核函数(如尝试获得semaphore)。 kretprobe是通过取代返回地址为预定义的trampoline的地址来实现的,因此栈回溯和gcc内嵌函数...