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=yCONFIG_PSTORE_RAM=yCONFIG_PSTORE_CONSOLE=yCONFIG_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_CONSOLE 或 CONFIG_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 实战:一次完整的日志获取流程
假设你拿到一台崩溃的设备,该怎么做?我一般按这个顺序来:
- 先看串口:如果设备还能输出,串口是最快的。直接看最后几行,往往就能定位到 panic 的调用栈。
- 再翻 pstore:重启设备,进系统后立刻
cp -r /sys/fs/pstore /data/。然后慢慢分析。 - 检查 last_kmsg:如果是老设备,顺手
cat /proc/last_kmsg看看。 - 最后看 dmesg:如果系统还活着,
dmesg > /data/dmesg.log保存一份。
我曾经遇到一个 case:串口完全没输出,pstore 也是空的。后来发现是硬件工程师把串口 TX 引脚接错了,而 pstore 的保留内存被 bootloader 覆盖了。嗯,这种硬件问题,日志工具再强也救不了。
避坑指南:拿到日志后,第一时间用 md5sum 计算哈希值。防止后续分析时不小心改动了文件,导致结论出错。我吃过这个亏,不骗你。
好了,日志获取就聊到这里。下一章我们讲讲怎么从这些日志里提取关键信息,比如调用栈、寄存器值、异常类型。到时候你会发现,日志里藏着很多「暗语」,读懂了它们,问题就解决了一半。