驅動程式開發除錯建議

本文件用作除錯裝置驅動程式的通用起點和查詢點。雖然本指南側重於需要重新編譯模組/核心的除錯,但 使用者空間除錯指南 將指導您使用動態除錯、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 device

struct module *owner

包含讀取/釋放函式的模組,使用 THIS_MODULE

void *data

read/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 device

void *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_len

sg_table 中資料的長度

返回

複製的位元組數

void dev_coredump_put(struct device *dev)

移除裝置核心轉儲

引數

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 device

struct module *owner

包含讀取/釋放函式的模組,使用 THIS_MODULE

void *data

read/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 device

struct scatterlist *table

轉儲資料

size_t datalen

資料長度

gfp_t gfp

分配標誌

說明

為給定裝置建立新的裝置核心轉儲。 如果之前的核心轉儲尚未被讀取,則新的核心轉儲將被丟棄。 資料生命週期由裝置核心轉儲框架確定,當不再需要資料時,它將釋放資料。

版權所有 ©2024:Collabora