Fprobe - 函式入口/出口探針

簡介

Fprobe 是一個基於 ftrace 中的函式圖跟蹤特性的函式入口/出口探針。 如果你不想跟蹤所有函式,而是想在特定函式的入口和出口處附加回調,類似於 kprobes 和 kretprobes,你可以使用 fprobe。 與 kprobes 和 kretprobes 相比,fprobe 可以用單個處理程式更快地對多個函式進行檢測。 本文件描述如何使用 fprobe。

fprobe 的用法

fprobe 是 ftrace(+ 類似 kretprobe 的返回回撥)的包裝器,用於將回調附加到多個函式入口和出口。 使用者需要設定 struct fprobe 並將其傳遞給 register_fprobe()

通常,fprobe 資料結構使用 entry_handler 和/或 exit_handler 初始化,如下所示。

struct fprobe fp = {
       .entry_handler  = my_entry_callback,
       .exit_handler   = my_exit_callback,
};

要啟用 fprobe,請呼叫 register_fprobe(), register_fprobe_ips(), 和 register_fprobe_syms() 之一。 這些函式使用不同型別的引數註冊 fprobe。

register_fprobe() 透過函式名過濾器啟用 fprobe。 例如,這會在 “func*()” 函式上啟用 @fp,但 “func2()” 除外。

register_fprobe(&fp, "func*", "func2");

register_fprobe_ips() 透過 ftrace 位置地址啟用 fprobe。 例如:

unsigned long ips[] = { 0x.... };

register_fprobe_ips(&fp, ips, ARRAY_SIZE(ips));

並且 register_fprobe_syms() 透過符號名稱啟用 fprobe。 例如:

char syms[] = {"func1", "func2", "func3"};

register_fprobe_syms(&fp, syms, ARRAY_SIZE(syms));

要停用(從函式中刪除)此 fprobe,請呼叫

unregister_fprobe(&fp);

你可以暫時(軟)停用 fprobe,方法是:

disable_fprobe(&fp);

並透過以下方式恢復:

enable_fprobe(&fp);

以上是透過包含標頭檔案定義的

#include <linux/fprobe.h>

與 ftrace 相同,註冊的回撥將在呼叫 register_fprobe() 之後和返回之前的一段時間後開始被呼叫。 請參閱 Documentation/trace/ftrace.rst

此外,unregister_fprobe() 將保證在 unregister_fprobe() 返回後,函式不再呼叫進入和退出處理程式,與 unregister_ftrace_function() 相同。

fprobe 進入/退出處理程式

進入/退出回撥函式的原型如下:

int entry_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct ftrace_regs *fregs, void *entry_data);

void exit_callback(struct fprobe *fp, unsigned long entry_ip, unsigned long ret_ip, struct ftrace_regs *fregs, void *entry_data);

請注意,@entry_ip 儲存在函式入口處並傳遞給退出處理程式。 如果進入回撥函式返回 !0,則將取消相應的退出回撥。

@fp

這是與此處理程式相關的 fprobe 資料結構的地址。 你可以將 fprobe 嵌入到你的資料結構中,並透過 @fp 中的 container_of() 宏獲取它。 @fp 不得為 NULL。

@entry_ip

這是被跟蹤函式的 ftrace 地址(包括進入和退出)。 請注意,這可能不是函式的實際入口地址,而是 ftrace 被檢測的地址。

@ret_ip

這是被跟蹤函式將返回到的返回地址,位於呼叫者中的某個位置。 這可以在進入和退出時使用。

@fregs

這是進入和退出時的 ftrace_regs 資料結構。 這包括函式引數或返回值。 因此,使用者可以透過適當的 ftrace_regs_* API 訪問這些值。

@entry_data

這是一個本地儲存,用於在進入和退出處理程式之間共享資料。 預設情況下,此儲存為 NULL。 如果使用者在註冊 fprobe 時指定了 exit_handler 欄位和 entry_data_size 欄位,則會分配儲存空間並將其傳遞給 entry_handlerexit_handler

同一函式上的進入資料大小和退出處理程式

由於進入資料透過每個任務的堆疊傳遞,並且其大小有限,因此每個探針的進入資料大小限制為 15 * sizeof(long)。 你還需要注意不同的 fprobe 正在探測同一個函式,此限制會變小。 進入資料大小與 sizeof(long) 對齊,並且每個具有退出處理程式的 fprobe 在堆疊上使用 sizeof(long) 空間,你應該儘可能減少同一函式上的 fprobe 數量。

與 kprobes 共享回撥

由於 fprobe(和 ftrace)的遞迴安全性與 kprobes 略有不同,如果使用者想要從 fprobe 和 kprobes 執行相同的程式碼,這可能會導致問題。

Kprobes 具有每個 CPU 的 ‘current_kprobe’ 變數,該變數在所有情況下都保護 kprobe 處理程式免受遞迴。 另一方面,fprobe 僅使用 ftrace_test_recursion_trylock()。 這允許中斷上下文在 fprobe 使用者處理程式執行時呼叫另一個(或相同的)fprobe。

如果通用回撥程式碼有自己的遞迴檢測,或者它可以處理不同上下文(普通/中斷/NMI)中的遞迴,則這不是問題。 但是,如果它依賴於 ‘current_kprobe’ 遞迴鎖,則必須檢查 kprobe_running() 並使用 kprobe_busy_*() API。

