内测包发给测试同事,第二天收到一句「它崩了」,附一张截图:满屏十六进制地址和几个看不懂的类名。这不是测试同学不会描述,而是崩溃报告还没被「符号化」。本文只讲一件事:怎么让堆栈从一串地址变成能定位到文件和行号的调用链。
先分清报告里哪几种内容看不懂
- 内存地址:形如
0x0000000104a2b3c4,脱离符号文件没有任何意义 - 混淆后的名字:
a.b.c、a.a()这类,是代码压缩工具改写过的产物 - 系统帧与业务帧混在一起:系统库的帧通常能直接显示,自己代码的帧才是要盯的部分
判断标准很简单:堆栈里出现文件名和行号,说明符号信息完整;只剩地址和偏移量,说明缺符号文件。
iOS:dSYM 与 UUID 必须对得上
- 打包时保留 dSYM。Xcode 归档后在 Organizer 里能看到对应文件,导出时不要只导出安装包
- 记录 UUID。用
dwarfdump --uuid读取 dSYM 的 UUID,与崩溃报告头部的 UUID 比对,一致才能符号化 - 执行符号化。命令行可用
atos,也可以使用 Xcode 自带的崩溃分析工具,按报告逐条还原 - 归档入库。dSYM 压缩后按「版本号 + UUID」命名存放,不要只留在某个人的本机
需要留意:若构建过程经过云端重编译,产物 UUID 可能变化,以 Apple 官方说明为准。UUID 对不上的 dSYM,符号化一定失败。
安卓:mapping.txt 要和安装包同生同灭
- 在
build.gradle里开启代码压缩后,构建会输出mapping.txt - 该文件只对当次构建有效,重新构建一次就作废
- 还原堆栈用 R8 自带的 retrace 工具,把混淆后的堆栈喂进去
- 缺了它只能看到
a.a.a这类名字,定位基本靠猜
建议:把「出包即上传符号文件」写进发版清单,与上传安装包放在同一步,避免几周后回头找不到。
符号文件与内测版本怎么绑定
| 文件 | 平台 | 作用 | 留存时机 |
|---|---|---|---|
app.dSYM |
iOS | 把崩溃地址还原成函数与行号 | 每次 Archive 后 |
mapping.txt |
Android | 还原混淆后的类名与方法名 | 每次 release 构建后 |
| 版本号记录 | 双端 | 与分发记录一一对应 | 每次上传安装包时 |
最后一行最容易被忽略:符号文件脱离版本号就只是一堆压缩包。内测阶段同一版本常连发几包,建议上传时把版本号与构建号写进应用名称,分发记录在虾分发控制台(https://xiafenfa.com)可按应用对应,事后找符号文件不必翻聊天记录。
分发环节最容易踩的三个坑
- 包与符号文件不同批:测试同学装的是第三包,你手里是第二包的 dSYM
- 符号文件只存在打包机:换人、换电脑、清磁盘之后就没了
- 二次重签名或重打包:安装包内容变了,UUID 随之改变,原 dSYM 失效
常见问题
| 问题 | 说明 |
|---|---|
| 只有崩溃截图能定位吗 | 截图只看得到表层调用,缺少寄存器与线程信息,精度有限 |
| dSYM 丢了还能补吗 | 不能,符号信息与构建产物绑定,只能重新构建同版本 |
| 安卓没开混淆也要留 mapping 吗 | 建议留,体积很小,出问题时能直接对照 |
| 测试用户不愿配合复现怎么办 | 优先依赖自动上报的崩溃记录,减少对复现的依赖 |
符号化不是可选项,而是内测阶段的基本功:包发出去的那一刻,对应的符号文件就该同时入库。把归档规范做一次,后面每次崩溃排查都能省下半天。