Kprobes 應用實例分析:從源碼到調試方法全面解讀
一、Kprobe 簡單介紹
kprobes Kretprobes 是 linux 系統的一個動態調試機制,使用它可以向內核添加探針(Probe),在代碼執行前或執行後觸發一個回調函數。這個機制通常用於調試內核代碼,跟蹤應用程序執行或收集性能統計信息。通過使用 kprobe,開發人員可以在不影響系統運行邏輯的情況下,對操作系統進行深入的分析和調試。
它的基本工作機制是:用戶指定一個探測點。並把一個用戶定義的處理函數關聯到該探測點。當內核運行到該探測點時,對應的關聯函數被運行。然後繼續運行正常的代碼路徑。
kprobe 實現了三種類型的探測點: kprobes, jprobes 和 kretprobes (也叫返回探測點)。
kprobes 是能夠被插入到內核的不論什麼指令位置的探測點,jprobes 則僅僅能被插入到一個內核函數的入口,而 kretprobes 則是在指定的內核函數返回時才被運行。使用 kprobe 的程序實現作一個內核模塊。模塊的初始化函數來負責安裝探測點。退出函數卸載那些被安裝的探測點。kprobe 提供了接口函數(APIs)來安裝或卸載探測點。
眼下 kprobe 支持例如以下架構:i386、x86_64、ppc64、ia64(不支持對 slot1 指令的探測)、sparc64 (返回探測還沒有實現)。
二、Kprobe 實現原理
當安裝一個 kprobes 探測點時。kprobe 首先備份被探測的指令,然後使用斷點指令 (即在 i386 和 x86_64 的 int3 指令) 來代替被探測指令的頭一個或幾個字節。當 CPU 運行到探測點時,將因運行斷點指令而運行 trap 操作,那將導致保存 CPU 的寄存器,調用對應的 trap 處理函數。而 trap 處理函數將調用對應的 notifier_call_chain(內核中一種異步工作機制)中註冊的全部 notifier 函數。kprobe 正是通過向 trap 對應的 notifier_call_chain 註冊關聯到探測點的處理函數來實現探測處理的。
當 kprobe 註冊的 notifier 被運行時,它首先運行關聯到探測點的 pre_handler 函數,並把對應的 kprobe struct 和保存的寄存器作爲該函數的參數,接着,kprobe 單步運行被探測指令的備份。最後,kprobe 運行 post_handler。等全部這些運行完成後。緊跟在被探測指令後的指令流將被正常運行。
kretprobe 也使用了 kprobes 來實現,當用戶調用 register_kretprobe() 時,kprobe 在被探測函數的入口建立了一個探測點。當運行到探測點時,kprobe 保存了被探測函數的返回地址並代替返回地址爲一個 trampoline 的地址,kprobe 在初始化時定義了該 trampoline 而且爲該 trampoline 註冊了一個 kprobe, 當被探測函數運行它的返回指令時。控制傳遞到該 trampoline,因此 kprobe 已經註冊的相應於 trampoline 的處理函數將被運行。而該處理函數會調用用戶關聯到該 kretprobe 上的處理函數。處理完成後,設置指令寄存器指向已經備份的函數返回地址。因而原來的函數返回被正常運行。
被探測函數的返回地址保存在類型爲 kretprobe_instance 的變量中。結構 kretprobe 的 maxactive 字段指定了被探測函數能夠被同一時候探測的實例數,函數 register_kretprobe() 將預分配指定數量的 kretprobe_instance。假設被探測函數是非遞歸的而且調用時已經保持了自旋鎖(spinlock),那麼 maxactive 爲 1 就足夠了;假設被探測函數是非遞歸的且執行時是搶佔失效的,那麼 maxactive 爲 NR_CPUS 就能夠了;假設 maxactive 被設置爲小於等於 0, 它被設置到缺省值(假設搶佔使能, 即配置了 CONFIG_PREEMPT,缺省值爲 10 和 2*NR_CPUS 中的最大值,否則缺省值爲 NR_CPUS)。
假設 maxactive 被設置的太小了,一些探測點的運行可能被丟失,可是不影響系統的正常運行,在結構 kretprobe 中 nmissed 字段將記錄被丟失的探測點運行數,它在返回探測點被註冊時設置爲 0,每次當運行探測函數而沒有 kretprobe_instance 可用時,它就加 1。
三、Kprobe 註冊函數
kprobe 爲每一類型的探測點提供了註冊和卸載函數。
1.register_kprobe
它用於註冊一個 kprobes 類型的探測點,其函數原型爲:
int register_kprobe(struct kprobe *kp);
爲了使用該函數。用戶須要在源文件裏包括頭文件 linux/kprobes.h。
該函數的參數是 struct kprobe 類型的指針。struct kprobe 包括了字段 addr、pre_handler、post_handler 和 fault_handler,addr 指定探測點的位置,pre_handler 指定運行到探測點時運行的處理函數,post_handler 指定運行完探測點後運行的處理函數。fault_handler 指定錯誤處理函數,當在運行 pre_handler、post_handler 以及被探測函數期間錯誤發生時。它會被調用。在調用該註冊函數前。用戶必須先設置好 struct kprobe 的這些字段,用戶能夠指定不論什麼處理函數爲 NULL。
該註冊函數會在 kp->addr 地址處註冊一個 kprobes 類型的探測點,當運行到該探測點時,將調用函數 kp->pre_handler。運行完被探測函數後,將調用 kp->post_handler。假設在運行 kp->pre_handler 或 kp->post_handler 時或在單步跟蹤被探測函數期間錯誤發生,將調用 kp->fault_handler。
該函數成功時返回 0,否則返回負的錯誤碼。探測點處理函數 pre_handler 的原型例如以下:
int pre_handler(struct kprobe *p, struct pt_regs *regs);
用戶必須依照該原型參數格式定義自己的 pre_handler,當然函數名取決於用戶自己。
參數 p 就是指向該處理函數關聯到的 kprobes 探測點的指針,能夠在該函數內部引用該結構的不論什麼字段。就如同在使用調用 register_kprobe 時傳遞的那個參數。參數 regs 指向執行到探測點時保存的寄存器內容。kprobe 負責在調用 pre_handler 時傳遞這些參數,用戶不必關心,僅僅是要知道在該函數內你能訪問這些內容。
一般地,它應當始終返回 0。除非用戶知道自己在做什麼。探測點處理函數 post_handler 的原型例如以下:
void post_handler(struct kprobe *p, struct pt_regs *regs,
unsigned long flags);
前兩個參數與 pre_handler 同樣,第三個參數 trapnr 是與錯誤處理相關的架構依賴的 trap 號(比如,對於 i386,通常的保護錯誤是 13。而頁失效錯誤是 14)。假設成功地處理了異常。它應當返回 1。
2 . register_kretprobe
該函數用於註冊類型爲 kretprobes 的探測點。它的原型例如以下:
爲了使用該函數,用戶須要在源文件裏包括頭文件 linux/kprobes.h。該註冊函數的參數爲 struct kretprobe 類型的指針,用戶在調用該函數前必須定義一個 struct kretprobe 的變量並設置它的 kp.addr、handler 以及 maxactive 字段。kp.addr 指定探測點的位置,handler 指定探測點的處理函數。maxactive 指定能夠同一時候執行的最大處理函數實例數,它應當被恰當設置。否則可能丟失探測點的某些執行。
該註冊函數在地址 rp->kp.addr 註冊一個 kretprobe 類型的探測點。當被探測函數返回時。rp->handler 會被調用。假設成功,它返回 0,否則返回負的錯誤碼。
kretprobe 處理函數的原型例如以下:
int kretprobe_handler(struct kretprobe_instance *ri, struct pt_regs *regs);
參數 regs 指向保存的寄存器。ri 指向類型爲 struct kretprobe_instance 的變量,該結構的 ret_addr 字段表示返回地址,rp 指向對應的 kretprobe_instance 變量,task 字段指向對應的 task_struct。結構 struct kretprobe_instance 是註冊函數 register_kretprobe 依據用戶指定的 maxactive 值來分配的。kprobe 負責在調用 kretprobe 處理函數時傳遞對應的 kretprobe_instance。
- 相應於每個註冊函數。有相應的卸載函數。
void unregister_kprobe(struct kprobe *kp);
void unregister_jprobe(struct jprobe *jp);
void unregister_kretprobe(struct kretprobe *rp);
上面是相應與三種探測點類型的卸載函數。當使用探測點的模塊卸載或須要卸載已經註冊的探測點時,須要使用相應的卸載函數來卸載已經註冊的探測點。kp,jp 和 rp 分別爲指向結構 struct kprobe,struct jprobe 和 struct kretprobe 的指針。它們應當指向調用相應的註冊函數時使用的那個結構。也就說註冊和卸載必須針對相同的探測點。否則會導致系統崩潰。這些卸載函數能夠在註冊後的不論什麼時刻調用。
四 、Kprobe 限制
kprobe 同意在同一地址註冊多個 kprobes,可是不能同一時候在該地址上有多個 jprobes。通常,用戶能夠在內核的不論什麼位置註冊探測點,特別是能夠對中斷處理函數註冊探測點,可是也有一些例外。假設用戶嘗試在實現 kprobe 的代碼 (包含 kernel/kprobes.c 和 arch/*/kernel/kprobes.c 以及 do_page_fault 和 notifier_call_chain) 中註冊探測點。register_*probe 將返回 - EINVAL。
假設爲一個內聯 (inline) 函數註冊探測點,kprobe 無法保證對該函數的全部實例都註冊探測點,由於 gcc 可能隱式地內聯一個函數。因此,要記住,用戶可能看不到預期的探測點的運行。一個探測點處理函數可以改動被探測函數的上下文,如改動內核數據結構,寄存器等。因此,kprobe 可以用來安裝 bug 解決代碼或注入一些錯誤或測試代碼。
假設一個探測處理函數調用了還有一個探測點,該探測點的處理函數不將執行,可是它的 nmissed 數將加 1。多個探測點處理函數或同一處理函數的多個實例可以在不同的 CPU 上同一時候執行。除了註冊和卸載,kprobe 不會使用 mutexe 或分配內存。探測點處理函數在執行時是失效搶佔的。依賴於特定的架構,探測點處理函數執行時也可能是中斷失效的。因此,對於不論什麼探測點處理函數,不要使用導致睡眠或進程調度的不論什麼內核函數(如嘗試獲得 semaphore)。
kretprobe 是通過代替返回地址爲提前定義的 trampoline 的地址來實現的。因此棧回溯和 gcc 內嵌函數__builtin_return_address() 調用將返回 trampoline 的地址而不是真正的被探測函數的返回地址。
假設一個函數的調用次數與它的返回次數不同樣,那麼在該函數上註冊的 kretprobe 探測點可能產生無法預料的結果(do_exit() 就是一個典型的樣例,但 do_execve() 和 do_fork() 沒有問題)。
五、怎樣在內核中引入 Kprobe
probe 已經被包括在 2.6 內核中。可是僅僅有最新的內核才提供了上面描寫敘述的所有功能,因此假設讀者想實驗本文附帶的內核模塊,須要最新的內核,作者在 2.6.18 內核上測試的這些代碼。內核缺省時並沒有使能 kprobe,因此用戶需使能它。
爲了使能 kprobe。用戶必須在編譯內核時設置 CONFIG_KPROBES,即選擇在 “Instrumentation Support“中的“Kprobes” 項。假設用戶希望動態載入和卸載使用 kprobe 的模塊,還必須確保 “Loadable module support” (CONFIG_MODULES) 和“Module unloading” (CONFIG_MODULE_UNLOAD)設置爲 y。假設用戶還想使用 kallsyms_lookup_name()來得到被探測函數的地址,也要確保 CONFIG_KALLSYMS 設置爲 y,當然設置 CONFIG_KALLSYMS_ALL 爲 y 將更好。
內核中引入 Kprobe 需要進行以下步驟:
-
首先需要確認內核版本是否支持 Kprobe,可以通過查詢文檔或者源代碼來確定。
-
在內核配置文件中開啓 CONFIG_KPROBES 選項。
-
編譯內核,並安裝新的內核。
-
寫一個 Kprobe 監聽函數,在該函數中可以添加相應的邏輯,例如日誌輸出、性能統計等等。Kprobe 監聽函數需要使用 Kprobe API 來註冊到系統中。
-
使用 insmod 命令加載編寫好的模塊,即可開始監聽指定的內核函數並執行相應操作。
六、Kprobe 使用實例
kprobe 的使用方式有兩種:
方式一:通過編寫內核模塊,向內核註冊探測點。探測函數可根據需要自行定製,使用靈活方便;
方式二:是使用 kprobes on ftrace,這種方式是 kprobe 和 ftrace 結合使用,即可以通過 kprobe 來優化 ftrace 來跟蹤函數的調用。
6.1 編寫 kprobe 探測模塊
內核提供了一個 struct kprobe 結構體以及一系列的內核 API 函數接口,用戶可以通過這些接口自行實現探測回調函數並實現 struct kprobe 結構,然後將它註冊到內核的 kprobes 子系統中來達到探測的目的。struct kprobe 結構體定義如下:
struct kprobe {
...
kprobe_opcode_t *addr; /* 被探測點的地址 */
const char *symbol_name; /* 被探測函數的名稱 */
unsigned int offset; /* 被探測點在函數內部的偏移,可用於探測函數內部的指令,若爲0則表示函數入口 */
kprobe_pre_handler_t pre_handler; /* 該回調函數用於在執行被探測指令前執行 */
kprobe_post_handler_t post_handler; /* 該回調函數用於在執行完被探測指令後執行 */
kprobe_fault_handler_t fault_handler; /* 此函數用於在出現內存訪問錯誤時進行處理 */
kprobe_opcode_t opcode;
...
};
kprobe 另外提供了註冊與卸載 kprobe 探測點的 API 函數接口,其函數原型定義如下:
int register_kprobe(struct kprobe *kp); /* 向內核註冊kprobe探測點 */
void unregister_kprobe(struct kprobe *kp); /* 從內核卸載kprobe探測點 */
在內核的 samples/kprobes 目錄下有一個實例 kprobe_example.c 描述了 kprobe 模塊最簡單的編寫方式,後續分析實際問題時,我們可以以此爲模板編寫自己的探測模塊。
定義回調函數
static int handler_pre(struct kprobe *p, struct pt_regs *regs)
{
pr_info("<%s> pre_handler: p->addr = 0x%p, ip = %lx, flags = 0x%lx\n",
p->symbol_name, p->addr, regs->ip, regs->flags);
return 0;
}
/* kprobe post_handler: called after the probed instruction is executed */
static void handler_post(struct kprobe *p, struct pt_regs *regs,
unsigned long flags)
{
pr_info("<%s> post_handler: p->addr = 0x%p, flags = 0x%lx\n",
p->symbol_name, p->addr, regs->flags);
}
int handler_fault(struct kprobe *p, struct pt_regs *regs, int trapnr)
{
pr_info("fault_handler: p->addr = 0x%p, trap #%dn", p->addr, trapnr);
/* Return 0 because we don't handle the fault. */
return 0;
}
定義 kprobe 結構
static struct kprobe kp = {
.symbol_name = "do_fork", // 要追蹤的內核函數爲 do_fork
.pre_handler = handler_pre // pre_handler 回調函數
.post_handler = handler_post; // post_handler 回調函數
.fault_handler = handler_fault; // fault_handler 回調函數
};
註冊探測點
static int __init kprobe_init(void)
{
int ret;
ret = register_kprobe(&kp);
if (ret < 0) {
pr_err("register_kprobe failed, returned %d\n", ret);
return ret;
}
pr_info("Planted kprobe at %p\n", kp.addr);
return 0;
}
static void __exit kprobe_exit(void)
{
unregister_kprobe(&kp);
pr_info("kprobe at %p unregistered\n", kp.addr);
}
module_init(kprobe_init)
module_exit(kprobe_exit)
MODULE_LICENSE("GPL");
最後,使用如下 Makefile 編譯 kprobe_example 模塊:
obj-m := kprobe_example.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
rm -f *.mod.c *.ko *.o
安裝內核模塊,任意執行一條 shell 命令,都會在後臺看到 kprobe_example 模塊的輸出。
6.2 基於 ftrace 使用 kprobe
kprobe 和內核的 ftrac 結合使用,需要對內核進行配置,然後添加探測點、進行探測、查看結果。
kprobe 配置
打開 "General setup"->"Kprobes",以及 "Kernel hacking"->"Tracers"->"Enable kprobes-based dynamic events"。
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 事件相關的節點有如下:
/sys/kernel/debug/tracing/kprobe_events-----------------------配置kprobe事件屬性,增加事件之後會在kprobes下面生成對應目錄。
/sys/kernel/debug/tracing/kprobe_profile----------------------kprobe事件統計屬性文件。
/sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/enabled-------使能kprobe事件
/sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/filter--------過濾kprobe事件
/sys/kernel/debug/tracing/kprobes/<GRP>/<EVENT>/format--------查詢kprobe事件顯示格式
下面就結合實例,看一下如何使用 kprobe 事件。
kprobe 事件配置
新增一個 kprobe 事件,通過寫 kprobe_events 來設置。
p[:[GRP/]EVENT] [MOD:]SYM[+offs]|MEMADDR [FETCHARGS]-------------------設置一個probe探測點
r[:[GRP/]EVENT] [MOD:]SYM[+0] [FETCHARGS]------------------------------設置一個return probe探測點
-:[GRP/]EVENT----------------------------------------------------------刪除一個探測點
細節解釋如下:
GRP : Group name. If omitted, use "kprobes" for it.------------設置後會在events/kprobes下創建<GRP>目錄。
EVENT : Event name. If omitted, the event name is generated based on SYM+offs or MEMADDR.---指定後在events/kprobes/<GRP>生成<EVENT>目錄。MOD : Module name which has given SYM.--------------------------模塊名,一般不設
SYM[+offs] : Symbol+offset where the probe is inserted.-------------被探測函數名和偏移
MEMADDR : Address where the probe is inserted.----------------------指定被探測的內存絕對地址
FETCHARGS : Arguments. Each probe can have up to 128 args.----------指定要獲取的參數信息。%REG : Fetch register REG---------------------------------------獲取指定寄存器值
@ADDR : Fetch memory at ADDR (ADDR should be in kernel)--------獲取指定內存地址的值
@SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol)---獲取全局變量的值 $stackN : Fetch Nth entry of stack (N >= 0)----------------------------------獲取指定棧空間值,即sp寄存器+N後的位置值
$stack : Fetch stack address.-----------------------------------------------獲取sp寄存器值
$retval : Fetch return value.(*)--------------------------------------------獲取返回值,用戶return kprobe
$comm : Fetch current task comm.----------------------------------------獲取對應進程名稱。
+|-offs(FETCHARG) : Fetch memory at FETCHARG +|- offs address.(**)------------- NAME=FETCHARG : Set NAME as the argument name of FETCHARG.
FETCHARG:TYPE : Set TYPE as the type of FETCHARG. Currently, basic types (u8/u16/u32/u64/s8/s16/s32/s64), hexadecimal types
(x8/x16/x32/x64), "string" and bitfield are supported.----------------設置參數的類型,可以支持字符串和比特類型
(*) only for return probe.
(**) this is useful for fetching a field of data structures.
執行如下兩條命令就會生成目錄 / sys/kernel/debug/tracing/events/kprobes/myprobe;第三條命令則可以刪除指定 kprobe 事件,如果要全部刪除則 echo > /sys/kernel/debug/tracing/kprobe_events。
echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' > /sys/kernel/debug/tracing/kprobe_events
echo 'r:myretprobe do_sys_open ret=$retval' >> /sys/kernel/debug/tracing/kprobe_events-----------------------------------------------------這裏面一定要用">>",不然就會覆蓋前面的設置。
echo '-:myprobe' >> /sys/kernel/debug/tracing/kprobe_events
echo '-:myretprobe' >> /sys/kernel/debug/tracing/kprobe_events
參數後面的寄存器是跟架構相關的,%ax、%dx、%cx 表示第 1/2/3 個參數,超出部分使用 $stack 來存儲參數。
函數返回值保存在 $retval 中。
kprobe 使能
對 kprobe 事件的是能通過往對應事件的 enable 寫 1 開啓探測;寫 0 暫停探測。
echo > /sys/kernel/debug/tracing/trace
echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' > /sys/kernel/debug/tracing/kprobe_events
echo 'r:myretprobe do_sys_open ret=$retval' >> /sys/kernel/debug/tracing/kprobe_events
echo 1 > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable
echo 1 > /sys/kernel/debug/tracing/events/kprobes/myretprobe/enable
ls
echo 0 > /sys/kernel/debug/tracing/events/kprobes/myprobe/enable
echo 0 > /sys/kernel/debug/tracing/events/kprobes/myretprobe/enable
cat /sys/kernel/debug/tracing/trace
然後在 / sys/kernel/debug/tracing/trace 中可以看到結果。
sourceinsight4.-3356 [000] .... 3542865.754536: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd6764a0 filename=0x8000 flags=0x1b6 mode=0xe3afff48ffffffff
bash-26041 [001] .... 3542865.757014: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x8241 flags=0x1b6 mode=0xe0c0ff48ffffffff
ls-18078 [005] .... 3542865.757950: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x1 mode=0xc1b7bf48ffffffff
ls-18078 [005] d... 3542865.757953: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls-18078 [005] .... 3542865.757966: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x6168 mode=0xc1b7bf48ffffffff
ls-18078 [005] d... 3542865.757969: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls-18078 [005] .... 3542865.758001: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x6168 mode=0xc1b7bf48ffffffff
ls-18078 [005] d... 3542865.758004: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls-18078 [005] .... 3542865.758030: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x1000 mode=0xc1b7bf48ffffffff
ls-18078 [005] d... 3542865.758033: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls-18078 [005] .... 3542865.758055: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x1000 mode=0xc1b7bf48ffffffff
ls-18078 [005] d... 3542865.758057: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls-18078 [005] .... 3542865.758080: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x19d0 mode=0xc1b7bf48ffffffff
ls-18078 [005] d... 3542865.758082: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls-18078 [005] .... 3542865.758289: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x8000 flags=0x1b6 mode=0xc1b7bf48ffffffff
ls-18078 [005] d... 3542865.758297: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls-18078 [005] .... 3542865.758339: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x88000 flags=0x0 mode=0xc1b7bf48ffffffff
ls-18078 [005] d... 3542865.758343: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
ls-18078 [005] .... 3542865.758444: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x98800 flags=0x2 mode=0xc1b7bf48ffffffff
ls-18078 [005] d... 3542865.758446: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
bash-26041 [001] .... 3542865.760416: myprobe: (do_sys_open+0x0/0x290) dfd=0xffffffffbd676460 filename=0x8241 flags=0x1b6 mode=0xe0c0ff48ffffffff
bash-26041 [001] d... 3542865.760426: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
bash-26041 [001] d... 3542865.793477: myretprobe: (SyS_open+0x1e/0x20 <- do_sys_open) ret=0x3
kprobe 事件過濾
跟蹤函數需要通過 filter 進行過濾,可以有效過濾掉冗餘信息。filter 文件用於設置過濾條件,可以減少 trace 中輸出的信息,它支持的格式和 c 語言的表達式類似,支持 ==,!=,>,<,>=,<= 判斷,並且支持與 &&,或 ||,還有 ()。
echo 'filename==0x8241' > /sys/kernel/debug/tracing/events/kprobes/myprobe/filter
kprobe 和棧配合使用
如果要在顯示函數的同時顯示其棧信息,可以通過配置 trace_options 來達到。
echo stacktrace > /sys/kernel/debug/tracing/trace_options
kprobe_profile 統計信息
獲取一段 kprobe 時間之後,可以再 kprobe_profile 中查看統計信息。
後面兩列分別表示命中和未命中的次數。
cat /sys/kernel/debug/tracing/kprobe_profile myprobe
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/qVKd_ejWCLP9yx982JPPCg