bugreport分析
adb bugreport 不是简单执行一次 dumpsys,也不是把当前 logcat 输出重命名为 zip。它是一条跨越 host ADB、设备 bugreportz、dumpstate、文件同步和主机文件系统的诊断工件流水线:设备端生成一组系统证据,host 端根据状态行决定何时拉取、保存在哪里,再由工程师按 section、时间窗和来源交叉分析。
本文重点阅读 packages/modules/adb/client/bugreport.cpp、frameworks/native/cmds/bugreportz 和 frameworks/native/cmds/dumpstate。前置文章是 ADB架构、ADB shell机制、ADB网络与转发 和 ADB logcat进阶。若要从快照回到单个 Binder 服务的即时输出,可继续阅读 dumpsys诊断。
本文面向已经能采集日志和运行 ADB 命令、但还不能区分报告生成、状态协议、文件传输与内容分析的 读者。边界止于 AOSP 的 bugreport 编排和工件验证,不替代每个系统服务的专题诊断。读完后,应能从 Bugreport::DoIt 追到 bugreportz、dumpstate 与 sync pull,解释取消或断线后哪些结果可以复用, 并用 hash、zip test、时间窗和 section 来源判断一份报告能够证明什么。
1. 产物与生成路径
| 形式 | 设备侧入口 | host 侧结果 | 适合什么场景 |
|---|---|---|---|
| plain text | bugreport | stdout 文本 | 旧设备兼容、管道采集 |
| zip 文件 | bugreportz | 先生成再 sync pull | 默认离线分析 |
| zip stream | bugreportz -s | 设备字节直接进入 stdout | 支持 stream 的脚本或管道 |
| pstore/单项证据 | dumpstate 或独立命令 | 不是完整 bugreport | 针对性补充验证 |
默认 adb bugreport 优先尝试 zip;只有设备没有可用 bugreportz 时,host 才回退到 plain bugreport。显式要求路径或 --stream 时,能力不满足会报错,而不是悄悄输出一大段文本。
2. Bugreport 编排器
2.1 版本探测
client/bugreport.cpp::Bugreport::DoIt() 首先调用 SendShellCommand("bugreportz -v")。版本从 stderr 读取,stdout 只是辅助输出;host 解析主次版本号后,再决定后续命令。
如果命令失败或版本为空:
- 没有显式路径或
--stream时,打印提示并调用 plainbugreport; - 用户显式要求 zip/stream 时直接失败,并提示旧设备应使用重定向的 plain 命令。
这条分支保证 adb bugreport 在旧设备上仍有兼容行为,同时避免用户指定文件时得到不可预期的大量终端文本。
2.2 PATH 的语义
adb bugreport
adb bugreport /tmp/reports
adb bugreport /tmp/reports/car.zip
adb bugreport --stream > report.zip无参数时,host 使用当前工作目录,文件名等设备返回的 zip 名称;参数是已存在目录时,目录由用户指定、文件名仍由设备返回;参数是文件时,host 使用该文件名,缺少 .zip 会自动追加。--stream 不使用本地目标路径,而是执行 bugreportz -s,输出由 shell/host 管道决定。
3. 状态行协议
3.1 状态行
Android 17 host callback 按换行组装 stdout,每行匹配固定前缀:
| 行 | 含义 | host 动作 |
|---|---|---|
BEGIN:/path/report.zip | 设备开始/确定工件路径 | 记录 source path;目录目标可改用设备文件名 |
PROGRESS:50/100 | 生成进度 | 更新终端进度,忽略倒退百分比 |
OK:/path/report.zip | 生成完成 | 发起 sync pull |
FAIL:message | 设备生成失败 | 记录错误,不执行 pull |
callback 允许一次输出被拆成多个底层 buffer。它把未遇到换行的字符保存在 line_,在下一次回调或 Done() 时继续拼接。因此不能假设一次 read() 对应一条状态行。
源码文件:frameworks/native/cmds/bugreportz/bugreportz.cpp
相关函数/类型:write_line
static void write_line(const std::string& line, bool show_progress) {
if (line.empty()) return;
if (!show_progress && (android::base::StartsWith(line, "PROGRESS:")
|| android::base::StartsWith(line, "BEGIN:"))) {
return;
}
android::base::WriteStringToFd(line, STDOUT_FILENO);
}设备侧 write_line 是协议生产者,host callback 是消费者;默认模式过滤 BEGIN/PROGRESS 是为了不破坏 ADB 对最终 OK/FAIL 状态的解析,而不是为了隐藏生成进度。
3.2 未知行边界
未知前缀会进入 invalid_lines_,在结束时警告设备可能不支持 zip bugreport。即使已经出现 OK,这些未知行也会被报告;它们不是状态协议的一部分,不能被分析脚本当作进度或路径。
3.3 进度显示
dumpstate 的实际任务耗时和进度估计可能让百分比回退。host 保存 last_progress_percentage_,只显示向前推进的百分比。这是 UI 行为,不是设备生成任务真的回滚;分析生成性能时应使用设备侧 dumpstate 时间信息,而不是终端进度刷新次数。
4. 生成与复制
OK: 只表示设备端 zip 已生成。host 随后构造 destination,调用 DoSyncPull(),成功后才打印 Bug report copied to ...。所以以下三种情况不能混写:
- dumpstate 仍在生成;
- dumpstate 已完成但 zip 尚未拉到 host;
- host 已收到文件但文件尚未被验证、解压或分析。
如果 pull 失败,host 会提示手动执行 adb pull <device path> <directory>。这时设备文件可能仍然存在,重新复制不需要重新生成报告;但设备端文件的保留时间和权限由产品实现决定。
源码文件:packages/modules/adb/client/bugreport.cpp
相关函数/类型:BugreportStandardStreamsCallback::Done
if (status_ == 0) {
if (src_file_.empty()) {
fprintf(stderr, "bugreportz did not return a status line\n");
return -1;
}
SetLineMessage("pulling");
status_ = br_->DoSyncPull(srcs, destination.c_str(), false,
line_message_.c_str()) ? 0 : 1;
if (status_ == 0) {
printf("Bug report copied to %s\n", destination.c_str());
} else {
fprintf(stderr, "Bug report finished but could not be copied to '%s'.\n",
destination.c_str());
}
}src_file_ 非空只证明设备报告路径已经由状态行交付;DoSyncPull 才决定 host 是否拥有副本。用不可写 目标目录运行测试时,预期是复制失败提示而不是重新生成报告;之后可单独 adb pull,但仍要用 unzip -t 验证工件完整性。
5. plain fallback
旧设备或 bugreportz -v 不可用时,host 调用 SendShellCommand("bugreport", false),plain 内容直接进入 stdout。它没有 BEGIN/OK 路径,也没有设备文件可供后续 pull。
adb bugreport > bugreport.txt重定向由 host shell 执行,文件写在开发机。若直接运行 adb bugreport,终端会接收完整文本;这可能远大于用户预期,因此显式 PATH 模式不会自动回退为 plain。
plain 报告可以用于人工阅读和兼容性采集,但缺少 zip 目录边界、单文件压缩和部分二进制附件的原始保存方式。分析时应记录报告生成模式,不能把 plain 与 zip 的文件清单直接比较。
6. bugreportz
bugreportz 负责面向调用者的生成/状态/打包接口;真正收集大量系统证据的是 dumpstate。dumpstate 通过一组 section 和任务收集:系统属性、进程、服务 dump、日志、trace、网络、电源、存储等,具体集合受版本、产品和权限影响。
不要把 bugreport 中出现的每个文件都归因于 adb:ADB 负责 transport、状态行和 pull;dumpstate 负责 section 产生;各系统服务负责自己的 dump 输出;init、kernel、HAL 和厂商服务可能提供额外证据。
7. zip 工件读取
7.1 原始文件
sha256sum bugreport-*.zip > bugreport.sha256
unzip -l bugreport.zip > bugreport.files.txt
unzip -t bugreport.zip先记录大小、hash、生成时间和 serial,再解压到只读分析目录。unzip -t 只能证明压缩结构可读,不能证明每个 section 内容完整,也不能证明 dumpstate 在生成时没有超时或权限拒绝。
7.2 文件边界
不同 Android release、产品和厂商可以改变 section 文件名、目录和压缩策略。分析脚本应优先按文件内容中的 section header、时间戳、命令标题和版本字段识别,不应只依赖一个固定 glob。
可以先做结构索引:
unzip -Z1 bugreport.zip | sort > files.sorted.txt
rg -n "BEGIN|END|DUMP OF SERVICE|SYSTEM LOG|BUILD" extracted/ > section-index.txtunzip -Z1 只读取目录,不解压全部内容;对大报告先建立索引,再针对故障时间窗读取相关 section,可以降低误操作和分析成本。
7.3 敏感数据处理
bugreport 可能包含 serial、账号标识、网络地址、进程命令行、日志正文、应用路径和厂商内部信息。公开给他人前应建立脱敏副本,保留原始 zip 的 hash 和访问控制;不要直接在正文或 issue 中粘贴完整报告。
8. 时间窗对齐
bugreport 是多源快照,不是单一时间线。建立故障时间窗时应记录:
| 来源 | 可提供的时间信息 | 典型用途 |
|---|---|---|
| logcat | wall clock、epoch、monotonic、PID/TID | 事件顺序和触发点 |
| service dump | 采样时状态、历史记录、计数器 | 当前状态和服务内部证据 |
| traces/tombstone | 生成时刻、线程栈、信号 | crash/ANR 归因 |
| kernel/proc | uptime、调度、设备状态 | 系统级压力和驱动行为 |
| build/property | release、variant、配置 | 解释版本和产品边界 |
不要用 zip 文件的 mtime 代替事件时间;它只反映 host pull 或文件复制时间。也不要把不同设备的 wall clock 直接对齐而忽略时区和校时。
9. 分析顺序
9.1 分析顺序入口
检查报告中的 build fingerprint、build id、Android release、设备 serial(按隐私规则处理)和生成时间。若报告来自错误设备或旧 build,后续源码结论全部失去上下文。
9.2 crash
以用户报告的时间点为中心,分别检索:
crash、system、main中的 fatal、exception、ANR、watchdog、kill;- 对应服务 dump 的状态和历史;
- traces、tombstone、dropbox 或 perfetto 入口;
- kernel/proc 的 I/O、内存、调度和驱动错误。
每条观察记录文件路径、section 标题、时间戳、PID/UID、原始行和解释。解释必须与原始事实分开,避免把关键词出现当作根因。
9.3 源码owner
如果日志显示 service 返回错误,进入拥有该状态的 service 源码;如果 dumpstate section 超时,先确认收集命令和 timeout,再判断被采集服务是否异常;如果只有 host pull 失败,不应得出设备服务故障结论。
10. 失败分流
| 现象 | 失败阶段 | 首个入口 | 恢复方向 |
|---|---|---|---|
bugreportz -v 失败 | 能力探测 | Bugreport::DoIt | plain fallback 或确认设备版本 |
FAIL:... | 设备生成 | bugreportz/dumpstate | 查看设备错误和权限/空间 |
| 长时间无进度 | 生成或状态通道 | callback / dumpstate | 保持 transport,检查设备负载和 timeout |
OK 后 pull 失败 | 文件同步/host 目录 | DoSyncPull | 检查目标可写性,手动 pull |
| zip 解压测试失败 | 工件传输不完整 | host 文件 | 重新 pull,核对 hash/大小 |
| section 缺失 | dumpstate 配置/权限/超时 | dumpstate section | 查报告中的 timeout/permission 行 |
| 只有旧日志 | 生成时 buffer 已 wrap | logcat/logd | 增大 buffer、提前采集、补充其他来源 |
10.1 取消边界
Android 17 对 bugreportz 1.0 会明确提示生成可能需要几分钟,并要求不要取消或断开设备。zip 生成是设备端任务,host 终端进度停止不等于设备已经失败。强行断开可能只留下半成品或无法 pull 的临时文件。
10.2 FAIL
生成失败通常需要重新运行 bugreport;pull 失败通常可以保留设备路径后单独重试。两者恢复动作不同,报告中应记录 FAIL 是否已经出现以及 OK 后是否开始 pull。
11. 工件校验
11.1 ADB测试
| 测试场景 | 断言 | 证明边界 |
|---|---|---|
| pre-N/no bugreportz | 调用 plain bugreport 并捕获 stdout | fallback 分支,不证明旧设备实际 dump 内容 |
| version 1.0 | 使用 bugreportz,无 progress | 旧 zip 协议兼容 |
| version 1.1 | 解析 BEGIN/PROGRESS/OK 并 pull | 状态行、进度、路径和 pull 编排 |
| 分片 buffer | 两次 stdout callback 拼成一条 OK 行 | callback 不依赖 read 边界 |
| FAIL | 输出设备错误并不 pull | 生成失败清理边界 |
| pull failure | 提示手动 adb pull | 生成完成与 host 落盘分离 |
11.2 报告测试
frameworks/native/cmds/bugreportz 的测试验证参数、状态输出和错误分支;dumpstate 测试验证 section、超时、统计和格式处理。它们能约束协议与编排,却不能覆盖每个厂商 hook、HAL dump、内核配置或用户隐私策略。
设备侧 bugreportz_test.cpp 用 pipe 写入分片状态和异常 EOF,断言 bugreportz() 会按换行交付状态、过滤 进度行,并在读取错误时输出 FAIL:。host 的 packages/modules/adb/bugreport_test.cpp 则 mock SendShellCommand 和 DoSyncPull,断言版本 1.0/1.1 的命令选择、plain fallback 与 pull 失败提示。前者 证明协议生产者,后者证明协议消费者;两者都不证明 dumpstate 每个 section 的内容正确。
12. 五个最小实验
12.1 默认 zip
adb bugreport ./reports观察 host 先发 bugreportz -v,再显示生成/拉取进度,最后以设备返回的文件名写入目录。验证目录参数不会强制使用固定文件名。
12.2 明确文件名
adb bugreport ./reports/repro.zip检查缺少或已有 .zip 时的命名结果,并记录 host 目标路径与设备 source path 是两个不同字符串。
12.3 stream
adb bugreport --stream > reports/repro-stream.zip确认设备 bugreportz 版本支持 stream。此模式没有 OK:path 后续 pull;shell stdout 本身就是 zip 字节,stderr 仍可能包含诊断信息,脚本不能把 stderr 合并进文件。
12.4 plain
在不支持 bugreportz 的设备上执行:
adb bugreport > reports/repro.txt这条路径的输出格式和 zip section 不同,分析记录必须标明 plain。
12.5 pull 重试
模拟或观察目标目录不可写时的 pull failure,保存 host 提示的设备路径,再用单独的 adb pull 复制。这个实验验证恢复路径,不需要重新触发昂贵的 dumpstate。
13. 采集前先保护现场
13.1 时间边界
bugreport 的价值依赖采集时仍存在的状态。下面这些动作会改变或删除证据:
adb logcat -c清空 logd ring buffer;- 重启设备清除大量进程和 service 内存状态;
- force-stop 应用改变进程、任务、Alarm、Job 和权限使用状态;
- 调整
logcat -G、prune 或开发者选项改变日志保留策略; - 删除 tombstone、trace、dropbox 或临时文件;
- 反复复现导致最初故障日志被高频新日志覆盖。
更可靠的顺序是先记录当前时间、设备 serial、用户、build fingerprint 和复现步骤,立即采集 bugreport,再决定是否修改状态进行第二轮实验。若必须重启,应分别保存“重启前”和“重启后”工件,不能覆盖同名文件。
13.2 报告边界
dumpstate 可能运行数分钟。报告中有些 section 在开始阶段采样,有些在末尾采样,service dump 之间也存在先后。分析时应区分:
- 用户观察到故障的时间;
adb bugreport命令开始时间;- 某个 section 的采样时间;
- zip 完成时间;
- host pull 完成时间。
不能把 zip mtime 直接当作所有 section 的采样时间。对短暂状态尤其要检查 section header 和日志时间戳。
13.3 故障锚点
外部锚点可以是测试脚本输出、用户操作时间、唯一 request id、截图时间、服务端请求日志或车辆总线事件。它帮助把 bugreport 内部的 wall clock、monotonic 和进程生命周期对齐。没有锚点时,只能从大量相似日志中推断,结论置信度会显著下降。
14. 检查报告质量
14.1 完整性测试
打开内容前先核对:
| 检查 | 目的 |
|---|---|
| host 文件大小非零且稳定 | 排除仍在复制或空文件 |
| zip test 通过 | 排除中央目录或压缩块损坏 |
| build fingerprint 与目标一致 | 排除采错设备/版本 |
| 生成时间覆盖故障窗口 | 排除旧报告 |
| section 中存在完成标记或摘要 | 识别 dumpstate 中途退出 |
| stderr/状态行没有 FAIL | 区分生成失败与分析失败 |
zip 能打开只是最低条件。某个大 section 可能超时但 zip 仍完整,某个厂商服务可能返回 permission denied 但总体 bugreport 仍成功。
14.2 section错误
建立全局错误索引时,可以优先搜索:
rg -n "timed out|TIMEOUT|permission denied|Permission denied|FAILED|Exception|not found" extracted/搜索结果要回到 section 上下文。报告中的应用日志可能本来就在讨论 FAILED 字符串;只有 dumpstate 自己的命令前缀、duration、timeout 或 stderr 才能证明采集步骤失败。
14.3 记录缺失边界
预期 section 不存在时,应在分析报告中明确写出:预期来源、实际目录搜索、可能的产品差异以及该缺失对结论的影响。例如,没有 tombstone 时不能声称 native crash 不存在,只能说当前工件中未发现对应 tombstone。
15. 故障分类
15.1 Java crash
先在 crash/main buffer 中定位 FATAL EXCEPTION、process、PID 和异常栈,再用 ActivityManager 记录确认进程死亡与重启。随后检查 package version、用户、组件和相关 service dump。不要只截取异常最后一行;异常类型、cause chain、首个业务栈帧和进程身份共同决定入口。
15.2 Nativecrash
先定位 tombstone 或 crash buffer 中的 signal、fault address、abort message、进程和线程,再核对 build id、ABI 和加载库。logcat 中的 native backtrace 可能被截断,完整 tombstone 才能提供寄存器、memory map 和线程信息。没有符号文件时,bugreport 只能提供地址证据,不能自动生成源码行。
15.3 ANR
ANR 分析至少需要:system_server 的判定日志、目标进程 PID、ANR reason、traces 中主线程和相关 binder/worker 线程、CPU/I/O/内存压力以及组件类型。ANR in PACKAGE 是结果标签,不是根因;主线程等待 binder 时还要找到远端 service 和其线程池状态。
15.4 卡顿/watchdog
查看 system_server/watchdog 日志、线程栈、binder 状态、CPU 调度、I/O stall、内存压力和关键 native service。一次 service dump timeout 可能是卡顿证据,也可能只是 dump 实现慢;需要与同一时间窗的调度和线程状态交叉验证。
15.5 入口分类
结合 Connectivity、Wi-Fi/telephony、network policy、DNS、socket/route 和应用日志。bugreport 中的“当前网络状态”可能采集在故障恢复之后,因此历史 logcat 和统计记录比单个当前状态 dump 更能解释瞬时断网。
15.6 入口定位
结合 batterystats、Alarm、JobScheduler、DeviceIdle、PowerManager、process state 和应用 standby。单看某个 wakelock 名称不能得出耗电根因;必须对齐持有时长、UID、唤醒原因、CPU 活跃和实际业务事件。
16. 事实分层
一条高质量分析记录应分三栏:
| 类型 | 示例 |
|---|---|
| 事实 | 10:21:05.123,PID 2345 主线程在 binder transaction 中等待 |
| 解释 | 应用主线程可能同步调用远端服务并被阻塞 |
| 待验证 | 查 transaction 对端 PID、服务线程池和同时间 CPU/I/O |
如果把解释直接写成事实,后续读者无法判断结论来自原始工件还是作者推断。教材文章尤其要保留原始入口:文件、section、时间、PID/TID、关键字段和源码 owner。
16.1 结论约束
认为“内存不足导致进程死亡”时,应同时查找 LMKD/AMS kill reason、内存压力、oom score 和是否存在 crash。认为“Binder 死锁导致 ANR”时,应确认双方线程栈和等待关系,而不是只看到一个 binder 调用。主动寻找竞争解释可以避免关键词驱动的误诊。
16.2 当前状态边界
dumpsys activity、dumpsys package 等 section 多为采集时快照;故障发生后状态可能已经恢复。历史日志、event、dropbox 和 trace 负责证明过去发生了什么,当前 dump 负责说明采集时系统处于什么状态。二者冲突时,应首先检查采样时间差。
17. 自动索引
17.1 自动索引入口
mkdir -p analysis/raw analysis/index
cp bugreport.zip analysis/raw/
sha256sum analysis/raw/bugreport.zip > analysis/index/sha256.txt
unzip -Z1 analysis/raw/bugreport.zip | sort > analysis/index/files.txt
unzip -q analysis/raw/bugreport.zip -d analysis/extracted保留 raw、index 和 extracted 三层可以避免修改原始 zip。分析生成的 grep 结果、脚本输出和注释放在 index 或单独 notes,不写回 extracted。
17.2 section索引
rg -n "^------|^DUMP OF SERVICE|^== .* ==|BEGIN|END" analysis/extracted \
> analysis/index/sections.txt标题语法会随 release 和 section 变化,因此正则只是导航,不是完整 parser。真正自动化时应按 Android 17 实际格式构建解析器,并在未知 section 出现时保留而不是丢弃。
17.3 自动索引入口路径
rg -n "08-18 10:2[0-5]:" analysis/extracted > analysis/index/window.txt时间正则需要匹配报告实际 format。跨年、UTC、epoch、monotonic 和无年份日志不能共用一个简单字符串排序。先确认每个来源的时间基准,再合并。
18. 隐私关联
18.1 原始报告
bugreport 可能包含电话号码、Wi-Fi SSID/BSSID、IP/MAC、账号、通知内容、应用日志、文件路径、车辆或设备标识。脱敏必须在副本上进行,原始工件保持只读和受控访问。
18.2 脱敏关联
把同一 UID、包名或地址随机替换成不同值,会破坏跨 section 关联。更好的方法是使用稳定映射,例如把同一账号始终替换为 ACCOUNT_1,同一 IP 替换为 IP_3,并将映射表单独受控保存。
18.3 隐私追踪入口
记录原始 hash、脱敏工具版本、处理规则、输出 hash和执行时间。这样别人可以确认分析基于哪个工件,也能判断某个字段缺失是设备未采集还是脱敏工具删除。
19. 常见误区
误区一:bugreport 成功就代表所有 section 成功。 总体 zip 可以成功,但单个命令可能 timeout、permission denied 或返回空内容。
误区二:zip 文件名就是故障时间。 文件名和 mtime 更接近生成/复制时间,故障时间需要外部锚点和内部日志确认。
误区三:关键词第一次出现就是根因。 很多错误是上游失败的传播结果,应追踪最早状态变化和 owner。
误区四:当前 dumpsys 可以证明故障时状态。 当前快照与历史日志必须按采样时间区分。
误区五:重新生成的报告等价。 重启、复现和等待都会改变 ring buffer、PID、任务和系统负载;新报告只能作为新实验。
误区六:pull 成功证明内容完整。 pull 只证明文件字节传到 host,仍需 hash、zip test 和关键 section 完整性检查。
20. 分析线索
20.1 结论来源
可以为每条结论使用下面的最小字段:
| 字段 | 示例 | 作用 |
|---|---|---|
| report hash | sha256:... | 固定输入工件 |
| file | FS/data/anr/traces.txt | 指明文件来源 |
| section | DUMP OF SERVICE activity | 缩小上下文 |
| timestamp | 08-18 10:21:05.123 | 对齐事件时间 |
| identity | PID/TID/UID/package | 关联进程和用户 |
| observation | 主线程等待 transaction | 只写可见事实 |
| interpretation | 可能同步等待远端服务 | 标注推断 |
| 下一步核对 | 对端线程栈、binder 状态 | 指定反证方向 |
如果同一事实出现在 logcat、service dump 和 trace,应保留三个来源,而不是只复制一份到总结。来源之间的差异本身可能暴露采样延迟、缓存或状态机竞争。
20.2 信息缺失
| 假设 | 支持证据 | 反证证据 | 当前结论 |
|---|---|---|---|
| 应用主线程被远端服务阻塞 | traces 主线程 binder wait;service 日志同一时间处理慢 | 对端线程正常且请求已返回 | 待确认对端 transaction |
| 日志丢失导致看不到 crash | buffer wrap/容量不足;pstore 无记录 | tombstone 存在完整栈 | crash 证据仍可用 |
| pull 造成报告损坏 | hash 不一致;zip test 失败 | hash 与设备端记录一致 | 重新传输 |
这种矩阵比“根因:xxx”更便于逐层核对,因为它明确哪些事实已经得到支持、哪些仍然缺少依据。
20.3 unknown
一个 section 没有输出、一个服务 dump 超时或一个时间戳无法对齐时,应写成 unknown 或 not collected,不要自动填成 false。缺失数据和否定事实在故障分析中完全不同:前者表示没有观察到,后者表示观察到不存在。
21. 完整性判断
21.1 进度边界
PROGRESS:n/total 是 bugreportz 对 dumpstate 过程的粗粒度报告,不一定与 zip 中已完成的文件数量线性对应。某个耗时很长的 service dump 可能只占一个进度单位;压缩和 sync pull 也发生在不同阶段。因此不要用 50% 进度推断报告已有一半 section 可安全分析。
21.2 增长分析
如果使用 --stream,下游管道可能在 zip 完成前看不到可用中央目录;如果使用 pull,只有 OK 后设备文件才是完整候选。在线解析应明确“临时输入”与“最终输入”,否则会把未完成的文件误报为缺失 section。
21.3 超时section
dumpstate 可能为单个命令设置超时并继续生成其他 section。最终 zip 仍可下载,但报告中会留下 timeout 文字或错误摘要。分析时要把 timeout 当作一条系统状态证据:它说明采集时某个命令没有在规定时间内返回,但不自动证明被采集服务在正常业务路径中也超时。
21.4 两份报告边界
进程 PID、时间戳、内存地址、计数器、网络状态和日志窗口每次都会变化,直接对两个解压目录执行全文 diff 会产生大量噪声。比较“正常”和“异常”报告时,应先固定 build、用户、操作步骤和采集时间,再按稳定字段比较:配置、服务状态、队列长度、错误计数、线程等待类型和关键 section 是否缺失。
对于数值指标,要记录采样单位和时间范围。例如累计计数变大不一定表示当前故障,瞬时队列为零也不能否定故障发生时曾经堆积。差异只有放回指标语义和采样时机才有解释价值。
同一设备的两份报告也要记录采集前是否经过重启、用户切换、网络变化和应用重装。只要这些前置状态不同,差异就不能直接归因于待分析的代码改动。
21.5 完整性判断入口
脚本可以提取 fingerprint、ANR、crash、timeout 和 service dump 摘要,但输出必须包含原始文件路径和行号。否则脚本规则升级后无法复核旧结论,也难以发现正则把普通应用文本误识别为 dumpstate 错误。
21.6 脚本约束
bugreportz -v 的版本、生成状态、stream 字节和诊断信息不一定在同一个标准流。特别是 adb bugreport --stream > report.zip 时,只能把 stdout 写入 zip;若使用 2>&1 合并 stderr,任何提示文字都会破坏压缩文件。自动化脚本还必须检查 ADB 退出码、目标文件大小和 zip test,不能只判断文件是否存在。
对于默认 pull 模式,终端中的 generating 和 pulling 是 host UI,不是 zip 内容。脚本解析时优先使用退出码和最终文件,再把进度作为可选观测信息;进度缺失不等于失败,FAIL: 或 pull error 才决定相应阶段没有完成。
22. 源码阅读路线
packages/modules/adb/client/commandline.cpp:确认bugreport分派到Bugreport::DoIt;client/bugreport.cpp::DoIt:阅读版本协商、参数、fallback 和目标文件逻辑;BugreportStandardStreamsCallback:理解状态行、分片、进度和 pull;packages/modules/adb/bugreport_test.cpp:对照版本、FAIL、pull 和 callback 测试;frameworks/native/cmds/bugreportz:确认设备侧 zip/stream 命令和输出协议;frameworks/native/cmds/dumpstate/main.cpp与dumpstate.cpp:理解生成器的进程入口、任务和 section;bugreport-format.md:识别报告格式边界,但最终以 Android 17 源码和实际工件为准;- 把报告内每个结论回连到产生它的 service、buffer、trace 或 kernel owner。
23. 分析顺序
bugreport 的价值不是“文件很大”,而是它在一个受控时间点把多个 owner 的证据集合起来。正确分析必须保留生成模式、设备身份、状态协议、传输结果、文件完整性和 section 来源;只有这样,报告中的现象才能被复现、交叉验证,并最终落到 Android 17 的真实代码路径上。
