Linux内核调试技术之动态调试

前言

使用printk的打印方式只能通过设置输出等级来进行控制,具备一定的局限性。在实际系统运行过程中,我们更希望能选择性地打开某些子系统或者模块的输出,为此内核提供了动态调试技术。内核中包括pr_debug、dev_dbg接口都使用了动态调试技术。

动态调试配置与使用

配置内核选项

要使用动态调试,需要在内核编译时打开动态调试开关,配置选项为CONFIG_DYNAMIC_DEBUG以及CONFIG_DEBUG_FS:
在这里插入图片描述在这里插入图片描述

挂载debugfs文件系统

在Linux系统启动后,手动执行命令挂载debugfs文件系统:

mount -t debugfs debugfs /sys/kernel/debug

执行完成后,查看/sys/kernel/debug目录,可以发现目录下有一个dynamic_debug的文件夹,里面有个control文件节点,这是动态调试提供的配置入口:
在这里插入图片描述

使用动态调试

控制动态调试信息打印的方法如下:

// 打开指定文件中所有的动态调试信息
echo  'file 文件名 +p' > /sys/kernel/debug/dynamic_debug/control

// 打开指定模块中所有的动态调试信息
echo  'module 模块名 +p' > /sys/kernel/debug/dynamic_debug/control

// 打开指定函数中所有的动态调试信息
echo  'func 函数名 +p' > /sys/kernel/debug/dynamic_debug/control

// 打开系统中所有的动态调试信息
echo -n '+p' > /sys/kernel/debug/dynamic_debug/control

// 关闭指定文件中所有的动态调试信息
echo  'file 文件名 -p' > /sys/kernel/debug/dynamic_debug/control

举例说明:
在这里插入图片描述

除了p选项外,动态调试也支持其它的选项,用于输出一些额外信息,如函数名、行号、模块名字以及线程ID等:

  • p:打开动态调试打印;
  • f:输出函数名;
  • l:输出行号;
  • m:输出模块名字;
  • t:输出线程ID。

动态调试基本原理

当配置了CONFIG_DYNAMIC_DEBUG选项,内核会在编译阶段把所有动态调试的使用信息记录下来,包括文件名路径、模块名、函数、输出所在行号以及要输出的打印等,这些信息我们可以通过/sys/kernel/debug/dynamic_debug/control文件节点查询出来:
在这里插入图片描述

通过查询control文件节点,我们获取到系统中包含的所有动态调试信息,通过配置指定字段,可以选择我们需要打印的信息。

pr_debug接口实现

这里通过pr_debug接口说明动态调试的实现,代码如下:

#if defined(CONFIG_DYNAMIC_DEBUG) || \
	(defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE))
#include <linux/dynamic_debug.h>

#define pr_debug(fmt, ...)			\
	dynamic_pr_debug(fmt, ##__VA_ARGS__)
#elif defined(DEBUG)
#define pr_debug(fmt, ...) \
	printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#else
#define pr_debug(fmt, ...) \
	no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__)
#endif

可以看到当配置了动态调试之后,pr_debug会调用dynamic_pr_debug接口来控制打印的输出:

#define dynamic_pr_debug(fmt, ...)				\
	_dynamic_func_call(fmt,	__dynamic_pr_debug,		\
			   pr_fmt(fmt), ##__VA_ARGS__)

#define _dynamic_func_call(fmt, func, ...)				\
	__dynamic_func_call(__UNIQUE_ID(ddebug), fmt, func, ##__VA_ARGS__)
	
#define __dynamic_func_call(id, fmt, func, ...) do {	\
	DEFINE_DYNAMIC_DEBUG_METADATA(id, fmt); // 根据打印方式和信息生成标识信息,与control节点的某一打印对应		\
	if (DYNAMIC_DEBUG_BRANCH(id))			\
		func(&id, ##__VA_ARGS__);		\
} while (0)

#define DYNAMIC_DEBUG_BRANCH(descriptor) \
	unlikely(descriptor.flags & _DPRINTK_FLAGS_PRINT) // 检查标志位,标志位通过debugfs可以进行设置

dynamic_pr_debug接口调用到最后会根据descriptor的标志位_DPRINTK_FLAGS_PRINT的设置情况,来判断是否进行输出。

相关参考

  • 《奔跑吧,Linux内核》
  • https://cloud.tencent.com/developer/article/1819284
  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值