内核调用栈调试

更多内容可以加入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;
}
相关推荐
Android系统攻城狮4 小时前
Linux Gstreamer深度解析之gst_audio_ring_buffer_delay调用流程与实战(七十二)
linux·运维·服务器·gstreamer音视频·音视频进阶
快乐的韭菜根根5 小时前
ubuntu 安装slurm并配置
linux·运维·ubuntu
倒头就睡的小比特6 小时前
Linux基础指令3
linux·服务器
巴渝小禹7 小时前
单片机死循环delay_us()计算方法
单片机·嵌入式硬件
梦帮科技7 小时前
Kali Linux Scapy 畸形报文构造学——深度解剖非法 TCP Flags 与反常序列号
linux·爬虫·神经网络·算法·数学建模·系统架构·模拟退火算法
半杯咖啡半行码8 小时前
Linux 驱动开发入门:内核模块编程基础
linux·服务器·网络
别动我齐刘海8 小时前
Linux 系统调试学习2
linux·运维·c++·人工智能·学习·机器学习·机器人
龙虾电话8 小时前
语音机器人打断不进去:VAD 双阈值迟滞里一个共用的 bool,和它引发的连锁误判
linux
honsor8 小时前
机房IP化改造首选:以太网温湿度传感器,PoE供电+Modbus TCP一网搞定
linux·运维·服务器
科芯创展8 小时前
XZ3445 高集成升压/升降压DC-DC电源芯片技术解析
单片机·嵌入式硬件