GWP-ASan

重要提示：GWP-Asan 是用于调试和减少内存错误的众多工具之一。如需简要了解所有工具，请参阅调试和减少内存错误。

GWP-ASan 是一种原生内存分配器功能，可帮助查找释放后使用和堆缓冲区溢出 bug。它的非正式名称采用递归缩写，即“GWP-ASan Will Provide Allocation SANity”。与 HWASan 或 Malloc 调试 不同，GWP-ASan 不需要源代码或重新编译（即使用预构建文件），并且同时适用于 32 位和 64 位进程（但 32 位崩溃的调试信息较少）。本主题概述了在应用中启用此功能所需执行的操作。GWP-ASan 适用于以 Android 11（API 级别 30）或更高版本为目标平台的应用。

概览
--

在进程启动时（或在 zygote 派生时），GWP-ASan 会在一些随机选择的系统应用和平台可执行文件上启用。在您自己的应用中启用 GWP-ASan 可帮助您查找与内存相关的错误，还可以让您的应用做好准备，实现对 ARM 内存标记扩展 (MTE) 的支持。分配采样机制还能够可靠地防范会导致崩溃的查询。

启用后，GWP-ASan 会拦截随机选择的堆分配子集，并将其放入特殊区域，以便捕获难以检测到的堆内存损坏错误。只要用户足够多，即使是这种低采样率，也会发现常规测试未能发现的堆内存安全错误。例如，GWP-ASan 在 Chrome 浏览器中发现了大量错误（其中许多错误的查看权限仍未公开）。

GWP-ASan 会收集有关它拦截的所有分配的额外信息。此类信息在 GWP-ASan 检测到内存安全违规行为时提供，并会自动纳入到原生代码崩溃报告中，这对调试有很大帮助（请参阅示例）。

GWP-ASan 不会产生显著的 CPU 开销。启用后，GWP-ASan 会产生较小的固定 RAM 开销。此开销由 Android 系统决定，目前每个受影响的进程大约为 70 千字节 (KiB)。

## 选择在您的应用中启用

可以使用应用清单中的 `android:gwpAsanMode` 标记在进程级别按应用启用 GWP-ASan。支持的选项如下：

* 始终停用 (`android:gwpAsanMode="never"`)：此设置会完全停用应用中的 GWP-ASan，这也是非系统应用的默认设置。
* 始终启用 (`android:gwpAsanMode="always"`)：此设置会启用应用中的 GWP-ASan，其中包含以下几项：
	1. 操作系统会为 GWP-ASan 操作预留固定数量的 RAM，每个受影响的进程大约为 70KiB（如果您的应用对内存用量的增加不是非常敏感，请启用 GWP-ASan）。
	2. GWP-ASan 会拦截随机选择的堆分配子集，并将其放入特殊区域，以便可靠地检测内存安全违规行为。
	3. 如果在特殊区域出现内存安全违规行为，GWP-ASan 会终止相应进程。
	4. GWP-ASan 在崩溃报告中提供了有关该故障的其他信息。

如需为应用全局启用 GWP-ASan，请在您的 `AndroidManifest.xml` 文件中添加以下代码：

```
<application android:gwpAsanMode="always">

```

```
  ...

```

```
</application>

```

此外，您还可以为应用的特定子进程显式启用或停用 GWP-ASan。您可以使用已显式启用或停用 GWP-ASan 的进程确定活动和服务。请查看以下示例：

```
<application>

```

```
  <processes>

```

```

```

```
    <process />

```

```

```

```
    <process android:process=":gwp_asan_enabled"

```

```
               android:gwpAsanMode="always" />

```

```
    <process android:process=":gwp_asan_disabled"

```

```
               android:gwpAsanMode="never" />

```

```
  </processes>

```

```

```

```
  <activity android:name="android.gwpasan.GwpAsanEnabledActivity"

```

```
            android:process=":gwp_asan_enabled" />

```

```
  <activity android:name="android.gwpasan.GwpAsanDisabledActivity"

```

```
            android:process=":gwp_asan_disabled" />

```

```
  <service android:name="android.gwpasan.GwpAsanEnabledService"

```

```
           android:process=":gwp_asan_enabled" />

```

```
  <service android:name="android.gwpasan.GwpAsanDisabledService"

```

```
           android:process=":gwp_asan_disabled" />

```

```
</application>

```

### 缺少分配/取消分配跟踪

如果您诊断出的原生代码崩溃似乎缺少分配/取消分配帧，那您的应用可能缺少帧指针。出于性能方面的原因，GWP-ASan 使用帧指针记录对分配和取消分配的跟踪情况，如果不存在帧指针，GWP-ASan 将无法展开堆栈轨迹。

对于 arm64 设备，帧指针默认处于启用状态；对于 arm32 设备，帧指针默认处于停用状态。由于应用不能控制 libc，因此通常 GWP-ASan 无法收集 32 位可执行文件或应用的分配/取消分配跟踪记录。64 位应用应确保它们不使用 `-fomit-frame-pointer` 进行构建，以便 GWP-ASan 可以收集分配和取消分配堆栈轨迹。

### 重现安全违规行为

