了解 HWASan 报告

当 HWASan 工具检测到内存错误时,进程会通过 abort() 终止,并且报告会打印到 stderr 和 logcat。与 Android 上的所有原生崩溃一样,HWASan 错误位于 /data/tombstones 下。

报告示例

与常规原生崩溃相比,HWASan 在 tombstone 顶部的中止消息字段中携带了额外的信息。下面是一个基于堆的崩溃示例。对于堆栈错误,请参阅注释中的堆栈特定部分。

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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 错误都是标记不匹配错误,即指针标记与相应的内存标记不匹配的内存访问。这可能是以下任何一种情况:

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

部分

下面是对 HWASan 报告的每个部分的解释。

访问错误

包含关于错误内存访问的信息,包括:

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

访问堆栈跟踪

错误内存访问的堆栈跟踪。请参阅符号化以进行符号化。

原因

错误访问的潜在原因。如果有多个候选原因,则按可能性降序排列。位于关于潜在原因的详细信息之前。HWASan 可以诊断以下原因:

  • 释放后使用
  • 堆栈标记不匹配,可能是堆栈返回后使用、堆栈作用域后使用或越界
  • 堆缓冲区溢出
  • 全局溢出

内存信息

描述 HWASan 了解的关于被访问内存的信息,并且可能因错误类型而异

错误类型 原因 报告格式
标记不匹配 释放后使用 使用此报告格式
<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:
堆栈标记不匹配 堆栈报告不区分溢出或下溢错误与返回后使用错误。此外,为了找到作为错误源的堆栈分配,需要一个离线符号化步骤。请参阅了解堆栈报告
无效释放 释放后使用 双重释放错误。如果这发生在进程关闭时,则可能表示 ODR 违规
<address> is located N bytes inside of M-byte region [<start>, <end>)
freed by thread T0 here:
无法描述地址 要么是野指针释放(释放之前未分配的内存),要么是在分配的内存从 HWASan 的空闲缓冲区中清除后进行双重释放。
0x... 是 HWAsan 影子内存 野指针释放,因为应用尝试释放 HWASan 内部的内存。

释放分配堆栈跟踪

内存被释放位置的堆栈跟踪。仅针对释放后使用或无效释放错误显示。请参阅符号化以进行符号化。

分配堆栈跟踪

内存被分配位置的堆栈跟踪。请参阅符号化以进行符号化。

高级调试信息

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))表示堆栈返回后使用错误。

寄存器转储

HWASan 报告中的寄存器转储对应于执行无效内存访问的指令。此转储之后是来自常规 Android 信号处理程序的另一个寄存器转储。忽略第二个转储,因为它是在 HWASan 调用 abort() 时获取的,与该错误无关。

符号化

为了在堆栈跟踪中获取函数名称和行号(并获取作用域后使用错误的变量名),需要一个离线符号化步骤。

首次设置:安装 llvm-symbolizer

要进行符号化,您的系统必须安装 llvm-symbolizer 并且可以从 $PATH 访问。在 Debian 上,您可以使用 sudo apt install llvm 安装它。

获取符号文件

对于符号化,我们需要包含符号的未剥离二进制文件。它们的位置取决于构建类型:

  • 对于本地构建,符号文件位于 out/target/product/<product>/symbols/ 中。
  • 对于 AOSP 构建(例如,从 Android Flash Tool 刷写的构建),这些构建位于 Android CI 上。在构建的 Artifacts 中,有一个 ${PRODUCT}-symbols-${BUILDID}.zip 文件。
  • 对于来自您组织的内部构建,请查看您组织的文档以获取有关获取符号文件的帮助。

符号化

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

了解堆栈报告

对于使用堆栈变量发生的错误,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)
  [...]

为了帮助您了解堆栈错误,HWASan 会跟踪过去的堆栈帧。HWASan 不会将这些转换为错误报告中人类可理解的内容,并且需要额外的符号化步骤

ODR 违规

HWASan 报告的一些释放后使用错误可能表示违反了单一定义规则 (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,则查阅内存映射转储以找出地址属于哪个映射(如果有)可能会有所帮助。

同一线程中的嵌套错误

这意味着在生成 HWASan 崩溃报告时出现了一个错误。这通常是由于 HWASan 运行时中的错误引起的。提交错误并提供有关如何重现该问题的说明(如果可能)。