踩内存问题一键定位:HWASAN + BinXO无源码内存检测实战指南
HWASAN(Hardware-Assisted Address Sanitizer,硬件辅助地址消毒器)是 Clang 开源社区推出的一种内存检测工具,主要用于开发态的内存问题检测。它利用 ARMv8.5 及以上芯片的 MTE(Memory Tagging Extension)硬件特性,在内存访问时通过 tag 比对快速发现踩内存问题。

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:
踩内存问题一键定位:HWASAN + BinXO无源码内存检测实战指南-华为开发者话题 | 华为开发者联盟
适用版本:DevEco Studio 6.1.0.830 及以上版本
【背景与收益】
什么是 HWASAN?
HWASAN(Hardware-Assisted Address Sanitizer,硬件辅助地址消毒器)是 Clang 开源社区推出的一种内存检测工具,主要用于开发态的内存问题检测。它利用 ARMv8.5 及以上芯片的 MTE(Memory Tagging Extension)硬件特性,在内存访问时通过 tag 比对快速发现踩内存问题。
注意:HWASAN 定位为开发态检测工具,不建议在发布版本中使能。其主要用于开发阶段和测试阶段快速发现内存问题,提升应用稳定性。
在鸿蒙应用开发中,踩内存(Memory Corruption) 是 C++ Native 层最常见的稳定性问题之一。踩内存问题包括堆缓冲区溢出(heap-buffer-overflow)、堆内存释放后使用(heap-use-after-free)、双重释放(double-free)等,这些问题往往导致应用随机崩溃,且由于崩溃现场与根因脱节,定位极其困难。
为什么需要 HWASAN + BinXO?
传统 HWASAN(Hardware-Assisted Address Sanitizer)需要开发者基于源码重新编译,面临以下痛点:
| 痛点 | 说明 |
| 跨部门沟通成本高 | 应用内部模块多,二进制文件.so 散落在各部门,需要所有库重新编译 |
| 四方库无法插桩 | 第三方动态库无源码,传统 HWASAN 无法检测 |
| 定位效率低 | 未插桩模块踩内存导致随机崩溃,根因无法关联 |
HWASAN + BinXO 方案的核心收益:
• ✅ 无源码插桩:基于 BinXO 二进制编译优化技术,对已编译的 .so 动态库直接进行 HWASAN 插桩,无需源码
• ✅ 一键使能:DevEco Studio 6.1.0.830+ 版本支持 IDE 界面勾选,无需手动添加编译参数
• ✅ 全面覆盖:同时开启源码编译插桩 + 无源码二进制插桩,检测覆盖率大幅提升
• ✅ 快速定位:IDE 集成堆栈跟踪分析工具,直接定位报错栈和申请栈
【技术原理】
BinXO 二进制插桩原理
BinXO 是华为自研的轻量化二进制编译优化器,可以在二进制层级进行优化和运行时信息采集。基于 BinXO 的 SourceLess HWASAN 方案,在汇编层级对已编译的 .so 动态库进行 HWASAN 插桩,实现无源码场景下的内存访问检测。
核心流程如下:
1. 访存指令插桩:在汇编层级识别所有 load/store 访存指令,插入 tag 检查逻辑
• 保存部分寄存器于栈上,用于后续计算
• 提取访问地址 addr
• 计算对应 tag(基于地址高位 / shadow memory)
• 与 shadow memory 中存储的 tag 对比
• 不匹配则触发异常(report + abort),匹配则恢复相关寄存器继续运行
2. 堆内存插桩:识别 malloc/free 函数的调用点,动态插入 tag 分配和回收代码
• 分配内存时自动添加 tag
• 释放内存时自动清除 tag 信息
• 为返回地址高位打上 tag
3. 运行时检测:每次内存访问时,检查地址高位 tag 与 shadow memory 中的 tag 是否一致,不匹配则触发异常
支持检测的内存问题类型
| 问题类型 | 源码编译插桩 | BinXO 二进制插桩 |
| heap-buffer-overflow(堆缓冲区溢出) | ✅ | ✅ |
| heap-use-after-free(堆释放后使用) | ✅ | ✅ |
| double-free(双重释放) | ✅ | ✅ |
| alloc-size-too-big(分配过大) | ✅ | ✅ |
| out-of-memory(内存不足) | ✅ | ✅ |
| stack-buffer-overflow(栈缓冲区溢出) | ✅ | ❌ |
| global-overflow(全局变量溢出) | ✅ | ❌ |
注意:BinXO 二进制插桩当前主要支持堆类内存检测,栈类检测需使用源码编译插桩。
可检测的访存指令类型
BinXO 支持以下访存指令的检测:
标量指令:
• A64_LDP_STP
• A64_LDR_STR_IMMED
• A64_LDR_STR_REG
• A64_LDR_STR_UNSIGNED_IMMED
SIMD 指令:
• A64_LDX_STX_MULTIPLE
• A64_LDX_STX_MULTIPLE_POST
• A64_LDX_STX_SINGLE
• A64_LDX_STX_SINGLE_POST
【典型使用场景】
以下场景推荐使用 HWASAN + BinXO 进行内存问题检测:
1. C++ Native 占比高的应用:如音视频处理、游戏引擎、图像处理等大量使用 C++ 的应用
2. 集成第三方 SDK 的应用:第三方 SDK 通常只有编译好的 .so 文件,无源码可用
3. 线上踩内存问题频发:天网版本检测到大量踩内存问题,需要快速定位根因
4. 跨模块内存问题排查:多个模块间内存交互复杂,需要全量插桩检测
未使用 HWASAN + BinXO 时,开发者可能遇到以下问题:
| 问题现象 | 影响 |
| 应用随机崩溃,日志无法定位根因 | 崩溃现场与踩内存根因脱节,排查困难 |
| 第三方 SDK 踩内存无法检测 | 无源码场景下传统 HWASAN 无法插桩 |
| 跨模块内存问题定位周期长 | 以某应用为例,未插桩模块踩内存攻关一个月才定位 |
| 小概率问题为结论处理比例高 | 踩内存导致随机崩溃,领域投入分析积极性低 |
【详细操作指南】
方式一:IDE 界面配置(推荐)
适用场景:调试/本地场景,DevEco Studio 6.1.0.830 及以上版本
步骤 1:创建 Native 工程
在 DevEco Studio 中创建包含 C++ Native 的代码工程。
步骤 2:开启 HWASAN
1. 点击菜单 Run > Edit Configurations
2. 进入 Diagnostics 选项卡
3. 勾选 Hardware-Assisted Address Sanitizer,开启 C++ 源码编译插桩检测

