動態除錯

簡介

動態除錯允許您動態地啟用/停用核心除錯列印程式碼,以獲取額外的核心資訊。

如果存在 /proc/dynamic_debug/control,則您的核心具有動態除錯功能。您需要 root 許可權(sudo su)才能使用它。

動態除錯提供

  • 核心中所有 prdbgs 的目錄。 使用 cat /proc/dynamic_debug/control 檢視它們。

  • 一個簡單的查詢/命令語言,可以透過以下 0 個或 1 個的任意組合來更改 prdbgs

    • 原始檔名

    • 函式名

    • 行號(包括行號範圍)

    • 模組名

    • 格式字串

    • 類名(由每個模組已知/宣告)

注意:要實際獲取控制檯上的除錯列印輸出,您可能需要調整核心 loglevel=,或者使用 ignore_loglevel。 請閱讀 核心的命令列引數 中有關這些核心引數的資訊。

檢視動態除錯行為

您可以在 prdbg 目錄中檢視當前配置的行為

:#> head -n7 /proc/dynamic_debug/control
# filename:lineno [module]function flags format
init/main.c:1179 [main]initcall_blacklist =_ "blacklisting initcall %s\012
init/main.c:1218 [main]initcall_blacklisted =_ "initcall %s blacklisted\012"
init/main.c:1424 [main]run_init_process =_ "  with arguments:\012"
init/main.c:1426 [main]run_init_process =_ "    %s\012"
init/main.c:1427 [main]run_init_process =_ "  with environment:\012"
init/main.c:1429 [main]run_init_process =_ "    %s\012"

第 3 個以空格分隔的列顯示當前標誌,前面有一個 = 以方便與 grep/cut 一起使用。 =p 顯示已啟用的呼叫點。

控制動態除錯行為

prdbg站點的行為透過將查詢/命令寫入控制檔案來控制。 例如

# grease the interface
:#> alias ddcmd='echo $* > /proc/dynamic_debug/control'

:#> ddcmd '-p; module main func run* +p'
:#> grep =p /proc/dynamic_debug/control
init/main.c:1424 [main]run_init_process =p "  with arguments:\012"
init/main.c:1426 [main]run_init_process =p "    %s\012"
init/main.c:1427 [main]run_init_process =p "  with environment:\012"
init/main.c:1429 [main]run_init_process =p "    %s\012"

錯誤訊息將傳送到控制檯/系統日誌

:#> ddcmd mode foo +p
dyndbg: unknown keyword "mode"
dyndbg: query parse failed
bash: echo: write error: Invalid argument

如果還啟用並掛載了 debugfs,則 dynamic_debug/control 也位於掛載目錄下,通常是 /sys/kernel/debug/

命令語言參考

在基本詞法級別上,命令是由空格或製表符分隔的單詞序列。 因此,這些都是等效的

:#> ddcmd file svcsock.c line 1603 +p
:#> ddcmd "file svcsock.c line 1603 +p"
:#> ddcmd '  file   svcsock.c     line  1603 +p  '

命令提交受 write() 系統呼叫的限制。 可以將多個命令一起寫入,用 ;\n 分隔

:#> ddcmd "func pnpacpi_get_resources +p; func pnp_assign_mem +p"
:#> ddcmd <<"EOC"
func pnpacpi_get_resources +p
func pnp_assign_mem +p
EOC
:#> cat query-batch-file > /proc/dynamic_debug/control

您還可以在每個查詢項中使用萬用字元。 匹配規則支援 *(匹配零個或多個字元)和 ?(恰好匹配一個字元)。 例如,您可以匹配所有 usb 驅動程式

:#> ddcmd file "drivers/usb/*" +p     # "" to suppress shell expansion

從語法上講,命令是關鍵字值對,後跟標誌更改或設定

command ::= match-spec* flags-spec

match-spec 從目錄中選擇 prdbgs,在其上應用 flags-spec,所有約束都一起進行 AND 運算。 缺少關鍵字與關鍵字 “*” 相同。

匹配規範是一個關鍵字,用於選擇要比較的呼叫點的屬性,以及要比較的值。 可能的關鍵字是:

match-spec ::= 'func' string |
               'file' string |
               'module' string |
               'format' string |
               'class' string |
               'line' line-range

line-range ::= lineno |
               '-'lineno |
               lineno'-' |
               lineno'-'lineno

lineno ::= unsigned-int

注意

