动态调试¶
简介¶
动态调试允许您动态地启用/禁用内核调试打印代码,以获取额外的内核信息。
如果存在 /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
文件中给出的参数一起传递给内核,顺序如下
通过
/etc/modprobe.d/*.conf
给出的参数options foo dyndbg=+pt options foo dyndbg # defaults to +p
引导参数中给出的
foo.dyndbg
,foo.
被剥离并传递foo.dyndbg=" func bar +p; func buz +mp"
传递给 modprobe 的参数
modprobe foo dyndbg==pmf # override previous settings
这些 dyndbg
查询按顺序应用,最后一个具有最终决定权。 这允许引导参数覆盖或修改 /etc/modprobe.d
中的参数(合理,因为 1 是系统范围的,2 是内核或引导特定的),modprobe 参数覆盖两者。
在 foo.dyndbg="QUERY"
形式中,查询必须排除 module foo
。foo
从参数名称中提取,并应用于 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
。