
更多内容可以加入Linux系统知识库套餐(教程+视频+答疑)
驱动跑飞了?不知道代码执行路径?调用栈(Call Stack) 是调试的杀手锏。从 dump_stack 到 panic,从轻量打印到系统崩溃,本文带你掌握内核调用栈调试的四大武器。
一、为什么需要调用栈?
当驱动出现异常时,光知道"出错了"是不够的,我们需要知道:
🤔 错误是从哪条路径过来的?
🤔 是谁调用了出问题的函数?
🤔 完整的函数调用链是什么?
调用栈(Call Stack)就能回答这些问题。它记录了函数调用的层级关系,让你一眼看清代码执行路径。
二、四大调试武器对比
内核提供了多种调用栈相关的调试手段,从轻到重排列:
| 函数 | 严重程度 | 是否打印栈 | 系统是否继续运行 | 适用场景 |
|---|---|---|---|---|
dump_stack() |
轻微 | 是 | 是 ✓ | 调试打印,查看执行路径 |
WARN_ON(cond) |
警告 | 是 | 是 ✓ | 不应该发生但可恢复的情况 |
BUG_ON(cond) |
严重 | 是 | 否 ✗ | 严重错误,必须停止 |
panic() |
致命 | 是 | 否 ✗ | 系统无法继续运行 |
三、dump_stack() --- 轻量打印
dump_stack() 是最温和的调试手段,它只打印当前调用栈,系统继续正常运行。
使用示例:
#include <linux/kernel.h>
static int my_driver_probe(struct platform_device *pdev)
{
// 想看这个函数是怎么被调用的
dump_stack();
dev_info(&pdev->dev, "probe start\n");
// ... 驱动代码
return 0;
}
输出示例:
Call Trace:
[] dump_stack+0x8c/0xd4
[] my_driver_probe+0x24/0x120 [my_driver]
[] platform_drv_probe+0x58/0xc8
[] driver_probe_device+0x20c/0x3e4
[] bus_for_each_drv+0x88/0x94
[] __device_attach+0xb0/0x148
[] device_initial_probe+0x20/0x28
[] bus_probe_device+0x98/0xa4
[] deferred_probe_work_func+0x8c/0x9c
[] process_one_work+0x1cc/0x344
[] worker_thread+0x2c0/0x408
[] kthread+0xe4/0xf8
[] ret_from_fork+0x10/0x1c
💡 读栈技巧:从上往下看,最上面是当前函数,越往下越接近调用源头。
四、WARN_ON() --- 警告检测
WARN_ON(condition) 当条件为真时,打印警告信息和调用栈,但系统继续运行。
// 如果指针为NULL,打印警告
WARN_ON(ptr == NULL);
// WARN 变种
WARN(condition, "警告信息: %d\n", value);
WARN_ON_ONCE(condition); // 只警告一次,避免刷屏
⚠️ 适用场景:检测到不应该发生的情况,但系统还能继续运行。常用于参数校验、状态检查。
五、BUG_ON() --- 致命错误
BUG_ON(condition) 当条件为真时,触发内核 oops,打印调用栈后系统崩溃(当前 CPU 停止调度)。
// 严重错误,必须停止
BUG_ON(!dev);
// 等价写法
if (!dev)
BUG();
💀 适用场景:遇到无法恢复的严重错误,继续运行可能导致更严重后果。谨慎使用!
六、panic() --- 系统崩溃
panic() 是最严重的级别,整个系统停止运行,打印崩溃信息。
panic("系统无法继续运行: %s\n", reason);
通常由内核在极其严重的情况下自动调用,驱动开发中一般不直接使用。
七、实战调试场景
场景1:不知道函数被谁调用
// 在怀疑的函数里加 dump_stack
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
dump_stack(); // 看看中断上下文的调用链
return IRQ_HANDLED;
}
场景2:参数合法性检查
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
// 不应该收到空指针,但万一收到了打印警告
if (WARN_ON(file->private_data == NULL))
return -EINVAL;
// ... 正常处理
}
场景3:状态异常检测
static int my_suspend(struct device *dev)
{
struct my_data *data = dev_get_drvdata(dev);
// 休眠时不应该还有未完成的请求
WARN_ON(data->pending_req > 0);
// 正常休眠流程
return 0;
}