步骤 3:开启 BinXO(无源码二进制插桩)
从 DevEco Studio 6.1.0.830 版本开始,在同一界面勾选 BinXO check,开启无源码 .so 文件的 HWASAN 插桩检测。

步骤 4:构建 HAP 包
点击构建按钮,DevEco Studio 将自动完成 HWASAN 源码插桩和 BinXO 二进制插桩,生成检测包。
步骤 5:推包并运行测试
1. 将构建的 HAP 包推送到设备
2. 关闭应用 freeze 检测(依赖 HAP 包指定为 DEBUG 包):
aa attach -b <bundleName>
3. 启动应用,执行相关用例
步骤 6:获取 HWASAN 日志
IDE 将自动收集 HWASAN 日志,可在日志视图中查看。

步骤 7:堆栈跟踪分析
1. 将 HWASAN 日志拷贝到 IDE 分析工具的左上角区域
2. 勾选 “解读堆栈跟踪“
3. 导入符号表信息
4. 点击 “开始分析“
5. 观测右下角的 报错栈 和 申请栈,直接定位问题

方式二:配置文件
适用场景:需要持久化配置或流水线构建
系统级配置
在工程目录下的 AppScope/app.json5 文件中添加 HWASAN 配置开关:
{
"app": {
"hwasanEnabled": true
}
}
模块级配置
在模块级 build-profile.json5 中添加构建参数:
{
"buildOption": {
"externalNativeOptions": {
"arguments": [
"-DOHOS_ENABLE_HWASAN=ON",
"-DOHOS_ENABLE_BINXO=ON"
]
}
}
}
版本说明:
• DevEco Studio 6.1.0.830 以下版本:仅配置 -DOHOS_ENABLE_HWASAN=ON(仅源码插桩)
• DevEco Studio 6.1.0.830 及以上版本:同时配置两个参数,开启源码 + 无源码检测
方式三:命令行参数
适用场景:流水线/CI 构建
# DevEco Studio 6.1.0.830 及以上版本
hvigorw assembleHar -p ohos-enable-hwasan=true -p ohos-enable-binxo=true
# DevEco Studio 6.1.0.830 以下版本(仅源码插桩)
hvigorw assembleHar -p ohos-enable-hwasan=true
可选配置:排除特定 SO
部分 .so 由于技术原理限制不支持 BinXO 插桩,需要配置忽略以避免构建报错:
{
"buildOption": {
"nativeLib": {
"excludeSoFromBinXO": ["**/liblibrary.so"]
}
}
}
支持正则匹配,可在工程级或模块级 build-profile.json5 中配置。
【真实案例分析】
XXXX App 案例
XXXX App 使用 SourceLess HWASAN 后,帮助开发者发现了 libxxxa.so 中的访存问题。
原始日志:
Reason:HWASAN
==appspawn==14177==ERROR: HWAddressSanitizer: allocation-tail-overwritten; heap object [0x00130004a600,0x00130004aa51) of size 1105
Stack of invalid access unknown. Issue detected at deallocation time.
deallocated here:
#0 0x5ae366b394 (/system/asan/lib64/libclang_rt.hwasan.so+0x2b394) (BuildId: ace54fff777e92f7758544f77b2b931f9f1c7b55)
#1 0x651af0b720 (/data/storage/el1/bundle/libs/arm64/libtocks.so+0xb720) (BuildId: 85c301da032d17f70c0e1d6bf4de932b58317447)
#2 0x5aee4e6c84 (/system/asan/lib64/platformsdk/libace_napi.z.so+0x66c84) (BuildId: ccd61d3dec598e77f88de9fb142e4095)
#3 0x7e86a5b5cc (/system/asan/lib64/module/arkcompiler/stub.an+0x5c85cc)
#4 0x7e864a3320 (/system/asan/lib64/module/arkcompiler/stub.an+0x10320)
#5 0x275bdd79f4 ([anon:ArkTS Heapsemi space]+0x179f4)
allocated here:
#0 0x5ae366b0e0 (/system/asan/lib64/libclang_rt.hwasan.so+0x2b0e0) (BuildId: ace54fff777e92f7758544f77b2b931f9f1c7b55)
#1 0x651af12d54 (/data/storage/el1/bundle/libs/arm64/libtocks.so+0x12d54) (BuildId: 85c301da032d17f70c0e1d6bf4de932b58317447)
#2 0x651af0b6b0 (/data/storage/el1/bundle/libs/arm64/libtocks.so+0xb6b0) (BuildId: 85c301da032d17f70c0e1d6bf4de932b58317447)
#3 0x5aee4e6c84 (/system/asan/lib64/platformsdk/libace_napi.z.so+0x66c84) (BuildId: ccd61d3dec598e77f88de9fb142e4095)
#4 0x7e86a5b5cc (/system/asan/lib64/module/arkcompiler/stub.an+0x5c85cc)
#5 0x7e864a3320 (/system/asan/lib64/module/arkcompiler/stub.an+0x10320)
#6 0x275bdd79f4 ([anon:ArkTS Heapsemi space]+0x179f4)
从日志可以看出,堆内存[0x00130004a600,0x00130004aa51) 出现了越界访问。该块内存的申请的代码在libtocks.so+0x12d54的位置。

