Native 崩溃日志只剩一串地址:HWASan 怎么抓 use-after-free 和越界
线上偶发 Native 崩溃,FaultLog 里只有访问地址和调用栈;重跑几十次也不复现。团队开始怀疑线程调度,却没有先回答一个更基础的问题:这块内存是在释放后又被访问,还是写越界破坏了邻接对象?HWASan 的价值正是把非法访问与分配、释放位置放进同一条证据链。
验证范围:本文依据华为开发者官网截至 2026-09-22 的公开资料整理。当前本机 DevEco SDK 为 API 24,且没有连接 HDC 真机;涉及 API 26 的接口片段用于说明接入与排障边界,不宣称已经完成 API 26 编译或真机验证。文中的时间戳校验、状态机、坐标换算、去重合并与资源预算逻辑已通过 Node.js 宿主测试,正式上线仍需在 API 26 SDK 和目标设备上补齐编译、权限、异常分支与性能验收。
先把误判停下来
HWASan 不是所有内存问题的统一答案。它适合定位 use-after-free、double-free、栈或堆越界等非法访问;“内存一直涨”仍要用泄漏与资源工具。启用插桩也会改变性能和内存开销,因此只用于调试构建与可控测试,不应把插桩包当正式发布包。
structFrameBuffer{uint8_t*data{nullptr};size_t size{0};};boolWriteByte(FrameBuffer&buf,size_t index,uint8_tvalue){if(buf.data==nullptr||index>=buf.size)returnfalse;buf.data[index]=value;returntrue;}案例一:图片缓存淘汰后回调仍持有裸指针
主线程淘汰缓存并释放 Pixel 数据,后台解码完成回调仍用旧地址写入。HWASan 报告中的“访问位置、释放位置、分配位置”应一起看;只修崩溃行的空指针判断无法解决悬空指针。更稳妥的做法是共享所有权或用世代号拒绝过期回调。
案例二:按像素数分配,却按字节数写入
RGBA 图片需要考虑每像素字节数与行跨度。若分配width * height,写入却按width * height * 4,错误只在特定尺寸触发。修复后要在边界尺寸、奇数行跨度和失败分配路径下重复跑同一用例。
boolRequiredBytes(size_t width,size_t height,size_t channels,size_t&out){if(channels==0||width>SIZE_MAX/channels)returnfalse;size_t row=width*channels;if(height>SIZE_MAX/row)returnfalse;out=row*height;returntrue;}证据怎么对齐
| 观察项 | 错误做法 | 可复核做法 |
|---|---|---|
| use-after-free | 崩溃点附近加锁 | 核对分配、释放、访问三段栈 |
| 堆越界 | 扩大固定缓冲区 | 按尺寸、通道和 rowStride 计算 |
| double-free | 第二次释放前判空 | 统一所有权和释放责任 |
| 内存增长 | 直接启用 HWASan | 改用泄漏和资源分类工具 |
为什么采用这条路径
先用错误类型决定工具,再按报告还原对象生命周期。修复目标不是“这次不崩”,而是让所有权、边界和异步取消能被代码表达。
上线前复核
- 调试构建开启 HWASan,发布构建保持关闭。
- 保留完整报告与未裁剪的分配/释放栈。
- 复现脚本固定输入、次数和并发时序。
- 修复后覆盖取消、超时、页面退出和资源淘汰。
- 内存增长问题不与非法访问混用同一结论。
官方资料
- HWASan 使用指导
- 内存泄漏类问题检测方法
一串崩溃地址没有告诉你所有权,但 HWASan 能把非法访问放回生命周期。读懂三段栈,比在崩溃行继续堆判空更接近根因。