崩溃与 Dump

自动关联目录:崩溃与 Dump

1. 定位

C++ 的崩溃是"内存早就被写坏了,只是现在才发现"。崩溃现场那一行代码,常常不是问题发生的地方。

一句话定位:先确定"坏在哪一类",再选工具。用错工具查一天,用对工具十分钟。

2. 崩溃类型与判据

类型典型表现常见原因
空指针解引用访问地址 0x0 附近未判空、异步回调时对象已销毁
野指针 / UAF随机地址、随机时机已释放内存继续用
栈溢出stack overflow深递归、栈上大数组
堆损坏malloc/free 断言、随机崩越界写、double free
虚表损坏崩在奇怪的虚调用对象已被覆盖或已析构
断言失败check / ensure / assert前置条件不满足
OOM分配失败泄漏或真的不够

按崩溃地址快速判断:

地址含义
0x00000000 附近(小偏移)空指针 + 访问成员
0xCCCCCCCC / 0xCDCDCDCDMSVC Debug:未初始化 / 已释放内存
0xDDDDDDDD已释放(MSVC Debug 填充)
随机大地址野指针

3. 拿到栈

平台做法
开发机(IDE)直接看调用栈(配好符号)
Windows 现场minidump,用 WinDbg / VS 打开
Androidaddr2line 还原 tombstone 里的地址
iOS崩溃日志 + dSYM 符号化
Linuxcore 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/越界内存与速度代价大真机长时间测试较难,可针对性开
StompAllocatorUAF 立刻崩在现场内存开销巨大只在专门排查时开
保留符号线上崩溃可读包体(可剥离存档)从不做——这是必须的
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 步"复现"是分水岭:能稳定复现的都能查清;不能复现的,靠崩溃聚合 + 增加诊断信息 + 等下一次。

参考