动态调试

简介

动态调试允许您动态地启用/禁用内核调试打印代码,以获取额外的内核信息。

如果存在 /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