2、崩溃日志获取:串口、pstore、last_kmsg、dmesg

日志是内核崩溃分析的「第一现场」。没有日志,再厉害的分析技巧都是空谈。

我刚开始做内核稳定性时,就吃过这个亏。有一次客户反馈设备频繁死机,我远程连上去,结果系统已经彻底卡死,dmesg 也刷不出来。后来才知道,原来还有 pstore 这种「死后验尸」的机制。

今天我们就来聊聊,怎么把这些日志「捞」出来。

2.1 串口日志:最原始的调试手段

串口,说白了就是一根线,一头连开发板,一头连电脑。系统从 bootloader 开始的所有输出,都会通过串口打印出来。

我个人习惯在项目初期就焊好串口座子。为什么?因为一旦系统在启动阶段就挂了,其他手段全废,只有串口能救你。

串口配置要点:

  • 波特率:通常是 115200 或 921600
  • 硬件流控:关闭(RTS/CTS 不用管)
  • 数据位:8,停止位:1,无校验

在 Linux 内核中,串口驱动通常叫 uart。你要确保内核开启了 CONFIG_SERIAL_8250_CONSOLE 这个选项。否则,串口就是个摆设。

# 在电脑端用 minicom 或 screen 连接
sudo minicom -D /dev/ttyUSB0 -b 115200

# 或者用 screen
sudo screen /dev/ttyUSB0 115200

嗯,这里要注意:串口日志是「实时流」,不会自动保存。你得在电脑端开个日志记录功能。我一般用 script 命令或者 minicom 的 -C 参数。

我的经验:串口日志里经常夹杂着乱码,别慌。通常是波特率不匹配,或者串口线太长信号衰减了。换根短线试试。

2.2 pstore/ramoops:死后验尸的利器

系统崩溃后,内存里的数据会丢失。但 pstore 机制会在内存中保留一块「保留区」,专门存放最后一次崩溃的日志。

说白了,pstore 就是内核的「黑匣子」。它把 oops 信息、console 日志、甚至 ftrace 数据都存到 RAM 里。下次重启后,你可以在 /sys/fs/pstore 目录下找到它们。

# 查看 pstore 目录
ls -l /sys/fs/pstore/

# 典型的输出
console-ramoops-0
dmesg-ramoops-0
ftrace-ramoops-0

ramoops 是 pstore 的一种后端实现。它依赖 ramoops 驱动,需要在设备树或内核参数中配置保留内存的大小和地址。

内核配置要求:

  • CONFIG_PSTORE=y
  • CONFIG_PSTORE_RAM=y
  • CONFIG_PSTORE_CONSOLE=y
  • CONFIG_PSTORE_DMESG=y

设备树中的配置示例:

reserved-memory {
    ramoops@0xbf000000 {
        compatible = "ramoops";
        reg = <0x0 0xbf000000 0x0 0x00100000>;
        record-size = <0x4000>;
        console-size = <0x4000>;
        ftrace-size = <0x4000>;
        pmsg-size = <0x4000>;
    };
};

我曾经遇到过一个坑:ramoops 的保留内存地址和别的驱动冲突了,导致系统启动时直接 panic。后来我改用 memmap 内核参数手动预留内存,才搞定。

注意:pstore 日志在重启后只能读取一次。如果你不小心 rm 掉了,那就再也找不回来了。建议第一时间 cp 到安全位置。

2.3 last_kmsg:曾经的经典,现在的遗产

如果你用过 Android 4.x 或更早的版本,一定对 /proc/last_kmsg 不陌生。它本质上和 pstore 干的是同一件事——保存最后一次内核日志。

但为什么说它是「遗产」?因为从 Android 5.0 开始,Google 就逐步废弃了 last_kmsg,全面转向 pstore/ramoops。现在的新内核里,你基本看不到 /proc/last_kmsg 了。

# 老版本 Android 上查看
cat /proc/last_kmsg > /sdcard/last_kmsg.txt

如果你还在维护老旧项目,可能会遇到 last_kmsg 为空的情况。别慌,检查一下内核有没有开启 CONFIG_ANDROID_RAM_CONSOLECONFIG_ANDROID_RAM_CONSOLE_ENABLE_VERBOSE

我的建议:新项目直接上 pstore,别折腾 last_kmsg 了。除非你手头有 legacy 设备,否则没必要在这上面浪费时间。

2.4 dmesg:活着的系统日志

dmesg 是内核的环形缓冲区,记录了从系统启动到当前时刻的所有内核日志。它最大的优点是——系统还活着的时候,你可以随时查看。

# 查看所有 dmesg 日志
dmesg

# 查看最后 100 行
dmesg | tail -100

# 实时监控新日志
dmesg -w

dmesg 的缓冲区大小是有限的。默认情况下,它可能只有 128KB 或 256KB。如果日志太多,早期的内容会被覆盖掉。

调整 dmesg 缓冲区大小:

  • 内核配置:CONFIG_LOG_BUF_SHIFT(值越大,缓冲区越大)
  • 运行时调整:echo 1048576 > /proc/sys/kernel/printk_devkmsg(部分内核支持)

我个人习惯在调试阶段把 CONFIG_LOG_BUF_SHIFT 设成 18(即 256KB)或 20(1MB)。等产品稳定了再改小,省内存。

你想想看,dmesg 和 pstore 有什么区别?

  • dmesg:系统活着时看,重启后丢失
  • pstore:系统死后看,重启后还在

两者是互补关系。我一般先看 dmesg 找线索,如果系统已经挂了,就去翻 pstore。

注意:dmesg 日志里可能包含敏感信息(比如内核地址、文件路径)。发布给客户前,记得用 dmesg -l 过滤一下级别,或者用 cat /dev/kmsg 配合 klogd 做日志脱敏。

2.5 实战:一次完整的日志获取流程

假设你拿到一台崩溃的设备,该怎么做?我一般按这个顺序来:

  1. 先看串口:如果设备还能输出,串口是最快的。直接看最后几行,往往就能定位到 panic 的调用栈。
  2. 再翻 pstore:重启设备,进系统后立刻 cp -r /sys/fs/pstore /data/。然后慢慢分析。
  3. 检查 last_kmsg:如果是老设备,顺手 cat /proc/last_kmsg 看看。
  4. 最后看 dmesg:如果系统还活着,dmesg > /data/dmesg.log 保存一份。

我曾经遇到一个 case:串口完全没输出,pstore 也是空的。后来发现是硬件工程师把串口 TX 引脚接错了,而 pstore 的保留内存被 bootloader 覆盖了。嗯,这种硬件问题,日志工具再强也救不了。

避坑指南:拿到日志后,第一时间用 md5sum 计算哈希值。防止后续分析时不小心改动了文件,导致结论出错。我吃过这个亏,不骗你。

好了,日志获取就聊到这里。下一章我们讲讲怎么从这些日志里提取关键信息,比如调用栈、寄存器值、异常类型。到时候你会发现,日志里藏着很多「暗语」,读懂了它们,问题就解决了一半。