调试和减少内存错误

Android 支持使用多种工具来调试内存错误。这些工具各有利弊,因此,请阅读下文以决定哪种方式最适合您的使用情形。本文档简要介绍了可用的工具,以便您决定需要进一步了解哪些工具,但本文档旨在简明扼要,如需了解详情,请参阅各个工具的文档。

tl;dr

  • 尽可能使用内存安全型语言来避免内存错误
  • 始终使用 PAC/BTI 来缓解 ROP/JOP 攻击
  • 始终使用 GWP-ASan 来检测生产环境中的罕见内存错误
  • 使用 HWASan 来检测测试期间的内存错误
  • 2023 年未正式发布支持 MTE 的设备,但如果您能够检测生产环境中的错误,仍可以使用此类设备
  • 只有在万不得已的情况下,才在测试期间使用 ASan

内存安全型语言

使用内存安全型语言是彻底避免和减少内存错误的唯一方式。本页面介绍的其他工具可以帮助您提高内存不安全代码的安全性和可靠性,但使用内存安全型语言可以彻底解决此类问题。

官方支持的 Android 内存安全型语言是 Java 和 Kotlin。 使用其中一种语言,可以帮助您更轻松地开发大多数 Android 应用。

尽管如此,有些应用开发者仍会提供使用 Rust 编写的代码,如果您正在阅读本页内容,说明您很可能具备充分的理由需要使用原生代码(可移植性、性能或两者兼有)。Rust 是 Android 上编写内存安全型原生代码的最佳选择。NDK 团队不一定能帮助您解决您在采用这种方式时遇到的问题。不过,我们希望能收到您的反馈

PAC/BTI

Pointer Authentication 和 Branch Target Identification(也称为 PAC/BTI)是适合在生产环境中使用的缓解工具。虽然它们是两种不同的技术,但都由同一个编译器标记控制,因此总是一起使用。

由于使用的新指令在版本较低的设备上为空操作,因此这些功能在不支持它们的设备上可以向后兼容。此外,还必须拥有足够新的内核及 OS 版本。在 /proc/cpuinfo 下查找 pacabti 会显示您是否拥有足够新的硬件和内核。Android 12 (API 31) 具有必要的用户空间支持。

优点:

  • 可在所有 build 中启用,而不会在旧版设备或内核上引发问题(但请确保已在不支持它的设备/内核/OS 组合上进行了实际测试!)

缺点:

  • 仅适用于 64 位应用
  • 不会减少在不支持它的设备上发生的错误
  • 代码大小开销为 1%

GWP-Asan

GWP-ASan 可用于检测现场的内存错误,但采样率太低,无法有效减少错误。

优点:

  • 没有大量 CPU 开销或内存开销
  • 部署简单:无需重新构建原生代码
  • 适用于 32 位应用

缺点:

  • 采样率低,需要大量用户才能有效发现 bug
  • 仅检测堆错误,不检测堆栈错误

HWASan

Hardware Address Sanitizer(也称为 HWASan),最适合用于在测试期间发现内存错误。此工具在自动化测试中最为有用,尤其是在您运行模糊测试时。不过,根据应用的性能需求,它也可以在使用 dogfood 设置的高端手机上使用。

优点:

  • 无假正例
  • 可以检测 ASan 无法检测的其他类型错误(返回之后的堆栈使用情况)
  • 假负例率低于 MTE(1/256 对 1/16)
  • 内存开销低于 ASan(ASan 是最接近它的替代工具)

缺点:

  • 大量 CPU 开销(约为 100%)、代码大小开销(约为 50%)和内存开销 (10% - 35%)
  • 在 API 34 和 NDK r26 之前,需要刷写与 HWASan 兼容的映像
  • 仅适用于 64 位应用

MTE

Memory Tagging Extension(也称为 MTE)是 HWASan 的低成本替代方案,适合在现场使用。如果您有用于测试 MTE build 的硬件,则应启用 MTE。

优点:

  • 开销低,很多应用都可以在生产环境中使用
  • 无假正例
  • 无需重新构建代码来检测堆错误(但需要重新构建代码来检测堆栈错误)

缺点:

  • 2023 年没有发布启用 MTE 的商业化设备
  • 假负例率为 1/16,而 HWASan 为1/256
  • 仅适用于 64 位应用
  • 需要分别针对启用 MTE 和未启用 MTE 的设备构建库

ASan

Address Sanitizer(也称为 ASan)是最早且最广泛使用的工具。它可以在其他工具均不适用的情况下,用于在测试期间发现内存错误,并调试那些只影响旧款设备的问题。请尽可能使用 HWASan。

优点:

  • 适用广泛。可以在 KitKat 版本的旧款设备上运行
  • 如果使用得当,不会出现假正例或假负例

缺点:

  • 难以正确构建和打包
  • 在所有工具中开销最高:CPU 开销约为 100%、代码大小开销约为 50%、内存用量开销约为 100%
  • 不再受支持
  • 存在已知 bug 且不会修复