驅動程式開發除錯建議¶
本文件用作除錯裝置驅動程式的通用起點和查詢點。雖然本指南側重於需要重新編譯模組/核心的除錯,但 使用者空間除錯指南 將指導您使用動態除錯、ftrace 和其他工具,這些工具可用於除錯問題和行為。 有關一般除錯建議,請參閱 一般建議文件。
以下部分向您展示可用的工具。
printk() 和朋友¶
這些是 printf() 的派生形式,具有不同的目標,並且支援或不支援動態開啟或關閉。
簡單 printk()¶
經典方式,可以非常有效地用於新模組的快速而粗糙的開發,或者提取任意必要的資料以進行故障排除。
先決條件:CONFIG_PRINTK(通常預設啟用)
優點:
無需學習任何東西,簡單易用
易於精確地修改以滿足您的需求(資料格式 (參見:如何正確使用 printk 格式說明符),日誌中的可見性)
可能會導致程式碼執行延遲(有助於確認時序是否是一個因素)
缺點:
需要重建核心/模組
可能會導致程式碼執行延遲(這可能導致問題無法重現)
有關完整文件,請參閱 使用 printk 記錄訊息
Trace_printk¶
先決條件:CONFIG_DYNAMIC_FTRACE & #include <linux/ftrace.h>
使用起來比 printk() 稍微不方便一點,因為您必須從跟蹤檔案中讀取訊息(參見:讀取 ftrace 日誌 而不是從核心日誌中讀取,但當 printk() 向程式碼執行中新增不必要的延遲時非常有用,這會導致問題不穩定或隱藏。)
如果此過程仍然導致時序問題,那麼您可以嘗試 trace_puts()。
有關完整文件,請參見 trace_printk()
dev_dbg¶
列印語句,可以透過 動態除錯 定位,其中包含有關上下文中使用的裝置的附加資訊。
什麼時候適合在程式碼中保留除錯列印?
永久除錯語句對於開發人員解決驅動程式行為不端的問題必須有用。 判斷這一點有點像藝術而不是科學,但是 編碼風格指南 中有一些指導原則。 在幾乎所有情況下,除錯語句都不應該被提交到上游,因為一個工作的驅動程式應該是靜默的。
自定義 printk¶
示例
#define core_dbg(fmt, arg...) do { \
if (core_debug) \
printk(KERN_DEBUG pr_fmt("core: " fmt), ## arg); \
} while (0)
您應該在什麼時候這樣做?
最好只使用 pr_debug(),以後可以使用動態除錯來開啟/關閉它。 此外,許多驅動程式透過模組引數設定的變數(如 core_debug)來啟用這些列印。 但是,模組引數 不再推薦。
Ftrace¶
建立自定義 Ftrace 追蹤點¶
追蹤點會在您的程式碼中新增一個鉤子,當追蹤點被啟用時,將會呼叫並記錄該鉤子。 例如,這可以用於追蹤命中條件分支,或者在除錯會話期間轉儲程式碼流程中特定點的內部狀態。
這是 如何實現新追蹤點 的基本描述。
有關完整的事件追蹤文件,請參見 事件追蹤
有關完整的 Ftrace 文件,請參見 ftrace - 函式追蹤器
DebugFS¶
先決條件:CONFIG_DEBUG_FS` & `#include <linux/debugfs.h>
DebugFS 與其他除錯方法不同,因為它不會將訊息寫入核心日誌,也不會向程式碼新增追蹤。 相反,它允許開發人員處理一組檔案。 使用這些檔案,您可以儲存變數的值或進行暫存器/記憶體轉儲,也可以使這些檔案可寫並修改驅動程式中的值/設定。
可能的用例包括
儲存暫存器值
跟蹤變數
儲存錯誤
儲存設定
切換設定,如除錯開啟/關閉
錯誤注入
當資料轉儲的大小難以作為通用核心日誌的一部分消化(例如,當轉儲原始位元流資料時),或者當您不一直對所有值感興趣,而是可以檢查它們時,這尤其有用。
總體思路是
在 probe 期間建立目錄 (
struct dentry *parent = debugfs_create_dir("my_driver", NULL);)建立檔案 (
debugfs_create_u32("my_value", 444, parent, &my_variable);)在此示例中,該檔案位於
/sys/kernel/debug/my_driver/my_value中(具有使用者/組/所有人的讀取許可權)對檔案的任何讀取都將返回變數
my_variable的當前內容
移除裝置時清理目錄 (
debugfs_remove(parent);)
有關完整文件,請參見 DebugFS。
KASAN、UBSAN、lockdep 和其他錯誤檢查器¶
KASAN (核心地址清理器)¶
先決條件:CONFIG_KASAN
KASAN 是一種動態記憶體錯誤檢測器,可幫助查詢 use-after-free 和 out-of-bounds 錯誤。 它使用編譯時檢測來檢查每次記憶體訪問。
有關完整文件,請參見 核心地址清理器 (KASAN)。
UBSAN (未定義行為清理器)¶
先決條件:CONFIG_UBSAN
UBSAN 依賴於編譯器檢測和執行時檢查來檢測未定義的行為。 它旨在查詢各種問題,包括有符號整數溢位、陣列索引越界等等。
有關完整文件,請參見 未定義行為清理器 - UBSAN
lockdep (鎖依賴驗證器)¶
先決條件:CONFIG_DEBUG_LOCKDEP
lockdep 是一種執行時鎖依賴驗證器,可檢測核心中潛在的死鎖和其他與鎖定相關的問題。 它會跟蹤鎖的獲取和釋放,構建依賴關係圖,並分析該圖以查詢潛在的死鎖。 lockdep 對於驗證核心中鎖順序的正確性尤其有用。
PSI (壓力暫緩資訊跟蹤)¶
先決條件:CONFIG_PSI
PSI 是一種測量工具,用於識別硬體資源上的過度過提交,這可能會導致效能中斷甚至 OOM 終止。
裝置核心轉儲¶
先決條件:CONFIG_DEV_COREDUMP & #include <linux/devcoredump.h>
為驅動程式提供基礎設施,以便向用戶空間提供任意資料。 它最常與 udev 或類似的使用者空間應用程式結合使用,以監聽核心 uevent,這些 uevent 指示轉儲已準備就緒。 Udev 具有一些規則,可以將該檔案複製到某個位置以進行長期儲存和分析,因為預設情況下,轉儲的資料會在預設的 5 分鐘後自動清理。 該資料使用特定於驅動程式的工具或 GDB 進行分析。
可以使用 vmalloc 區域建立裝置核心轉儲,帶有讀取/釋放方法,或者作為分散/聚集列表。
您可以在以下位置找到示例實現:drivers/media/platform/qcom/venus/core.c,在 Bluetooth HCI 層中,在多個無線驅動程式中,以及在多個 DRM 驅動程式中。
devcoredump 介面¶
-
void dev_coredumpm(struct device *dev, struct module *owner, void *data, size_t datalen, gfp_t gfp, ssize_t (*read)(char *buffer, loff_t offset, size_t count, void *data, size_t datalen), void (*free)(void *data))¶
使用讀取/釋放方法建立裝置核心轉儲
引數
struct device *dev崩潰裝置的
struct devicestruct module *owner包含讀取/釋放函式的模組,使用
THIS_MODULEvoid *dataread/free 函式的資料 cookie
size_t datalen資料長度
gfp_t gfp分配標誌
ssize_t (*read)(char *buffer, loff_t offset, size_t count, void *data, size_t datalen)從給定緩衝區讀取的函式
void (*free)(void *data)釋放給定緩衝區的函式
說明
為給定裝置建立新的裝置核心轉儲。 如果之前的核心轉儲尚未被讀取,則新的核心轉儲將被丟棄。 資料生命週期由裝置核心轉儲框架確定,當不再需要資料時,將呼叫 free 函式來釋放資料。
-
void dev_coredumpv(struct device *dev, void *data, size_t datalen, gfp_t gfp)¶
使用 vmalloc 資料建立裝置核心轉儲
引數
struct device *dev崩潰裝置的
struct devicevoid *data包含裝置核心轉儲的 vmalloc 資料
size_t datalen資料長度
gfp_t gfp分配標誌
說明
此函式獲取 vmalloc’ed 資料的所有權,並在不再使用時釋放它。 有關更多資訊,請參見 dev_coredumpm()。
-
void devcd_free_sgtable(void *data)¶
釋放給定 scatterlist 表的所有記憶體(即頁面和 scatterlist 例項)
引數
void *data指向要釋放的 sg_table 的指標
注意
如果使用 devcd_alloc_sgtable 分配了兩個表,然後使用 sg_chain 函式將它們連結起來,則應該只在連結表上呼叫該函式一次
-
ssize_t devcd_read_from_sgtable(char *buffer, loff_t offset, size_t buf_len, void *data, size_t data_len)¶
將資料從 sg_table 複製到給定緩衝區並返回讀取的位元組數
引數
char *buffer要將資料複製到的緩衝區
loff_t offset從給定 scatterlist 中資料的頭部開始的第 offset**** 位元組開始複製
size_t buf_len緩衝區長度
void *data要從中複製的 scatterlist 表
size_t data_lensg_table 中資料的長度
返回
複製的位元組數
引數
struct device *dev崩潰裝置的
struct device
說明
dev_coredump_put() 從檔案系統中移除給定裝置的核心轉儲(如果存在),並釋放其關聯資料;否則,不執行任何操作。
它對於不想在解除安裝後保持核心轉儲可用的模組很有用。
-
void dev_coredumpm_timeout(struct device *dev, struct module *owner, void *data, size_t datalen, gfp_t gfp, ssize_t (*read)(char *buffer, loff_t offset, size_t count, void *data, size_t datalen), void (*free)(void *data), unsigned long timeout)¶
使用具有自定義超時的讀取/釋放方法建立裝置核心轉儲。
引數
struct device *dev崩潰裝置的
struct devicestruct module *owner包含讀取/釋放函式的模組,使用
THIS_MODULEvoid *dataread/free 函式的資料 cookie
size_t datalen資料長度
gfp_t gfp分配標誌
ssize_t (*read)(char *buffer, loff_t offset, size_t count, void *data, size_t datalen)從給定緩衝區讀取的函式
void (*free)(void *data)釋放給定緩衝區的函式
unsigned long timeout移除核心轉儲的時間(以 jiffies 為單位)
說明
為給定裝置建立新的裝置核心轉儲。 如果之前的核心轉儲尚未被讀取,則新的核心轉儲將被丟棄。 資料生命週期由裝置核心轉儲框架確定,當不再需要資料時,將呼叫 free 函式來釋放資料。
-
void dev_coredumpsg(struct device *dev, struct scatterlist *table, size_t datalen, gfp_t gfp)¶
建立將 scatterlist 用作資料引數的裝置核心轉儲
引數
struct device *dev崩潰裝置的
struct devicestruct scatterlist *table轉儲資料
size_t datalen資料長度
gfp_t gfp分配標誌
說明
為給定裝置建立新的裝置核心轉儲。 如果之前的核心轉儲尚未被讀取,則新的核心轉儲將被丟棄。 資料生命週期由裝置核心轉儲框架確定,當不再需要資料時,它將釋放資料。
版權所有 ©2024:Collabora