使用 Address Sanitizer 调试内存损坏

本文档介绍了如何在使用 AGDE 时启用特殊调试工具。这些工具可以帮助您解决难以诊断的内存损坏和覆盖错误。

HWAddress Sanitizer 和 Address Sanitizer

HWAddress Sanitizer (HWASan) 和 Address Sanitizer (ASan) 是内存损坏调试工具,有助于调试内存损坏和覆盖错误,例如:

  • 堆栈缓冲区溢出和下溢
  • 堆缓冲区溢出和下溢
  • 堆栈超出其作用域使用
  • 双重释放和野指针释放错误
  • 返回后堆栈使用(仅限 HWASan)

我们建议仅在调试问题时或作为自动化测试的一部分启用 HWASan 或 ASan。虽然这些工具性能良好,但使用它们会带来一定的开销。

运行时行为

启用后,HWASan 和 ASan 都会自动检查应用中的内存损坏。

如果检测到内存错误,应用会因 SIGBART(信号中止)错误而崩溃,并将详细消息打印到 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)

前提条件

HWASan 要求

要使用 HWASan:

  • 您必须使用 AGDE 24.1.99 或更高版本。
  • 应用必须使用 NDK 26 或更高版本构建。
  • 应用必须使用目标 SDK 34 或更高版本构建。
  • 目标必须是运行 Android 14 (API 级别 34) 或更高版本的 arm64-v8a 设备。

在项目中使用共享 C++ 标准库

由于已知问题,ASan 在使用 libc++_static 时与 C++ 异常处理不兼容。使用 libc++_shared 时不会出现此问题。

HWASan 有其自己的运算符 newdelete 实现,如果标准库静态链接到项目中,则无法使用这些实现。

要更改此设置,请参阅本文档的链接 C++ 标准库部分。

启用帧指针生成

HWASan 和 ASan 使用基于快速帧指针的解栈器为内存分配和解除分配事件生成堆栈跟踪信息。这意味着您必须在 C++ 编译器设置中启用帧指针生成才能使用这些功能。也就是说,您需要禁用帧指针省略优化。

要更改此设置,请参阅本文档的启用帧指针生成部分。

配置您的 Visual Studio 项目以使用 HWASan 或 ASan

启用 HWASan 或 ASan

要启用 HWASan 或 ASan,请在项目的属性页中依次前往配置属性 > 常规

The Visual Studio Solution Explorer properties menu for the current
project.

图 1:Visual Studio 解决方案资源管理器窗口中的项目属性选项。

The project Property Pages dialog with General properties shown, and Address
Sanitizer settings highlighted.

图 2:项目常规属性中的 Address Sanitizer (ASan) 设置。

要为项目启用 HWASan,请将 Address Sanitizer (ASan) 设置更改为 Hardware ASan Enabled (fsanitize=hwaddress)

要为项目启用 ASan,请将 Address Sanitizer (ASan) 设置更改为 ASan Enabled (fsanitize=address)

启用帧指针生成

帧指针生成由 Omit Frame Pointer C/C++ 编译器设置控制,可在项目属性页配置属性 > C/C++ > 优化下找到。

The project Property Pages dialog with C/C++ Optimization properties shown,
and Omit Frame Pointer settings
highlighted.

图 3Omit Frame Pointer 设置的位置。

使用 HWASan 或 ASan 时,请将 Omit Frame Pointer 设置为 No (-fno-omit-frame-pointer)

以共享库模式链接 C++ 标准库

C++ 标准库的链接器模式设置可在项目属性页中依次前往配置属性 > 常规下的项目默认值部分找到。

The project Property Pages dialog with the General category selected, and the
Use of STL setting
highlighted.

图 4:C++ 标准库链接器模式设置的位置。

使用 HWASan 或 ASan 时,请将 Use of STL 设置为 Use C++ Standard Libraries (.so)。此值会将 C++ 标准库作为共享库链接到项目中,这是 HWASan 和 ASan 正常运行所必需的。

创建用于 Address Sanitizer 的构建配置

如果您倾向于暂时性使用 HWASan 或 ASan,您可能不想专门为它们创建新的构建配置。这可能适用于项目规模较小、您正在探索该功能或在测试期间发现问题的情况。

但是,如果您觉得它有用并计划定期使用,您可以考虑为 HWASan 或 ASan 创建新的构建配置,如Teapot 示例中所示。例如,如果您定期在单元测试中运行 Address Sanitizer,或在游戏的夜间冒烟测试期间运行,则可以执行此操作。

如果您有一个大型项目,其中使用了大量不同的第三方库,并且您通常将它们与 C++ 标准库静态链接,那么创建单独的构建配置可能特别有用。专用的构建配置有助于确保您的项目设置始终准确。

要创建构建配置,请从项目的属性页中点击配置管理器…按钮,然后打开活动解决方案配置下拉菜单。然后选择 ,并创建一个具有适当名称(例如,HWASan enabled)的新构建配置。

将 HWASan 与自定义内存分配器一起使用

HWASan 会自动拦截通过 malloc(或 new)分配的内存,以便它可以将标签注入指针并检查标签不匹配。

但是,当使用自定义内存分配器时,HWASan 无法自动拦截您的自定义内存分配方法。因此,如果您想将 HWASan 与自定义内存分配器一起使用,请对内存分配器进行插桩以显式调用 HWASan。这只需几行代码即可完成。

前提条件

您需要调用的 HWASan 方法在此头文件中定义:

#include "sanitizer/hwasan_interface.h"

对您的内存分配方法进行插桩

  1. 以 16 字节块粒度和对齐方式分配对象。例如,如果您有一个池分配器,它提供 24 字节固定大小的对象,请将您的分配向上取整到 32 字节,并对齐到 16 字节。

  2. 生成一个 8 位标记。您的标记不得使用 0-16 范围内的值,因为这些值保留供内部使用。

  3. 启用 HWASan 以开始使用该标记跟踪内存区域

    __hwasan_tag_memory((void*) address, tag, size);
    
  4. 将标记注入指针的最高 8 位

    address = __hwasan_tag_pointer((void*) address, tag);
    

对您的内存解除分配方法进行插桩

  1. 重置内存区域的标记,以使通过现有标记指针的进一步访问失败

    __hwasan_tag_memory(__hwasan_tag_pointer(ptr, 0), 0, size);
    

使用预分配的对象池

如果您的内存分配器在池中预分配对象并将对象返回到池中而不是实际释放它们,那么您的解除分配方法可以直接用新值覆盖内存的标记和指针。

```
__hwasan_tag_memory(__hwasan_tag_pointer(ptr, 0), tag, size);
ptr = __hwasan_tag_pointer((void*)ptr, tag);
```

如果您使用此技术,您的分配方法不需要标记指针或内存块,而是在预分配池中的对象时标记指针和内存块。有关使用此风格的示例,请参阅PoolAllocator 示例