1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
Linux内核kretprobe机制
http://www.ibm.com/developerworks/cn/linux/l-cn-systemtap1/index.html
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。
http://hi.baidu.com/lixiang1988/item/8884bc286c9920ceddf69acd
kretprobe的实现
相关数据结构与函数分析
1) struct kretprobe结构
该结构是kretprobe实现的基础数据结构,以下是该结构的成员:
1 2 3 4 5 6 7 | |
2) struct kretprobe_instance结构
该结构表示一个返回地址实例。因为函数每次被调用的地方不同,这造成了返回地址不同,因此需要为每一次发生的调用记录在这样一个结构里面。以下是该结构的成员:
1 2 3 4 5 | |
3) pre_handler_kretprobe()函数
该函数在kretprobe探测点被执行到后,用于修改被探测函数的返回地址。
4) trampoline_handler()函数
该函数用于执行调试者定义的回调函数以及把被探测函数的返回地址修改回原来的返回地址。
kretprobe处理流程分析
kretprobe探测方式是基于kprobe实现的又一种内核探测方式,该探测方式主要用于在函数返回时进行探测,获得内核函数的返回值,还可以用于计算函数执行时间等方面。
1) kretprobe的注册过程
调试者要进行kretprobe调试首先要注册处理,这需要在调试模块中调用register_kretprobe(),下文中称该函数为 kretprobe 注册器。kretprobe注册器对传入的kretprobe结构的中kprobe.pre_handler赋值为 pre_handler_kretprobe()函数,用于在探测点被触发时被调用。接着,kretprobe注册器还会初始化kretprobe的一些 成员,比如分配返回地址实例的空间等操作。最后,kretprobe注册器会利用 kretprobe内嵌的structkprobe结构进行kropbe的注册。自此,kretprobe注册过程就完成了。
2) kretprobe探测点的触发过程
kretprobe触发是在刚进入被探测函数的第一条汇编指令时发生的,因为 kretprobe注册时把该地址修改位int3指令。
此时发生了一次CPU异常处理,这与kprobe探测点被触发相同。但与kprobe处理不同的是,这里并不是运行用户定义的pre_handler函 数,而是执行pre_handler_kretprobe()函数,该函数又会调用arch_prepare_kretprobe()函数。 arch_prepare_kretprobe()函数的主要功能是把被探测函数的返回地址变换为&kretprobe_trampoline所 在的地址,这是一个汇编地址标签。这个标签的地址在kretprobe_trampoline_holder()中用汇编伪指令定义。替换函数返回地址是 kretprobe实现的关键。当被探测函数返回时,返回到&kretprobe_trampoline地址处开始运行。
接着,在 一些保护现场的处理后,又去调用trampoline_handler()函数。该函数的主要有两个作用,一是根据当前的实例去运行用户定义的调试函数, 也就是kretprobe结构中的handler所指向的函数,二是把返回值设成被探测函数正真的返回地址。最后,在进行一些堆栈的处理后,被探测函数又 返回到了正常执行流程中去。
以上讨论的就是kretprobe的执行过程,可以看出,该调试方式的关键点在于修改被探测函数的返回地址到kprobes的控制流程中,之后再把返回地址修改到原来的返回地址并使得该函数继续正常执行。
Linux内核kprobe机制
- 探测点处理函数在运行时是失效抢占的,依赖于特定的架构,探测点处理函数运行时也可能是中断失效的。
因此,对于任何探测点处理函数,不要使用导致睡眠或进程调度的任何内核函数(如尝试获得semaphore)。
Kprobe机制是内核提供的一种调试机制,它提供了一种方法,能够在不修改现有代码的基础上,灵活的跟踪内核函数的执行。它的基本工作原理是:用户指定一个探测点,并把一个用户定义的处理函数关联到该探测点,当内核执行到该探测点时,相应的关联函数被执行,然后继续执行正常的代码路径。
Kprobe提供了三种形式的探测点,一种是最基本的kprobe,能够在指定代码执行前、执行后进行探测,但此时不能访问被探测函数内的相关变量信 息;一种是jprobe,用于探测某一函数的入口,并且能够访问对应的函数参数;一种是kretprobe,用于完成指定函数返回值的探测功能。其中最基 本的就是kprobe机制,jprobe以及kretprobe的实现都依赖于kprobe,但其代码的实现都很巧妙,强烈建议每一个内核爱好者阅读。
代码:
首先是struct kprobe结构,每一个探测点的基本结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | |
对于kprobe功能的实现主要利用了内核中的两个功能特性:异常(尤其是int 3),单步执行(EFLAGS中的TF标志)。
大概的流程:
1 2 3 4 5 6 | |
下面又进入代码时间,首先看一下kprobe模块的初始化代码,初始化代码主要做了两件事:标记出哪些代码是不能被探测的,这些代码属于kprobe实现的关键代码;注册通知链到die_notifier,用于接收异常通知。
初始化代码位于kernel/kprobes.c中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | |
其中的通知链:
1 2 3 4 5 | |
kprobe的注册流程register_kprobe。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | |
注册完毕,就开始kprobe的执行流程了。对于该探测点,由于其起始指令已经被修改为int3,因此在执行到该地址时,必然会触发3号中断向量的处理流程do_int3.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | |
在do_int3中触发kprobe注册的通知链函数,kprobe_exceptions_notify。由于kprobe以及jprobe等机制的处 理核心都在此函数内,这里只针对kprobe的流程进行分析:进入函数的原因是DIE_INT3,并且是第一次进入该函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | |
对应kprobe,pre_handle的执行就结束了,按照代码,程序开始执行保存的被探测点的指令,由于开启了单步调试模式,执行完指令后会继续触发异常,这次的是do_debug异常处理流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | |
至此,一个典型的kprobe的流程已经执行完毕了。