HWAddress Sanitizer

从 NDK r21 和 Android 10(API 级别 29)开始,Android NDK 支持 HWAddress Sanitizer(也称为 HWASan)。HWASan 仅适用于 64 位 Arm 设备。

HWASan 是一款类似于 ASan 的内存错误检测工具。与传统的 ASan 相比,HWASan 具有如下特征:

  • 类似的 CPU 开销(约为 2 倍)
  • 类似的代码大小开销 (40 - 50%)
  • 更小的 RAM 开销 (10% - 35%)

HWASan 能检测到 ASan 所能检测到的同一系列错误:

  • 堆栈和堆缓冲区上溢或下溢
  • 释放之后的堆使用情况
  • 超出范围的堆栈使用情况
  • 重复释放或错误释放

此外,HWASan 还可以检测:

  • 返回之后的堆栈使用情况

示例应用

示例应用展示了如何为 hwasan 配置 build 变体

构建

若要使用 HWAddress Sanitizer 构建应用的原生 (JNI) 代码,请执行以下操作:

ndk-build

在您的 Application.mk 文件中:

APP_STL := c++_shared # Or system, or none, but not c++_static.
APP_CFLAGS := -fsanitize=hwaddress -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=hwaddress

CMake

在模块的 build.gradle 文件中:

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                # Can also use system or none as ANDROID_STL, but not c++_static.
                arguments "-DANDROID_STL=c++_shared"
            }
        }
    }
}

对于 CMakeLists.txt 中的每个目标:

target_compile_options(${TARGET} PUBLIC -fsanitize=hwaddress -fno-omit-frame-pointer)
target_link_options(${TARGET} PUBLIC -fsanitize=hwaddress)

使用 NDK 27 或更高版本时,您还可以在 build.gradle 中使用以下内容,而无需更改 CMakeLists.txt:

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                arguments "-DANDROID_SANITIZE=hwaddress"
            }
        }
    }
}

使用 ANDROID_USE_LEGACY_TOOLCHAIN_FILE=false 时,此方法不起作用。

Android 14 或更高版本:添加 wrap.sh

如果您运行的是 Android 14 或更高版本,则可以使用 wrap.sh 脚本在任何由 Android 提供支持的设备上运行您的可调试应用。如果您选择按照设置说明中的步骤操作,则可以跳过此步骤。

按照说明打包 wrap.sh 脚本,为 arm64-v8a 添加以下 wrap.sh 脚本。

#!/system/bin/sh
LD_HWASAN=1 exec "$@"

运行

如果您使用的 Android 版本低于 14,或者未添加 wrap.sh 脚本,请先按照设置说明操作,然后再运行应用。

照常运行应用。当检测到内存错误时,应用会因 SIGABRT 而崩溃,并向 Logcat 输出详细消息。您可以在 /data/tombstones 下的文件中找到该消息的副本,内容如下:

ERROR: HWAddressSanitizer: tag-mismatch on address 0x0042a0826510 at pc 0x007b24d90a0c
WRITE of size 1 at 0x0042a0826510 tags: 32/3d (ptr/mem) in thread T0
    #0 0x7b24d90a08  (/data/app/com.example.hellohwasan-eRpO2UhYylZaW0P_E0z7vA==/lib/arm64/libnative-lib.so+0x2a08)
    #1 0x7b8f1e4ccc  (/apex/com.android.art/lib64/libart.so+0x198ccc)
    #2 0x7b8f1db364  (/apex/com.android.art/lib64/libart.so+0x18f364)
    #3 0x7b8f2ad8d4  (/apex/com.android.art/lib64/libart.so+0x2618d4)

0x0042a0826510 is located 0 bytes to the right of 16-byte region [0x0042a0826500,0x0042a0826510)
allocated here:
    #0 0x7b92a322bc  (/apex/com.android.runtime/lib64/bionic/libclang_rt.hwasan-aarch64-android.so+0x212bc)
    #1 0x7b24d909e0  (/data/app/com.example.hellohwasan-eRpO2UhYylZaW0P_E0z7vA==/lib/arm64/libnative-lib.so+0x29e0)
    #2 0x7b8f1e4ccc  (/apex/com.android.art/lib64/libart.so+0x198ccc)

消息后面可能还跟着其他调试信息,包括应用中的活动线程列表、临近内存分配的标记和 CPU 寄存器值。

如需详细了解 HWASan 错误消息,请参阅了解 HWASan 报告

构建命令行可执行文件

您可以在 Android 14 及更高版本上构建和运行使用 HWASan 插桩的可执行文件。您可以使用与构建中所述的 ndk-build 或 CMake 配置相同的配置来构建可执行文件。将可执行文件推送到搭载 Android 14 或更高版本的设备,然后使用 shell 照常运行它。

如果您使用的是 libc++,请确保您使用的是共享 STL,并将其推送到设备,并在运行二进制文件时将 LD_LIBRARY_PATH 设置为包含该二进制文件的目录。

如果您不使用 Gradle,请参阅 NDK 文档,了解如何使用 CMakendk-build 从命令行构建。

Android 13 或更低版本:设置

如果您的设备搭载的是 Android 14 或更高版本,您可以跳过此步骤,然后按照构建部分中的使用 wrap.sh 的说明进行操作。您也可以选择按照本部分操作,并跳过使用 wrap.sh 的说明。

在 Android 14 之前,HWASan 应用需要 Android 的 HWASan build 才能运行。您可以将预构建的 HWASan 映像刷写到支持的 Pixel 设备上。在 ci.android.com 上可以找到这些 build,您可以在此页面上点击所需的确切 build 的方格来获取 Flash Build 链接。您需要知道您手机的代号

刷写设备 build

改为直接访问 flash.android.com 会使操作更简单,因为该流程会首先检测您的设备,且仅显示您可以使用的 build。下面的图片说明了此工具中的设置流程。

在设备上启用开发者模式,然后使用 USB 线将其连接到计算机。点击 Add new device,从对话框中选择您的设备,然后点击 Connect

检测要刷写的设备 选择要连接的设备

您的设备连接后,点击它来配置 build。在 Select a build ID 框中,选择 aosp-master-with-phones-throttled 分支以自动为您连接的设备选择正确的映像。

选择要刷写的设备 确认刷写选项并刷写设备

点击 Install 以刷写您的设备。

如需详细了解必要的设置,请参阅 Android 刷写工具文档。或者,您也可以查看 AOSP 文档,了解如何从源代码构建 HWASan 映像。