动态调试

简介

动态调试允许您动态启用/禁用内核调试打印代码以获取其他内核信息。

如果存在 /proc/dynamic_debug/control,则您的内核具有动态调试功能。您需要 root 权限 (sudo su) 才能使用此功能。

动态调试提供

  • 内核中所有 prdbg 的目录。使用 cat /proc/dynamic_debug/control 查看它们。

  • 一种简单的查询/命令语言,可通过选择以下任意 0 或 1 个组合来更改 prdbg

    • 源文件名

    • 函数名

    • 行号(包括行号范围)

    • 模块名称

    • 格式字符串

    • 类名(由每个模块已知/声明)

注意:要实际在控制台上获取调试打印输出,您可能需要调整内核的 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"

第三个以空格分隔的列显示当前标志,前面有一个 =,以便于与 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

匹配规范从目录中选择 prdbg,然后对其应用标志规范,所有约束都一起进行 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_name 的列表。如果为模块找到了 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 标志才有意义,其他标志将被忽略。

请注意,正则表达式 ^[-+=][fslmpt_]+$ 匹配标志规范。要一次清除所有标志,请使用 =_-fslmpt

启动过程中的调试消息

要在启动过程中(甚至在用户空间和 debugfs 存在之前)激活核心代码和内置模块的调试消息,请使用 dyndbg="QUERY"module.dyndbg="QUERY"。QUERY 遵循上述语法,但不得超过 1023 个字符。您的引导加载程序可能会施加较低的限制。

这些 dyndbg 参数在早期初始化调用的一部分中,在处理 ddebug 表之后立即处理。因此,您可以通过此引导参数启用在此早期初始化调用之后运行的所有代码中的调试消息。

例如,在 x86 系统上,ACPI 启用是一个 subsys_initcall,并且

dyndbg="file ec.c +p"

如果您的机器(通常是笔记本电脑)具有嵌入式控制器,则将在 ACPI 设置期间显示早期的嵌入式控制器事务。PCI(或其他设备)初始化也是使用此引导参数进行调试的热门选择。

如果 foo 模块不是内置的,foo.dyndbg 仍然会在启动时被处理,但不会生效,当模块稍后加载时会被重新处理。单独的 dyndbg= 仅在启动时被处理。

模块初始化时的调试信息

当调用 modprobe foo 时,modprobe 会扫描 /proc/cmdline 中的 foo.params,去除 foo.,并将其与 modprobe 参数或 /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 中的每个查询,并且每种类型只允许 1 个匹配规范。

dyndbg 选项是一个“伪”模块参数,这意味着

  • 模块不需要显式定义它

  • 每个模块都默认拥有它,无论它们是否使用 pr_debug

  • 它不会出现在 /sys/module/$module/parameters/ 中。要查看它,请 grep 控制文件,或检查 /proc/cmdline.

对于 CONFIG_DYNAMIC_DEBUG 内核,启动时给出的任何设置(或在编译期间通过 -DDEBUG 标志启用的设置)如果不再需要调试消息,可以通过 debugfs 接口稍后禁用

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_DEBUGprint_hex_dump_debug() 只是 print_hex_dump(KERN_DEBUG) 的快捷方式。

对于 print_hex_dump_debug()/print_hex_dump_bytes(),格式字符串是它的 prefix_str 参数(如果它是常量字符串);或者在 prefix_str 是动态构建的情况下为 hexdump