1. 简介
在调试linux的驱动的时候,会遇到 dev_dbg (“xxx”); ,如何让他显示出来,是本片文章的重点目的。
2. 分析
dev_dbg 的源码
#if defined(CONFIG_DYNAMIC_DEBUG)
#define dev_dbg(dev, format, ...) \
do { \
dynamic_dev_dbg(dev, format, ##__VA_ARGS__); \
} while (0)
#elif defined(DEBUG)
#define dev_dbg(dev, format, arg...) \
dev_printk(KERN_DEBUG, dev, format, ##arg)
#else
#define dev_dbg(dev, format, arg...) \
({ \
if (0) \
dev_printk(KERN_DEBUG, dev, format, ##arg); \
})
#endif
看来想要显示,需要配置上 CONFIG_DYNAMIC_DEBUG 这个宏,打开配置的图形界面,make menuconfig,在其中搜索 CONFIG_DYNAMIC_DEBUG,结果如下
Location:
-> Kernel hacking
(1) -> printk and dmesg options
按照提示,配置上,重新编译内核,并烧写
> Kernel hacking > printk and dmesg options
[*] Enable dynamic printk() support
内核配置中选中 serial 模块
> Device Drivers > USB support > USB Gadget Support 下的
<M> Serial Gadget (with CDC ACM and CDC OBEX support)
编译内核
make -j8
3. 使用
当重新烧写了内核后,先用 mount 命令查看有没有挂载 debugfs,如果没有,按如下方式挂载
mount -t debugfs none /sys/kernel/debug
查看系统默认打印级别
源码中的值小于下边配置的阈值时会打印,打印级别从 0~7
cat /proc/sys/kernel/printk
7 4 1 7
4个数字分别对应
[控制台日志级别 默认的消息日志级别 最低的控制台日志级别 默认的控制台日志级别]
我们关注的时第一个,当源文件中打印级别数字小于 控制台日志级别 时,才会输出到dmesg中。
看来就是最高,不需要改动
需要查看以下文件进行详细了解
kernel/Documentation/dynamic-debug-howto.txt
我们打开想要查看的文件中的相关输出
echo -n 'file xxx.c [line xxx] +p' > /sys/kernel/debug/dynamic_debug/control
echo -n 'file drivers/usb/gadget/function/f_serial.c +flp' > /sys/kernel/debug/dynamic_debug/control
echo -n 'file drivers/usb/gadget/function/f_acm.c +flp' > /sys/kernel/debug/dynamic_debug/control
可以先查看能显示的输出
cat /sys/kernel/debug/dynamic_debug/control
当驱动加载,或者有打印时,dmesg就能显示了。
4. dynamic-debug-howto
翻译 kernel/Documentation/dynamic-debug-howto.txt 文件。
介绍
===
这个文档描述如何使用动态调试(ddebug)特性。
动态调试设计的目的是允许你动态的打开或关闭内核代码的额外信息。 现在, 假如
CONFIG_DYNAMIC_DEBUG 已经设置好了, 那么所有 pr_debug()/dev_debug() 之类的函数调用都可以动态的在代码里使能。
动态调试有很多有用的特性:
* 简单的查询语句允许通过下边和配合0和1开启或者关闭调试状态:
- 源文件名
- 函数名
- 行号(可以是一个范围)
- module 名
- 格式化字符串
* 提供一个 debugfs 控制文件: <debugfs>/dynamic_debug/control,可以读这个文件显示能打开的调试列表,来帮助指导你。<debugfs> 一般是 /sys/kernel/debug
控制动态调试的行为
===============
pr_debug()/dev_dbg() 的行为是通过控制'debugfs'文件系统上写一个控制文件。因此,你需要先挂载 debugfs 文件系统(mount -t debugfs none /sys/kernel/debug)。然后,我们看控制文件: <debugfs>/dynamic_debug/control。比如,如果你想打开 'svcsock.c'的1603行 的打印信息:
nullarbor:~ # echo 'file svcsock.c line 1603 +p' > <debugfs>/dynamic_debug/control
如果语法错误,写会失败,如下:
nullarbor:~ # echo 'file svcsock.c wtf 1 +p' > <debugfs>/dynamic_debug/control
-bash: echo: write error: Invalid argument
查看动态调试的行为
===============
你可以查看当前可以配置的输出:
nullarbor:~ # cat <debugfs>/dynamic_debug/control
# filename:lineno [module]function flags format
/usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svc_rdma.c:323 [svcxprt_rdma]svc_rdma_cleanup =_ "SVCRDMA Module Removed, deregister RPC RDMA transport\012"
/usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svc_rdma.c:341 [svcxprt_rdma]svc_rdma_init =_ "\011max_inline : %d\012"
/usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svc_rdma.c:340 [svcxprt_rdma]svc_rdma_init =_ "\011sq_depth : %d\012"
/usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svc_rdma.c:338 [svcxprt_rdma]svc_rdma_init =_ "\011max_requests : %d\012"
...
你也可以应用标准的Unix文本过滤命令来过滤这些数据, 例如。
nullarbor:~ # grep -i rdma <debugfs>/dynamic_debug/control | wc -l 62
nullarbor:~ # grep -i tcp <debugfs>/dynamic_debug/control | wc -l 42
在第三列显示了调试状态下的目前使能的标志(下边有标志位的定义)。如果想使用默认值,不想使能任何的标志,使用"-"。你可以查看任何不是默认标志的状态位通过下面的命令:
nullarbor:~ # awk '$3 != "=_"' <debugfs>/dynamic_debug/control
# filename:lineno [module]function flags format
/usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svcsock.c:1603 [sunrpc]svc_send p "svc_process: st_sendto returned %d\012"
命令语言参考
==========
在词汇层面上,一个命令由一系列由空格分割的单词组成。所以这些是等价的:
nullarbor:~ # echo -c 'file svcsock.c line 1603 +p' > <debugfs>/dynamic_debug/control
nullarbor:~ # echo -c ' file svcsock.c line 1603 +p ' > <debugfs>/dynamic_debug/control
nullarbor:~ # echo -n 'file svcsock.c line 1603 +p' > <debugfs>/dynamic_debug/control
命令是通过write()提交的。多个命令可以写在一起,需要使用';'或'\n'隔开。
~# echo "func pnpacpi_get_resources +p; func pnp_assign_mem +p" \
> <debugfs>/dynamic_debug/control
如果你的命令太多,也可以把这些命令放在文件中
~# cat query-batch-file > <debugfs>/dynamic_debug/control
在语法层面上,一个命令有一系列的规格匹配组成,随后由一个标记来改变这风格。
command ::= match-spec* flags-spec
match-spec被用来选择一个 pr_debug() 的子集来套用flags-spec。把他们当做彼此之间的每对做隐式 与 查询。注意一个空的match_specs列表是有可能的,但不是非常有用,因为它不会匹配任何调用点的调试子句。
一个匹配规范由一个关键字组成,关键字控制被比较的调用点的属性和要比较的值。可能关键字是:
match-spec ::= 'func' string |
'file' string |
'module' string |
'format' string |
'line' line-range
line-range ::= lineno |
'-'lineno |
lineno'-' |
lineno'-'lineno
// 注意: line-range 不能包含空格, 例如
// "1-30" 是有效的范围,但是"1 - 30"无效。
lineno ::= unsigned-int
每个关键字的含义:
func
给定的字符串和每个调用点的函数名进行比较。例如:
func svc_tcp_accept
file
给定的字符串跟每个调用点的源文件的全路径名或相对路径名比较。例如:
file svcsock.c
file kernel/freezer.c
file /usr/src/packages/BUILD/sgi-enhancednfs-1.4/default/net/sunrpc/svcsock.c
module
给定的字符串跟每个调用点的模块名进行比较。模块名是“lsmod”里显示的字符串,举例来说,没有路径,没有.ko后缀,名字中的'-'改为'_'。
module sunrpc
module nfsd
format
给定的字符串在动态调试字符串中查找。注意字符串不必完全匹配format,只需要一部分。空格和其他的特殊字符用c的八进制语法转义,例如空字符是 \040。作为选择,这个字符串可以用双引号或单引号包含起来。
例子:
format svcrdma: // 大多数 NFS/RDMA 服务器的 dprintks
format readahead // 一些在预加载缓存里的 dprintks
format nfsd:\040SETATTR // 一个使用空格来匹配格式的方式
format "nfsd: SETATTR" // 一个整洁的方法来用空格匹配格式
format 'nfsd: SETATTR' // 和上边相同
line
给定的行号,或者行的范围和每个dprintk()调用点的行号进行比较。单独的行号会精确的对比,范围的会包含范围的边界,一个空的开头,表示从第一行开始。一个空在范围的末尾,表示到结尾。例如:
line 1603 // 1603 行
line 1600-1605 // 1600 到 1605 行
line -1605 // 1 到 1605 行
line 1600- // 1600 到 文件的结尾
指定的标志通过以下的一个或多个标志进行改变操作,变化操作如下:
- 移除给定的标记
+ 加入给定的标记
= 设置标记到给定的标记上
标记如下:
f 在打印信息中包含函数名
l 在打印信息中包含行号
m ...模块名
p 产生一个printk()消息到显示系统启动日志
t ...包含不是从中断上下文差生的线程ID
注意正则表达式 ^[-+=][flmpt]+$ 匹配一个标记规范。
同样也要注意,没有便捷的语法来一次性移除所有的标记,你需要使用"-flmpt"。
引导进程中的调试信息
=================
在用户空间和debugfs存在前,使能引导进程中的调试信息,使用boot参数:ddebug_query="QUERY"
QUERY遵循上述语法,但是一定不能超过1023字节。调试信息的使能是arch_initcall做的。这样你可以使能arch_initcall之后的所有消息经过这个boot的参数。
在一个x86系统中,例如ACPI启用是一个subsys_initcall并且如果你的机器(通常是一台笔记本)有一个嵌入式的控制器,ddebug_query=“fileec.c+p”将会在ACPI装备期间显示早期的嵌入式控制器事务。PCI(或其他设备)的初始化也是使用这个引导参数来调试的热门候选。
实例
===
// 使能文件 svcsock.c 1603 行信息
nullarbor:~ # echo -n 'file svcsock.c line 1603 +p' > <debugfs>/dynamic_debug/control
// 使能文件 svcsock.c 所有信息
nullarbor:~ # echo -n 'file svcsock.c +p' > <debugfs>/dynamic_debug/control
// 使能 NFS服务模块 所有的信息
nullarbor:~ # echo -n 'module nfsd +p' > <debugfs>/dynamic_debug/control
// 使能函数 svc_process() 中的所有12条信息
nullarbor:~ # echo -n 'func svc_process +p' > <debugfs>/dynamic_debug/control
// 不再显示 函数svc_process()的所有12条信息
nullarbor:~ # echo -n 'func svc_process -p' > <debugfs>/dynamic_debug/control
// 使能 NFS 调用的所有以READ开始的信息.
nullarbor:~ # echo -n 'format "nfsd: READ" +p' > <debugfs>/dynamic_debug/control