如需了解如何读取 HWASan 崩溃,请参阅了解 HWASan 报告!
硬件辅助的 AddressSanitizer (HWASan) 是一款类似于 AddressSanitizer 的内存错误检测工具。与 ASan 相比,HWASan 使用的内存少得多,因而更适合用于整个系统的清理。HWASan 仅适用于 Android 10 及更高版本,且只能用于 AArch64 硬件。
虽然 HWASan 主要适用于 C/C++ 代码,但也有助于调试导致用于实现 Java 接口的 C/C++ 代码崩溃的 Java 代码。HWASan 在这种情况下很有用,因为它能捕获到发生的内存错误,并直接将您带到导致问题发生的代码。
您可以通过 ci.android.com,将预构建的 HWASan 映像刷写到支持的 Pixel 设备上(详细设置说明)。
与传统的 ASan 相比,HWASan 具有如下特征:
- 类似的 CPU 开销(约为 2 倍)
- 类似的代码大小开销 (40 - 50%)
- 更小的 RAM 开销 (10% - 35%)
HWASan 能检测到 ASan 所能检测到的同一系列错误:
- 堆栈和堆缓冲区上溢/下溢
- 释放之后的堆使用情况
- 超出范围的堆栈使用情况
- 重复释放/错误释放
此外,HWASan 还可以检测返回之后的堆栈使用情况。
HWASan(与 ASan 相同)与 UBSan 兼容,您可以对一个目标同时启用这二者。
实现详情和限制
HWASan 基于内存标记方法,在这种方法中,小的随机标记值同时与指针和内存地址范围相关联。为使内存访问有效,指针和内存标记必须匹配。HWASan 依赖于 ARMv8 功能 Top-Byte-Ignore(TBI,也称为虚拟地址标记)将指针标记存储在地址的最高位。
如需详细了解 HWASan 的设计,请前往 Clang 文档所在的网站。
从设计上讲,ASan 对检测上溢规定了有限大小的红色区域,并对检测释放后使用情况规定了有限容量隔离区,而 HWASan 则没有这些规定。因此,无论上溢有多大或内存在多久之前解除分配,HWASan 都可以检测到 bug。这赋予 HWASan 比 ASan 大得多的优势。
不过,HWASan 具有有限数量的可能标记值(256 个),这意味着在一次程序执行期间忽略任何 bug 的概率为 0.4%。
要求
较新版本(4.14 及更高版本)的通用 Android 内核开箱即支持 HWASan。Android 10 专属分支不支持 HWASan。
从 Android 11 开始提供对 HWASan 的用户空间支持。
如果您使用的是其他内核,则 HWASan 要求 Linux 内核接受系统调用参数中已加标记的指针。在下列上游补丁集中实现了对此项要求的支持:
- arm64 已标记地址 ABI
- arm64:对传递给内核的用户指针取消标记
- mm:避免在 brk()/mmap()/mremap() 中创建虚拟地址别名
- arm64:验证从内核线程调用的 access_ok() 中的已标记地址
如果您要使用自定义工具链进行构建,请确保它包含直到 LLVM 提交 c336557f 的所有内容。
使用 HWASan
如需使用 HWASan 构建整个平台,请使用以下命令:
lunch aosp_walleye-userdebug # (or any other product)
export SANITIZE_TARGET=hwaddress
m -j
为方便起见,您可以向产品定义中添加 SANITIZE_TARGET 设置,与 aosp_coral_hwasan 类似。
对于熟悉 AddressSanitizer 的用户来说,构建过程中的许多复杂问题都不复存在了:
- 无需运行两次 make。
- 增量 build 可以直接使用。
- 无需刷写 userdata。
此外,还取消了一些 AddressSanitizer 限制:
- 支持静态可执行文件。
- 可以跳过对除 libc 以外的任何目标的排错。与 ASan 不同,如果库经过排错,并不要求对与其关联的任何可执行文件也进行排错。
可以在 build 号相同(或更高)的 HWASan 和常规映像之间自由切换。无需擦除设备。
如需跳过对某个模块的排错,请使用 LOCAL_NOSANITIZE := hwaddress
(Android.mk) 或 sanitize: { hwaddress: false }
(Android.bp)。
对单个目标进行清理
可以在常规(未排错)build 中按目标启用 HWASan,只要 libc.so
也进行了排错即可。将 hwaddress: true
添加到 bionic/libc/Android.bp 的 "libc_defaults"
中的 sanitize 块。然后,在要处理的目标中执行相同的操作。
请注意,如果对 libc 进行排错,便会在整个系统范围内对堆内存分配添加标记,并检查 libc.so
中的内存操作的标记。如果错误的内存访问位于 libc.so
中(例如经过 delete()
的互斥量上的 pthread_mutex_unlock()
),即使在未启用 HWASan 的二进制文件中,这也可能会捕获 bug。
如果整个平台是使用 HWASan 构建的,则无需更改任何 build 文件。
Flashstation
出于开发目的,您可以使用 Flashstation 将支持 HWASan 的 AOSP build 刷写到引导加载程序已解锁的 Pixel 设备上。 选择 _hwasan 目标,例如 aosp_flame_hwasan-userdebug。如需了解详情,请参阅面向应用开发者的 HWASan NDK 文档。
更出色的堆栈轨迹
HWASan 使用基于帧指针的快速展开程序 (unwinder),根据程序中的每个内存分配和取消分配事件来记录堆栈跟踪信息。默认情况下,Android 在 AArch64 代码中启用帧指针,因此这在实际操作中非常有用。如果您需要通过托管代码展开,请在进程环境中设置 HWASAN_OPTIONS=fast_unwind_on_malloc=0
。请注意,错误的内存访问堆栈跟踪会默认使用“慢速”展开程序;此设置仅影响分配和取消分配跟踪。此选项可能对 CPU 要求极高,具体取决于负载。
符号化
请参阅“了解 HWASan 报告”中的符号化。
在应用中使用 HWASan
与 AddressSanitizer 类似,HWASan 无法检查 Java 代码,但可以检测 JNI 库中的 bug。在 Android 14 之前,不支持在非 HWASan 设备上运行 HWASan 应用。
在 HWASan 设备上,可以使用 HWASan 检查应用,方法是使用 Make 中的 SANITIZE_TARGET:=hwaddress
或编译器标志中的 -fsanitize=hwaddress
来构建代码。在搭载 Android 14 或更高版本的非 HWASan 设备上,必须添加 wrap.sh 文件设置 LD_HWASAN=1
。如需了解更多详情,请参阅应用开发者文档。