内核 Oops 日志经常很长,里面有寄存器、调用栈、模块列表、CPU 状态和一段反汇编。第一次看时如果直接跳到调用栈最后一个函数,很容易把“崩溃现场”误当成“根因”。

第一遍阅读的目标不是马上定位补丁,而是先回答几个基础问题:系统为什么停下来,在哪个上下文停下来,访问了什么地址,以及这个访问为什么不应该发生。

先看异常类型

日志开头通常会说明异常类型,例如空指针访问、非法地址访问、BUG、WARNING 或 page fault。

需要区分:

  • 空指针还是野指针
  • 读访问还是写访问
  • 用户地址还是内核地址
  • 同步异常还是异步错误
  • panic 是直接触发,还是 warning 后升级

如果地址接近 0x0,通常要怀疑空指针或结构体成员偏移。如果地址看起来像已经释放的对象,就要往生命周期和并发方向看。

IP 告诉你在哪里炸,不告诉你为什么炸

RIP 或其它架构上的异常 PC 指向真正触发异常的指令。它很重要,但它通常只回答“在哪里炸”。

根因可能在更早的位置:

  • 指针初始化失败
  • 错误路径没有清理状态
  • 引用计数提前归零
  • 回调在对象释放后仍然执行
  • probe 失败后还有异步 work 在跑

所以看到某个函数名后,不要急着认为这个函数有 bug。要继续看它拿到的输入是否已经坏了。

调用栈要结合上下文

同一个函数在不同上下文里意义不同。比如它是在进程上下文、软中断、硬中断、workqueue 还是 timer 里执行。

上下文会限制可能的原因:

  • 中断上下文不能睡眠
  • workqueue 常常暴露异步生命周期问题
  • timer 回调常见 use-after-free
  • probe/remove 相关栈要重点看资源释放顺序
  • 文件系统路径要看锁和引用关系

如果栈里出现 kworker,要继续确认 work 的来源。单看 process_one_work 没有意义。

寄存器能还原参数

很多架构会在 Oops 中打印通用寄存器。对于 x86_64,函数参数通常能从寄存器里反推。结合反汇编,可以判断哪个参数参与了非法访问。

这一步能把问题从“某函数崩了”缩小到“某个对象字段坏了”。

如果有调试符号,可以用:

addr2line -e vmlinux <address>

或者在 crash/gdb 中查看具体源码行。没有源码行时,也可以通过反汇编判断访问偏移。

第一遍只做判断框架

第一次读 Oops,最好先写下四句话:

  1. 异常类型是什么
  2. 崩溃指令访问了什么
  3. 当前上下文是什么
  4. 最可疑的对象是谁

这四句话写清楚后,后续分析会稳定很多。否则很容易被调用栈里的熟悉函数名带偏。

结尾

Oops 日志不是答案,而是现场记录。第一遍阅读的价值,是把混乱日志整理成可以验证的假设。

能稳定回答“在哪里、什么上下文、访问了什么、对象为什么会坏”,调试就已经走出了最容易误判的一步。