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自己先挂了。

避坑指南:我曾经在低内存设备上跑bugreport,等了10分钟没反应。后来发现是dumpstate在收集/proc/pid/maps时卡住了。建议在系统相对空闲时执行,或者使用 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 locklocked 同时出现。这就是典型的死锁。线程A持有锁1,等待锁2;线程B持有锁2,等待锁1。两个线程互相等,谁也动不了。

关键点:ANR不一定是主线程慢。很多时候是主线程在等一个锁,而这个锁被其他线程持有了。所以分析ANR时,不光看主线程,还要看其他线程在干什么。

我曾经遇到一个案例:一个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个字节处崩溃了。

调试技巧:拿到Tombstone后,用 addr2line 工具把地址转换成源码行号。命令:addr2line -f -e libfoo.so 0x1234。这样就能直接定位到出错的C++代码行。

我个人习惯,先看signal和fault addr。SIGSEGV + 地址为0,基本就是空指针。SIGABRT通常是主动调用了abort(),比如检查到状态异常。然后看堆栈回溯,找到最顶层的应用代码(不是系统库),那就是问题所在。

25.5 知识体系:一张图看懂

下面这张图,是我自己总结的BugReport分析流程。从拿到报告到定位问题,每一步该看什么:

BugReport 分析流程 adb bugreport EVENTS LOG CRASH LOG KERNEL LOG ANR 分析 查看 /data/anr/traces Java Crash 查看堆栈和异常类型 Native Crash 查看 Tombstone 主线程阻塞? 检查锁等待和I/O 空指针/类型转换? 检查代码逻辑 SIGSEGV/SIGABRT? addr2line 定位源码 定位问题 → 修复 → 验证

25.6 实战:从BugReport中快速定位ANR

假设你拿到了一份BugReport,用户反馈App卡死了。你会怎么做?

我的步骤是这样的:

  1. 打开EVENTS LOG,搜索 am_anr。找到ANR发生的时间点。
  2. 打开ANR LOG,找到对应时间点的traces文件。看主线程状态。
  3. 如果主线程是RUNNABLE,说明它在执行代码,但执行太慢。看它调用了什么函数。
  4. 如果主线程是BLOCKED,说明它在等锁。找到锁的持有者线程。
  5. 如果主线程是WAIT,说明它在等某个条件变量。看谁在notify。
核心原则:ANR的本质是主线程“该动的时候没动”。要么是它自己跑得慢(CPU密集型),要么是它在等别人(锁、I/O、Binder调用)。找到“等”的原因,就找到了问题。

我记得有一次,一个App的ANR日志显示主线程在 ViewRootImpl.draw() 里卡住了。我一开始以为是布局太复杂。后来仔细看,发现它是在等一个 SurfaceFlinger 的Binder调用。嗯,这就不是App的问题了,是SurfaceFlinger响应慢。最后发现是GPU驱动有bug。

你看,BugReport的价值就在这里。它不光告诉你“哪里出了问题”,还能帮你判断“问题出在哪一层”。

个人建议:平时多收集一些BugReport样本。正常系统的、ANR的、Crash的,都存一份。遇到新问题时,拿来对比。你会发现,很多问题的模式是相似的。

好了,关于dumpstate和BugReport,我就讲这么多。记住,工具是死的,思路是活的。拿到报告别慌,按流程走,总能找到线索。


公众号:蓝海资料掘金营,微信deep3321