了解 HWASan 报告

当 HWASan 工具检测到内存 bug 时,系统会通过 abort() 终止进程,并将报告输出到 stderr 和 logcat。与 Android 上的所有原生代码崩溃问题一样,HWASan 错误也可以在 /data/tombstones 下找到。

示例报告

与常规原生代码崩溃不同的是,HWASan 会在 Tombstone 顶部附近的 Abort message 字段中包含额外信息。以下是基于堆的示例崩溃。有关堆栈 bug,请参阅堆栈专属部分的说明

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/flame_hwasan/flame:Tiramisu/MASTER/7956676:userdebug/dev-keys'
Revision: 'DVT1.0'
ABI: 'arm64'
Timestamp: 2019-04-24 01:13:22+0000
pid: 11154, tid: 11154, name: sensors@1.0-ser  >>> /vendor/bin/hw/android.hardware.sensors@1.0-service <<<
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: '

[...]

[0x00433ae20040,0x00433ae20060) is a small unallocated heap chunk; size: 32 offset: 5








[ … regular crash dump follows …]

这与 AddressSanitizer 报告类似。 不同的是,几乎所有 HWASan bug 都是标记不匹配错误,即在访问内存时,指针标记与相应的内存标记不匹配。可能是以下任何一种情况:

  • 对堆栈或堆的访问出界
  • 堆上的释放后使用错误
  • 堆栈上的返回后使用错误

版块

以下是对 HWASan 报告各个部分的说明。

访问错误

包含与错误的内存访问有关的信息,其中包括:

  • 访问权限类型(READWRITE
  • 访问大小(尝试访问的字节数)
  • 访问的线程数
  • 指针标记和内存标记(用于高级调试)

访问堆栈轨迹

错误的内存访问的堆栈轨迹。如需进行符号化处理,请参阅符号化

原因

造成访问错误的潜在原因。如果有多种候选原因,这些原因会按可能性降序排列,后跟有关潜在原因的详细信息。HWASan 可以诊断以下原因:

  • 释放后再使用
  • 堆栈标记不匹配,可能是因为堆栈返回后使用、堆栈范围外使用或出界
  • 堆缓冲区溢出
  • 全局溢出

内存信息

描述 HWASan 对所访问内存的了解,可能会因 bug 类型而异:

bug 类型 原因 报告格式
标记不匹配 释放后再使用 请使用以下报告格式:
<address> is located N bytes inside of M-byte region [<start>, <end>)
freed by thread T0 here:
堆缓冲区溢出 请注意,这也可能是下溢。
<address> is located N bytes to the right of M-byte region [<start>, <end>)
allocated here:
堆栈标记不匹配 堆栈报告不会区分上溢/下溢和返回后使用 bug。此外,如需查找属于错误来源的堆栈分配,需要执行离线符号化步骤。请参阅了解堆栈报告
无效释放 释放后再使用 重复释放 bug。如果进程关闭时发生这种情况,这可能意味着违反 ODR
<address> is located N bytes inside of M-byte region [<start>, <end>)
freed by thread T0 here:
无法描述地址 从 HWASan 的可用缓冲区中逐出错误释放(释放之前未分配的内存)或分配内存后的重复释放。
0x... 是 HWASan 影子内存 错误释放,因为应用尝试释放的是 HWASan 内部的内存。

取消分配堆栈轨迹

在取消分配内存情况下的堆栈轨迹。仅适用于“释放后使用”或“无效释放”bug。如需进行符号化处理,请参阅符号化

分配堆栈轨迹

在分配内存情况下的堆栈轨迹。 如需进行符号化处理,请参阅符号化

高级调试信息

HWASan 报告还包含一些高级调试信息,其中依次包括:

  1. 进程中的线程列表
  2. 进程中的线程列表
  3. 错误内存附近的内存标记的值
  4. 内存访问点的寄存器转储

内存标记转储

您可以使用标记内存转储来查找带有与指针标记相同的标记的附近内存分配。这些标记可能指向具有较大偏移的出界访问。一个标记对应 16 字节的内存;指针标记是地址的前 8 位。标记内存转储可以给出提示,例如,右侧是缓冲区溢出:

tags: ad/5c (ptr/mem)
[...]
Memory tags around the buggy address (one tag corresponds to 16 bytes):
  0x006f33ae1ff0: 0e  0e  0e  57  20  20  20  20  20  2e  5e  5e  5e  5e  5e  b5
=>0x006f33ae2000: f6  f6  f6  f6  f6  4c  ad  ad  ad  ad  ad  ad [5c] 5c  5c  5c
  0x006f33ae2010: 5c  04  2e  2e  2e  2e  2e  2f  66  66  66  66  66  80  6a  6a
Tags for short granules around the buggy address (one tag corresponds to 16 bytes):
  0x006f33ae1ff0: ab  52  eb  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..
=>0x006f33ae2000: ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  .. [..] ..  ..  ..
  0x006f33ae2010: ..  5c  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..  ..

请注意,左侧运行的 6 × 16 = 96 字节的 ad 标记与指针标记相匹配)。