Fprobe 具有 FPROBE_FL_KPROBE_SHARED 標誌來執行此操作。 如果你的通用回撥程式碼將與 kprobes 共享,請在註冊 fprobe 之前 設定 FPROBE_FL_KPROBE_SHARED,如下所示

fprobe.flags = FPROBE_FL_KPROBE_SHARED;

register_fprobe(&fprobe, "func*", NULL);

這將保護你的通用回撥免受巢狀呼叫。

丟失計數器

fprobe 資料結構具有 fprobe::nmissed 計數器欄位,與 kprobes 相同。 在以下情況下,此計數器會遞增:

  • fprobe 無法獲取 ftrace_recursion 鎖。 這通常意味著其他 ftrace 使用者跟蹤的函式是從 entry_handler 呼叫的。

  • 由於無法從每個任務的影子堆疊中分配資料緩衝區,fprobe 無法設定函式退出。

fprobe::nmissed 欄位在這兩種情況下都會遞增。 因此,前者跳過進入和退出回撥,後者跳過退出回撥,但在兩種情況下,計數器都將增加 1。

請注意,如果在註冊 fprobe 時,你將 FTRACE_OPS_FL_RECURSION 和/或 FTRACE_OPS_FL_RCU 設定為 fprobe::ops::flags (ftrace_ops::flags),則此計數器可能無法正常工作,因為 ftrace 會跳過增加計數器的 fprobe 函式。

函式和結構

struct fprobe_hlist_node

基於地址的 fprobe 雜湊列表節點。

定義:

struct fprobe_hlist_node {
    struct hlist_node       hlist;
    unsigned long           addr;
    struct fprobe           *fp;
};

成員

hlist

地址搜尋雜湊表的 hlist 節點。

addr

fp 的探測地址之一。

fp

擁有此節點的 fprobe。

struct fprobe_hlist

fprobe 的雜湊列表節點。

定義:

struct fprobe_hlist {
    struct hlist_node               hlist;
    struct rcu_head                 rcu;
    struct fprobe                   *fp;
    int size;
    struct fprobe_hlist_node        array[] ;
};

成員

hlist

用於存在性檢查雜湊表的 hlist 節點。

rcu

用於 RCU 延遲釋放的 rcu_head。

fp

擁有此 fprobe_hlist 的 fprobe。

size

array 的大小。

array

每個要探測的地址的 fprobe_hlist_node。

struct fprobe

基於 ftrace 的探針。

定義:

struct fprobe {
    unsigned long           nmissed;
    unsigned int            flags;
    size_t entry_data_size;
    fprobe_entry_cb entry_handler;
    fprobe_exit_cb exit_handler;
    struct fprobe_hlist     *hlist_array;
};

成員

nmissed

用於丟失事件的計數器。

flags

狀態標誌。

entry_data_size

私有資料儲存大小。

entry_handler

函式入口的回撥函式。

exit_handler

函式退出的回撥函式。

hlist_array

用於從 IP 雜湊表搜尋 fprobe 的 fprobe_hlist。

void disable_fprobe(struct fprobe *fp)

停用 fprobe

引數

struct fprobe *fp

要停用的 fprobe。

描述

這將軟停用 fp。 請注意,這不會從函式入口中刪除 ftrace 掛鉤。

void enable_fprobe(struct fprobe *fp)

啟用 fprobe

引數

struct fprobe *fp

要啟用的 fprobe。

描述

這將軟啟用 fp

int register_fprobe(struct fprobe *fp, const char *filter, const char *notfilter)

透過模式將 fprobe 註冊到 ftrace。

引數

struct fprobe *fp

要註冊的 fprobe 資料結構。

const char *filter

探測符號的萬用字元模式。

const char *notfilter

NOT 探測符號的萬用字元模式。

描述

fp 註冊到 ftrace,以在與 filter 匹配的符號上啟用探測。 如果 notfilter 不為 NULL,則不會探測與 notfilter 匹配的符號。

如果 fp 註冊成功,則返回 0,否則返回 -errno。

int register_fprobe_ips(struct fprobe *fp, unsigned long *addrs, int num)

透過地址將 fprobe 註冊到 ftrace。

引數

struct fprobe *fp

要註冊的 fprobe 資料結構。

unsigned long *addrs

目標函式地址的陣列。

int num

addrs 的條目數。

描述

fp 註冊到 ftrace,以在 addrs 給定的地址上啟用探測。 addrs 必須是 ftrace 位置地址的地址,該地址可能是符號地址 + 架構相關的偏移量。 如果你不確定這意味著什麼,請使用其他註冊函式。

如果 fp 註冊成功,則返回 0,否則返回 -errno。

int register_fprobe_syms(struct fprobe *fp, const char **syms, int num)

透過符號將 fprobe 註冊到 ftrace。

引數

struct fprobe *fp

要註冊的 fprobe 資料結構。

const char **syms

目標符號的陣列。

int num

syms 的條目數。

描述

fp 註冊到 syms 陣列給定的符號。 如果你確定符號存在於核心中,這將非常有用。

如果 fp 註冊成功,則返回 0,否則返回 -errno。

int unregister_fprobe(struct fprobe *fp)

登出 fprobe。

引數

struct fprobe *fp

要登出的 fprobe 資料結構。

描述

登出 fprobe(並從函式條目中刪除 ftrace 掛鉤)。

如果 fp 登出成功,則返回 0,否則返回 -errno。