加入星計劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴散
  • 作品版權(quán)保護
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 簡介
    • 原理
    • 如何優(yōu)化kprobes
    • kprobes黑名單
    • 架構(gòu)支持
    • 配置Kprobes
    • API參考
    • kprobes的特性和限制
    • The kprobes debugfs interface
    • The kprobes sysctl interface
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

【調(diào)試】kprobes(一)基本概念

2023/04/17
1491
閱讀需 39 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

簡介

開發(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_handlerbreak_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_handlerkp->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";
    1. 如果探測點的符號的偏移量是已知的,則可以使用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

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

本公眾號專注原創(chuàng)電子軟硬件相關(guān)教程,希望這些教程能夠幫助到大家!