如果分配的大小不是 16 的倍数,其余大小将存储为内存标记,并且标记将存储为短粒度标记。在上面的示例中,在加粗的分配标记 ad 之后,我们对标记 5c 进行了 5 × 16 + 4 = 84 字节分配。

零内存标记(例如 tags: ad/00 (ptr/mem))表示“堆栈返回后使用”bug。

寄存器转储

HWASan 报告中的寄存器转储与执行无效内存访问的实际指令对应。此转储后跟常规 Android 信号处理程序的另一个寄存器转储。忽略第二个转储,因为它是在 HWASan 调用 abort() 时采用的,与 bug 无关。

符号化

如需获取堆栈轨迹中的函数名称和行号(以及获取“范围外使用”bug 的变量名称),需要执行离线符号化步骤。

首次设置:安装 llvm-symbolizer

如需进行符号化处理,您的系统必须安装 llvm-symbolizer 并且可通过 $PATH 访问。在 Debian 上,您可以使用 sudo apt install llvm 进行安装。

获取符号文件

为了进行符号化处理,我们需要包含符号的未剥离版二进制文件。其位置取决于 build 的类型:

  • 对于本地 build,符号文件位于 out/target/product/<product>/symbols/ 中。
  • 对于 AOSP build(例如,从 Android 刷写工具刷写的 AOSP build),相应 build 可在 Android CI 上找到。该 build 的制品中有一个 ${PRODUCT}-symbols-${BUILDID}.zip 文件。
  • 对于组织的内部 build,请参阅组织的文档,以获得与获取符号文件相关的帮助。

符号化处理

hwasan_symbolize --symbols <DECOMPRESSED_DIR>/out/target/product/*/symbols < crash

了解堆栈报告

对于堆栈变量出现的 bug,HWASan 报告包含如下详细信息:

Cause: stack tag-mismatch
Address 0x007d4d251e80 is located in stack of thread T64
Thread: T64 0x0074000b2000 stack: [0x007d4d14c000,0x007d4d255cb0) sz: 1088688 tls: [0x007d4d255fc0,0x007d4d259000)
Previously allocated frames:
  record_addr:0x7df7300c98 record:0x51ef007df3f70fb0  (/apex/com.android.art/lib64/libart.so+0x570fb0)
  record_addr:0x7df7300c90 record:0x5200007df3cdab74  (/apex/com.android.art/lib64/libart.so+0x2dab74)
  [...]

为了帮助您了解堆栈 bug,HWASan 会跟踪过去的堆栈帧。 HWASan 不会在 bug 报告中将此类内容转换为人类可理解的内容,而是需要额外的符号化步骤

违反 ODR

HWASan 报告的一些“释放后使用”bug 可能表明违反了单一定义规则 (ODR)。 如果在同一程序中多次定义同一变量,就会发生 ODR 违规行为。 这也意味着,该变量被多次销毁,从而可能导致出现“释放后使用”错误。

经过符号化后,如果存在违反 ODR 的情况,在无效访问堆栈和在此释放堆栈中会显示具有 __cxa_finalize 的释放后使用错误。之前在此分配堆栈包含 __dl__ZN6soinfo17call_constructorsEv,并且应指向程序中在堆栈上的较高位置定义变量的位置。

如果使用了静态库,可能会违反 ODR。如果定义 C++ 全局变量的静态库关联到多个共享库或可执行文件,那么同一符号的多个定义可能会位于同一地址空间中,从而导致 ODR 错误。

问题排查

本部分介绍了一些错误及其解决方法。

HWAddressSanitizer 无法更详细地描述地址

有时,HWASan 可能会因与过去的内存分配相关的信息而用尽空间。在这种情况下,报告将仅包含一条即时内存访问的堆栈轨迹,后跟备注:

HWAddressSanitizer can not describe address in more detail.

在某些情况下,您可以通过多次运行测试来解决此问题。另一种方法是增加 HWASan 历史记录大小。您可以在 build/soong/cc/sanitize.go 中(查找 hwasanGlobalOptions)或您的进程环境中(可尝试使用 adb shell echo $HWASAN_OPTIONS 查看当前设置)全局执行此操作。

如果所访问的内存未映射或由非 HWASan 感知分配器分配,也可能会发生这种错误。在这种情况下,崩溃标头中列出的 mem 标记通常为 00。如果您有权访问完整的 Tombstone,不妨参考内存映射转储,找出地址所属的映射(如果有)。

同一线程中的嵌套式 bug

这意味着在生成 HWASan 崩溃报告时出现了 bug。这通常是因为 HWASan 运行时中存在 bug。请提交 bug 并尽可能提供有关如何重现该问题的说明。