Skip to content

bugreport分析

追踪 adb bugreport 的能力协商、bugreportz 状态协议、zip 拉取与 plain fallback,并建立可复现的 bugreport 分析路径。

基于android-17.0.0_r1
AndroidADBbugreportdumpstate故障诊断

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 textbugreportstdout 文本旧设备兼容、管道采集
zip 文件bugreportz先生成再 sync pull默认离线分析
zip streambugreportz -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 时,打印提示并调用 plain bugreport;
  • 用户显式要求 zip/stream 时直接失败,并提示旧设备应使用重定向的 plain 命令。

这条分支保证 adb bugreport 在旧设备上仍有兼容行为,同时避免用户指定文件时得到不可预期的大量终端文本。

2.2 PATH 的语义 ​

bash
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

cpp
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 ...。所以以下三种情况不能混写:

  1. dumpstate 仍在生成;
  2. dumpstate 已完成但 zip 尚未拉到 host;
  3. host 已收到文件但文件尚未被验证、解压或分析。

如果 pull 失败,host 会提示手动执行 adb pull <device path> <directory>。这时设备文件可能仍然存在,重新复制不需要重新生成报告;但设备端文件的保留时间和权限由产品实现决定。

源码文件:packages/modules/adb/client/bugreport.cpp

相关函数/类型:BugreportStandardStreamsCallback::Done

cpp
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。

bash
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 原始文件 ​

bash
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。

可以先做结构索引:

bash
unzip -Z1 bugreport.zip | sort > files.sorted.txt
rg -n "BEGIN|END|DUMP OF SERVICE|SYSTEM LOG|BUILD" extracted/ > section-index.txt

unzip -Z1 只读取目录,不解压全部内容;对大报告先建立索引,再针对故障时间窗读取相关 section,可以降低误操作和分析成本。

7.3 敏感数据处理 ​

bugreport 可能包含 serial、账号标识、网络地址、进程命令行、日志正文、应用路径和厂商内部信息。公开给他人前应建立脱敏副本,保留原始 zip 的 hash 和访问控制;不要直接在正文或 issue 中粘贴完整报告。

8. 时间窗对齐 ​

bugreport 是多源快照,不是单一时间线。建立故障时间窗时应记录:

来源可提供的时间信息典型用途
logcatwall clock、epoch、monotonic、PID/TID事件顺序和触发点
service dump采样时状态、历史记录、计数器当前状态和服务内部证据
traces/tombstone生成时刻、线程栈、信号crash/ANR 归因
kernel/procuptime、调度、设备状态系统级压力和驱动行为
build/propertyrelease、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::DoItplain fallback 或确认设备版本
FAIL:...设备生成bugreportz/dumpstate查看设备错误和权限/空间
长时间无进度生成或状态通道callback / dumpstate保持 transport,检查设备负载和 timeout
OK 后 pull 失败文件同步/host 目录DoSyncPull检查目标可写性,手动 pull
zip 解压测试失败工件传输不完整host 文件重新 pull,核对 hash/大小
section 缺失dumpstate 配置/权限/超时dumpstate section查报告中的 timeout/permission 行
只有旧日志生成时 buffer 已 wraplogcat/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 并捕获 stdoutfallback 分支,不证明旧设备实际 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 ​

bash
adb bugreport ./reports

观察 host 先发 bugreportz -v,再显示生成/拉取进度,最后以设备返回的文件名写入目录。验证目录参数不会强制使用固定文件名。

12.2 明确文件名 ​

bash
adb bugreport ./reports/repro.zip

检查缺少或已有 .zip 时的命名结果,并记录 host 目标路径与设备 source path 是两个不同字符串。

12.3 stream ​

bash
adb bugreport --stream > reports/repro-stream.zip

确认设备 bugreportz 版本支持 stream。此模式没有 OK:path 后续 pull;shell stdout 本身就是 zip 字节,stderr 仍可能包含诊断信息,脚本不能把 stderr 合并进文件。

12.4 plain ​

在不支持 bugreportz 的设备上执行:

bash
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错误 ​

建立全局错误索引时,可以优先搜索:

bash
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 自动索引入口 ​

bash
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索引 ​

bash
rg -n "^------|^DUMP OF SERVICE|^== .* ==|BEGIN|END" analysis/extracted \
  > analysis/index/sections.txt

标题语法会随 release 和 section 变化,因此正则只是导航,不是完整 parser。真正自动化时应按 Android 17 实际格式构建解析器,并在未知 section 出现时保留而不是丢弃。

17.3 自动索引入口路径 ​

bash
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 hashsha256:...固定输入工件
fileFS/data/anr/traces.txt指明文件来源
sectionDUMP OF SERVICE activity缩小上下文
timestamp08-18 10:21:05.123对齐事件时间
identityPID/TID/UID/package关联进程和用户
observation主线程等待 transaction只写可见事实
interpretation可能同步等待远端服务标注推断
下一步核对对端线程栈、binder 状态指定反证方向

如果同一事实出现在 logcat、service dump 和 trace,应保留三个来源,而不是只复制一份到总结。来源之间的差异本身可能暴露采样延迟、缓存或状态机竞争。

20.2 信息缺失 ​

假设支持证据反证证据当前结论
应用主线程被远端服务阻塞traces 主线程 binder wait;service 日志同一时间处理慢对端线程正常且请求已返回待确认对端 transaction
日志丢失导致看不到 crashbuffer 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. 源码阅读路线 ​

  1. packages/modules/adb/client/commandline.cpp:确认 bugreport 分派到 Bugreport::DoIt;
  2. client/bugreport.cpp::DoIt:阅读版本协商、参数、fallback 和目标文件逻辑;
  3. BugreportStandardStreamsCallback:理解状态行、分片、进度和 pull;
  4. packages/modules/adb/bugreport_test.cpp:对照版本、FAIL、pull 和 callback 测试;
  5. frameworks/native/cmds/bugreportz:确认设备侧 zip/stream 命令和输出协议;
  6. frameworks/native/cmds/dumpstate/main.cpp 与 dumpstate.cpp:理解生成器的进程入口、任务和 section;
  7. bugreport-format.md:识别报告格式边界,但最终以 Android 17 源码和实际工件为准;
  8. 把报告内每个结论回连到产生它的 service、buffer、trace 或 kernel owner。

23. 分析顺序 ​

bugreport 的价值不是“文件很大”,而是它在一个受控时间点把多个 owner 的证据集合起来。正确分析必须保留生成模式、设备身份、状态协议、传输结果、文件完整性和 section 来源;只有这样,报告中的现象才能被复现、交叉验证,并最终落到 Android 17 的真实代码路径上。