本文转载自微信公众号《人人都是极客》,作者布道者Peter。转载本文请联系大家是极客公众号。上一篇我们讲了Kprobe的用法。这次我们就来看看它的实现原理。在上一个模块示例中插入du??mp_stack函数获取调用栈,根据栈逆向调用流程:Calltrace:[]dump_backtrace+0x0/0x268[]show_stack+0x20/0x28[]dump_stack+0xb4/0xf0[]handler_pre+0x38/0x50[kprobe_example][]kprobe_breakpoint_handler+0x160/0x1d4[]brk_handler+0x7c/0x90[]do_debug_exception+0xa0/0x174Exceptionstack(0xffff000012f7bd40to0xffff000012f7be80)bd40:0000000001200011000000000000000000000000000000000000000000000000bd60:0000f39a6ce0555800000000000000000000f39a6ce055580000000000000073bd80:00000000000000dc000000000000000000000000000000000000000000000000bda0:0000f39a6ce05558000000000000000000000000ffffffff0000fffffa1150d8bdc0:ffff0000080e1b400000f39a6c99fd1000000000000000080000000000000000bde0:000000000120001100000000ffffffff0000f39a6c99fd300000000040000000be00:0000000000000015000000000000012400000000000000dcffff000009122000be20:ffff8008f0385700ffff000012f7be80ffff0000080e1b84ffff000012f7be80be40:ffff0000080e1620000000008000014500000000ffffffff6544f7a9c1a3c100be60:0000ffffffffffffffff000008083ac0ffff000012f7be80ffff0000080e1620[]el1_dbg+0x18/0x74[]_do_fork+0x0/0x414可以看出流程为:el1_dbg->do_debug_exception->brk_handler->kprobe_breakpoint_handler->kprobe_handler->handler_pre从从上图可以看出,当触发中断时,进入el1_sync,然后读取esr_el1寄存器的值,判断异常的具体类型ESR_ELx_E??C_BREAKPT_CUR=0x31,即EC=110001,进入el1_dbg函数.根据EC=11000的类型,我们知道当前触发的中断是断点异常,如下图:那么问题来了,断点命令是如何触发的呢?弄清楚这个问题后,我们就明白了kprobe加探针的本质了。替换断点命令,先看kprobe的注册过程:register_kprobe->arm_kprobe->__arm_kprobe->arch_arm_kprobe/*armkprobe:installbreakpointintext*/void__kprobesarch_arm_kprobe(structkprobe*p){patch_text(p->addr,BRK64_OPCODE_KPROBES);}可以清楚的看到这里出去把addr对应的指令改成brk指令,cpu一旦执行addr就会触发brk。从而进入上面提到的中断函数el1_sync,然后进入kprobe_handler.staticvoid__kprobeskprobe_handler(structpt_regs*regs){structkprobe*p,*cur_kprobe;structkprobe_ctlblk*kcb;unsignedlongaddr=instruction_pointer(regs);kcb=get_kprobe_ctlblk()=kcurbe_ctlblk()(kcurbe);;p=get_kprobe((kprobe_opcode_t*)addr);//根据pc值获取kprobeif(p){if(cur_kprobe){if(reenter_kprobe(p,regs,kcb))return;}else{/*Probehit*/set_current_kprobe(p);kcb->kprobe_status=KPROBE_HIT_ACTIVE;//开始处理kprobeif(!p->pre_handler||!p->pre_handler(p,regs)){setup_singlestep(p,regs,kcb,0);return;}}......}可以看出kprobe_handler首先进入pre_handler,然后通过setup_singlestep设置单步相关寄存器,为原指令执行时单步异常的发生做准备下一步执行。进入单步经过以上步骤,pre_handler被执行,从异常状态返回后,原来的指令也被执行了,但是因为设置了单步模式,执行完原来的指令后,马上就进入了单步例外。流程是:el1_dbg->do_debug_exception->single_step_handler->kprobe_single_step_handler->post_kprobe_handler->post_handler综上所述,我们知道Kprobe实现的本质就是断点和单步的结合,这和大多数调试工具一样,例如kgdb/gdb。上面我们从trace信息中推导出执行过程,现在我们从前面梳理一下整个过程的来龙去脉:registerkprobe。每个注册的kprobe对应一个kprobe结构体,记录插入点(位置)和插入点对应的指令original_opcode;替换原来的指令。启用kprobe时,将插入点处的指令替换为异常(BRK)指令,这样当CPU执行到插入点时,就会陷入异常状态;执行pre_handler。进入异常状态后,先执行pre_handler,然后利用CPU提供的单步调试(single-step)功能设置相应的寄存器,在插入点处设置下一条指令为原指令,从返回异常状态;异常状态。上一步设置了单步相关的寄存器,所以original_opcode一执行,就会再次陷入异常状态。此时signle-step会被清除,post_handler会被执行,然后从异常状态安全返回。第2、3、4步是kprobe工作的过程。它的一个基本思想是将原来执行一条指令扩展为执行kprobe->pre_handler--->originalinstruction--->kprobe-->post_handler。过程。考虑到放太多代码不利于阅读,本文不详细解释上述过程的代码实现。感兴趣的朋友可以自行阅读。遇到问题可以留言或者群里讨论。最后整理一下涉及到的代码。相关寄存器。相关的寄存器PSTATEPSTATE并不是一个寄存器,它代表的是一组寄存器或者一些保存当前进程状态信息的标志位信息。负数标志Negativeconditionflag零数标志Zeroconditionflag进位标志Carryconditionflag溢出标志OverflowconditionflagD:debugexceptionMASK:Watchpoint,Breakpoint,andSoftwareStepexceptionsA:SErrorinterruptMASKI:IRQinterruptMASKF:FIQinterruptMASKEL,bits[3:2]00EL001EL110EL211EL3SP,bit[0]0UseSP_EL0atallExceptionlevels.1UseSP_ELxforExceptionlevelELx.PAN,bit[22]Privilegedaccesssystem0Privilegedreadsandwritearenotdisabledbythismechanism.1DisablesprivilegedreadandwriteaccessestoaddressesaccessibleatEL0foranenabledstage1translationregimeththatdefinestheEL0permissionsSPSRWhenanexceptionoccurs,savethecurrentPSTATE(CPSR)state.PSTATE.{N,Z,C,V}:条件标志位,这些位的含义与前面的AArch32位相同,分别表示补码标志、运算结果为0标志、进位标志、进位标志有符号位溢出标志。PSTATE.SS:异常发生时,通过设置MDSCR_EL1.SS为1启动单步调试机制。PSTATE.IL:异常执行状态标志。当发生非法异常时,该标志位将被置位,从而引发事件。PSTATE.{D,A,I,F}:D表示产生调试异常,如软件断点指令/断点/观察点/向量捕获/软件单步等;A、I、F表示异步异常标志,异步异常有两种:一种是物理中断产生的,包括SError(系统错误类型,包括外部数据终止)、IRQ或FIQ;另一个是由虚拟中断产生的,当启用EL2管理器时发生:vSError,vIRQ,vFIQ;MDSCR_EL1MonitorDebugSystemControlRegister