1、6、在kprobes的注册和注销过程中不会使用mutex锁和动态的申请内存;7、kprobes回调函数的运行期间是关闭内核抢占的,同时也可能在关闭中断的情况下执行,具体要视CPU架构而定。因此不论在何种情况下,在回调函数中不要调用会放弃CPU的函数(如信号量、mutex锁等);8、kretprobe通过替换返回地址为预定义的trampoline的地址来实现,因此栈回溯和gcc内嵌函数_builtin_return_address()调用将返回trampoline的地址而不是真正的被探测函数的返回地址;9、如果一个函数的调用此处和返回次数不相等,则在类似这样的函数上注册kretprobe将可能不
2、会达到预期的效果,例如do_exit()函数会存在问题,而do_execve()函数和do_fork()函数不会;10、如果当在进入和退出一个函数时,CPU运行在非当前任务所有的栈上,那么往该函数上注册kretprobe可能会导致不可预料的后果,因此,kprobes不支持在X86_64的结构下为_switch_to()函数注册kretprobe,将直接返回-EINVAL。二、kprobe原理下面来介绍一下kprobe是如何工作的。具体流程见下图:1、当用户注册一个探测点后,kprobe首先备份被探测点的对应指令,然后将原始指令的入口点替换为断点指令,该指令是CPU架构相关的,如i386和x86
3、_64是int3,arm是设置一个未定义指令(目前的x86_64架构支持一种跳转优化方案Jump Optimization,内核需开启CONFIG_OPTPROBES选项,该种方案使用跳转指令来代替断点指令);2、当CPU流程执行到探测点的断点指令时,就触发了一个trap,在trap处理流程中会保存当前CPU的寄存器信息并调用对应的trap处理函数,该处理函数会设置kprobe的调用状态并调用用户注册的pre_handler回调函数,kprobe会向该函数传递注册的struct kprobe结构地址以及保存的CPU寄存器信息;3、随后kprobe单步执行前面所拷贝的被探测指令,具体执行方式各个
4、架构不尽相同,arm会在异常处理流程中使用模拟函数执行,而x86_64架构则会设置单步调试flag并回到异常触发前的流程中执行;4、在单步执行完成后,kprobe执行用户注册的post_handler回调函数;5、最后,执行流程回到被探测指令之后的正常流程继续执行。三、kprobe使用实例在分析kprobe的实现之前先来看一下如何利用kprobe对函数进行探测,以便于让我们对kprobre所完成功能有一个比较清晰的认识。目前,使用kprobe可以通过两种方式,第一种是开发人员自行编写内核模块,向内核注册探测点,探测函数可根据需要自行定制,使用灵活方便;第二种方式是使用kprobes on ft
5、race,这种方式是kprobe和ftrace结合使用,即可以通过kprobe来优化ftrace来跟踪函数的调用。下面来分别介绍:1、编写kprobe探测模块内核提供了一个struct kprobe结构体以及一系列的内核API函数接口,用户可以通过这些接口自行实现探测回调函数并实现struct kprobe结构,然后将它注册到内核的kprobes子系统中来达到探测的目的。同时在内核的samples/kprobes目录下有一个例程kprobe_example.c描述了kprobe模块最简单的编写方式,开发者可以以此为模板编写自己的探测模块。1.1、kprobe结构体与API介绍struct kp
6、robe结构体定义如下:cpp view plain copy 在CODE上查看代码片派生到我的代码片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
7、 *addr; /* Allow user to indicate symbol name of the probe point */ const char *symbol_name; /* Offset into the symbol */ unsigned int offset; /* Called before addr is executed. */ kprobe_pre_handler_t pre_handler; /* Called after addr is executed, unless. */ kprobe_post_handler_t post_handler; /* *
8、 . 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; * . called if breakpoint trap occurs in probe handler. * Return 1 if it handled break, otherwise kernel will see it. kprobe_break_handle
9、r_t break_handler; /* Saved opcode (which has been replaced with breakpoint) */ kprobe_opcode_t opcode; /* copy of the original instruction */ struct arch_specific_insn ainsn; * Indicates various status flags. * Protected by kprobe_mutex after this kprobe is registered. u32 flags;其中各个字段的含义如下:struct
10、hlist_node hlist:被用于kprobe全局hash,索引值为被探测点的地址;struct list_head list:用于链接同一被探测点的不同探测kprobe;kprobe_opcode_t *addr:被探测点的地址;const char *symbol_name:被探测函数的名字;unsigned int offset:被探测点在函数内部的偏移,用于探测函数内部的指令,如果该值为0表示函数的入口;kprobe_pre_handler_t pre_handler:在被探测点指令执行之前调用的回调函数;kprobe_post_handler_t post_handler:在被
11、探测指令执行之后调用的回调函数;kprobe_fault_handler_t fault_handler:在执行pre_handler、post_handler或单步执行被探测指令时出现内存异常则会调用该回调函数;kprobe_break_handler_t break_handler:在执行某一kprobe过程中触发了断点指令后会调用该函数,用于实现jprobe;kprobe_opcode_t opcode:保存的被探测点原始指令;struct arch_specific_insn ainsn:被复制的被探测点的原始指令,用于单步执行,架构强相关(可能包含指令模拟函数);u32 flags:
12、状态标记。涉及的API函数接口如下:int register_kprobe(struct kprobe *kp) /向内核注册kprobe探测点 void unregister_kprobe(struct kprobe *kp) /卸载kprobe探测点 int register_kprobes(struct kprobe *kps, int num) /注册探测函数向量,包含多个探测点 void unregister_kprobes(struct kprobe *kps, int num) /卸载探测函数向量,包含多个探测点 int disable_kprobe(struct kprobe
13、*kp) /临时暂停指定探测点的探测 int enable_kprobe(struct kprobe *kp) /恢复指定探测点的探测 1.2、用例kprobe_example.c分析与演示该用例函数非常简单,它实现了内核函数do_fork的探测,该函数会在fork系统调用或者内核kernel_thread函数创建进程时被调用,触发也十分的频繁。下面来分析一下用例代码:/* For each probe you need to allocate a kprobe structure */ static struct kprobe kp = .symbol_name = do_fork, sta
14、tic 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 addr = 0x%p, ip = %lx, flags = 0x%lxnaddr, regs-ip, regs-flags);#endif #ifdef CONFIG_PPC addr = 0x%p, nip = 0x%lx, msr = 0x%lxn
15、nip, regs-msr);#ifdef CONFIG_MIPS addr = 0x%p, epc = 0x%lx, status = 0x%lxncp0_epc, regs-cp0_status);#ifdef CONFIG_TILEGX addr = 0x%p, pc = 0x%lx, ex1 = 0x%lxnpc, regs-ex1); /* A dump_stack() here will give a stack backtrace */ handler_pre回调函数的第一个入参是注册的struct kprobe探测实例,第二个参数是保存的触发断点前的寄存器状态,它在do_for
16、k函数被调用之前被调用,该函数仅仅是打印了被探测点的地址,保存的个别寄存器参数。由于受CPU架构影响,这里对不同的架构进行了宏区分(虽然没有实现arm架构的,但是支持的,可以自行添加);/* kprobe post_handler: called after the probed instruction is executed */ static void handler_post(struct kprobe *p, struct pt_regs *regs, unsigned long flags) post_handler:addr = 0x%p, flags = 0x%lxnaddr =
17、 0x%p, msr = 0x%lxnaddr = 0x%p, status = 0x%lxnaddr = 0x%p, ex1 = 0x%lxnhandler_post回调函数的前两个入参同handler_pre,第三个参数目前尚未使用,全部为0;该函数在do_fork函数调用之后被调用,这里打印的内容同handler_pre类似。/* * fault_handler: this is called if an exception is generated for any * instruction within the pre- or post-handler, or when Kprobe
18、s * single-steps the probed instruction. static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr) fault_handler:addr = 0x%p, trap #%dnaddr, trapnr); /* Return 0 because we dont handle the fault. */ handler_fault回调函数会在执行handler_pre、handler_post或单步执行do_fork时出现错误时调用,这里第三个参数时具体发生错误的t
19、rap number,与架构相关,例如i386的page fault为14。下面将它编译成模块在我的x86(CentOS 3.10)环境下进行演示,首先确保架构和内核已经支持kprobes,开启以下选项(一般都是默认开启的):Symbol: KPROBES =y Type : boolean Prompt: Kprobes Location:(3) - General setup Defined at arch/Kconfig:37 Depends on: MODULES =y & HAVE_KPROBES =y Selects: KALLSYMS =y 174 Selected by: X8
20、6 =y 然后使用以下Makefile单独编译kprobe_example.ko模块:obj-m := kprobe_example.o CROSS_COMPILE=KDIR := /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M=$(PWD) modules clean: rm -f *.ko *.o *.mod.o *.mod.c .*.cmd *.symvers modul* 加载到内核中后,随便在终端上敲一个命令,可以看到dmesg中打印如下信息:addr = 0xc0439cc0, ip = c0439cc1, flags = 0x246addr = 0xc0439cc0, flags = 0x246addr = 0xc0439cc0, ip = c0439cc1, flags =
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1