5.3 崩溃分析与符号化:没有符号的崩溃日志是废纸
核心结论
线上崩溃日志( native crash )默认只有一串地址,看不出是哪个函数。必须保留每个发布构建的符号文件并做符号化,才能把"地址 0x1234abcd"翻译成"UMyCharacter::Tick 第 37 行"。这是从"游戏崩了"到"知道为什么崩"的关键一步。
符号文件是什么
| 平台 | 符号文件 | 生成位置 |
|---|---|---|
| Android | .so 对应的符号(带符号的 libUE4.so) |
打包时 Build 输出 / Engine/Saved |
| iOS | .dSYM 包 |
Xcode Archive 时生成 |
铁律:每次发布构建立刻把符号归档(版本号 + 平台命名),丢了就再也符号化不了——线上崩溃来了只能干瞪眼。
Android 崩溃分析流程
- 拿日志:
adb logcat或崩溃收集服务导出。 - 定位地址:找到
Signal 11 (SIGSEGV)那段,下面有 backtrace 与偏移地址。 - 符号化:用 NDK 的
addr2line或 UE 的 CrashReportClient:
# addr2line 示例(路径与参数按实际)
llvm-addr2line -e libUE4.sym.so -f -C 0x1234abcd
- 看懂 backtrace:从上往下,第一个属于自己的模块/函数通常是元凶(前面的 libc 之类是异常处理路径)。
iOS 崩溃分析流程
- 从 TestFlight/用户设备导出
.crash文件。 - 符号化:把 .crash 与对应 .dSYM 放同目录,用 Xcode 的 Organizer 或 symbolication 工具:
atos -arch arm64 -o <App.dSYM> -l <loadAddress> <address>- 崩溃报告结构:先看 Exception Type,再看线程回溯(标
0 MyGame的帧)。
常见崩溃大类(按我的经验排序)
| 类型 | 典型特征 | 查的方向 |
|---|---|---|
| 空指针访问 | SIGSEGV,集中在某个游戏逻辑 | 最近改动的 UPROPERTY 空检查 |
| 越界 | SIGSEGV,随机出现在数组操作处 | TArray 边界检查 |
| GC 相关 | 间歇性、UObject 相关 | UPROPERTY 是否正确标记(没标记被 GC 收走) |
| 多线程 | 难复现、计时敏感 | 是否有非主线程操作 U 对象 |
| 内存不足 | OOM 崩溃前有内存增长曲线 | 大贴图未卸载/泄漏(见 0303 流送池) |
常见坑与解法
坑 1:符号文件缺失,线上崩溃没法查
解法:把符号归档做成 CI 强制步骤:构建结束 → 符号上传到符号库(版本/平台目录)→ 未上传则构建失败。
坑 2:符号化结果"串线"(函数名不对)
原因:符号与构建版本不匹配(同版本号重编过)。
解法:符号与包按构建号(而非版本号)配对;CI 构建号唯一递增。
坑 3:iOS 符号化只显示 hex 地址
原因:dSYM 没匹配上(.crash 的 UUID 与 dSYM 不一致)。
解法:用 dwarfdump --uuid <App.dSYM> 对比崩溃日志头部的 UUID,一致才能符号化。
坑 4:偶发崩溃无法复现
解法: - 加崩溃上报(自建或第三方服务):上传崩溃 minidump + 设备信息。 - 重视稀有机型/系统版本分布:如果 80% 崩溃集中在 2% 的设备,考虑在设备描述里降档(见第一章)。
我的"崩溃分级响应"
| 严重度 | 定义 | 响应 |
|---|---|---|
| P0 | 启动即崩/核心流程崩 | 热修复/紧急版 24h 内 |
| P1 | 非核心流程必崩 | 下个版本 |
| P2 | 低于 0.1% 用户偶发 | 排队观察 |
关键指标:崩溃率(崩溃用户/日活)。移动平台底线经验:>2% 就该停手处理,>5% 是事故。
相关节点速查
- Android 符号化:NDK 的 addr2line(NDK 目录 toolchain 下)
- iOS 符号化:atos / Xcode
- 崩溃上报:UE 的 CrashReportClient(PC 端收集器需另行部署)
心得:我经历过最惨的一次是上线第一天崩溃率 8%,但符号文件丢了,团队两天内对着一堆地址干瞪眼。从那以后"符号先归档再发包"成了不可动摇的铁律,写进了 CI 脚本。符号文件就是崩溃日志的翻译机。