line-range 不能包含空格,例如“1-30”是有效範圍,但“1 - 30”不是。

每個關鍵字的含義是

func

給定的字串與每個呼叫點的函式名稱進行比較。 例子

func svc_tcp_accept
func *recv*             # in rfcomm, bluetooth, ping, tcp
file

給定的字串與每個呼叫點的 src-root 相對路徑名或原始檔的基本名稱進行比較。 例子

file svcsock.c
file kernel/freezer.c   # ie column 1 of control file
file drivers/usb/*      # all callsites under it
file inode.c:start_*    # parse :tail as a func (above)
file inode.c:1-100      # parse :tail as a line-range (above)
module

給定的字串與每個呼叫點的模組名稱進行比較。 模組名稱是 lsmod 中看到的字串,即沒有目錄或 .ko 字尾,並且 - 更改為 _。 例子

module sunrpc
module nfsd
module drm*     # both drm, drm_kms_helper
format

在動態除錯格式字串中搜索給定的字串。 請注意,該字串不需要匹配整個格式,只需匹配部分即可。 可以使用 C 八進位制字元轉義 \ooo 符號來轉義空格和其他特殊字元,例如,空格字元是 \040。 或者,字串可以用雙引號字元(")或單引號字元(')括起來。 例子

format svcrdma:         // many of the NFS/RDMA server pr_debugs
format readahead        // some pr_debugs in the readahead cache
format nfsd:\040SETATTR // one way to match a format with whitespace
format "nfsd: SETATTR"  // a neater way to match a format with whitespace
format 'nfsd: SETATTR'  // yet another way to match a format with whitespace
class

給定的 class_name 針對每個模組進行驗證,這些模組可能已宣告已知 class_names 的列表。 如果找到模組的 class_name,則會繼續進行呼叫點和類匹配及調整。 例子

class DRM_UT_KMS        # a DRM.debug category
class JUNK              # silent non-match
// class TLD_*          # NOTICE: no wildcard in class names
line

給定的行號或行號範圍與每個 pr_debug() 呼叫點的行號進行比較。 單個行號與呼叫點行號完全匹配。 行號範圍匹配第一個和最後一個行號(含)之間的任何呼叫點。 空的第一個數字表示檔案中的第一行,空的最後一個行號表示檔案中的最後一個行號。 例子

line 1603           // exactly line 1603
line 1600-1605      // the six lines from line 1600 to line 1605
line -1605          // the 1605 lines from line 1 to line 1605
line 1600-          // all lines from line 1600 to the end of the file

標誌規範包括一個更改操作,後跟一個或多個標誌字元。 更改操作是以下字元之一

-    remove the given flags
+    add the given flags
=    set the flags to the given flags

標誌是

p    enables the pr_debug() callsite.
_    enables no flags.

Decorator flags add to the message-prefix, in order:
t    Include thread ID, or <intr>
m    Include module name
f    Include the function name
s    Include the source file name
l    Include line number

對於 print_hex_dump_debug()print_hex_dump_bytes(),只有 p 標誌有意義,其他標誌將被忽略。

請注意,regexp ^[-+=][fslmpt_]+$ 匹配標誌規範。 要一次清除所有標誌,請使用 =_-fslmpt

啟動過程中的除錯訊息

要在啟動過程中啟用核心程式碼和內建模組的除錯訊息,甚至在使用者空間和 debugfs 存在之前,請使用 dyndbg="QUERY"module.dyndbg="QUERY"。 QUERY 遵循上述語法,但不得超過 1023 個字元。 您的引導載入程式可能會施加更低的限制。

這些 dyndbg 引數在 ddebug 表處理之後,作為 early_initcall 的一部分進行處理。 因此,您可以透過此引導引數啟用在此 early_initcall 之後執行的所有程式碼中的除錯訊息。

例如,在 x86 系統上,ACPI 啟用是 subsys_initcall,並且

dyndbg="file ec.c +p"

如果您的機器(通常是筆記型電腦)具有嵌入式控制器,則將在 ACPI 設定期間顯示早期的嵌入式控制器事務。 PCI(或其他裝置)初始化也是使用此引導引數進行除錯的熱門候選者。

如果 foo 模組不是內建的,則 foo.dyndbg 仍將在引導時處理,但沒有效果,但在稍後載入模組時將重新處理。 裸 dyndbg= 僅在引導時處理。

模組初始化時的除錯訊息

當呼叫 modprobe foo 時,modprobe 掃描 /proc/cmdline 以查詢 foo.params,剝離 foo.,並將其與 modprobe args 或 /etc/modprobe.d/*.conf 檔案中給出的引數一起傳遞給核心,順序如下

  1. 透過 /etc/modprobe.d/*.conf 給出的引數

    options foo dyndbg=+pt
    options foo dyndbg # defaults to +p
    
  2. 引導引數中給出的 foo.dyndbgfoo. 被剝離並傳遞

    foo.dyndbg=" func bar +p; func buz +mp"
    
  3. 傳遞給 modprobe 的引數

    modprobe foo dyndbg==pmf # override previous settings
    

這些 dyndbg 查詢按順序應用,最後一個具有最終決定權。 這允許引導引數覆蓋或修改 /etc/modprobe.d 中的引數(合理,因為 1 是系統範圍的,2 是核心或引導特定的),modprobe 引數覆蓋兩者。

foo.dyndbg="QUERY" 形式中,查詢必須排除 module foofoo 從引數名稱中提取,並應用於 QUERY 中的每個查詢,並且只允許每種型別的一個匹配規範。

dyndbg 選項是一個“偽”模組引數,這意味著

  • 模組不需要顯式定義它

  • 每個模組都會預設獲得它,無論它們是否使用 pr_debug

  • 它不會出現在 /sys/module/$module/parameters/ 中。 要檢視它,請 grep 控制檔案,或檢查 /proc/cmdline.

對於 CONFIG_DYNAMIC_DEBUG 核心,如果不再需要除錯訊息,則可以透過 debugfs 介面停用引導時給出的任何設定(或透過編譯期間的 -DDEBUG 標誌啟用)。

echo "module module_name -p" > /proc/dynamic_debug/control

示例

// enable the message at line 1603 of file svcsock.c
:#> ddcmd 'file svcsock.c line 1603 +p'

// enable all the messages in file svcsock.c
:#> ddcmd 'file svcsock.c +p'

// enable all the messages in the NFS server module
:#> ddcmd 'module nfsd +p'

// enable all 12 messages in the function svc_process()
:#> ddcmd 'func svc_process +p'

// disable all 12 messages in the function svc_process()
:#> ddcmd 'func svc_process -p'

// enable messages for NFS calls READ, READLINK, READDIR and READDIR+.
:#> ddcmd 'format "nfsd: READ" +p'

// enable messages in files of which the paths include string "usb"
:#> ddcmd 'file *usb* +p'

// enable all messages
:#> ddcmd '+p'

// add module, function to all enabled messages
:#> ddcmd '+mf'

// boot-args example, with newlines and comments for readability
Kernel command line: ...
  // see what's going on in dyndbg=value processing
  dynamic_debug.verbose=3
  // enable pr_debugs in the btrfs module (can be builtin or loadable)
  btrfs.dyndbg="+p"
  // enable pr_debugs in all files under init/
  // and the function parse_one, #cmt is stripped
  dyndbg="file init/* +p #cmt ; func parse_one +p"
  // enable pr_debugs in 2 functions in a module loaded later
  pc87360.dyndbg="func pc87360_init_device +p; func pc87360_find +p"

核心配置

動態除錯透過核心配置項啟用

CONFIG_DYNAMIC_DEBUG=y        # build catalog, enables CORE
CONFIG_DYNAMIC_DEBUG_CORE=y   # enable mechanics only, skip catalog

如果您不想全域性啟用動態除錯(即在某些嵌入式系統中),您可以將 CONFIG_DYNAMIC_DEBUG_CORE 設定為動態除錯的基本支援,並將 ccflags := -DDYNAMIC_DEBUG_MODULE 新增到您希望稍後動態除錯的任何模組的 Makefile 中。

核心 prdbg API

啟用動態除錯時,將對以下函式進行編目和控制

pr_debug()
dev_dbg()
print_hex_dump_debug()
print_hex_dump_bytes()

否則,它們預設情況下是關閉的; 在原始檔中使用 ccflags += -DDEBUG#define DEBUG 將適當地啟用它們。

如果未設定 CONFIG_DYNAMIC_DEBUG,則 print_hex_dump_debug() 只是 print_hex_dump(KERN_DEBUG) 的快捷方式。

對於 print_hex_dump_debug()/print_hex_dump_bytes(),格式字串是它的 prefix_str 引數,如果它是常量字串; 如果 prefix_str 是動態構建的,則為 hexdump