崩溃与 Dump
自动关联目录:崩溃与 Dump
1. 定位
C++ 的崩溃是"内存早就被写坏了,只是现在才发现"。崩溃现场那一行代码,常常不是问题发生的地方。
一句话定位:先确定"坏在哪一类",再选工具。用错工具查一天,用对工具十分钟。
2. 崩溃类型与判据
| 类型 | 典型表现 | 常见原因 |
|---|---|---|
| 空指针解引用 | 访问地址 0x0 附近 | 未判空、异步回调时对象已销毁 |
| 野指针 / UAF | 随机地址、随机时机 | 已释放内存继续用 |
| 栈溢出 | stack overflow | 深递归、栈上大数组 |
| 堆损坏 | malloc/free 断言、随机崩 | 越界写、double free |
| 虚表损坏 | 崩在奇怪的虚调用 | 对象已被覆盖或已析构 |
| 断言失败 | check / ensure / assert | 前置条件不满足 |
| OOM | 分配失败 | 泄漏或真的不够 |
按崩溃地址快速判断:
| 地址 | 含义 |
|---|---|
0x00000000 附近(小偏移) | 空指针 + 访问成员 |
0xCCCCCCCC / 0xCDCDCDCD | MSVC Debug:未初始化 / 已释放内存 |
0xDDDDDDDD | 已释放(MSVC Debug 填充) |
| 随机大地址 | 野指针 |
3. 拿到栈
| 平台 | 做法 |
|---|---|
| 开发机(IDE) | 直接看调用栈(配好符号) |
| Windows 现场 | minidump,用 WinDbg / VS 打开 |
| Android | addr2line 还原 tombstone 里的地址 |
| iOS | 崩溃日志 + dSYM 符号化 |
| Linux | core dump + gdb |
# Android 还原(UE 提供 ndk 工具链)
aarch64-linux-android-addr2line.exe -f -C -e libMyGame.so 0x0000000000123456没有符号的栈等于没有栈。发布流程必须包含"保留符号表"这一步——符号可以剥离,但要单独存档(见 Security 中符号剥离的代价)。
4. 工具
| 工具 | 查什么 | 代价 |
|---|---|---|
| AddressSanitizer (asan) | 越界、UAF、double free | 内存 2–3 倍,速度慢约 2 倍 |
| UndefinedBehaviorSanitizer (ubsan) | 未定义行为 | 较轻 |
| ThreadSanitizer (tsan) | 数据竞争 | 较重 |
| MemorySanitizer (msan) | 未初始化读取 | 需重编依赖 |
| Valgrind | 泄漏、非法访问 | 极慢(Linux) |
| StompAllocator(UE) | 立刻捕获 UAF(页保护) | 内存开销大 |
| Vulkan Validation / Command Replay | 图形 API 侧错误 | — |
asan 是性价比最高的一个——越界写和 UAF 这两类最难查的问题,它能直接指到分配和释放的那两行。
项目实测的稳定性工具(Address Sanitizer、StompAllocator、Vulkan Validation 等)见 UE性能优化工具 的稳定性章节。
5. 代价与权衡
| 手段 | 收益 | 代价 | 什么时候不该用 |
|---|---|---|---|
| asan | 直接定位 UAF/越界 | 内存与速度代价大 | 真机长时间测试较难,可针对性开 |
| StompAllocator | UAF 立刻崩在现场 | 内存开销巨大 | 只在专门排查时开 |
| 保留符号 | 线上崩溃可读 | 包体(可剥离存档) | 从不做——这是必须的 |
ensure 代替 check | 现场保留,便于继续排查 | 错误被"放过" | 真正的致命错误仍应 check |
| 崩溃聚合 | 看出趋势与优先级 | 需要上报设施 | 单人开发可简化 |
6. 踩坑与排查
| 坑 | 现象 | 怎么验证 |
|---|---|---|
| 发布时不保留符号 | 拿到栈也读不懂 | 检查发布流程里的符号归档 |
| 只在 Release 下崩 | 通常是未初始化变量(Debug 会填 0xCC) | Debug 与 Release 都测 |
| 只在 Debug 下崩 | 迭代器/断言/内存布局差异 | 同上 |
| 崩溃点不是问题点 | 内存早被写坏 | 用 asan 找"谁写坏了它" |
| 加壳/混淆后栈读不懂 | 没留符号映射 | 见 Security 篇 |
用 check 处理可恢复错误 | 一次异常直接崩 | 改 ensure |
| 没做崩溃聚合 | 不知道哪个最该修 | 上线前要有聚合 |
异步回调直接用裸 this | 对象已销毁 | 用弱引用(见 GC) |
7. 排查顺序
1. 拿到栈(符号化)
2. 判断崩溃类型(地址/异常码)
3. 复现(最小步骤)
4. 缩小范围(二分、加日志、条件屏蔽)
5. 上工具(asan / StompAllocator)
6. 定位根因后,补一条断言或单测防回归第 3 步"复现"是分水岭:能稳定复现的都能查清;不能复现的,靠崩溃聚合 + 增加诊断信息 + 等下一次。
参考
- UE性能优化工具(稳定性章节含真实案例)
- 日志与断言体系 · GC 与对象生命周期 · Security
- 崩溃与调试(
Programing/C&C++的语言层视角)
许可协议:CC BY
作者:Davids
本文链接:https://hustjjd.github.io/939ee7a1.html
更新于:2026年10月10日