上一篇文章和大家简要说明了下kprobe到底应该怎样用,那么现在我们就揭开kprobe神秘的面纱,刨根问底,一睹kprobe的庐山真面目。
kprobe的工作过程大致如下:
1)注册kprobe。注册的每个kprobe对应一个kprobe结构体,该结构中记录着插入点(位置),以及该插入点本来对应的指令original_opcode;
2)替换原有指令。使能kprobe的时候,将插入点位置的指令替换为一条异常(BRK)指令,这样当CPU执行到插入点位置时会陷入到异常态;
3)执行pre_handler。进入异常态后,首先执行pre_handler,然后利用CPU提供的单步调试(single-step)功能,设置好相应的寄存器,将
下一条指令设置为插入点处本来的指令,从异常态返回;
4)再次陷入异常态。上一步骤中设置了single-step相关的寄存器,所以originnal_opcode刚一执行,便会二进宫:再次陷入异常态,此时将single-step
清除,并且执行post_handler,然后从异常态安全返回。
步骤2),3),4)便是一次kprobe工作的过程,它的一个基本思路就是将本来执行一条指令扩展成执行kprobe->pre_handler ---> 指令 ---> kprobe-->post_hander这样三个过程。下面详细解释每个过程:
指令替换过程:
上图中蓝色区域表示内存,红色标明了地址,绿色部分代表一条指令,上图的意思是,内存0xfffffc000162914处存放一条指令是0xa9bd7bfd。那么,现在我注册了一个kprobe,探测点是sys_write函数,该函数的起始位置就是0xffffffc000162914,现在我要使能kprobe了,那么我要做的就是把0xffffffc000162914处原来的指令0xa9bd7bfd替换成一条BRK指令,即上图所表示的一个移花接木过程。你可能会好奇原来的指令0xa9bd7bfd存在哪里?存在kprobe结构体的opcode域!这样当不再使能kprobe的时候,我再恢复回去。
触发BRK指令:
上面把人家指令给改了,那么CPU执行到BRK必然会引发内核陷入BRK异常状态:
蓝色部分依旧表示内存,绿色部分表示指令,红色表示CPU,上图表示CPU执行到0xffffffc000162914(sys_write)处,该处指令为BRK,于是内核陷入异常态。在异常态中,内核通过BRK指令的错误码判断这是一个kprobe异常,于是进入了kprobe处理函数。kprobe异常处理函数会根据发生异常的地址来找到对应的kprobe(kprobe的addr域记录着地址),执行kprobe的pre_handler函数,然后设置single-step相关的寄存器,为下一步执行原指令时发生single-step异常作准备。那么紧接着就是设置原指令的地址了,我们知道0xffffffc000162914处已经被替换成了BRK指令,原指令保存在kprobe结构体中,怎么保证下一步执行到原指令呢?最简单的做法是申请一块内存,然后将原指令复制到这块内存开始处,设置PC寄存器为该内存的首地址,这样当代码从异常态返回时,执行的第一条指令便是原指令了!
原指令得到执行,二进宫
经过上面一个步骤,pre_handler得到了执行,从异常态返回之后,原指令也得到了执行,但是由于设置了single-step模式,所以执行完原指令,马上又陷入了异常态,二进宫:
这次进入异常态后,先清一下single-step相关的寄存器,确保下次从异常返回时的指令不会由于single-step发生三进宫,然后执行post_handler,最后将地址0xfffffc000162918写入到PC寄存器,为什么是这个数值呢?它正是紧接着0xffffffc000162914的下一条指令的地址,有没有发现,至此我们已经完成了pre_handler->原指令->post_handler这样三个阶段,也就是说kprobe要做的事情都做完了,此时的工作就是收拾下残局,返回到正常的指令流程,我们的探测点在0xffffffc000162914处,下一条指令应该就是0xffffffc000162918了,所以把此值写入PC寄存器,让一切恢复正轨!
kprobe工作结束,走上正轨
上面把PC设置成了0xffffffc000162918,所以从异常态返回时,CPU就走上了正轨接着朝下面执行了,一个BRK指令引发的反应在此就告一段落了,但是每次当CPU执行到0xffffffc000162914处,都会触发上面的一连串操作,kprobe的机制也就是从一个BRK指令开始了。
注:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
由于kprobe涉及到程序指令的修改,这部分和体系结构相关,我选择的体系结构ARM64,如本文的BRK指令等均是ARM64中的概念,
x86中INT3与之对应。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------