反汇编该so,通过汇编可以看出,在0x12d6c的位置有一个越界访问。 如果开发者有源码, 可以更快的发现内存的问题。
检测效果:
• 无需获取第三方库源码
• 直接定位到具体 .so 文件中的问题
• 大幅缩短问题定位周期
【常见问题解答】
Q1:BinXO 二进制插桩有哪些规格限制?
| 限制项 | 规格 |
| ELF 文件大小 | ≤ 128MB |
| 目标文件类型 | C/C++ 编译的 .so 动态库 |
| 二进制体积增加 | 约 2 倍 |
| 内存占用增加 | 10% ~ 35% |
| 性能影响 | 在源码编译插桩基础上增加 1.5x ~ 3x |
Q2:HWASAN 使用 ARM64 地址高 8 位,会与应用冲突吗?
HWASAN 使用了 ARM64 虚拟地址的高 8 位作为 tag 存储。如果应用中也有动态库使用了高 8 位,会产生冲突导致误报。
规避方案:
1. 优先方案:该动态库针对 HWASAN 版本单独出一个二进制,关闭对地址高 8 位的使用
2. 次选方案:关闭该 .so 的 HWASAN/BinXO 插桩(影响:该 .so 的踩内存故障无法检测)
Q3:源码编译插桩遇到 `fixup not sufficiently aligned` 报错怎么办?
根因:HWASAN 源码编译要求栈变量 16 字节对齐,报错文件中有内联汇编开辟了未 16 字节对齐的栈变量,或定义了 pack 强制未 16 字节对齐的临时变量。
解决方案:
• 修改为 16 字节对齐
• 或在问题函数定义右侧添加修饰符:__attribute__((no_sanitize(“hwaddress“))),该函数不进行 HWASAN 编译插桩
Q4:BinXO 插桩报错如何处理?
| 错误码 | 错误描述 | 处理建议 |
| 0x11801001 | 输入文件已经是支持 HWASAN 的文件 | 跳过该文件 |
| 0x11801002 | 输入文件大小超过规格限制 | 跳过该文件或拆分减小文件大小 |
| 0x11801003 | 输入文件损坏或缺少相关 ELF 信息 | 跳过该文件 |
| 0x11801004 | 输入文件不是 C/C++ 编译而来 | 跳过该文件 |
| 0x11801007 | 输入文件不是标准 ELF 文件 | 跳过该文件 |
| 0x11801009 | 输入或输出文件无法打开 | 提供正确的文件名或路径 |
| 0x1180100A | 生成输出文件处理失败 | 跳过该文件 |
| 0x1180100B | 输出文件的临时文件被占用 | 删除/重命名输出目录下的临时文件(<输出文件名>.temp) |
【参考文档】
• HWASAN 原理介绍:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-address-sanitizer-principle
• HWASAN 使用指南:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-hwasan#section31022911513
• HWASAN 最佳实践:https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-stability-hwasan-detection
• Clang HWASAN 开源文档:https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html
总结:HWASAN + BinXO 方案通过二进制插桩技术,实现了无源码场景下的内存访问检测,大幅降低了踩内存问题的定位门槛。开发者只需在 DevEco Studio 中勾选两个选项,即可开启全面的内存检测能力,结合 IDE 集成的堆栈跟踪分析工具,能够快速定位内存问题根因,提升应用稳定性。
本文由 @华为开发者联盟 授权发布于人人都是产品经理。未经作者许可,禁止转载
题图来自Unsplash,基于CC0协议
该文观点仅代表作者本人,人人都是产品经理平台仅提供信息存储空间服务
- 目前还没评论,等你发挥!

起点课堂会员权益



