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 一张图看懂协同流程

下面这张图是我自己总结的排查路径,你可以保存下来当参考。

Logcat 与 dmesg 协同排查流程图 遇到系统问题 是否重启/死机? 是 / 否 查 last_kmsg /sys/fs/pstore/ 定位内核崩溃 panic / oops 同时抓取日志 logcat + dmesg 分析日志关联 时间戳对齐 输出:问题根因定位报告 用户态 / 内核态 / 协同问题 建议:问题复现时同时开启 logcat -v time 和 dmesg -w

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 会告诉你,但只有两者结合,才能告诉你完整的故事。