Skip to content

LV010-驱动调试

驱动程序出现 bug 时,除了 printk 打印信息,我们还可以怎么调试驱动?

一、相关调试函数

1. dump_stack()

1.1 函数说明

dump_stack() 函数声明如下:

c
extern asmlinkage void dump_stack(void) __cold;

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

c
/**
 * 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 源码
c
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() 函数定义如下:

c
#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 源码
c
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() 函数定义如下:

c
#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 源码
c
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() 函数定义如下:

c
/**
 *	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 源码
c
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