内核调用栈调试

更多内容可以加入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;
}
相关推荐
qetfw1 小时前
Debian Nginx + PHP-FPM 配置:FastCGI、站点目录与访问验证
linux·nginx·debian·php
zcmodeltech1 小时前
工程车模型多车型动作控制系统设计与实现方案——基于STM32与Modbus RTU的挖掘机、装载机、自卸车、起重机、电力工程车全场景控制方案,服务范围覆盖全国
分布式·stm32·单片机·嵌入式硬件·交互
Best-Wishes1 小时前
去除vmdk linux镜像密码
linux·运维·服务器
xianyuCcCcCCCcc1 小时前
OpenStack 核心组件原理与架构深度剖析
linux·运维·openstack
guo_wen_qiang1 小时前
一个好用的命令——查看文件大小
linux·macos
Zhang.Yi1 小时前
STM32-I2C
stm32·单片机·嵌入式硬件
LCG元1 小时前
STM32L431 低功耗实战:STOP2 模式 + LPUART 唤醒 + 实测电流对比
stm32·单片机·嵌入式硬件
Tassel_YUE1 小时前
非 ansible 主机批量执行命令方法:psssh 和 pscp(随手记)
linux·网络·ssh·scp·ansible
脚踏实地,坚持不懈!2 小时前
Android 上层卡顿在内核中的反应(基于 Linux v7.2.2)
android·linux·运维