LV06-09-调试与优化-02-驱动调试

驱动程序出现bug时,除了printk打印信息,我们还可以怎么调试驱动?若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
PC端开发环境 Windows Windows11
Ubuntu Ubuntu20.04.2的64位版本
VMware® Workstation 17 Pro 17.6.0 build-24238078
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
Win32DiskImager Win32DiskImager v1.0
Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板
uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04
linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
分类 网址 说明
官方网站 https://www.arm.com/ ARM官方网站,在这里我们可以找到Cotex-Mx以及ARMVx的一些文档
https://www.nxp.com.cn/ NXP官方网站
https://www.nxpic.org.cn/NXP 官方社区
https://u-boot.readthedocs.io/en/latest/u-boot官网
https://www.kernel.org/linux内核官网
点击查看相关文件下载
分类 网址 说明
NXP https://github.com/nxp-imx NXP imx开发资源GitHub组织,里边会有u-boot和linux内核的仓库
nxp-imx/linux-imx/releases/tag/v4.19.71 NXP linux内核仓库tags中的v4.19.71
nxp-imx/uboot-imx/releases/tag/rel_imx_4.19.35_1.1.0 NXP u-boot仓库tags中的rel_imx_4.19.35_1.1.0
I.MX6ULL i.MX 6ULL Applications Processors for Industrial Products I.MX6ULL 芯片手册(datasheet,可以在线查看)
i.MX 6ULL Applications ProcessorReference Manual I.MX6ULL 参考手册(下载后才能查看,需要登录NXP官网)
Source Code https://elixir.bootlin.com/linux/latest/source linux kernel源码
kernel/git/stable/linux.git - Linux kernel stable tree linux kernel源码(官网,tag 4.19.71)
https://elixir.bootlin.com/u-boot/latest/source uboot源码

一、相关调试函数

1. dump_stack()

1.1 函数说明

dump_stack()函数声明如下:

1
extern asmlinkage void dump_stack(void) __cold;

函数实际定义在 dump_stack.c - lib/dump_stack.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* dump_stack - dump the current task information and its stack trace
*
* Architectures can override this implementation by implementing its own.
*/
#ifdef CONFIG_SMP
static atomic_t dump_lock = ATOMIC_INIT(-1);

asmlinkage __visible void dump_stack(void)
{
// ......
local_irq_save(flags);
// ......
__dump_stack();
// ......
local_irq_restore(flags);
}
#else
asmlinkage __visible void dump_stack(void)
{
__dump_stack();
}
#endif
EXPORT_SYMBOL(dump_stack);

这个函数是打印内核调用堆栈, 并打印函数的调用关系。

1.2 使用实例

1.2.1 demo源码

1
2
3
4
5
6
7
8
9
static int scdev_create(_CHAR_DEVICE *p_chrdev)
{
// ......
/* 初始化等待队列头 */
init_waitqueue_head(&p_chrdev->r_wait);
dump_stack(); // 打印函数的调用关系
//PRT("scdev_create %s success!\n", p_chrdev->dev_name);
return 0;
}

1.2.2 开发板测试

如下图所示,在加载驱动的时候会打印出这个函数相关的一些调用关系:

image-20250214095301048

2. WARN_ON()

2.1 函数说明

WARN_ON()函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifdef CONFIG_BUG

#ifndef WARN_ON
#define WARN_ON(condition) ({ \
int __ret_warn_on = !!(condition); \
if (unlikely(__ret_warn_on)) \
__WARN(); \
unlikely(__ret_warn_on); \
})
#endif

#else

#ifndef HAVE_ARCH_WARN_ON
#define WARN_ON(condition) ({ \
int __ret_warn_on = !!(condition); \
unlikely(__ret_warn_on); \
})
#endif

#endif

在括号中的条件成立时, 内核会抛出栈回溯, 打印函数的调用关系。 通常用于内核抛出一个警告, 暗示某种不太合理的事情发生了。WARN_ON 实际上也是调用 dump_stack, 只是多了参数 condition 判断条件是否成立, 例如 WARN_ON (1)则条件判断成功, 函数会成功执行。

2.2 使用实例

2.2.1 demo源码

1
2
3
4
5
6
7
8
9
static int scdev_create(_CHAR_DEVICE *p_chrdev)
{
// ......
/* 初始化等待队列头 */
init_waitqueue_head(&p_chrdev->r_wait);
WARN_ON(1);
//PRT("scdev_create %s success!\n", p_chrdev->dev_name);
return 0;
}

2.2.2 开发板测试

如下图所示,在加载驱动的时候会打印出这个函数相关的一些调用关系:

image-20250214100838023

3. BUG_ON()

3.1 函数说明

BUG_ON()函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifdef CONFIG_BUG

#ifndef HAVE_ARCH_BUG_ON
#define BUG_ON(condition) do { if (unlikely(condition)) BUG(); } while (0)
#endif

#else /* !CONFIG_BUG */

#ifndef HAVE_ARCH_BUG_ON
#define BUG_ON(condition) do { if (condition) BUG(); } while (0)
#endif

#endif

内核中有许多地方调用类似 BUG_ON()的语句, 它非常像一个内核运行时的断言, 意味着本来不该执行到 BUG_ON()这条语句, 一旦 BUG_ON()执行内核就会立刻抛出 oops, 导致栈的回溯和错误信息的打印。 大部分体系结构把 BUG()和 BUG_ON()定义成某种非法操作, 这样自然会产生需要的 oops。 参数 condition 判断条件是否成立, 例如 BUG_ON (1)则条件判断成功,函数会成功执行。

3.2 使用实例

3.2.1 demo源码

1
2
3
4
5
6
7
8
9
static int scdev_create(_CHAR_DEVICE *p_chrdev)
{
// ......
/* 初始化等待队列头 */
init_waitqueue_head(&p_chrdev->r_wait);
BUG_ON(1);
//PRT("scdev_create %s success!\n", p_chrdev->dev_name);
return 0;
}

3.2.2 开发板测试

如下图所示,在加载驱动的时候会打印出栈相关的信息,并产生一个段错误,但是实际上并没有崩溃:

image-20250214103523849

4. panic()

4.1 函数说明

panic() 函数定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* panic - halt the system
* @fmt: The text string to print
*
* Display a message, then perform cleanups.
*
* This function never returns.
*/
void panic(const char *fmt, ...)
{
// ......
}

EXPORT_SYMBOL(panic);

该函数输出打印会造成系统死机并将函数的调用关系以及寄存器值都打印出来。

4.2 使用实例

4.2.1 demo源码

1
2
3
4
5
6
7
8
9
static int scdev_create(_CHAR_DEVICE *p_chrdev)
{
// ......
/* 初始化等待队列头 */
init_waitqueue_head(&p_chrdev->r_wait);
panic("!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
//PRT("scdev_create %s success!\n", p_chrdev->dev_name);
return 0;
}

4.2.2 开发板测试

如下图所示,在加载驱动的时候会打印出栈相关的信息,然后系统崩溃,终端也进不去了:

image-20250214104357023

二、驱动调试demo

demo源码可以看这里:10_driver_debug/02_debug_demo · 苏木/imx6ull-driver-demo - 码云 - 开源中国