25、dumpstate与BugReport:系统诊断信息收集、ANR/Tombstone分析与调试
做Android底层开发这些年,我遇到过最头疼的问题就是——系统突然卡死,或者App莫名其妙闪退。你盯着logcat看半天,啥也没发现。这时候,BugReport和dumpstate就是你的救命稻草。
说白了,BugReport就是系统的一份“体检报告”。它把CPU、内存、进程、日志、堆栈等信息全部打包。而dumpstate,就是生成这份报告的核心引擎。
我个人习惯,遇到疑难杂症第一件事就是:adb bugreport。别急着看代码,先拿报告。
25.1 dumpstate:BugReport的幕后英雄
dumpstate是一个C++编写的系统服务。它的任务很简单:收集所有能收集的诊断信息。你想想看,系统运行着上百个进程,每个进程都有状态。dumpstate要做的就是把这些状态“dump”出来。
它的工作流程大致如下:
// 伪代码示意
dumpstate() {
collect_kernel_info(); // dmesg, /proc
collect_system_properties();// build.prop
collect_process_info(); // ps, top
collect_anr_traces(); // /data/anr/
collect_tombstones(); // /data/tombstones/
collect_logcat(); // main, system, events
collect_dropbox(); // 系统关键事件
package_into_zip(); // 打包成zip
}
嗯,这里要注意:dumpstate的执行时间可能很长。如果系统已经严重卡顿,它本身也可能超时。我在项目中遇到过,系统OOM时跑bugreport,结果dumpstate自己先挂了。
adb bugreport timeout 120 设置超时。
25.2 BugReport的结构:别被吓到
第一次打开BugReport的人,多半会被吓到。动辄几十MB的文本,密密麻麻。其实它的结构很清晰:
| 章节 | 内容 | 用途 |
|---|---|---|
| SYSTEM LOG | logcat main + system | 看应用层日志 |
| EVENTS LOG | logcat events | 看系统事件(ANR、crash) |
| CRASH LOG | 最近一次crash信息 | 快速定位崩溃 |
| ANR LOG | /data/anr/ 下的traces | 分析ANR原因 |
| PROCESS INFO | ps、top、procrank | 看进程内存和CPU |
| KERNEL LOG | dmesg | 看内核日志 |
| PROPERTIES | build.prop + 运行时属性 | 确认系统版本和配置 |
我个人习惯,先看EVENTS LOG。因为ANR和crash都会在这里留下记录。找到时间点,再去对应的章节深挖。
25.3 ANR分析:从traces.txt找线索
ANR(Application Not Responding)是用户最直观的“卡死”体验。系统检测到主线程5秒没响应,就会生成ANR。
ANR的traces文件在 /data/anr/ 目录下。文件名格式是 traces_日期_时间.txt。打开后,你会看到所有进程的堆栈信息。重点看“main”线程:
----- pid 12345 at 2024-01-15 10:30:00 -----
Cmd line: com.example.slowness
...
"main" prio=5 tid=1 Blocked
| group="main" sCount=1 dsCount=0 flags=1 obj=0x...
| sysTid=12345 nice=0 cgrp=default sched=0/0 handle=0x...
| state=S schedstat=( 123456 7890 123 ) utm=5 stm=2 core=0
at com.example.MyActivity.onClick(MyActivity.java:50)
- waiting to lock <0x12345678> (a java.lang.Object)
at com.example.MyActivity$1.onClick(MyActivity.java:30)
- locked <0x87654321> (a java.lang.Object)
...
看到了吗?waiting to lock 和 locked 同时出现。这就是典型的死锁。线程A持有锁1,等待锁2;线程B持有锁2,等待锁1。两个线程互相等,谁也动不了。
我曾经遇到一个案例:一个App的ANR率突然飙升。查traces发现,主线程在等一个数据库写操作。而那个写操作因为磁盘I/O繁忙,卡了10秒。最后发现是另一个进程在疯狂写日志,把磁盘带宽占满了。嗯,这种“跨进程”的ANR原因,不看完整traces根本想不到。
25.4 Tombstone:Native Crash的尸检报告
Tombstone是Native层(C/C++)崩溃时生成的报告。它比Java层的crash日志详细得多。文件保存在 /data/tombstones/ 目录下,文件名是 tombstone_XX。
一个典型的Tombstone包含:
- 崩溃信号:SIGSEGV(段错误)、SIGABRT(主动abort)等
- 寄存器值:PC、LR、SP等,用于定位指令地址
- 堆栈回溯:调用链,但可能不完整
- 内存映射:/proc/pid/maps,看各个库的加载地址
- 堆信息:malloc统计,看是否有内存泄漏
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/...'
Revision: '0'
ABI: 'arm64'
Timestamp: 2024-01-15 10:30:00
pid: 12345, tid: 12345, name: my.native.app
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
x0 0x0000000000000000 x1 0x0000007f...
...
pc 0x0000007f12345678 /system/lib64/libfoo.so (bar_func+12)
lr 0x0000007f12345600 /system/lib64/libfoo.so (bar_func+4)
...
#00 pc 0x1234 /system/lib64/libfoo.so (bar_func+12)
#01 pc 0x5678 /system/lib64/libfoo.so (baz_func+24)
#02 pc 0x9abc /system/lib64/libmyapp.so (my_func+16)
...
看到 fault addr 0x0 了吗?这就是典型的空指针解引用。PC指向 bar_func+12,说明在bar函数的第12个字节处崩溃了。
addr2line 工具把地址转换成源码行号。命令:addr2line -f -e libfoo.so 0x1234。这样就能直接定位到出错的C++代码行。
我个人习惯,先看signal和fault addr。SIGSEGV + 地址为0,基本就是空指针。SIGABRT通常是主动调用了abort(),比如检查到状态异常。然后看堆栈回溯,找到最顶层的应用代码(不是系统库),那就是问题所在。
25.5 知识体系:一张图看懂
下面这张图,是我自己总结的BugReport分析流程。从拿到报告到定位问题,每一步该看什么:
25.6 实战:从BugReport中快速定位ANR
假设你拿到了一份BugReport,用户反馈App卡死了。你会怎么做?
我的步骤是这样的:
- 打开EVENTS LOG,搜索
am_anr。找到ANR发生的时间点。 - 打开ANR LOG,找到对应时间点的traces文件。看主线程状态。
- 如果主线程是RUNNABLE,说明它在执行代码,但执行太慢。看它调用了什么函数。
- 如果主线程是BLOCKED,说明它在等锁。找到锁的持有者线程。
- 如果主线程是WAIT,说明它在等某个条件变量。看谁在notify。
我记得有一次,一个App的ANR日志显示主线程在 ViewRootImpl.draw() 里卡住了。我一开始以为是布局太复杂。后来仔细看,发现它是在等一个 SurfaceFlinger 的Binder调用。嗯,这就不是App的问题了,是SurfaceFlinger响应慢。最后发现是GPU驱动有bug。
你看,BugReport的价值就在这里。它不光告诉你“哪里出了问题”,还能帮你判断“问题出在哪一层”。
好了,关于dumpstate和BugReport,我就讲这么多。记住,工具是死的,思路是活的。拿到报告别慌,按流程走,总能找到线索。
公众号:蓝海资料掘金营,微信deep3321