GWP-ASan 旨在捕获用户设备上的堆内存安全违规行为。GWP-ASan 会针对崩溃提供尽可能多的背景信息（违规行为的访问轨迹、原因字符串以及分配/取消分配轨迹），但可能仍然难以判断违规原因。遗憾的是，由于错误检测具有概率性，因此 GWP-ASan 报告通常很难在本地设备上重现。

在这些情况下，如果错误会影响到 64 位设备，您应使用 HWAddressSanitizer (HWASan)。HWASan 能够在堆栈、堆和全局变量上可靠地检测内存安全违规行为。在 HWASan 下运行应用或许能够可靠地重现 GWP-ASan 报告的相同结果。

如果在 HWASan 下运行您的应用不足以确定引起错误的根本原因，您应该对相关代码进行模糊处理。您可以根据 GWP-ASan 报告中的信息确定需要进行模糊处理的代码，该报告能够可靠地检测和显示底层代码运行的相关问题。

示例
--

此原生代码示例存在释放之后的堆使用情况错误：

```
#include <jni.h>

```

```
#include <string>

```

```
#include <string_view>

```

```
jstring native_get_string(JNIEnv* env) {

```

```
   std::string s = "Hellooooooooooooooo ";

```

```
   std::string_view sv = s + "World\n";

```

```
   // BUG: Use-after-free. `sv` holds a dangling reference to the ephemeral

```

```
   // string created by `s + "World\n"`. Accessing the data here is a

```

```
   // use-after-free.

```

```
   return env->NewStringUTF(sv.data());

```

```
}

```

```
extern "C" JNIEXPORT jstring JNICALL

```

```
Java_android11_test_gwpasan_MainActivity_nativeGetString(

```

```
    JNIEnv* env, jobject /* this */) {

```

```
  // Repeat the buggy code a few thousand times. GWP-ASan has a small chance

```

```
  // of detecting the use-after-free every time it happens. A single user who

```

```
  // triggers the use-after-free thousands of times will catch the bug once.

```

```
  // Alternatively, if a few thousand users each trigger the bug a single time,

```

```
  // you'll also get one report (this is the assumed model).

```

```
  jstring return_string;

```

```
  for (unsigned i = 0; i < 0x10000; ++i) {

```

```
    return_string = native_get_string(env);

```

```
  }

```

```
  return reinterpret_cast<jstring>(env->NewGlobalRef(return_string));

```

```
}

```

对于使用上述示例代码的测试运行，GWP-ASan 可以成功捕获到不当使用情况并触发以下崩溃报告。GWP-ASan 通过提供崩溃类型、分配元数据以及关联的分配和取消分配堆栈轨迹的相关信息来自动完善报告。

```
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/sargo/sargo:10/RPP3.200320.009/6360804:userdebug/dev-keys'
Revision: 'PVT1.0'
ABI: 'arm64'
Timestamp: 2020-04-06 18:27:08-0700
pid: 16227, tid: 16227, name: 11.test.gwpasan  >>> android11.test.gwpasan <<<
uid: 10238
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x736ad4afe0
Cause: [GWP-ASan]: Use After Free on a 32-byte allocation at 0x736ad4afe0

backtrace:
      #00 pc 000000000037a090  /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::CheckNonHeapValue(char, art::(anonymous namespace)::JniValueType)+448)
      #01 pc 0000000000378440  /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::CheckPossibleHeapValue(art::ScopedObjectAccess&, char, art::(anonymous namespace)::JniValueType)+204)
      #02 pc 0000000000377bec  /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::(anonymous namespace)::JniValueType*)+612)
      #03 pc 000000000036dcf4  /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::CheckJNI::NewStringUTF(_JNIEnv*, char const*)+708)
      #04 pc 000000000000eda4  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (_JNIEnv::NewStringUTF(char const*)+40)
      #05 pc 000000000000eab8  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+144)
      #06 pc 000000000000edf8  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
      ...

deallocated by thread 16227:
      #00 pc 0000000000048970  /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::AllocationMetadata::CallSiteInfo::RecordBacktrace(unsigned long (*)(unsigned long*, unsigned long))+80)
      #01 pc 0000000000048f30  /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::GuardedPoolAllocator::deallocate(void*)+184)
      #02 pc 000000000000f130  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (std::__ndk1::_DeallocateCaller::__do_call(void*)+20)
      ...
      #08 pc 000000000000ed6c  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >::~basic_string()+100)
      #09 pc 000000000000ea90  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+104)
      #10 pc 000000000000edf8  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
      ...

allocated by thread 16227:
      #00 pc 0000000000048970  /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::AllocationMetadata::CallSiteInfo::RecordBacktrace(unsigned long (*)(unsigned long*, unsigned long))+80)
      #01 pc 0000000000048e4c  /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::GuardedPoolAllocator::allocate(unsigned long)+368)
      #02 pc 000000000003b258  /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan_malloc(unsigned long)+132)
      #03 pc 000000000003bbec  /apex/com.android.runtime/lib64/bionic/libc.so (malloc+76)
      #04 pc 0000000000010414  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (operator new(unsigned long)+24)
      ...
      #10 pc 000000000000ea6c  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+68)
      #11 pc 000000000000edf8  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
      ...

```

## 更多信息

如需详细了解 GWP-ASan 的实现详情，请参阅 LLVM 文档。如需详细了解 Android 原生代码崩溃报告，请参阅诊断原生代码崩溃问题。