使用 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++ 标准库

受已知问题影响,使用 libc++_static 时 ASan 与 C++ 异常处理机制不兼容。使用 libc++_shared 时则不会发生这个问题。

HWASan 有自己的运算符 newdelete 实现,如果标准库是以静态方式关联到项目的,则您无法使用此实现。

如需更改此设置,请参阅本文档的关联 C++ 标准库部分。

启用帧指针生成功能

HWASan 和 ASan 使用基于帧指针的快速展开程序 (unwinder),根据内存分配和取消分配事件来生成堆栈轨迹信息。也就是说,您必须在 C++ 编译器设置中启用帧指针生成功能,才能使用这些功能。具体来说,您需要停用帧指针省略优化功能。

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

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

启用 HWASan 或 ASan

如需启用 HWASan 或 ASan,请在项目的 Property Pages 中,依次选择 Configuration Properties > General

当前项目的 Visual Studio Solution Explorer Properties 菜单。

图 1:Visual Studio Solution Explorer 窗口中项目的 Properties 选项。

项目的“Property Pages”对话框,其中显示了“General”属性,并突出显示了“Address Sanitizer”设置。

图 2:“General”项目属性中的 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++ 编译器设置控制。在项目的 Property Pages 中,依次选择 Configuration Properties > C/C++ > Optimization,即可找到该设置。

项目的“Property Pages”对话框,其中显示了“C/C++ Optimization”属性,并突出显示了“Omit Frame Pointer”设置。

图 3Omit Frame Pointer 设置的位置。

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

在共享库模式下关联 C++ 标准库

前往项目的 Property Pages,然后依次选择 Configuration Properties > General,即可在 Project Defaults 部分下找到 C++ 标准库的链接器模式设置。

项目的“Property Pages”对话框,其中“General”类别处于选中状态,突出显示了“Use of STL”设置。

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

使用 HWASan 或 ASan 时,请将 Use of STL 设置为 Use C++ Standard Libraries (.so)。此值会以“共享库”的形式,将 C++ 标准库关联到您的项目,而这是 HWASan 和 ASan 正常运行所必需的。

创建 build 配置以供 Address Sanitizer 使用

如果您只是想暂时使用 HWASan 或 ASan,可能就不会仅仅因为此目的而创建新的 build 配置。如果项目规模较小、您打算探索该功能或要处理测试过程中发现的问题,就可能会出现这种情况。

不过,如果您认为此功能很有用,并且计划经常使用,则可以考虑为 HWASan 或 ASan 创建新的 build 配置,如 Teapot 示例所示。举例来说,如果您要在单元测试过程中或游戏的夜间冒烟测试期间定期运行 Address Sanitizer,就可以采取这种做法。

如果您的大型项目使用大量不同的第三方库,且您通常会以静态方式将这些库关联到 C++ 标准库,那么创建单独的 build 配置可能会特别有用。专门的 build 配置有助于确保您的项目设置始终准确无误。

如需创建 build 配置,请在项目的 Property Pages 中,点击 Configuration Manager… 按钮,打开 Active solution configuration 下拉列表。然后选择所需项 ,使用适当的名称(例如“HWASan enabled”)创建新 build 配置。

将 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 示例