11、Logcat 与内核日志:dmesg 与 logcat 的协同使用
做 Android 系统开发,你迟早会遇到这么个场景:
App 闪退了,Logcat 里干干净净,啥报错都没有。或者系统突然重启了,Logcat 在重启那一刻就断了。
这时候你怎么办?
我刚开始做驱动移植那会儿,就吃过这个亏。盯着 Logcat 看了半天,死活找不到原因。后来老同事过来看了一眼,敲了条 dmesg 命令,问题直接浮出水面——内核报了个 OOM。
嗯,从那天起我就明白了:Logcat 和 dmesg 不是二选一,而是左右手。
11.1 它们到底有什么区别?
说白了,Logcat 管的是用户态,dmesg 管的是内核态。
你写的 Java 或 Kotlin 代码,调用的 Android Framework API,这些日志都走 Logcat。而驱动、内存管理、文件系统、电源管理这些底层模块,日志都走 dmesg。
| 对比维度 | Logcat | dmesg |
|---|---|---|
| 所属层级 | 用户空间(User Space) | 内核空间(Kernel Space) |
| 主要来源 | App、Framework、系统服务 | 驱动、内核模块、硬件中断 |
| 缓冲区大小 | 默认 256KB(可调) | 默认 128KB(可调) |
| 查看命令 | adb logcat |
adb shell dmesg |
| 重启后是否保留 | 不保留 | 不保留(但 last_kmsg 可保留) |
核心原则:用户态问题先看 Logcat,内核态问题先看 dmesg。如果 Logcat 突然断了,立刻去查 dmesg。
11.2 什么时候需要协同使用?
我个人习惯把问题分成三类,每类的排查路径都不一样。
场景一:App 闪退但 Logcat 无异常
这种情况我遇到过好几次。App 突然消失,Logcat 里只有正常的生命周期日志,没有任何 Crash 堆栈。
为什么会这样?
因为 App 不是自己崩溃的,而是被内核杀掉的。比如内存不足触发 LMK(Low Memory Killer),内核直接向进程发 SIGKILL。这种信号不会经过 Java 层的异常处理,Logcat 自然抓不到。
这时候 dmesg 里会有类似这样的记录:
[ 123.456789] lowmemorykiller: send sigkill to 12345 (com.example.app), adj 900, size 45678kB
我的排查套路:先 adb logcat -b crash 看有没有 Java 层崩溃,没有的话立刻 adb shell dmesg | grep -i "kill\|oom\|lowmemory"。
场景二:系统重启或死机
系统一旦重启,Logcat 缓冲区就清空了。你根本看不到重启前发生了什么。
但 dmesg 有个好东西——last_kmsg。如果内核配置了 CONFIG_PSTORE,重启前的内核日志会被保留下来。
adb shell cat /sys/fs/pstore/console-ramoops
我曾经排查过一个死机问题,Logcat 完全没用,全靠 last_kmsg 里的最后几行日志定位到是 GPU 驱动触发了 kernel panic。
注意:不是所有设备都支持 last_kmsg。如果这个文件不存在,可以试试 /sys/fs/pstore/ 目录下的其他文件,或者检查内核是否开启了 pstore 功能。
场景三:外设或传感器异常
比如触摸屏不灵、WiFi 连不上、蓝牙搜不到设备。这些问题的根因往往在驱动层。
Logcat 里可能只会报个 Service not connected 或者 Timeout,但真正的错误原因在 dmesg 里:
[ 456.789012] i2c_transfer: error -110 (timeout) on bus 2, addr 0x38
[ 456.789123] touch: failed to read firmware version
你想想看,如果只看 Logcat,你可能会去排查 Framework 层的服务绑定问题,方向完全错了。
11.3 协同使用的实战技巧
光知道概念不够,我给你几个真正能用的命令组合。
技巧一:时间戳对齐
Logcat 和 dmesg 的时间戳格式不一样。Logcat 用的是系统启动后的相对时间,dmesg 用的是内核启动后的秒数。
要协同分析,得先把它们对齐:
# 获取 Logcat 时间戳(相对时间)
adb logcat -v time | grep "com.example"
# 获取 dmesg 时间戳(内核秒数)
adb shell dmesg -T | grep "com.example"
dmesg -T 会把内核时间戳转换成可读的日期时间,这样你就能跟 Logcat 的时间对上号了。
技巧二:同时抓取
我建议你在复现问题的时候,同时开两个终端:
# 终端 1:抓 Logcat
adb logcat -v time > logcat.txt
# 终端 2:抓 dmesg
adb shell dmesg -w > dmesg.txt
dmesg -w 是实时监控模式,跟 logcat -v time 一样持续输出。这样问题复现时,两个日志文件都能抓到关键信息。
技巧三:关键字联合搜索
有些问题在两个日志里都有线索。比如 ANR:
# Logcat 里搜 ANR
grep -i "anr" logcat.txt
# dmesg 里搜 CPU 占用
grep -i "cpu\|sched" dmesg.txt
ANR 的原因可能是用户态死锁,也可能是内核态 CPU 资源被抢占。两边都查,才能定位根因。
11.4 一张图看懂协同流程
下面这张图是我自己总结的排查路径,你可以保存下来当参考。
11.5 避坑指南
最后分享几个我踩过的坑,你遇到了可以少走弯路。
坑一:dmesg 缓冲区溢出
dmesg 的环形缓冲区默认只有 128KB,日志量大的时候,早期的日志会被覆盖。我曾经排查一个间歇性死机问题,就是因为 dmesg 缓冲区太小,关键日志被冲掉了。
解决方法:在 kernel cmdline 里加大 log_buf_len,比如 log_buf_len=4M。
坑二:Logcat 和 dmesg 时间不同步
有些设备系统时间不准,或者内核时间跟用户态时间有偏差。直接对比时间戳可能会对不上。
解决方法:用 dmesg -T 转换时间格式,或者找一个两边都出现的事件作为锚点,比如某个驱动加载成功的日志。
我的小习惯:每次抓日志前,先在 Logcat 和 dmesg 里各打一条标记日志。比如在 Logcat 里 Log.d("DEBUG", "=== MARKER ==="),同时在 dmesg 里 echo "MARKER" > /dev/kmsg。这样两边的时间戳就能对齐了。
好了,Logcat 和 dmesg 的协同使用就讲到这里。记住一句话:用户态的问题 Logcat 会告诉你,内核态的问题 dmesg 会告诉你,但只有两者结合,才能告诉你完整的故事。