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