内核调用栈调试

更多内容可以加入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;
}
相关推荐
whyutianict_vv5 分钟前
零基础转 AI 运维:投入清单、技术路线与核对表
linux·运维·人工智能·ai
无言听风雨丶6 分钟前
systemd 与信号 / 僵尸进程:Linux 进程管理的底层逻辑与排错
linux·服务器·网络
Sakura_j_h_17 分钟前
51 单片机驱动四位数码管:动态扫描、限流电阻与消影的取舍
单片机·嵌入式硬件·51单片机·嵌入式·数码管·动态扫描·限流电阻
DevOpenClub25 分钟前
高考一分一段 API:分数查位次、科类枚举与历史对照
linux·数据库·高考
小贾要学习37 分钟前
【C++后端开发项目】Reactor 模型 / C++ 仿写 muduo HTTP 服务器:让底层网络库真正跑起网页
linux·服务器·c++·http·网络编程·muduo·reactor模型
Blockbuater_drug1 小时前
OpenMM分子动力学模拟02 安装:Linux:conda 一条命令装通 GPU 加速的分子模拟环境
linux·conda·cuda·gpu驱动·pythonpath·openmm安装
程序员JerrySUN1 小时前
PCIe Gen5 SSD 为什么读得快、写得慢?从存储原理到 Linux 性能实测
linux·服务器·网络
Android系统攻城狮1 小时前
Linux FFmpeg深度解析之avcodec_parameters_copy调用流程与实战(二十五)
linux·运维·服务器·ffmpeg·音视频·车载音频
ITKEY_2 小时前
emacs 文件管理Dired, the Directory Editor
linux·编辑器·emacs
源图客2 小时前
解决CentOS操作系统网络异常问题
linux·运维·centos