使用 ftrace 掛鉤函式¶
編寫於:4.14
簡介¶
ftrace 基礎設施最初是為了將回調附加到函式的開頭,以便記錄和跟蹤核心的流程。但是,函式開始處的回撥也可以有其他用例。無論是用於即時核心補丁,還是用於安全監控。本文件介紹瞭如何使用 ftrace 實現您自己的函式回撥。
ftrace 上下文¶
警告
能夠將回調新增到核心中幾乎任何函式都存在風險。回撥可以從任何上下文(正常、softirq、irq 和 NMI)呼叫。回撥也可以在即將進入空閒狀態、CPU 啟動和關閉期間或進入使用者空間之前呼叫。這需要格外注意回撥內部可以執行的操作。回撥可以在 RCU 的保護範圍之外呼叫。
有一些輔助函式可以幫助防止遞迴,並確保 RCU 正在監視。 這些將在下面解釋。
ftrace_ops 結構體¶
要註冊函式回撥,需要 ftrace_ops。此結構體用於告訴 ftrace 哪個函式應該作為回撥呼叫,以及回撥將執行哪些保護,而不需要 ftrace 處理。
使用 ftrace 註冊 ftrace_ops 時,只需要設定一個欄位
struct ftrace_ops ops = {
.func = my_callback_func,
.flags = MY_FTRACE_FLAGS
.private = any_private_data_structure,
};
.flags 和 .private 都是可選的。只有 .func 是必需的。
要啟用跟蹤,請呼叫
register_ftrace_function(&ops);
要停用跟蹤,請呼叫
unregister_ftrace_function(&ops);
以上定義透過包含標頭檔案
#include <linux/ftrace.h>
註冊的回撥將在呼叫 register_ftrace_function() 之後以及返回之前開始被呼叫。回撥開始被呼叫的確切時間取決於架構和服務排程。回撥本身必須處理任何同步,如果它必須在確切的時刻開始。
unregister_ftrace_function() 將保證在 unregister_ftrace_function() 返回後,回撥不再被函式呼叫。請注意,為了執行此保證,unregister_ftrace_function() 可能需要一些時間才能完成。
回撥函式¶
回撥函式的原型如下(截至 v4.14)
void callback_func(unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *op, struct pt_regs *regs);
- @ip
這是正在跟蹤的函式的指令指標。(函式中 fentry 或 mcount 的位置)
- @parent_ip
這是呼叫正在跟蹤的函式的函式的指令指標(函式呼叫的位置)。
- @op
這是指向用於註冊回撥的 ftrace_ops 的指標。這可以用於透過 private 指標將資料傳遞給回撥。
- @regs
如果在 ftrace_ops 結構體中設定了 FTRACE_OPS_FL_SAVE_REGS 或 FTRACE_OPS_FL_SAVE_REGS_IF_SUPPORTED 標誌,那麼這將指向 pt_regs 結構體,就像在 ftrace 跟蹤的函式開始處設定斷點一樣。否則,它要麼包含垃圾資料,要麼為 NULL。
保護您的回撥¶
由於函式可以從任何地方呼叫,並且回撥呼叫的函式也可能被跟蹤,並呼叫同一個回撥,因此必須使用遞迴保護。 有兩個輔助函式可以幫助解決這個問題。 如果您使用以下程式碼開始您的程式碼:
int bit;
bit = ftrace_test_recursion_trylock(ip, parent_ip);
if (bit < 0)
return;
並用以下程式碼結束:
ftrace_test_recursion_unlock(bit);
中間的程式碼可以安全使用,即使它最終呼叫了回撥正在跟蹤的函式。請注意,成功時,ftrace_test_recursion_trylock() 將停用搶佔,而 ftrace_test_recursion_unlock() 將再次啟用它(如果之前已啟用)。指令指標 (ip) 及其父指標 (parent_ip) 將傳遞給 ftrace_test_recursion_trylock() 以記錄遞迴發生的位置(如果設定了 CONFIG_FTRACE_RECORD_RECURSION)。
或者,如果在 ftrace_ops 上設定了 FTRACE_OPS_FL_RECURSION 標誌(如下所述),則將使用輔助 trampoline 來測試回撥的遞迴,並且無需進行遞迴測試。 但是,這是以額外的函式呼叫帶來的略微更多的開銷為代價的。
如果您的回撥訪問任何需要 RCU 保護的資料或臨界區,最好確保 RCU 正在“監視”,否則該資料或臨界區將不會像預期那樣受到保護。 在這種情況下,新增
if (!rcu_is_watching())
return;
或者,如果在 ftrace_ops 上設定了 FTRACE_OPS_FL_RCU 標誌(如下所述),則將使用輔助 trampoline 來測試回撥的 rcu_is_watching,並且無需進行其他測試。 但是,這是以額外的函式呼叫帶來的略微更多的開銷為代價的。
ftrace FLAGS¶
ftrace_ops 標誌都在 include/linux/ftrace.h 中定義和記錄。 有些標誌用於 ftrace 的內部基礎設施,但使用者應該注意的標誌如下
- FTRACE_OPS_FL_SAVE_REGS
如果回撥需要讀取或修改傳遞給回撥的 pt_regs,則必須設定此標誌。 在不支援將 pt_regs 傳遞給回撥的架構上註冊設定了此標誌的 ftrace_ops 將失敗。
- FTRACE_OPS_FL_SAVE_REGS_IF_SUPPORTED
與 SAVE_REGS 類似,但在不支援傳遞 regs 的架構上註冊 ftrace_ops 不會因為設定了此標誌而失敗。 但是回撥必須檢查 regs 是否為 NULL 才能確定架構是否支援它。
- FTRACE_OPS_FL_RECURSION
預設情況下,期望回撥可以處理遞迴。 但是如果回撥不太擔心開銷,那麼設定此位將透過呼叫將進行遞迴保護的輔助函式並在未遞迴時僅呼叫回撥來在回撥周圍新增遞迴保護。
請注意,如果未設定此標誌,並且確實發生了遞迴,則可能會導致系統崩潰,並可能透過三重故障重新啟動。
請注意,如果設定了此標誌,則始終會在停用搶佔的情況下呼叫回撥。 如果未設定此標誌,則回撥可能會(但不能保證)在可搶佔上下文中呼叫。
- FTRACE_OPS_FL_IPMODIFY
需要設定 FTRACE_OPS_FL_SAVE_REGS。 如果回撥要“劫持”被跟蹤的函式(呼叫另一個函式而不是被跟蹤的函式),則需要設定此標誌。 這就是即時核心補丁使用的。 如果沒有此標誌,則無法修改 pt_regs->ip。
請注意,一次只能將一個設定了 FTRACE_OPS_FL_IPMODIFY 的 ftrace_ops 註冊到任何給定的函式。
- FTRACE_OPS_FL_RCU
如果設定了此標誌,則回撥僅會被 RCU 正在“監視”的函式呼叫。 如果回撥函式執行任何
rcu_read_lock()操作,則需要此標誌。當系統進入空閒狀態、CPU 關閉並重新聯機以及從核心進入使用者空間並返回核心空間時,RCU 停止監視。 在這些轉換期間,可能會執行回撥,並且 RCU 同步不會保護它。
- FTRACE_OPS_FL_PERMANENT
如果在任何 ftrace ops 上設定了此標誌,則無法透過將 0 寫入 proc sysctl ftrace_enabled 來停用跟蹤。 同樣,如果 ftrace_enabled 為 0,則無法註冊設定了此標誌的回撥。
Livepatch 使用它來不丟失函式重定向,因此係統保持受保護。
過濾要跟蹤的函式¶
如果僅從特定函式呼叫回撥,則必須設定過濾器。 過濾器按名稱新增,如果已知,則按 ip 新增。
int ftrace_set_filter(struct ftrace_ops *ops, unsigned char *buf,
int len, int reset);
- @ops
設定過濾器的 ops
- @buf
儲存函式過濾器文字的字串。
- @len
字串的長度。
- @reset
非零值表示在應用此過濾器之前重置所有過濾器。
過濾器表示啟用跟蹤時應啟用哪些函式。 如果 @buf 為 NULL 且設定了 reset,則將啟用所有函式進行跟蹤。
@buf 也可以是 glob 表示式,以啟用與特定模式匹配的所有函式。
請參閱 Documentation/trace/ftrace.rst 中的“過濾器命令”。
僅跟蹤 schedule 函式
ret = ftrace_set_filter(&ops, "schedule", strlen("schedule"), 0);
要新增更多函式,請多次呼叫 ftrace_set_filter(),並將 @reset 引數設定為零。 要刪除當前過濾器集並將其替換為 @buf 定義的新函式,請將 @reset 設定為非零值。
要刪除所有過濾的函式並跟蹤所有函式
ret = ftrace_set_filter(&ops, NULL, 0, 1);
有時多個函式具有相同的名稱。 要在這種情況下僅跟蹤特定函式,可以使用 ftrace_set_filter_ip()。
ret = ftrace_set_filter_ip(&ops, ip, 0, 0);
雖然 ip 必須是函式中 fentry 或 mcount 呼叫的地址。 此函式由 perf 和 kprobes 使用,它們從使用者那裡獲取 ip 地址(通常使用來自核心的除錯資訊)。
如果使用 glob 設定過濾器,則可以將函式新增到“notrace”列表,這將阻止這些函式呼叫回撥。“notrace”列表優先於“filter”列表。如果兩個列表都非空並且包含相同的函式,則回撥將不會被任何函式呼叫。
空的“notrace”列表意味著允許跟蹤過濾器定義的所有函式。
int ftrace_set_notrace(struct ftrace_ops *ops, unsigned char *buf,
int len, int reset);
這與 ftrace_set_filter() 採用相同的引數,但會將它找到的函式新增到不被跟蹤的列表中。這是與過濾器列表分開的列表,此函式不會修改過濾器列表。
非零 @reset 將在將與 @buf 匹配的函式新增到“notrace”列表之前清除該列表。
清除“notrace”列表與清除過濾器列表相同
ret = ftrace_set_notrace(&ops, NULL, 0, 1);
可以隨時更改過濾器和 notrace 列表。如果只有一組函式應該呼叫回撥,最好在註冊回撥之前設定過濾器。但是,更改也可能在註冊回撥之後發生。
如果過濾器已就位,並且 @reset 為非零值,並且 @buf 包含與函式匹配的 glob,則切換將在 ftrace_set_filter() 呼叫期間發生。任何時候都不會有所有函式呼叫回撥。
ftrace_set_filter(&ops, "schedule", strlen("schedule"), 1);
register_ftrace_function(&ops);
msleep(10);
ftrace_set_filter(&ops, "try_to_wake_up", strlen("try_to_wake_up"), 1);
與
ftrace_set_filter(&ops, "schedule", strlen("schedule"), 1);
register_ftrace_function(&ops);
msleep(10);
ftrace_set_filter(&ops, NULL, 0, 1);
ftrace_set_filter(&ops, "try_to_wake_up", strlen("try_to_wake_up"), 0);
不同,因為後者在重置時間和新過濾器設定時間之間,將有一個短時間所有函式都會呼叫回撥。