簡(jiǎn)介
開發(fā)人員在內(nèi)核或者模塊的調(diào)試過程中,往往會(huì)需要要知道其中的一些函數(shù)有無(wú)被調(diào)用、何時(shí)被調(diào)用、執(zhí)行是否正確以及函數(shù)的入?yún)⒑头祷刂凳鞘裁吹鹊取?/p>
比較簡(jiǎn)單的做法是在內(nèi)核代碼對(duì)應(yīng)的函數(shù)中添加日志打印信息,但這種方式往往需要重新編譯內(nèi)核或模塊,重新啟動(dòng)設(shè)備之類的,操作較為復(fù)雜甚至可能會(huì)破壞原有的代碼執(zhí)行過程。
而利用kprobes技術(shù),用戶可以定義自己的回調(diào)函數(shù),然后在內(nèi)核或者模塊中幾乎所有的函數(shù)中動(dòng)態(tài)的插入探測(cè)點(diǎn),當(dāng)內(nèi)核執(zhí)行流程執(zhí)行到指定的探測(cè)函數(shù)時(shí),會(huì)調(diào)用該回調(diào)函數(shù),用戶即可收集所需的信息了,同時(shí)內(nèi)核最后還會(huì)回到原本的正常執(zhí)行流程。
如果用戶已經(jīng)收集足夠的信息,不再需要繼續(xù)探測(cè),則同樣可以動(dòng)態(tài)的移除探測(cè)點(diǎn)。因此kprobes技術(shù)具有對(duì)內(nèi)核執(zhí)行流程影響小和操作方便的優(yōu)點(diǎn)。
kprobes技術(shù)包括3種探測(cè)手段分別是kprobe、jprobe和kretprobe。
首先kprobe是最基本的探測(cè)方式,是實(shí)現(xiàn)后兩種的基礎(chǔ),它可以在任意的位置放置探測(cè)點(diǎn)(就連函數(shù)內(nèi)部的某條指令處也可以),它提供了探測(cè)點(diǎn)的調(diào)用前、調(diào)用后和內(nèi)存訪問出錯(cuò)3種回調(diào)方式,分別是pre_handler、post_handler和fault_handler,其中pre_handler函數(shù)將在被探測(cè)指令被執(zhí)行前回調(diào),post_handler會(huì)在被探測(cè)指令執(zhí)行完畢后回調(diào)(注意不是被探測(cè)函數(shù)),fault_handler會(huì)在內(nèi)存訪問出錯(cuò)時(shí)被調(diào)用;
jprobe基于kprobe實(shí)現(xiàn),它用于獲取被探測(cè)函數(shù)的入?yún)⒅怠?/p>
最后kretprobe從名字就可以看出其用途了,它同樣基于kprobe實(shí)現(xiàn),用于獲取被探測(cè)函數(shù)的返回值。
原理
kprobe的實(shí)現(xiàn)原理是把指定地址(探測(cè)點(diǎn))的指令替換成一個(gè)可以讓cpu進(jìn)入debug模式的指令,使執(zhí)行路徑暫停,跳轉(zhuǎn)到probe 處理函數(shù)后收集、修改信息,再跳轉(zhuǎn)回來(lái)繼續(xù)執(zhí)行。
X86中使用的是int3指令,ARM64中使用的是BRK指令進(jìn)入debug monitor模式。
kprobe 工作原理
當(dāng)一個(gè)kprobe被注冊(cè)時(shí),Kprobes會(huì)復(fù)制一個(gè)被探測(cè)的指令的副本,并將被探測(cè)指令的第一個(gè)字節(jié)替換為替換為斷點(diǎn)指令(例如,i386和x86_64上的int3,ARM64的BRK指令)。
當(dāng)CPU碰到斷點(diǎn)指令時(shí),就會(huì)發(fā)生一個(gè)trap,CPU的寄存器被保存起來(lái),控制權(quán)通過 "通知器調(diào)用鏈 "(notifier_call_chain )傳遞給Kprobes。Kprobes會(huì)執(zhí)行 "pre_handler",將處理程序的地址傳遞給 kprobe結(jié)構(gòu)和保存的寄存器的地址。
接下來(lái),kprobes會(huì)對(duì)剛剛復(fù)制的指令進(jìn)行單步操作。在執(zhí)行單步指令后,會(huì)調(diào)用post_handler,然后繼續(xù)執(zhí)行探測(cè)點(diǎn)之后的指令。
jprobe工作原理
jprobe是基于kprobe實(shí)現(xiàn)的,探測(cè)的位置在函數(shù)的入口點(diǎn)。它采用了一個(gè)簡(jiǎn)單的鏡像原則來(lái)允許無(wú)縫訪問被探測(cè)函數(shù)的參數(shù)。
jprobe程序的參數(shù)和返回值必須和被探測(cè)函數(shù)完全相同,并且必須始終以調(diào)用函數(shù)jprobe_return()
作為返回值。
它的工作原理是這樣的:
當(dāng)探針被命中時(shí),Kprobes會(huì)復(fù)制一個(gè)保存的寄存器和堆棧的一部分。然后Kprobes將保存的指令指針指向jprobe的處理程序,并從trap中返回, 控制權(quán)傳遞給處理程序,而處理程序的寄存器和堆棧內(nèi)容與被探測(cè)函數(shù)相同。
當(dāng)它處理完成后,處理程序調(diào)用jprobe_return(),再次捕獲以恢復(fù)原來(lái)的堆棧內(nèi)容和處理器狀態(tài)并切換到內(nèi)容和處理器狀態(tài),并切換到被探測(cè)函數(shù)。
注意,被探測(cè)的函數(shù)的args可以在堆棧中,也可以在寄存器中。jprobe在這兩種情況下都可以工作,只要處理程序的原型與被探測(cè)函數(shù)的原型一致即可。
kretprobes工作原理
當(dāng)調(diào)用register_kretprobe()
時(shí),Kprobes在函數(shù)的入口處建立了一個(gè)kprobe。當(dāng)被探測(cè)的函數(shù)被調(diào)用時(shí),Kprobes會(huì)保存一份返回地址的副本,并將返回地址替換為 "trampoline "的地址。
trampoline是一段任意的代碼——通常只是一條nop指令。在啟動(dòng)時(shí),Kprobes在trampoline上注冊(cè)了一個(gè)kprobe。
當(dāng)被探測(cè)的函數(shù)執(zhí)行其返回指令時(shí),控制權(quán)傳遞給trampoline。該探針被擊中。Kprobes的trampoline處理程序調(diào)用用戶指定的與Kretprobe相關(guān)的返回處理程序,然后將保存的指令指針設(shè)置為保存的返回地址。這就是從trap返回時(shí)恢復(fù)執(zhí)行的地方。
當(dāng)被探測(cè)的函數(shù)正在執(zhí)行時(shí),它的返回地址被存儲(chǔ)在一個(gè)kretprobe_instance
類型的對(duì)象中。
在調(diào)用register_kretprobe()
之前,用戶設(shè)置kretprobe
結(jié)構(gòu)的maxactive
字段,以指定可以同時(shí)探測(cè)多少個(gè)指定函數(shù)的實(shí)例。register_kretprobe()
預(yù)先分配了指定數(shù)量的kretprobe_instance
對(duì)象。
例如,如果該函數(shù)是非遞歸的,并且在調(diào)用時(shí)持有一個(gè)自旋鎖的情況下,maxactive = 1
就足夠了。
如果該函數(shù)是非遞歸,并且永遠(yuǎn)占有CPU(例如,通過semaphore 或搶占),NR_CPUS
應(yīng)該足夠了。
默認(rèn)值為maxactive <= 0
。如果CONFIG_PREEMPT
被啟用,默認(rèn)的是max(10, 2*NR_CPUS)
。否則,默認(rèn)值是NR_CPUS
。
如果你把maxactive
設(shè)置得太低,也沒有什么問題,只是會(huì)錯(cuò)過 一些probe。
在kretprobe結(jié)構(gòu)中,nmissed
字段在注冊(cè)返回的探針時(shí)被設(shè)置為零,并且在每次進(jìn)入探針函數(shù)但沒有探針的情況下,nmissed
字段都會(huì)被增加。被探測(cè)的函數(shù)被輸入但沒有kretprobe_instance
對(duì)象可用于建立返回探針。
Kretprobe entry-handler
Kretprobes還提供了一個(gè)可選的用戶指定的處理程序,在函數(shù)輸入時(shí)運(yùn)行。這個(gè)處理程序是通過設(shè)置kretprobe結(jié)構(gòu)的 entry_handler
字段來(lái)指定的。
每當(dāng)由kretprobe放置在 函數(shù)入口處的kprobe被擊中時(shí),用戶定義的entry_handler
(如果有的話)被調(diào)用。
如果entry_handler
返回0(成功),那么保證在函數(shù)返回時(shí)調(diào)用一個(gè)相應(yīng)的返回處理程序。
如果entry_handler
返回一個(gè)非零的錯(cuò)誤,那么Kprobes將返回地址保持原樣。
此外,用戶 也可以指定每個(gè)返回實(shí)例的私有數(shù)據(jù)作為每個(gè)~ kretprobe_instance ~對(duì)象的一部分。當(dāng)在相應(yīng)的用戶入口和返回處理之間共享私有數(shù)據(jù)時(shí),這一點(diǎn)特別有用 。每個(gè)私有數(shù)據(jù)對(duì)象的大小可以在kretprobe注冊(cè)時(shí)通過設(shè)置kretprobe結(jié)構(gòu)的data_size
字段指定。這些數(shù)據(jù)可以通過每個(gè)kretprobe_instance
對(duì)象的data字段來(lái)訪問。
如果被探測(cè)的函數(shù)被輸入,但沒有可用的kretprobe_instance對(duì)象,那么除了增加nmissed計(jì)數(shù)外。entry_handler
程序的調(diào)用也會(huì)被跳過。
如何優(yōu)化kprobes
如果你的內(nèi)核編譯選項(xiàng)CONFIG_OPTPROBES=y
并且 debug.kprobes_optimization
內(nèi)核參數(shù)設(shè)置為1,Kprobes試圖通過在每個(gè)探測(cè)點(diǎn)使用跳轉(zhuǎn)指令而不是斷點(diǎn)指令來(lái)減少探測(cè)命中的開銷。
Init a Kprobe
當(dāng)一個(gè)probe被注冊(cè)時(shí),在嘗試這種優(yōu)化之前,Kprobes會(huì)在指定的地址插入一個(gè)普通的、基于斷點(diǎn)的kprobe。
因此,即使不可能對(duì)這個(gè)特定的探測(cè)點(diǎn)進(jìn)行優(yōu)化,那里也會(huì)有一個(gè)探測(cè)。
Safety Check
在優(yōu)化一個(gè)probe之前,Kprobes會(huì)進(jìn)行以下安全檢查。
Kprobes將會(huì)驗(yàn)證將被跳轉(zhuǎn)指令替換的區(qū)域("優(yōu)化區(qū)域")是否完全在一個(gè)函數(shù)內(nèi)。(一條跳轉(zhuǎn)指令是多個(gè)字節(jié),所以可能會(huì)疊加多個(gè) 指令)。
Kprobes分析整個(gè)函數(shù)并驗(yàn)證是否有 跳轉(zhuǎn)到優(yōu)化區(qū)域。比如:
該函數(shù)不包含間接跳轉(zhuǎn)。
該函數(shù)不包含導(dǎo)致異常的指令(因?yàn)橛僧惓S|發(fā)的修復(fù)代碼可以跳回優(yōu)化區(qū)域——Kprobes檢查異常表以驗(yàn)證這一點(diǎn))。
沒有跳轉(zhuǎn)到優(yōu)化區(qū)域(除了到第一個(gè) 字節(jié))。
- 對(duì)于優(yōu)化區(qū)域中的每一條指令,Kprobes都會(huì)驗(yàn)證該指令是否可以被越級(jí)執(zhí)行。
Preparing Detour Buffer
接下來(lái),Kprobes準(zhǔn)備了一個(gè)"detour" buffer,其中包含以下 指令序列。
- 推進(jìn)CPU寄存器的代碼(模擬斷點(diǎn)trap)調(diào)用trampoline代碼,調(diào)用用戶的探測(cè)處理程序。恢復(fù)寄存器的代碼執(zhí)行來(lái)自優(yōu)化區(qū)域的指令跳回原來(lái)的執(zhí)行路徑。
Pre-optimization
在準(zhǔn)備好"detour" buffer后,Kprobes會(huì)驗(yàn)證以下情況是否存在。
- probe是否有break_handler(即,它是一個(gè)jprobe)或apost_handler。優(yōu)化區(qū)域內(nèi)的其他指令被探測(cè)到。probe被禁用。
在上述任何一種情況下,Kprobes都不會(huì)優(yōu)化probe。因?yàn)檫@些都是暫時(shí)的情況,如果情況改變了,Kprobes會(huì)嘗試重新開始優(yōu)化它。
如果kprobe可以被優(yōu)化,Kprobes將kprobe排到優(yōu)化列表中,并啟動(dòng)kprobe-optimizer
的工作隊(duì)列來(lái)優(yōu)化它。
如果待優(yōu)化的探測(cè)點(diǎn)在被優(yōu)化前被命中,Kprobes通過將CPU的指令指針設(shè)置為"detour" buffer中復(fù)制的代碼,將控制權(quán)返回到原始指令路徑中——這樣至少可以避免單步。
Optimization
Kprobe-optimizer
并沒有立即插入跳轉(zhuǎn)指令;相反,為了安全起見,它首先調(diào)用synchronize_sched()
,因?yàn)镃PU有可能在執(zhí)行優(yōu)化區(qū)域(*)的過程中被中斷。
synchronize_sched()
可以確保所有在調(diào)用synchronize_sched()
時(shí)處于活動(dòng)狀態(tài)的中斷被完成,但前提是CONFIG_PREEMPT=n
。
所以,這個(gè)版本的kprobe優(yōu)化只支持CONFIG_PREEMPT=n
的內(nèi)核。
之后,Kprobe-optimizer
調(diào)用stop_machine()
,用text_poke_smp()
的跳轉(zhuǎn)指令替換優(yōu)化后的區(qū)域到"detour" buffer。
Unoptimization
當(dāng)一個(gè)已優(yōu)化的kprobe被取消注冊(cè)、禁用或被另一個(gè)kprobe阻止時(shí),它將被取消優(yōu)化。
如果這種情況發(fā)生在優(yōu)化完成之前,該kprobe只是從優(yōu)化列表中將其刪除。如果優(yōu)化已經(jīng)完成,通過使用text_poke_smp()
,跳轉(zhuǎn)被替換成原始代碼(除了第一個(gè)字節(jié)的int3斷點(diǎn))。
請(qǐng)想象一下,第2條指令被中斷,然后優(yōu)化器在中斷處理程序運(yùn)行時(shí)用跳轉(zhuǎn)的地址替換了第2條指令。當(dāng)中斷返回到原始地址時(shí),沒有有效的指令,這就造成了一個(gè)意外的結(jié)果。
這種優(yōu)化安全檢查可以用ksplice用于支持CONFIG_PREEMPT=y
的stop-machine方法取代內(nèi)核。
注意:
跳躍優(yōu)化改變了kprobe的pre_handler
行為。在沒有優(yōu)化的情況下,pre_handler
可以通過改變regs->ip
并返回1來(lái)改變內(nèi)核的執(zhí)行路徑。
然而,當(dāng)probe被優(yōu)化時(shí),這種修改會(huì)被忽略。因此,如果你想調(diào)整內(nèi)核的執(zhí)行路徑,你需要抑制優(yōu)化,可以使用以下方法。
為kprobe的post_handler
或break_handler
指定一個(gè)空函數(shù)。
執(zhí)行sysctl -w debug.kprobes_optimization=n
'。
kprobes黑名單
Kprobes可以探測(cè)除自己以外的大部分內(nèi)核。這意味著有一些函數(shù)是Kprobes不能探測(cè)的。
探測(cè)這些函數(shù)可能會(huì)導(dǎo)致遞歸trap或嵌套的探測(cè)處理程序可能永遠(yuǎn)不會(huì)被調(diào)用。Kprobes將這類函數(shù)作為黑名單來(lái)管理。如果你想把一個(gè)函數(shù)加入黑名單,你只需要
包括linux/kprobes.h
使用NOKPROBE_SYMBOL()
宏來(lái)指定一個(gè)黑名單上的函數(shù)。Kprobes根據(jù)黑名單檢查給定的探測(cè)地址,如果給定的地址在黑名單中,則拒絕注冊(cè)。
架構(gòu)支持
Kprobes, jprobes, and kretprobes 支持以下架構(gòu):
i386 (Supports jump optimization)
x86_64 (AMD-64, EM64T) (Supports jump optimization)
ppc64
ia64 (Does not support probes on instruction slot1.)
sparc64 (Return probes not yet implemented.)
arm
ppc
mips
s390
配置Kprobes
CONFIG_KPROBES?=?y
CONFIG_MODULES?=?y
CONFIG_MODULE_UNLOAD?=?y
CONFIG_KALLSYMS_ALL?=?y
CONFIG_DEBUG_INFO?=?y
API參考
register_kprobe
#include?<linux/kprobes.h>
int?register_kprobe(struct?kprobe?*kp);
在地址kp->addr
處設(shè)置一個(gè)斷點(diǎn)。當(dāng)斷點(diǎn)被命中時(shí),Kprobes調(diào)用kp->pre_handler
。
在被探測(cè)的指令被單步執(zhí)行后,Kprobe調(diào)用kp->post_handler
。
如果在執(zhí)行kp->pre_handler
或kp->post_handler
的過程中,或者在被探測(cè)指令的單步執(zhí)行過程中發(fā)生故障,Kprobes會(huì)調(diào)用kp->fault_handler
。
所有的處理程序都可以設(shè)置成NULL。如果kp->flags被
設(shè)置為KPROBE_FLAG_DISABLED
,該kp將被注冊(cè)但被禁用。
因此,在調(diào)用enable_kprobe(kp)
之前,其處理程序不會(huì)被擊中。
注意:
- 隨著 "symbol_name "字段被引入到kprobe結(jié)構(gòu)中,探測(cè)點(diǎn)的地址解析現(xiàn)在將由內(nèi)核來(lái)處理了,具體如下所示:
kp.symbol_name?=?"symbol_name";
-
- 如果探測(cè)點(diǎn)的符號(hào)的偏移量是已知的,則可以使用kprobe結(jié)構(gòu)的 "offset "字段,這個(gè)字段用于計(jì)算 探測(cè)點(diǎn)。kprobe的 "symbol_name "或 "addr"只能指定一個(gè)。如果兩者都被指定,kprobe注冊(cè)將以-EINVAL失敗。對(duì)于CISC架構(gòu)(如i386和x86_64),kprobes代碼 不會(huì)驗(yàn)證
kprobe.addr
- 是否在指令邊界上。謹(jǐn)慎地使用 "offset"。
register_kprobe()
成功時(shí)返回0,否則返回負(fù)的errno。
pre-handler?(kp->pre_handler)
#include?<linux/kprobes.h>
#include?<linux/ptrace.h>
int?pre_handler(struct?kprobe?*p,?struct?pt_regs?*regs);
pre-handler
被調(diào)用時(shí),p指向與斷點(diǎn)相關(guān)的kprobe。而regs則是指向包含了斷點(diǎn)時(shí)保存的寄存器的結(jié)構(gòu)。
post-handler?(kp->post_handler):
#include?<linux/kprobes.h>
#include?<linux/ptrace.h>
void?post_handler(struct?kprobe?*p,?struct?pt_regs?*regs,unsigned?long?flags);
p和regs與pre_handler的描述相同。flags一般為零。
fault-handler?(kp->fault_handler):
#include?<linux/kprobes.h>
#include?<linux/ptrace.h>
int?fault_handler(struct?kprobe?*p,?struct?pt_regs?*regs,?int?trapnr);
p和regs與pre_handler的描述相同。如果成功地處理了該異常,則返回1。
register_jprobe
#include?<linux/kprobes.h>
int?register_jprobe(struct?jprobe?*jp)
在地址jp->kp.addr
處設(shè)置一個(gè)斷點(diǎn),該地址必須是一個(gè)函數(shù)的第一條指令的地址。當(dāng)斷點(diǎn)被命中時(shí),Kprobes運(yùn)行地址為jp->entry
的處理程序。
處理程序應(yīng)該有與被探測(cè)函數(shù)相同的參數(shù)列表和返回類型;在它返回之前,必須調(diào)用jprobe_return()
。(處理程序?qū)嶋H上從未返回,因?yàn)?code>jprobe_return()將控制權(quán)返回給Kprobes。)
如果被探測(cè)的函數(shù)被聲明為amlinkage或其他影響args傳遞方式的東西,處理程序的聲明必須與之匹配。
register_jprobe()
成功時(shí)返回0,否則返回負(fù)的errno。
register_kretprobe
#include?<linux/kprobes.h>
int?register_kretprobe(struct?kretprobe?*rp);
為地址為rp->kp.addr
的函數(shù)建立一個(gè)返回探針。當(dāng)該函數(shù)返回時(shí),Kprobes調(diào)用rp->handler
。你必須在調(diào)用rp->maxactive
之前適當(dāng)?shù)卦O(shè)置rp->maxactive
。
register_kretprobe()
成功時(shí)返回0,否則返回一個(gè)負(fù)的errno
return-probe?handler?(rp->handler):
#include?<linux/kprobes.h>
#include?<linux/ptrace.h>
int?kretprobe_handler(struct?kretprobe_instance?*ri,?struct?pt_regs?*regs);
regs與kprobe.pre_handler
的描述相同。ri 指針指向kretprobe_instance
對(duì)象,kretprobe_instance
包含以下字段
- ret_addr:返回地址rp:指向相應(yīng)的kretprobe對(duì)象task:指向相應(yīng)的任務(wù)結(jié)構(gòu)data:指向每個(gè)返回實(shí)例的私有數(shù)據(jù);
regs_return_value(regs)
宏提供了一個(gè)簡(jiǎn)單的抽象,用于 從適當(dāng)?shù)募拇嫫髦刑崛》祷刂怠?/p>
unregister_*probe
#include?<linux/kprobes.h>
void?unregister_kprobe(struct?kprobe?*kp);
void?unregister_jprobe(struct?jprobe?*jp);
void?unregister_kretprobe(struct?kretprobe?*rp);
移除指定的probe。取消注冊(cè)函數(shù)可以在probe被注冊(cè)后的任何時(shí)候被調(diào)用。
注意:
如果這些函數(shù)發(fā)現(xiàn)一個(gè)不正確的probe(例如,一個(gè)未注冊(cè)的probe)。它們會(huì)清除probe的addr字段。
register_*probes
#include?<linux/kprobes.h>
int?register_kprobes(struct?kprobe?**kps,?int?num);
int?register_kretprobes(struct?kretprobe?**rps,?int?num);
int?register_jprobes(struct?jprobe?**jps,?int?num);
注冊(cè)指定數(shù)組中的每一個(gè)n個(gè)probe。如果在注冊(cè)過程中發(fā)生任何 錯(cuò)誤,直到 在register_*probes
函數(shù)返回之前,數(shù)組中的所有probe,都可以被安全地取消注冊(cè)。
- kps/rps/jps:指向*探針數(shù)據(jù)結(jié)構(gòu)的指針陣列num:數(shù)組條目的數(shù)量。
在使用這些函數(shù)之前,你必須分配(或定義)一個(gè)指針數(shù)組并設(shè)置所有的數(shù)組條目。
unregister_*probes
#include?<linux/kprobes.h>
void?unregister_kprobes(struct?kprobe?**kps,?int?num);
void?unregister_kretprobes(struct?kretprobe?**rps,?int?num);
void?unregister_jprobes(struct?jprobe?**jps,?int?num);
一次性刪除指定數(shù)組中的num 個(gè)probe 。
注意:
如果這些函數(shù)在指定的數(shù)組中發(fā)現(xiàn)一些不正確的probe(例如:未注冊(cè)的probe),它們會(huì)清除這些不正確probe的addr字段。但是,數(shù)組中的其他probe會(huì)被正確地取消注冊(cè)。
disable_*probe
#include?<linux/kprobes.h>
int?disable_kprobe(struct?kprobe?*kp);
int?disable_kretprobe(struct?kretprobe?*rp);
int?disable_jprobe(struct?jprobe?*jp);
暫時(shí)停用指定的probe(已經(jīng)注冊(cè)的)。你可以通過使用 enable_*probe()
再次使能。
enable_*probe
#include?<linux/kprobes.h>
int?enable_kprobe(struct?kprobe?*kp);
int?enable_kretprobe(struct?kretprobe?*rp);
int?enable_jprobe(struct?jprobe?*jp);
啟用已經(jīng)被disable_*probe()
禁用的*probe
(probe必須已經(jīng)被注冊(cè))。
kprobes的特性和限制
Kprobes允許在同一地址有多個(gè)probe。但是,目前在同一個(gè)函數(shù)上不能同時(shí)有多個(gè)jprobes。
另外,一個(gè)有jprobe或post_handler
的探測(cè)點(diǎn)不能被優(yōu)化。因此,如果你在一個(gè)已優(yōu)化的探測(cè)點(diǎn)上安裝一個(gè)jprobe,或者一個(gè)帶有post_handler
的kprobe,那么該探測(cè)點(diǎn)將自動(dòng)被取消優(yōu)化。
一般來(lái)說(shuō),你可以在內(nèi)核的任何地方安裝一個(gè)探針,也可以探測(cè)中斷處理程序。
如果你試圖在實(shí)現(xiàn)Kprobes的代碼中安裝一個(gè)probe,register_*probe
函數(shù)將返回-EINVAL
如果你在一個(gè)可內(nèi)聯(lián)的函數(shù)中安裝一個(gè)probe,Kprobes不會(huì)試圖追尋所有內(nèi)聯(lián)函數(shù)的實(shí)例并在那里安裝probe。gcc可能不經(jīng)詢問就內(nèi)聯(lián)一個(gè)函數(shù),所以如果你沒有看到你期望的探測(cè)結(jié)果,請(qǐng)記住這一點(diǎn)。
probe處理程序可以修改被探測(cè)函數(shù)的環(huán)境——例如,通過修改內(nèi)核數(shù)據(jù)結(jié)構(gòu),或通過修改pt_regs
結(jié)構(gòu)的內(nèi)容(從斷點(diǎn)返回時(shí),這些內(nèi)容會(huì)被恢復(fù)到寄存器中)。
因此,Kprobes可以被用來(lái)安裝一個(gè)錯(cuò)誤修復(fù)程序或注入故障進(jìn)行測(cè)試。當(dāng)然,Kprobes沒有辦法區(qū)分故意注入的故障和意外的故障。
Kprobes沒有試圖防止probe處理程序相互影響——例如,探測(cè)printk()
,然后從探測(cè)處理程序中調(diào)用printk()
。如果一個(gè)probe處理程序碰到了一個(gè)probe,第二個(gè)probe的處理程序就不會(huì)在該實(shí)例中運(yùn)行,第二個(gè)probe的kprobe.nmissed
成員將被遞增。
Kprobes不使用互斥,也不分配內(nèi)存,除非在注冊(cè)和取消注冊(cè)時(shí)。
probe處理程序在運(yùn)行時(shí)禁止搶占。根據(jù)架構(gòu)和優(yōu)化狀態(tài),處理程序也可能在禁用中斷的情況下運(yùn)行(例如,kretprobe處理程序和優(yōu)化的kprobe處理程序在x86/x86-64上運(yùn)行時(shí)沒有禁用中斷)。
由于return probe是通過用trampoline的地址替換返回地址來(lái)實(shí)現(xiàn)的,因此,堆?;厮莺驼{(diào)用 __builtin_return_address()
通常會(huì)得到trampoline的地址,而不是kretprobed函數(shù)的真正返回地址。
如果一個(gè)函數(shù)被調(diào)用的次數(shù)與它返回的次數(shù)不一致,在該函數(shù)上注冊(cè)一個(gè)return probe可能會(huì)產(chǎn)生不理想的結(jié)果。在這種情況下,有一行異常信息被打印出來(lái)。有了這些信息,我們就能確定導(dǎo)致問題的kretprobe的確切實(shí)例。
kretprobe BUG!。Processing kretprobe d000000000041aa8 @ c00000000004f48c
如果在進(jìn)入或退出一個(gè)函數(shù)時(shí),CPU運(yùn)行在當(dāng)前任務(wù)以外的堆棧中,在該函數(shù)上注冊(cè)一個(gè)return probe可能會(huì)產(chǎn)生不好的結(jié)果。由于這個(gè)原因,Kprobes在x86_64版本的__switch_to()
上不支持返回探針(或kprobes或jprobes)。
在x86/x86-64上,由于Kprobes的跳轉(zhuǎn)優(yōu)化對(duì)指令進(jìn)行了廣泛的修改,所以對(duì)優(yōu)化有一些限制。想象一下,一個(gè)由兩條2字節(jié)指令和一條3字節(jié)指令組成的3條指令序列。
IA
|
[-2][-1][0][1][2][3][4][5][6][7]
[ins1][ins2][ ins3 ]
[<- DCR ->]
[<- JTPR ->]
- ins1: 第1條指令ins2: 第二條指令ins3: 第3條指令I(lǐng)A:插入地址JTPR: 跳躍目標(biāo)禁止區(qū)DCR:"detour" buffer
DCR中的指令被復(fù)制到kprobe的"detour" buffer,因?yàn)镈CR中的字節(jié)被一個(gè)5字節(jié)的跳轉(zhuǎn)指令所取代。所以有幾個(gè)限制。
- DCR中的指令必須是可重定位的。DCR中的指令必須不包括調(diào)用指令。JTPR不能成為任何跳轉(zhuǎn)或調(diào)用指令的目標(biāo)。DCR不能跨過函數(shù)之間的邊界。
總之,這些限制由內(nèi)核內(nèi)指令解碼器檢查,所以你不需要擔(dān)心這個(gè)。
The kprobes debugfs interface
在最近的內(nèi)核(>2.6.20)中,注冊(cè)的kprobes列表在/sys/kernel/debug/kprobes/
目錄下(假設(shè)debugfs被安裝在/sys/kernel/debug
)。
/sys/kernel/debug/kprobes/list
: 列出系統(tǒng)中所有注冊(cè)的探針
c015d71a??k??vfs_read+0x0
c011a316??j??do_fork+0x0
c03dedc5??r??tcp_v4_rcv+0x0
第一列提供了插入探針的內(nèi)核地址。
第二列是探針的類型(k-kprobe,r-kretprobe和j-jprobe),
第三列是探針的符號(hào)+offset。
如果被探測(cè)的函數(shù)屬于一個(gè)模塊,模塊名稱也被指定。下面幾欄顯示探針狀態(tài)。
如果probe是在一個(gè)不再有效的虛擬地址(模塊初始部分,模塊虛擬地址對(duì)應(yīng)于已經(jīng)被卸載的模塊)。這種探針會(huì)被標(biāo)記為[GONE]。
如果probe被暫時(shí)禁用,這樣的probe被標(biāo)記為[DISABLED]。
如果probe被優(yōu)化了,它被標(biāo)記為[OPTIMIZED]。如果probe是基于ftrace的,它被標(biāo)記為[FTRACE]。
/sys/kernel/debug/kprobes/enabled
。強(qiáng)制打開/關(guān)閉kprobes。提供一個(gè)開關(guān)來(lái)全局地強(qiáng)制打開或關(guān)閉注冊(cè)的kprobes。默認(rèn)情況下,所有的kprobes都被啟用。
通過向該文件echo 0
,所有注冊(cè)的probe將被解除,直到向該文件echo 1
。
注意,這個(gè)開關(guān)只是解除所有kprobes,并不改變每個(gè)probe的禁用狀態(tài)。這意味著,如果你用這個(gè)開關(guān)打開所有的kprobes,被禁用的kprobes(標(biāo)記為[DISABLED])將不會(huì)被啟用。
The kprobes sysctl interface
/proc/sys/debug/kprobes-optimization
。打開/關(guān)閉kprobes優(yōu)化。
當(dāng)CONFIG_OPTPROBES=y
時(shí),這個(gè)sysctl界面就會(huì)出現(xiàn),它提供了一個(gè)全局性的、強(qiáng)行打開或關(guān)閉跳轉(zhuǎn)優(yōu)化的開關(guān)。它提供了一個(gè)開關(guān)來(lái)全局地強(qiáng)制打開或關(guān)閉跳轉(zhuǎn)優(yōu)化。默認(rèn)情況下,跳轉(zhuǎn)優(yōu)化是允許的(ON)。
如果你在這個(gè)文件中echo 0
或者通過sysctl將debug.kprobes_optimization
設(shè)置為0,所有已優(yōu)化的探針將不被優(yōu)化,此后注冊(cè)的任何新探針都不會(huì)被優(yōu)化。
注意,這個(gè)開關(guān)改變了優(yōu)化狀態(tài)。這意味著已優(yōu)化的probe(標(biāo)記為[OPTIMIZED])將被取消優(yōu)化([OPTIMIZED]標(biāo)簽將被刪除)。如果這個(gè)開關(guān)被打開,它們將再次被優(yōu)化。
本文參考
https://blog.csdn.net/luckyapple1028/article/details/52972315
https://www.kernel.org/doc/html/latest/trace/kprobes.html
https://blog.csdn.net/luckyapple1028/article/details/52972315
https://www.cnblogs.com/hpyu/p/14257305.html
kprobes.txt