前言
上一節(jié)介紹了kprobe的基本概念,下面我們將使用幾個(gè)具體的例子,看下kprobe在實(shí)際使用中有那些應(yīng)用場景。
kprobe
內(nèi)核的samples/kprobe目錄下有kprobe相關(guān)的例子,我們以這些例子為基礎(chǔ),簡單修改下。
查看函數(shù)的入?yún)?/p>
我們所有的例子都是探測do_sys_open() 或者_(dá)do_fork(),以下是內(nèi)核中的源碼。
do_sys_open
structaudit_names; structfilename{ constchar*name;/*pointertoactualstring*/ const__userchar*uptr;/*originaluserlandpointer*/ structaudit_names*aname; intrefcnt; constchariname[]; }; longdo_sys_open(intdfd,constchar__user*filename,intflags,umode_tmode) { structopen_flagsop; intfd=build_open_flags(flags,mode,&op); structfilename*tmp; if(fd) returnfd; tmp=getname(filename); if(IS_ERR(tmp)) returnPTR_ERR(tmp); fd=get_unused_fd_flags(flags); if(fd>=0){ structfile*f=do_filp_open(dfd,tmp,&op); if(IS_ERR(f)){ put_unused_fd(fd); fd=PTR_ERR(f); }else{ fsnotify_open(f); fd_install(fd,f); } } putname(tmp); returnfd; }
_do_fork
long_do_fork(unsignedlongclone_flags,
unsignedlongstack_start,
unsignedlongstack_size,
int__user*parent_tidptr,
int__user*child_tidptr,
unsignedlongtls)
{
structtask_struct*p;
inttrace=0;
longnr;
/*
*Determinewhetherandwhicheventtoreporttoptracer.When
*calledfromkernel_threadorCLONE_UNTRACEDisexplicitly
*requested,noeventisreported;otherwise,reportiftheevent
*forthetypeofforkingisenabled.
*/
if(!(clone_flags&CLONE_UNTRACED)){
if(clone_flags&CLONE_VFORK)
trace=PTRACE_EVENT_VFORK;
elseif((clone_flags&CSIGNAL)!=SIGCHLD)
trace=PTRACE_EVENT_CLONE;
else
trace=PTRACE_EVENT_FORK;
if(likely(!ptrace_event_enabled(current,trace)))
trace=0;
}
p=copy_process(clone_flags,stack_start,stack_size,
child_tidptr,NULL,trace,tls,NUMA_NO_NODE);
/*
*Dothispriorwakingupthenewthread-thethreadpointer
*mightgetinvalidafterthatpoint,ifthethreadexitsquickly.
*/
if(!IS_ERR(p)){
structcompletionvfork;
structpid*pid;
cpufreq_task_times_alloc(p);
trace_sched_process_fork(current,p);
pid=get_task_pid(p,PIDTYPE_PID);
nr=pid_vnr(pid);
if(clone_flags&CLONE_PARENT_SETTID)
put_user(nr,parent_tidptr);
if(clone_flags&CLONE_VFORK){
p->vfork_done=&vfork;
init_completion(&vfork);
get_task_struct(p);
}
wake_up_new_task(p);
/*forkingcompleteandchildstartedtorun,tellptracer*/
if(unlikely(trace))
ptrace_event_pid(trace,pid);
if(clone_flags&CLONE_VFORK){
if(!wait_for_vfork_done(p,&vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE,pid);
}
put_pid(pid);
}else{
nr=PTR_ERR(p);
}
returnnr;
}
實(shí)際調(diào)試中經(jīng)常需要調(diào)查函數(shù)使用的變量的值。要在kprobes的偵測器內(nèi)顯示某個(gè)函數(shù)的局部變量的值,需要一些技巧,原因是在printk的參數(shù)中無法直接指定變量名,因此必須給偵測器函數(shù)提供一個(gè)pt_regs結(jié)構(gòu),其中保存了指定地址的命令執(zhí)行時(shí)的寄存器信息。
當(dāng)然,不同架構(gòu)下該結(jié)構(gòu)的成員變量不盡相同,但用該結(jié)構(gòu)可以顯示變量等更為詳細(xì)的信息。
ARM64,ARM32,X86的寄存器及其訪問方式可以看文末的目錄
kprobe_example.c
/* *NOTE:Thisexampleisworksonx86andpowerpc. *Here'sasamplekernelmoduleshowingtheuseofkprobestodumpa *stacktraceandselectedregisterswhen_do_fork()iscalled. * *Formoreinformationontheoryofoperationofkprobes,see *Documentation/kprobes.txt * *Youwillseethetracedatain/var/log/messagesandontheconsole *whenever_do_fork()isinvokedtocreateanewprocess. */ #include#include #include #defineTRACE_SYMBOL"do_filp_open" /*Foreachprobeyouneedtoallocateakprobestructure*/ staticstructkprobekp={ .symbol_name=TRACE_SYMBOL, }; /*x86_64中寄存器中參數(shù)的順序:rdirsirdxrcxr8r9*/ /*aarch64:x0-x7對應(yīng)參數(shù)*/ /*kprobepre_handler:calledjustbeforetheprobedinstructionisexecuted*/ staticinthandler_pre(structkprobe*p,structpt_regs*regs) { intdfd=-1; structfilename*filename=NULL; #ifdefCONFIG_X86 dfd=regs->di; filename=(structfilename*)regs->si; #endif #ifdefCONFIG_ARM64 dfd=regs->regs[0]; filename=(structfilename*)regs->regs[1]; #endif if(filename&&!(strcmp(filename->name,"testfile"))) printk(KERN_INFO"handler_pre:%s:dfd=%d,name=%s ",p->symbol_name,dfd,filename->name); return0; } /*kprobepost_handler:calledaftertheprobedinstructionisexecuted*/ staticvoidhandler_post(structkprobe*p,structpt_regs*regs, unsignedlongflags) { //printk(KERN_INFO"handler_post "); } /* *fault_handler:thisiscalledifanexceptionisgeneratedforany *instructionwithinthepre-orpost-handler,orwhenKprobes *single-stepstheprobedinstruction. */ staticinthandler_fault(structkprobe*p,structpt_regs*regs,inttrapnr) { /*printk(KERN_INFO"fault_handler:p->addr=0x%p,trap#%dn", p->addr,trapnr);*/ /*Return0becausewedon'thandlethefault.*/ return0; } staticint__initkprobe_init(void) { intret; kp.pre_handler=handler_pre; kp.post_handler=handler_post; kp.fault_handler=handler_fault; ret=register_kprobe(&kp); if(ret0)?{ ??printk(KERN_INFO?"register_kprobe?failed,?returned?%d ",?ret); ??return?ret; ?} ?printk(KERN_INFO?"Planted?kprobe?at?%p ",?kp.addr); ?return?0; } static?void?__exit?kprobe_exit(void) { ?unregister_kprobe(&kp); ?printk(KERN_INFO?"kprobe?at?%p?unregistered ",?kp.addr); } module_init(kprobe_init) module_exit(kprobe_exit) MODULE_LICENSE("GPL");
我們以內(nèi)核目錄下的例程做一個(gè)簡單修改,探測do_filp_open函數(shù),當(dāng)打開testfile文件時(shí),自動(dòng)打印出文件的路徑。
為了減少無效信息的打印,我們將handler_post,handler_fault直接注釋掉。
當(dāng)探測點(diǎn)do_filp_open命中時(shí),Kprobes調(diào)用handler_pre。在handler_pre根據(jù)struct filename *pathname來獲得文件的名字。
在x86_64架構(gòu)中,函數(shù)的參數(shù)從左到右分別保存在rdi、rsi、rdx、rcx、r8、r9中,因此查看rdi和rsi就能得到第1個(gè)、第2個(gè)參數(shù)的值。
同理,在ARM64架構(gòu)中, 函數(shù)的參數(shù)1~參數(shù)8分別保存到 X0~X7 寄存器中 ,剩下的參數(shù)從右往左依次入棧。因此,X0和X1分別存放dfd, pathname的值。
makefile
CROSS_COMPILE:=aarch64-linux-gnu- ARCH:=arm64 CC:=$(CROSS_COMPILE)gcc LD:=$(CROSS_COMPILE)ld PWD:=$(shellpwd) obj-m:=kprobe_example.ojprobe_example.okretprobe_example.o KERNELDIR:=/home/zhongyi/code/rk3399_linux_release_v2.5.1_20210301/kernel all: make-C$(KERNELDIR)M=$(PWD)modulesARCH=$(ARCH) clean: rm-f*.o rm-f*.symvers rm-f*.order rm-f*.ko rm-f*.mod.c
執(zhí)行make編譯后,在開發(fā)板上將驅(qū)動(dòng)加載后,手動(dòng)打開testfile文件。
insmodkprobe_example.ko vimtestfile rmmodkprobe_example.ko dmesg
使用dmesg可以看到成功輸出文件名和dfd。
[307.572314]Plantedkprobeatffffff80081fdf84 [311.997767]handler_predfd=-100,name=testfile [312.034774]handler_predfd=-100,name=testfile [347.969572]kprobeatffffff80081fdf84unregistered
顯示棧跟蹤
使用kprobes的另一個(gè)有效的調(diào)試方法,就是顯示棧跟蹤。
我們只需要在handler_pre中調(diào)用dump_stack();即可。
/*x86_64中寄存器中參數(shù)的順序:rdirsirdxrcxr8r9*/
/*aarch64:x0-x7對應(yīng)參數(shù)*/
/*kprobepre_handler:calledjustbeforetheprobedinstructionisexecuted*/
staticinthandler_pre(structkprobe*p,structpt_regs*regs)
{
dump_stack();
return0;
}
編譯加載
insmodkprobe_example.ko rmmodkprobe_example.ko dmesg
成功打印出棧的信息。
[451.620803]CPU:4PID:1299Comm:rmmodTainted:GO4.4.194+#18 [451.620809]Hardwarename:Firefly-RK3399Board(LinuxOpensource)(DT) [451.620813]Calltrace: [451.620820][]dump_backtrace+0x0/0x220 [451.620828][ ]show_stack+0x24/0x30 [451.620834][ ]dump_stack+0x94/0xbc [451.620842][ ]handler_pre+0x14/0x24[kprobe_example] [451.620848][ ]kprobe_breakpoint_handler+0x100/0x14c [451.620855][ ]brk_handler+0x54/0x80 [451.620860][ ]do_debug_exception+0x58/0xc0 [451.620866]Exceptionstack(0xffffffc0f2ef7c40to0xffffffc0f2ef7d70) [451.620879]7c40:ffffffc0ef7820000000008000000000ffffffc0f2ef7e20ffffff80081fdf84 [451.620886]7c60:0000000060000145ffffff8008efc228ffffffc0ceff2a50ffffffc0ee7d2988 [451.620892]7c80:ffffffc0f2ef7ca0ffffff80081c0dc8ffffffc0f0582e7000e80000e95f3f53 [451.620898]7ca0:ffffffc0f2ef7d70ffffff8008efe3e8ffffffc0f2ef7ec00000005583d31928 [451.620905]7cc0:00000000000000550000000092000047ffffffc0ceec5100ffffffc0dccbd500 [451.620911]7ce0:0000000000000024ffffffc0dccbd58000000000ffffff9cffffffc0ef782000 [451.620917]7d00:ffffffc0f2ef7e78000000000000000000000000000000000000000000000003 [451.620923]7d20:ffffffc0dcfc9a800000007fd94380e80000000000000000fefefefefefefeff [451.620929]7d40:00000000000000010000007fd9437db800000000000000000000000000000000 [451.620934]7d60:0000000000000000000000007fffffde [451.620940][ ]el1_dbg+0x18/0x7c [451.620947][ ]SyS_openat+0x3c/0x4c [451.620953][ ]el0_svc_naked+0x24/0x28 [451.630032]kprobeatffffff80081fdf84unregistered
任意位置通過變量名獲取信息
kprobes擁有更加強(qiáng)大的功能,那就是它能在內(nèi)核的任意地址插入偵測器。此外,偵測器可以在任意地址的指令執(zhí)行之前或之后執(zhí)行,或者前后都執(zhí)行。
因此,應(yīng)當(dāng)觀察匯編代碼,找到源代碼中想要調(diào)查的位置對應(yīng)于編譯后的二進(jìn)制文件中的什么地址,并調(diào)查希望顯示的變量保存在哪個(gè)寄存器、哪個(gè)內(nèi)存地址。
通常,我們希望在函數(shù)執(zhí)行的過程中變量,即打印一些流程中的東西,而不是函數(shù)本身被調(diào)用,此時(shí)我們不能簡單設(shè)置 kprobe->symbol_name 函數(shù)名字 ,假設(shè)我們期望獲取 _do_fork函數(shù)變量 nr 的值:
將vmlinux進(jìn)行反匯編,找出_do_fork的地址。
aarch64-linux-gnu-objdump-s-dvmlinux>vmlinux.asm
_do_fork 反匯編如下所示,地址為ffffff80080ba83c。
ffffff80080ba83c<_do_fork>: ffffff80080ba83c:a9b97bfdstpx29,x30,[sp,#-112]! ffffff80080ba840:910003fdmovx29,sp ffffff80080ba844:a90153f3stpx19,x20,[sp,#16] ffffff80080ba848:a9025bf5stpx21,x22,[sp,#32] ffffff80080ba84c:a90363f7stpx23,x24,[sp,#48] ffffff80080ba850:aa0003f5movx21,x0 ffffff80080ba854:aa0103f3movx19,x1 ffffff80080ba858:aa0203f6movx22,x2 ffffff80080ba85c:aa0303f7movx23,x3 ffffff80080ba860:aa0403f8movx24,x4 ffffff80080ba864:aa1e03e0movx0,x30 ffffff80080ba868:97ff4e8ablffffff800808e290<_mcount> ffffff80080ba86c:37b814f5tbnzw21,#23,ffffff80080bab08<_do_fork+0x2cc> ffffff80080ba870:37701495tbnzw21,#14,ffffff80080bab00<_do_fork+0x2c4> ffffff80080ba874:92401ea0andx0,x21,#0xff ffffff80080ba878:52800074movw20,#0x3//#3 ffffff80080ba87c:f100441fcmpx0,#0x11 ffffff80080ba880:1a9f1694csincw20,w20,wzr,ne//ne=any ffffff80080ba884:11000e81addw1,w20,#0x3 ............................ ffffff80080ba91c:b5000fb6cbnzx22,ffffff80080bab10<_do_fork+0x2d4> ffffff80080ba920:52800001movw1,#0x0//#0 ffffff80080ba924:aa1303e0movx0,x19 ffffff80080ba928:94006a17blffffff80080d5184ffffff80080ba92c:aa0003f6movx22,x0 ffffff80080ba930:94006a85blffffff80080d5344pid_vnr> ffffff80080ba934:93407c18sxtwx24,w0 ffffff80080ba938:36a00195tbzw21,#20,ffffff80080ba968<_do_fork+0x12c> ffffff80080ba93c:d5384101mrsx1,sp_el0 ffffff80080ba940:f9400422ldrx2,[x1,#8] ffffff80080ba944:aa1703e1movx1,x23 ffffff80080ba948:b1001021addsx1,x1,#0x4
nr 變量 是 函數(shù)pid_vnr的返回值(也是子進(jìn)程的pid) ,根據(jù)ARM調(diào)用規(guī)范,調(diào)用完成pid_vnr()后,寄存器x0存放的就是其函數(shù)返回值。
參考:ARM64調(diào)用標(biāo)準(zhǔn) https://blog.51cto.com/u_15333820/3452605
通過反匯編可以知道,pid_vnr在 ffffff80080ba930地址處被調(diào)用,因此,偵測器的插入地址就是在ffffff80080ba930之后,并且x0被改變之前。只要符合這兩個(gè)條件,放在哪里都無所謂。
因此,我們將kprobe的點(diǎn)設(shè)置為ffffff80080ba934,然后獲取 x0,就能獲取變量nr的值。
.offset 是探測點(diǎn)相對于_do_fork的偏移,在注冊時(shí)指定。我們這里的 offset = ffffff80080ba934 - ffffff80080ba83c = F8。
另外,反匯編能力就是多看匯編以及找到幾個(gè)關(guān)鍵點(diǎn)(例如常量,跳轉(zhuǎn)語句)就能定位到匯編對應(yīng)的源碼了,這里不再展開了。
/* *NOTE:Thisexampleisworksonx86andpowerpc. *Here'sasamplekernelmoduleshowingtheuseofkprobestodumpa *stacktraceandselectedregisterswhen_do_fork()iscalled. * *Formoreinformationontheoryofoperationofkprobes,see *Documentation/kprobes.txt * *Youwillseethetracedatain/var/log/messagesandontheconsole *whenever_do_fork()isinvokedtocreateanewprocess. */ #include#include #include /*Foreachprobeyouneedtoallocateakprobestructure*/ staticstructkprobekp={ .symbol_name="_do_fork", .offset=0xF8, }; /*kprobepre_handler:calledjustbeforetheprobedinstructionisexecuted*/ staticinthandler_pre(structkprobe*p,structpt_regs*regs) { #ifdefCONFIG_X86 printk(KERN_INFO"pre_handler:p->addr=0x%p,ip=%lx," "flags=0x%lx,rax=0x%lx ", p->addr,regs->ip,regs->flags,regs->ax); #endif #ifdefCONFIG_ARM64 pr_info("<%s>pre_handler:p->addr=0x%p,pc=0x%lx," "pstate=0x%lx,x0=0x%lx ", p->symbol_name,p->addr,(long)regs->pc,(long)regs->pstate,(long)regs->regs[0]); #endif /*Adump_stack()herewillgiveastackbacktrace*/ return0; } /*kprobepost_handler:calledaftertheprobedinstructionisexecuted*/ staticvoidhandler_post(structkprobe*p,structpt_regs*regs, unsignedlongflags) { #ifdefCONFIG_X86 printk(KERN_INFO"post_handler:p->addr=0x%p,flags=0x%lx ", p->addr,regs->flags); #endif #ifdefCONFIG_ARM64 pr_info("<%s>post_handler:p->addr=0x%p,pstate=0x%lx ", p->symbol_name,p->addr,(long)regs->pstate); #endif } /* *fault_handler:thisiscalledifanexceptionisgeneratedforany *instructionwithinthepre-orpost-handler,orwhenKprobes *single-stepstheprobedinstruction. */ staticinthandler_fault(structkprobe*p,structpt_regs*regs,inttrapnr) { printk(KERN_INFO"fault_handler:p->addr=0x%p,trap#%dn", p->addr,trapnr); /*Return0becausewedon'thandlethefault.*/ return0; } staticint__initkprobe_init(void) { intret; kp.pre_handler=handler_pre; kp.post_handler=handler_post; kp.fault_handler=handler_fault; ret=register_kprobe(&kp); if(ret0)?{ ??printk(KERN_INFO?"register_kprobe?failed,?returned?%d ",?ret); ??return?ret; ?} ?printk(KERN_INFO?"Planted?kprobe?at?%p ",?kp.addr); ?return?0; } static?void?__exit?kprobe_exit(void) { ?unregister_kprobe(&kp); ?printk(KERN_INFO?"kprobe?at?%p?unregistered ",?kp.addr); } module_init(kprobe_init) module_exit(kprobe_exit) MODULE_LICENSE("GPL");
insmodkprobe_example.ko rmmodkprobe_example.ko dmesg
編譯加載后,成功打印出rax的值。
[245.080636]pre_handler:p->addr=0x0000000050a6c3dd,ip=ffffffffa5ca0009,flags=0x246,rax=0x2 [245.080640]post_handler:p->addr=0x0000000050a6c3dd,flags=0x246 [245.080936]pre_handler:p->addr=0x0000000050a6c3dd,ip=ffffffffa5ca0009,flags=0x246,rax=0x2 [245.080938]post_handler:p->addr=0x0000000050a6c3dd,flags=0x246 [245.457340]pre_handler:p->addr=0x0000000050a6c3dd,ip=ffffffffa5ca0009,flags=0x246,rax=0x2 [245.457345]post_handler:p->addr=0x0000000050a6c3dd,flags=0x246 [245.457643]pre_handler:p->addr=0x0000000050a6c3dd,ip=ffffffffa5ca0009,flags=0x246,rax=0x2 [245.457645]post_handler:p->addr=0x0000000050a6c3dd,flags=0x246 [245.719208]pre_handler:p->addr=0x0000000050a6c3dd,ip=ffffffffa5ca0009,flags=0x246,rax=0x2 [245.719213]post_handler:p->addr=0x0000000050a6c3dd,flags=0x246 [245.719505]pre_handler:p->addr=0x0000000050a6c3dd,ip=ffffffffa5ca0009,flags=0x246,rax=0x2 [245.719507]post_handler:p->addr=0x0000000050a6c3dd,flags=0x246 [245.820761]pre_handler:p->addr=0x0000000050a6c3dd,ip=ffffffffa5ca0009,flags=0x246,rax=0x2 [245.820765]post_handler:p->addr=0x0000000050a6c3dd,flags=0x246 [245.821061]pre_handler:p->addr=0x0000000050a6c3dd,ip=ffffffffa5ca0009,flags=0x246,rax=0x2 [245.821063]post_handler:p->addr=0x0000000050a6c3dd,flags=0x246 [246.092572]pre_handler:p->addr=0x0000000050a6c3dd,ip=ffffffffa5ca0009,flags=0x246,rax=0x2 [246.092577]post_handler:p->addr=0x0000000050a6c3dd,flags=0x246 [246.095863]pre_handler:p->addr=0x0000000050a6c3dd,ip=ffffffffa5ca0009,flags=0x246,rax=0x2 [246.095867]post_handler:p->addr=0x0000000050a6c3dd,flags=0x246 [246.126196]kprobeat0000000050a6c3ddunregistered
jprobe
與kprobes相比,jprobes能更容易地獲取傳給函數(shù)的參數(shù)。有幾點(diǎn)需要注意:
處理程序應(yīng)該有與被探測函數(shù)相同的參數(shù)列表和返回類型;
返回之前,必須調(diào)用jprobe_return()(處理程序?qū)嶋H上從未返回,因?yàn)閖probe_return()將控制權(quán)返回給Kprobes) 。
查看函數(shù)的參數(shù)
/* *Here'sasamplekernelmoduleshowingtheuseofjprobestodump *theargumentsof_do_fork(). * *Formoreinformationontheoryofoperationofjprobes,see *Documentation/kprobes.txt * *Buildandinsertthekernelmoduleasdoneinthekprobeexample. *Youwillseethetracedatain/var/log/messagesandonthe *consolewhenever_do_fork()isinvokedtocreateanewprocess. *(Somemessagesmaybesuppressedifsyslogdisconfiguredto *eliminateduplicatemessages.) */ #include#include #include /* *Jumperprobefor_do_fork. *Mirrorprincipleenablesaccesstoargumentsoftheprobedroutine *fromtheprobehandler. */ /*Proxyroutinehavingthesameargumentsasactual_do_fork()routine*/ #defineTRACE_SYMBOL"do_filp_open" /*與do_filp_open的參數(shù)完全相同*/ staticstructfile*jp_do_filp_open(intdfd,structfilename*pathname, conststructopen_flags*op) { if(pathname&&!(strcmp(pathname->name,"testfile"))) printk(KERN_INFO"jprobe:dfd=%d,pathname=%s ",dfd,pathname->name); /*Alwaysendwithacalltojprobe_return().*/ jprobe_return(); return0; } staticstructjprobemy_jprobe={ .entry=jp_do_filp_open, .kp={ .symbol_name=TRACE_SYMBOL, }, }; staticint__initjprobe_init(void) { intret; ret=register_jprobe(&my_jprobe); if(ret0)?{ ??printk(KERN_INFO?"register_jprobe?failed,?returned?%d ",?ret); ??return?-1; ?} ?printk(KERN_INFO?"Planted?jprobe?at?%p,?handler?addr?%p ", ????????my_jprobe.kp.addr,?my_jprobe.entry); ?return?0; } static?void?__exit?jprobe_exit(void) { ?unregister_jprobe(&my_jprobe); ?printk(KERN_INFO?"jprobe?at?%p?unregistered ",?my_jprobe.kp.addr); } module_init(jprobe_init) module_exit(jprobe_exit) MODULE_LICENSE("GPL");
使用kprobes時(shí),必須通過寄存器或棧才能計(jì)算出參數(shù)的值。此外,計(jì)算方法還依賴于架構(gòu)。
如果使用jprobes,那么無須了解架構(gòu)的詳細(xì)知識(shí),也能簡單地查看參數(shù)的值。
編譯加載驅(qū)動(dòng)程序
insmodjprobe_example.ko vimtestfile rmmodjprobe_example.ko dmesg
成功打印出函數(shù)的參數(shù)
[612.670453]jprobeatffffff80081fdf84unregistered [867.293765]Plantedjprobeatffffff80081fdf84,handleraddrffffff8000f1a000 [871.107502]jprobe:dfd=-100,pathname=testfile [871.147747]jprobe:dfd=-100,pathname=testfile [875.723761]jprobeatffffff80081fdf84unregistered [907.706066]Plantedjprobeatffffff80081fdf84,handleraddrffffff8000f22000 [911.661891]jprobe:dfd=-100,pathname=testfile [911.694903]jprobe:dfd=-100,pathname=testfile [919.272187]jprobeatffffff80081fdf84unregistered [2296.830613]Plantedjprobeatffffff80081fdf84,handleraddrffffff8000f2a000 [2302.164861]jprobe:dfd=-100,pathname=testfile [2302.200634]jprobe:dfd=-100,pathname=testfile [2307.407014]jprobeatffffff80081fdf84unregistered
kretprobe
kretprobe 也是基于kprobe的,相比于kprobe和jprobe,實(shí)現(xiàn)相對復(fù)雜。下面我們以內(nèi)核目錄下的例程,簡單分析下。
kretprobe_example.c
/* *kretprobe_example.c * *Here'sasamplekernelmoduleshowingtheuseofreturnprobesto *reportthereturnvalueandtotaltimetakenforprobedfunction *torun. * *usage:insmodkretprobe_example.kofunc=* *Ifnofunc_nameisspecified,_do_forkisinstrumented * *Formoreinformationontheoryofoperationofkretprobes,see *Documentation/kprobes.txt * *Buildandinsertthekernelmoduleasdoneinthekprobeexample. *Youwillseethetracedatain/var/log/messagesandontheconsole *whenevertheprobedfunctionreturns.(Somemessagesmaybesuppressed *ifsyslogdisconfiguredtoeliminateduplicatemessages.) */ #include #include #include #include #include #include staticcharfunc_name[NAME_MAX]="do_sys_open"; module_param_string(func,func_name,NAME_MAX,S_IRUGO); MODULE_PARM_DESC(func,"Functiontokretprobe;thismodulewillreportthe" "function'sexecutiontime"); /*per-instanceprivatedata*/ structmy_data{ ktime_tentry_stamp; }; /*Hereweusetheentry_hanldertotimestampfunctionentry*/ staticintentry_handler(structkretprobe_instance*ri,structpt_regs*regs) { structmy_data*data; if(!current->mm) return1;/*Skipkernelthreads*/ data=(structmy_data*)ri->data; data->entry_stamp=ktime_get(); return0; } /* *Return-probehandler:Logthereturnvalueandduration.Durationmayturn *outtobezeroconsistently,dependinguponthegranularityoftime *accountingontheplatform. */ staticintret_handler(structkretprobe_instance*ri,structpt_regs*regs) { intretval=regs_return_value(regs); structmy_data*data=(structmy_data*)ri->data; s64delta; ktime_tnow; now=ktime_get(); delta=ktime_to_ns(ktime_sub(now,data->entry_stamp)); printk(KERN_INFO"%sreturned%dandtook%lldnstoexecute ", func_name,retval,(longlong)delta); return0; } staticstructkretprobemy_kretprobe={ .handler=ret_handler, .entry_handler=entry_handler, .data_size=sizeof(structmy_data), /*Probeupto20instancesconcurrently.*/ .maxactive=20, }; staticint__initkretprobe_init(void) { intret; my_kretprobe.kp.symbol_name=func_name; ret=register_kretprobe(&my_kretprobe); if(ret0)?{ ??printk(KERN_INFO?"register_kretprobe?failed,?returned?%d ", ????ret); ??return?-1; ?} ?printk(KERN_INFO?"Planted?return?probe?at?%s:?%p ", ???my_kretprobe.kp.symbol_name,?my_kretprobe.kp.addr); ?return?0; } static?void?__exit?kretprobe_exit(void) { ?unregister_kretprobe(&my_kretprobe); ?printk(KERN_INFO?"kretprobe?at?%p?unregistered ", ???my_kretprobe.kp.addr); ?/*?nmissed?>0suggeststhatmaxactivewassettoolow.*/ printk(KERN_INFO"Missedprobing%dinstancesof%s ", my_kretprobe.nmissed,my_kretprobe.kp.symbol_name); } module_init(kretprobe_init) module_exit(kretprobe_exit) MODULE_LICENSE("GPL");
struct kretprobe
/* *Function-returnprobe- *Note: *Userneedstoprovideahandlerfunction,andinitializemaxactive. *maxactive-Themaximumnumberofinstancesoftheprobedfunctionthat *canbeactiveconcurrently. *nmissed-tracksthenumberoftimestheprobedfunction'sreturnwas *ignored,duetomaxactivebeingtoolow. * */ structkretprobe{ structkprobekp; kretprobe_handler_thandler; kretprobe_handler_tentry_handler; intmaxactive; intnmissed; size_tdata_size; structhlist_headfree_instances; raw_spinlock_tlock; }; typedefint(*kretprobe_handler_t)(structkretprobe_instance*, structpt_regs*);
其中我們可以看到 struct kretprobe 結(jié)構(gòu)體中 有struct kprobe成員(kretprobe時(shí)基于 kprobe實(shí)現(xiàn)的)。handler:用戶自定義回調(diào)函數(shù),被探測函數(shù)返回后被調(diào)用,一般在這個(gè)函數(shù)中獲取被探測函數(shù)的返回值。
entry_handler:用戶自定義回調(diào)函數(shù),這是Kretprobes 提供了一個(gè)可選的用戶指定的處理程序,它在函數(shù)入口上運(yùn)行。 每當(dāng) kretprobe 放置在函數(shù)入口處的 kprobe 被命中時(shí),都會(huì)調(diào)用用戶定義的 entry_handler,如果有的話。 如果 entry_handler 返回 0(成功),則保證在函數(shù)返回時(shí)調(diào)用相應(yīng)的返回處理程序。 如果 entry_handler 返回非零錯(cuò)誤,則 Kprobes 將返回地址保持原樣,并且 kretprobe 對該特定函數(shù)實(shí)例沒有進(jìn)一步的影響。
maxactive:被探測函數(shù)可以同時(shí)活動(dòng)的最大實(shí)例數(shù)。來指定可以同時(shí)探測多少個(gè)指定函數(shù)的實(shí)例。register_kretprobe() 預(yù)分配指定數(shù)量的 kretprobe_instance 對象。
nmissed:跟蹤被探測函數(shù)的返回被忽略的次數(shù)(maxactive設(shè)置的過低)。
data_size:表示kretprobe私有數(shù)據(jù)的大小,在注冊kretprobe時(shí)會(huì)根據(jù)該大小預(yù)留空間。
free_instances :表示空閑的kretprobe運(yùn)行實(shí)例鏈表,它鏈接了本kretprobe的空閑實(shí)例struct kretprobe_instance結(jié)構(gòu)體表示。
struct kretprobe_instance
structkretprobe_instance{
structhlist_nodehlist;
structkretprobe*rp;
kprobe_opcode_t*ret_addr;
structtask_struct*task;
chardata[0];
};
這個(gè)結(jié)構(gòu)體表示kretprobe的運(yùn)行實(shí)例,前文說過被探測函數(shù)在跟蹤期間可能存在并發(fā)執(zhí)行的現(xiàn)象,因此kretprobe使用一個(gè)kretprobe_instance來跟蹤一個(gè)執(zhí)行流,支持的上限為maxactive。在沒有觸發(fā)探測時(shí),所有的kretprobe_instance實(shí)例都保存在free_instances表中,每當(dāng)有執(zhí)行流觸發(fā)一次kretprobe探測,都會(huì)從該表中取出一個(gè)空閑的kretprobe_instance實(shí)例用來跟蹤。
kretprobe_instance結(jié)構(gòu)提中的rp指針指向所屬的kretprobe;
ret_addr用于保存原始被探測函數(shù)的返回地址(后文會(huì)看到被探測函數(shù)返回地址會(huì)被暫時(shí)替換);
task用于綁定其跟蹤的進(jìn)程;
data保存用戶使用的kretprobe私有數(shù)據(jù),它會(huì)在整個(gè)kretprobe探測運(yùn)行期間在entry_handler和handler回調(diào)函數(shù)之間進(jìn)行傳遞(一般用于實(shí)現(xiàn)統(tǒng)計(jì)被探測函數(shù)的執(zhí)行耗時(shí))。
register_kretprobe
kretprobe探測點(diǎn)的blackpoint,用來表示不支持kretprobe探測的函數(shù)的信息。name表示該函數(shù)名,addr表示該函數(shù)的地址。
structkretprobe_blackpoint{
constchar*name;
void*addr;
};
1234
blackpoint與架構(gòu)相關(guān),x86架構(gòu)不支持的kretprobe探測點(diǎn)如下:
//arch/x86/kernel/kprobes/core.c
//不支持kretprobe探測的函數(shù),從blacklist這個(gè)名字中我們也知道其含義了。
structkretprobe_blackpointkretprobe_blacklist[]={
{"__switch_to",},/*Thisfunctionswitchesonlycurrenttask,but
doesn'tswitchkernelstack.*/
{NULL,NULL}/*Terminator*/
};
constintkretprobe_blacklist_size=ARRAY_SIZE(kretprobe_blacklist);
123456789
函數(shù)的開頭首先處理 kretprobe_blacklis t,如果指定的被探測函數(shù)在這個(gè)blacklist中就直接返回EINVAL,表示不支持探測,在x86架構(gòu)中是__switch_to 這個(gè)函數(shù),表示這個(gè)函數(shù)不能被kretprobe。
intregister_kretprobe(structkretprobe*rp)
{
intret=0;
structkretprobe_instance*inst;
inti;
void*addr;
if(kretprobe_blacklist_size){
addr=kprobe_addr(&rp->kp);
if(IS_ERR(addr))
returnPTR_ERR(addr);
//如果kretprobe到kretprobe_blacklist中函數(shù),則返回EINVAL
for(i=0;kretprobe_blacklist[i].name!=NULL;i++){
if(kretprobe_blacklist[i].addr==addr)
return-EINVAL;
}
}
//內(nèi)核設(shè)置回調(diào)函數(shù)pre_handler_kretprobe。
//與kprobe不同的是:kretprobe不支持用戶定義pre_handler和post_handler等回調(diào)函數(shù)。
rp->kp.pre_handler=pre_handler_kretprobe;
rp->kp.post_handler=NULL;
rp->kp.fault_handler=NULL;
rp->kp.break_handler=NULL;
/*Pre-allocatememoryformaxkretprobeinstances*/
if(rp->maxactive<=?0)?{
#ifdef?CONFIG_PREEMPT
??rp->maxactive=max_t(unsignedint,10,2*num_possible_cpus());
#else
rp->maxactive=num_possible_cpus();
#endif
}
raw_spin_lock_init(&rp->lock);
INIT_HLIST_HEAD(&rp->free_instances);
//根據(jù)maxactive值分配structkretprobe_instance內(nèi)存空間
for(i=0;imaxactive;i++){
inst=kmalloc(sizeof(structkretprobe_instance)+
rp->data_size,GFP_KERNEL);
if(inst==NULL){
free_rp_inst(rp);
return-ENOMEM;
}
INIT_HLIST_NODE(&inst->hlist);
hlist_add_head(&inst->hlist,&rp->free_instances);
}
rp->nmissed=0;
/*Establishfunctionentryprobepoint*/
//注冊kprobe探測點(diǎn)
ret=register_kprobe(&rp->kp);
if(ret!=0)
free_rp_inst(rp);
returnret;
}
EXPORT_SYMBOL_GPL(register_kretprobe);
最后調(diào)用 register_kprobe(&rp->kp),注冊kprobe點(diǎn),可以看出kretprobe也是基于kprobe機(jī)制實(shí)現(xiàn)的,kretprobe也是一種特殊形式的kprobe。
kretprobe注冊完成后就默認(rèn)啟動(dòng)探測。
pre_handler_kretprobe
pre_handler_kretprobe這個(gè)函數(shù)是內(nèi)核自己定義的,內(nèi)核已經(jīng)指定該回調(diào)函數(shù),不支持用戶自定義。這個(gè) kprobe pre_handler 在每個(gè) kretprobe 中注冊。 當(dāng)探針命中時(shí),它將設(shè)置返回探針。
#ifdefCONFIG_KRETPROBES
/*
*Thiskprobepre_handlerisregisteredwitheverykretprobe.Whenprobe
*hitsitwillsetupthereturnprobe.
*/
staticintpre_handler_kretprobe(structkprobe*p,structpt_regs*regs)
{
structkretprobe*rp=container_of(p,structkretprobe,kp);
unsignedlonghash,flags=0;
structkretprobe_instance*ri;
/*
*Toavoiddeadlocks,prohibitreturnprobinginNMIcontexts,
*justskiptheprobeandincreasethe(inexact)'nmissed'
*statisticalcounter,sothattheuserisinformedthat
*somethinghappened:
*/
if(unlikely(in_nmi())){
rp->nmissed++;
return0;
}
/*TODO:considertoonlyswaptheRAafterthelastpre_handlerfired*/
hash=hash_ptr(current,KPROBE_HASH_BITS);
raw_spin_lock_irqsave(&rp->lock,flags);
if(!hlist_empty(&rp->free_instances)){
ri=hlist_entry(rp->free_instances.first,
structkretprobe_instance,hlist);
hlist_del(&ri->hlist);
raw_spin_unlock_irqrestore(&rp->lock,flags);
ri->rp=rp;
ri->task=current;
(1)
if(rp->entry_handler&&rp->entry_handler(ri,regs)){
raw_spin_lock_irqsave(&rp->lock,flags);
hlist_add_head(&ri->hlist,&rp->free_instances);
raw_spin_unlock_irqrestore(&rp->lock,flags);
return0;
}
(2)
arch_prepare_kretprobe(ri,regs);
/*XXX(hch):whyistherenohlist_move_head?*/
INIT_HLIST_NODE(&ri->hlist);
kretprobe_table_lock(hash,&flags);
hlist_add_head(&ri->hlist,&kretprobe_inst_table[hash]);
kretprobe_table_unlock(hash,&flags);
}else{
rp->nmissed++;
raw_spin_unlock_irqrestore(&rp->lock,flags);
}
return0;
}
NOKPROBE_SYMBOL(pre_handler_kretprobe);
entry_handler
structkretprobe*rp rp->entry_handler&&rp->entry_handler(ri,regs)
entry_handler這個(gè)回調(diào)函數(shù)就是用戶自己定義的回調(diào)函數(shù)(可選的用戶指定的處理程序),前面我們已經(jīng)介紹過了,在這里不再介紹。
/*Hereweusetheentry_hanldertotimestampfunctionentry*/
staticintentry_handler(structkretprobe_instance*ri,structpt_regs*regs)
{
structmy_data*data;
//內(nèi)核線程task->mm==NULL
if(!current->mm)
return1;/*Skipkernelthreads*/
data=(structmy_data*)ri->data;
data->entry_stamp=ktime_get();
return0;
}
arch_prepare_kretprobe
arch_prepare_kretprobe(ri, regs)該函數(shù)架構(gòu)相關(guān),struct kretprobe_instance結(jié)構(gòu)體 的 ret_addr 成員用于保存并替換regs中的返回地址。返回地址被替換為kretprobe_trampoline。
x86架構(gòu)
//arch/x86/kernel/kprobes/core.c
#definestack_addr(regs)((unsignedlong*)kernel_stack_pointer(regs))
//x86_64
//arch/x86/include/asm/ptrace.h
staticinlineunsignedlongkernel_stack_pointer(structpt_regs*regs)
{
returnregs->sp;
}
//arch/x86/kernel/kprobes/core.c
voidarch_prepare_kretprobe(structkretprobe_instance*ri,structpt_regs*regs)
{
unsignedlong*sara=stack_addr(regs);
ri->ret_addr=(kprobe_opcode_t*)*sara;
/*Replacethereturnaddrwithtrampolineaddr*/
*sara=(unsignedlong)&kretprobe_trampoline;
}
NOKPROBE_SYMBOL(arch_prepare_kretprobe);
//structkretprobe_instance*ri;
//ri->ret_addr;
structkretprobe_instance{
kprobe_opcode_t*ret_addr;//用于保存原始被探測函數(shù)的返回地址
};
ARM64架構(gòu)
//arch/arm64/kernel/probes/kprobes.c
void__kprobesarch_prepare_kretprobe(structkretprobe_instance*ri,
structpt_regs*regs)
{
ri->ret_addr=(kprobe_opcode_t*)regs->regs[30];
/*replacereturnaddr(x30)withtrampoline*/
regs->regs[30]=(long)&kretprobe_trampoline;
}
ARM64架構(gòu)中regs->regs[30]是LR(procedure link register)寄存器(X30 :LR)。
小結(jié)
kretprobe是基于kprobe實(shí)現(xiàn)的,有一個(gè)固定的pre_handler回調(diào)函數(shù),在內(nèi)核中實(shí)現(xiàn),無需用戶編寫。而在kprobe中pre_handler函數(shù)是提供給用戶的回調(diào)函數(shù)。
rp->kp.pre_handler=pre_handler_kretprobe;//內(nèi)核中已經(jīng)實(shí)現(xiàn) rp->kp.post_handler=NULL; rp->kp.fault_handler=NULL; rp->kp.break_handler=NULL;
kretprobe提供給用戶的兩個(gè)回調(diào)函數(shù):
kretprobe_handler_thandler; kretprobe_handler_tentry_handler;//(可選)
pre_handler回調(diào)函數(shù)會(huì)為kretprobe探測函數(shù)執(zhí)行的返回值做準(zhǔn)備工作,其中最主要的就是替換掉正常流程的返回地址,讓被探測函數(shù)在執(zhí)行之后能夠跳轉(zhuǎn)到kretprobe設(shè)計(jì)的函數(shù) kretprobe_trampoline中去。
kretprobe_trampoline
pre_handler_kretprobe函數(shù)返回后,kprobe流程接著執(zhí)行singlestep流程并返回到正常的執(zhí)行流程,被探測函數(shù)(do_fork)繼續(xù)執(zhí)行,直到它執(zhí)行完畢并返回。
由于返回地址被替換為kretprobe_trampoline,所以跳轉(zhuǎn)到kretprobe_trampoline執(zhí)行,該函數(shù)架構(gòu)相關(guān)且有嵌入?yún)R編實(shí)現(xiàn)。
該函數(shù)會(huì)獲取被探測函數(shù)的寄存器信息并調(diào)用用戶定義的回調(diào)函數(shù)輸出其中的返回值,最后函數(shù)返回正常的執(zhí)行流程。
staticintret_handler(structkretprobe_instance*ri,structpt_regs*regs)
{
unsignedlongretval=regs_return_value(regs);
......
}
staticstructkretprobemy_kretprobe={
.handler=ret_handler,
};
x86架構(gòu)
(1)
kretprobe_trampoline -->trampoline_handler kretprobe_trampoline
(2)kretprobe_trampoline
//arch/x86/kernel/kprobes/core.c /* *Whenaretprobedfunctionreturns,thiscodesavesregistersand *callstrampoline_handler()runs,whichcallsthekretprobe'shandler. */ asm( ".globalkretprobe_trampoline " ".typekretprobe_trampoline,@function " "kretprobe_trampoline: " #ifdefCONFIG_X86_64 /*Wedon'tbothersavingthessregister*/ "pushq%rsp " "pushfq " SAVE_REGS_STRING "movq%rsp,%rdi " "calltrampoline_handler " /*Replacesavedspwithtruereturnaddress.*/ "movq%rax,152(%rsp) " RESTORE_REGS_STRING "popfq " #else "pushf " SAVE_REGS_STRING "movl%esp,%eax " "calltrampoline_handler " /*Moveflagstocs*/ "movl56(%esp),%edx " "movl%edx,52(%esp) " /*Replacesavedflagswithtruereturnaddress.*/ "movl%eax,56(%esp) " RESTORE_REGS_STRING "popf " #endif "ret " ".sizekretprobe_trampoline,.-kretprobe_trampoline " ); NOKPROBE_SYMBOL(kretprobe_trampoline); STACK_FRAME_NON_STANDARD(kretprobe_trampoline);
(3)trampoline_handler
//arch/x86/kernel/kprobes/core.c
/*
*Calledfromkretprobe_trampoline
*/
__visible__usedvoid*trampoline_handler(structpt_regs*regs)
{
structkretprobe_instance*ri=NULL;
structhlist_head*head,empty_rp;
structhlist_node*tmp;
unsignedlongflags,orig_ret_address=0;
unsignedlongtrampoline_address=(unsignedlong)&kretprobe_trampoline;
kprobe_opcode_t*correct_ret_addr=NULL;
INIT_HLIST_HEAD(&empty_rp);
kretprobe_hash_lock(current,&head,&flags);
/*fixupregisters*/
#ifdefCONFIG_X86_64
regs->cs=__KERNEL_CS;
#else
regs->cs=__KERNEL_CS|get_kernel_rpl();
regs->gs=0;
#endif
regs->ip=trampoline_address;
regs->orig_ax=~0UL;
/*
*Itispossibletohavemultipleinstancesassociatedwithagiven
*taskeitherbecausemultiplefunctionsinthecallpathhave
*returnprobesinstalledonthem,and/ormorethanone
*returnprobewasregisteredforatargetfunction.
*
*Wecanhandlethisbecause:
*-instancesarealwayspushedintotheheadofthelist
*-whenmultiplereturnprobesareregisteredforthesame
*function,the(chronologically)firstinstance'sret_addr
*willbetherealreturnaddress,andalltherestwill
*pointtokretprobe_trampoline.
*/
hlist_for_each_entry_safe(ri,tmp,head,hlist){
if(ri->task!=current)
/*anothertaskissharingourhashbucket*/
continue;
orig_ret_address=(unsignedlong)ri->ret_addr;
if(orig_ret_address!=trampoline_address)
/*
*Thisistherealreturnaddress.Anyother
*instancesassociatedwiththistaskarefor
*othercallsdeeperonthecallstack
*/
break;
}
kretprobe_assert(ri,orig_ret_address,trampoline_address);
correct_ret_addr=ri->ret_addr;
hlist_for_each_entry_safe(ri,tmp,head,hlist){
if(ri->task!=current)
/*anothertaskissharingourhashbucket*/
continue;
orig_ret_address=(unsignedlong)ri->ret_addr;
if(ri->rp&&ri->rp->handler){
__this_cpu_write(current_kprobe,&ri->rp->kp);
get_kprobe_ctlblk()->kprobe_status=KPROBE_HIT_ACTIVE;
ri->ret_addr=correct_ret_addr;
ri->rp->handler(ri,regs);
__this_cpu_write(current_kprobe,NULL);
}
recycle_rp_inst(ri,&empty_rp);
if(orig_ret_address!=trampoline_address)
/*
*Thisistherealreturnaddress.Anyother
*instancesassociatedwiththistaskarefor
*othercallsdeeperonthecallstack
*/
break;
}
kretprobe_hash_unlock(current,&flags);
hlist_for_each_entry_safe(ri,tmp,&empty_rp,hlist){
hlist_del(&ri->hlist);
kfree(ri);
}
return(void*)orig_ret_address;
}
NOKPROBE_SYMBOL(trampoline_handler);
(4)ri->rp->handler(ri, regs)表示執(zhí)行用戶態(tài)自定義的回調(diào)函數(shù)handler(用來獲取_do_fork函數(shù)的返回值),handler回調(diào)函數(shù)執(zhí)行完畢以后,調(diào)用recycle_rp_inst函數(shù)將當(dāng)前的kretprobe_instance實(shí)例從kretprobe_inst_table哈希表釋放,重新鏈入free_instances中,以備后面kretprobe觸發(fā)時(shí)使用,另外如果kretprobe已經(jīng)被注銷則將它添加到銷毀表中待銷毀。
ri->rp->handler(ri,regs);
->recycle_rp_inst(ri,&empty_rp);
12
voidrecycle_rp_inst(structkretprobe_instance*ri,
structhlist_head*head)
{
structkretprobe*rp=ri->rp;
/*removerpinstofftherprobe_inst_table*/
hlist_del(&ri->hlist);
INIT_HLIST_NODE(&ri->hlist);
if(likely(rp)){
raw_spin_lock(&rp->lock);
hlist_add_head(&ri->hlist,&rp->free_instances);
raw_spin_unlock(&rp->lock);
}else
/*Unregistering*/
hlist_add_head(&ri->hlist,head);
}
NOKPROBE_SYMBOL(recycle_rp_inst);
(5)trampoline_handler函數(shù)執(zhí)行完后,返回被探測函數(shù)的原始返回地址,執(zhí)行流程再次回到kretprobe_trampoline函數(shù)中,將保存的 sp 替換為真實(shí)的返回地址。從rax寄存器中取出原始的返回地址,然后恢復(fù)原始函數(shù)調(diào)用??臻g,最后跳轉(zhuǎn)到原始返回地址執(zhí)行,至此函數(shù)調(diào)用的流程就回歸正常流程了,整個(gè)kretprobe探測結(jié)束。
/*Replacesavedspwithtruereturnaddress.*/ "movq%rax,152(%rsp) " RESTORE_REGS_STRING "popfq " 1234
ARM64架構(gòu)
(1)
kretprobe_trampoline -->trampoline_probe_handler kretprobe_trampoline
(2)kretprobe_trampoline
//arch/arm64/kernel/probes/kprobes_trampoline.S ENTRY(kretprobe_trampoline) subsp,sp,#S_FRAME_SIZE save_all_base_regs movx0,sp bltrampoline_probe_handler /* *Replacetrampolineaddressinlrwithactualorig_ret_addrreturn *address. */ movlr,x0 restore_all_base_regs addsp,sp,#S_FRAME_SIZE ret ENDPROC(kretprobe_trampoline)
(3)trampoline_probe_handler
//arch/arm64/kernel/probes/kprobes.c
void__kprobes__used*trampoline_probe_handler(structpt_regs*regs)
{
structkretprobe_instance*ri=NULL;
structhlist_head*head,empty_rp;
structhlist_node*tmp;
unsignedlongflags,orig_ret_address=0;
unsignedlongtrampoline_address=
(unsignedlong)&kretprobe_trampoline;
kprobe_opcode_t*correct_ret_addr=NULL;
INIT_HLIST_HEAD(&empty_rp);
kretprobe_hash_lock(current,&head,&flags);
/*
*Itispossibletohavemultipleinstancesassociatedwithagiven
*taskeitherbecausemultiplefunctionsinthecallpathhave
*returnprobesinstalledonthem,and/ormorethanone
*returnprobewasregisteredforatargetfunction.
*
*Wecanhandlethisbecause:
*-instancesarealwayspushedintotheheadofthelist
*-whenmultiplereturnprobesareregisteredforthesame
*function,the(chronologically)firstinstance'sret_addr
*willbetherealreturnaddress,andalltherestwill
*pointtokretprobe_trampoline.
*/
hlist_for_each_entry_safe(ri,tmp,head,hlist){
if(ri->task!=current)
/*anothertaskissharingourhashbucket*/
continue;
orig_ret_address=(unsignedlong)ri->ret_addr;
if(orig_ret_address!=trampoline_address)
/*
*Thisistherealreturnaddress.Anyother
*instancesassociatedwiththistaskarefor
*othercallsdeeperonthecallstack
*/
break;
}
kretprobe_assert(ri,orig_ret_address,trampoline_address);
correct_ret_addr=ri->ret_addr;
hlist_for_each_entry_safe(ri,tmp,head,hlist){
if(ri->task!=current)
/*anothertaskissharingourhashbucket*/
continue;
orig_ret_address=(unsignedlong)ri->ret_addr;
if(ri->rp&&ri->rp->handler){
__this_cpu_write(current_kprobe,&ri->rp->kp);
get_kprobe_ctlblk()->kprobe_status=KPROBE_HIT_ACTIVE;
ri->ret_addr=correct_ret_addr;
ri->rp->handler(ri,regs);
__this_cpu_write(current_kprobe,NULL);
}
recycle_rp_inst(ri,&empty_rp);
if(orig_ret_address!=trampoline_address)
/*
*Thisistherealreturnaddress.Anyother
*instancesassociatedwiththistaskarefor
*othercallsdeeperonthecallstack
*/
break;
}
kretprobe_hash_unlock(current,&flags);
hlist_for_each_entry_safe(ri,tmp,&empty_rp,hlist){
hlist_del(&ri->hlist);
kfree(ri);
}
return(void*)orig_ret_address;
}
(4)將 lr寄存器中的trampoline地址替換為實(shí)際的 orig_ret_addr 返回地址。從x0寄存器中取出原始的返回地址,然后恢復(fù)原始函數(shù)調(diào)用??臻g,最后跳轉(zhuǎn)到原始返回地址執(zhí)行,至此函數(shù)調(diào)用的流程就回歸正常流程了,整個(gè)kretprobe探測結(jié)束。
/* *Replacetrampolineaddressinlrwithactualorig_ret_addrreturn *address. */ movlr,x0 restore_all_base_regs addsp,sp,#S_FRAME_SIZE ret
編譯運(yùn)行
insmodkprobe_example.ko vimtestfile rmmodkprobe_example.ko dmesg
成功打印出函數(shù)的執(zhí)行時(shí)間
[1056.875938]do_sys_openreturned-2andtook10500nstoexecute [1057.567400]do_sys_openreturned34andtook59208nstoexecute [1058.382932]do_sys_openreturned3andtook31469101nstoexecute [1058.567046]do_sys_openreturned34andtook61250nstoexecute [1058.975879]do_sys_openreturned3andtook224084nstoexecute [1058.975935]do_sys_openreturned3andtook16917nstoexecute [1058.976041]do_sys_openreturned3andtook13417nstoexecute [1058.976148]do_sys_openreturned3andtook15167nstoexecute [1058.976254]do_sys_openreturned3andtook15750nstoexecute [1058.976356]do_sys_openreturned3andtook16042nstoexecute [1058.978036]do_sys_openreturned-2andtook23041nstoexecute [1058.978074]do_sys_openreturned3andtook24500nstoexecute [1058.978175]do_sys_openreturned-2andtook9334nstoexecute [1058.978211]do_sys_openreturned3andtook23333nstoexecute [1058.978246]do_sys_openreturned3andtook13417nstoexecute [1058.978286]do_sys_openreturned3andtook14583nstoexecute [1058.989701]kretprobeatffffff80081ed6c8unregistered [1058.989709]Missedprobing0instancesofdo_sys_open
Kprobe-based Event Tracing
這些事件類似于基于tracepoint的事件。與Tracepoint不同,它是基于kprobes(kprobe和kretprobe)的。所以它可以探測任何kprobes可以探測的地方。與基于Tracepoint的事件不同的是,它可以動(dòng)態(tài)地添加和刪除。
要啟用這個(gè)功能,在編譯內(nèi)核時(shí)CONFIG_KPROBE_EVENTS=y
與 Event Tracing類似,這不需要通過current_tracer來激活。可以通過/sys/kernel/debug/tracing/kprobe_events添加探測點(diǎn),并通過/sys/kernel/debug/tracing/events/kprobes/
你也可以使用/sys/kernel/debug/tracing/dynamic_events,而不是kprobe_events。該接口也將提供對其他動(dòng)態(tài)事件的統(tǒng)一訪問。
Synopsis of kprobe_events
kprobe和內(nèi)核的ftrac結(jié)合使用,需要對內(nèi)核進(jìn)行配置,然后添加探測點(diǎn)、進(jìn)行探測、查看結(jié)果。
kprobe配置
CONFIG_KPROBES=y CONFIG_OPTPROBES=y CONFIG_KPROBES_ON_FTRACE=y CONFIG_UPROBES=y CONFIG_KRETPROBES=y CONFIG_HAVE_KPROBES=y CONFIG_HAVE_KRETPROBES=y CONFIG_HAVE_OPTPROBES=y CONFIG_HAVE_KPROBES_ON_FTRACE=y CONFIG_KPROBE_EVENT=y
kprobe trace events使用
kprobe事件相關(guān)的節(jié)點(diǎn)有如下:
/sys/kernel/debug/tracing/kprobe_events-----------------------配置kprobe事件屬性,增加事件之后會(huì)在kprobes下面生成對應(yīng)目錄。 /sys/kernel/debug/tracing/kprobe_profile----------------------kprobe事件統(tǒng)計(jì)屬性文件。 /sys/kernel/debug/tracing/kprobes// /enabled-------使能kprobe事件 /sys/kernel/debug/tracing/kprobes/ / /filter--------過濾kprobe事件 /sys/kernel/debug/tracing/kprobes/ / /format--------查詢kprobe事件顯示格式
kprobe事件配置
新增一個(gè)kprobe事件,通過寫kprobe_events來設(shè)置。
p[:[GRP/]EVENT][MOD:]SYM[+offs]|MEMADDR[FETCHARGS]-------------------設(shè)置一個(gè)probe探測點(diǎn) r[:[GRP/]EVENT][MOD:]SYM[+0][FETCHARGS]------------------------------設(shè)置一個(gè)returnprobe探測點(diǎn) -:[GRP/]EVENT----------------------------------------------------------刪除一個(gè)探測點(diǎn)
細(xì)節(jié)解釋如下:
GRP:Groupname.Ifomitted,use"kprobes"forit.------------設(shè)置后會(huì)在events/kprobes下創(chuàng)建目錄。 EVENT:Eventname.Ifomitted,theeventnameisgeneratedbasedonSYM+offsorMEMADDR.---指定后在events/kprobes/ 生成 目錄。MOD:ModulenamewhichhasgivenSYM.--------------------------模塊名,一般不設(shè) SYM[+offs]:Symbol+offsetwheretheprobeisinserted.-------------被探測函數(shù)名和偏移 MEMADDR:Addresswheretheprobeisinserted.----------------------指定被探測的內(nèi)存絕對地址 FETCHARGS:Arguments.Eachprobecanhaveupto128args.----------指定要獲取的參數(shù)信息。%REG:FetchregisterREG---------------------------------------獲取指定寄存器值 @ADDR:FetchmemoryatADDR(ADDRshouldbeinkernel)--------獲取指定內(nèi)存地址的值 @SYM[+|-offs]:FetchmemoryatSYM+|-offs(SYMshouldbeadatasymbol)---獲取全局變量的值$stackN:FetchNthentryofstack(N>=0)----------------------------------獲取指定??臻g值,即sp寄存器+N后的位置值 $stack:Fetchstackaddress.-----------------------------------------------獲取sp寄存器值 $retval:Fetchreturnvalue.(*)--------------------------------------------獲取返回值,用戶returnkprobe $comm:Fetchcurrenttaskcomm.----------------------------------------獲取對應(yīng)進(jìn)程名稱。 +|-offs(FETCHARG):FetchmemoryatFETCHARG+|-offsaddress.(**)-------------NAME=FETCHARG:SetNAMEastheargumentnameofFETCHARG. FETCHARG:TYPE:SetTYPEasthetypeofFETCHARG.Currently,basictypes(u8/u16/u32/u64/s8/s16/s32/s64),hexadecimaltypes (x8/x16/x32/x64),"string"andbitfieldaresupported.----------------設(shè)置參數(shù)的類型,可以支持字符串和比特類型 (*)onlyforreturnprobe. (**)thisisusefulforfetchingafieldofdatastructures.
執(zhí)行如下兩條命令就會(huì)生成目錄/sys/kernel/debug/tracing/events/kprobes/myprobe;第三條命令則可以刪除指定kprobe事件,如果要全部刪除則echo > /sys/kernel/debug/tracing/kprobe_events。
echo'p:myprobedo_sys_opendfd=%x0filename=%x1flags=%x2mode=+4($stack)'>/sys/kernel/debug/tracing/kprobe_events echo'r:myretprobedo_sys_openret=$retval'>>/sys/kernel/debug/tracing/kprobe_events-----------------------------------------------------這里面一定要用">>",不然就會(huì)覆蓋前面的設(shè)置。 echo'-:myprobe'>>/sys/kernel/debug/tracing/kprobe_eventsecho'-:myretprobe'>>/sys/kernel/debug/tracing/kprobe_events
參數(shù)后面的寄存器是跟架構(gòu)相關(guān)的,%x0、%x1、%x2表示第1/2/3個(gè)參數(shù),超出部分使用$stack來存儲(chǔ)參數(shù)。
函數(shù)返回值保存在$retval中
kprobe使能
對kprobe事件的是能通過往對應(yīng)事件的enable寫1開啟探測;寫0暫停探測。
echo>/sys/kernel/debug/tracing/trace echo'p:myprobedo_sys_opendfd=%x0filename=%x1flags=%x2mode=+4($stack)'>/sys/kernel/debug/tracing/kprobe_events echo'r:myretprobedo_sys_openret=$retval'>>/sys/kernel/debug/tracing/kprobe_events echo1>/sys/kernel/debug/tracing/events/kprobes/myprobe/enable echo1>/sys/kernel/debug/tracing/events/kprobes/myretprobe/enable ls echo0>/sys/kernel/debug/tracing/events/kprobes/myprobe/enable echo0>/sys/kernel/debug/tracing/events/kprobes/myretprobe/enable cat/sys/kernel/debug/tracing/trace
然后在/sys/kernel/debug/tracing/trace中可以看到結(jié)果。

總結(jié)
附錄
ARM32,ARM64,X86寄存器及訪問方式
ARM32
"r0",pt_regs->r0 "r1",pt_regs->r1 "r2",pt_regs->r2 "r3",pt_regs->r3 "r4",pt_regs->r4 "r5",pt_regs->r5 "r6",pt_regs->r6 "r7",pt_regs->r7 "r8",pt_regs->r8 "r9",pt_regs->r9 "r10",pt_regs->r10 "fp",pt_regs->fp "ip",pt_regs->ip "sp",pt_regs->sp "lr",pt_regs->lr "pc",pt_regs->pc
ARM64
"x0",pt_regs->regs[0] "x1",pt_regs->regs[1] "x2",pt_regs->regs[2] "x3",pt_regs->regs[3] "x4",pt_regs->regs[4] "x5",pt_regs->regs[5] "x6",pt_regs->regs[6] "x7",pt_regs->regs[7] "x8",pt_regs->regs[8] "x9",pt_regs->regs[9] "x10",pt_regs->regs[10] "x11",pt_regs->regs[11] "x12",pt_regs->regs[12] "x13",pt_regs->regs[13] "x14",pt_regs->regs[14] "x15",pt_regs->regs[15] "x16",pt_regs->regs[16] "x17",pt_regs->regs[17] "x18",pt_regs->regs[18] "x19",pt_regs->regs[19] "x20",pt_regs->regs[20] "x21",pt_regs->regs[21] "x22",pt_regs->regs[22] "x23",pt_regs->regs[23] "x24",pt_regs->regs[24] "x25",pt_regs->regs[25] "x26",pt_regs->regs[26] "x27",pt_regs->regs[27] "x28",pt_regs->regs[28] "x29",pt_regs->regs[29] "x30",pt_regs->regs[30] "sp",pt_regs->sp "pc",pt_regs->pc "pstate",pt_regs->pstate
X86
raxpt_regs->ax rcxpt_regs->cx rdxpt_regs->cx rbxpt_regs->bx rsppt_regs->sp rbppt_regs->bp rdipt_regs->di rsipt_regs->si r8pt_regs->r8 r9pt_regs->r9 r10pt_regs->r10 r11pt_regs->r11 r12pt_regs->r12 r13pt_regs->r13 r14pt_regs->r14 r15pt_regs->r15
審核編輯:湯梓紅
-
ARM
+關(guān)注
關(guān)注
135文章
9498瀏覽量
388417 -
內(nèi)核
+關(guān)注
關(guān)注
4文章
1436瀏覽量
42470 -
調(diào)試
+關(guān)注
關(guān)注
7文章
623瀏覽量
35372 -
源碼
+關(guān)注
關(guān)注
8文章
682瀏覽量
31063 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4405瀏覽量
66794
原文標(biāo)題:【調(diào)試】kprobes(二)使用方法
文章出處:【微信號(hào):嵌入式與Linux那些事,微信公眾號(hào):嵌入式與Linux那些事】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
示波器的使用方法
Matlab使用方法和程序設(shè)計(jì)
示波器的使用方法(三):示波器的使用方法詳解
ORCADCapture_使用方法與概念

kprobes的使用方法
評論