Address Sanitizer
注意:本文档将介绍如何在 Address Sanitizer 下运行使用 NDK 构建的 Android 应用。如需了解如何对 Android 平台组件使用 Address Sanitizer,请参阅 AOSP 文档。
从 API 级别 27 (Android O MR 1) 开始,Android NDK 可支持 Address Sanitizer(也称为 ASan)。
ASan 是一种基于编译器的快速检测工具,用于检测原生代码中的内存错误。ASan 可以检测以下问题:
- 堆栈和堆缓冲区上溢/下溢
- 释放之后的堆使用情况
- 超出范围的堆栈使用情况
- 重复释放/错误释放
ASan 可在 32 位和 64 位 ARM 以及 x86 和 x86-64 上运行。ASan 的 CPU 开销约为 2 倍,代码大小开销在 50% 到 2 倍之间,并且内存开销很大(具体取决于您的分配模式,但约为 2 倍)。
对于 64 位 ARM,强烈建议使用 HWAddress Sanitizer。
构建
如需使用 Address Sanitizer 构建应用的原生 (JNI) 代码,请执行以下操作:
在 Application.mk 中:
APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address
对于 Android.mk 中的每个模块:
LOCAL_ARM_MODE := arm
注意:在使用 libc++_static
时,ASan 目前不兼容 C++ 异常处理。使用 libc++_shared
或不使用异常处理的应用或者不受影响,或者有相应解决方法。如需了解详情,请参阅问题 988。
运行
从 Android O MR1(API 级别 27)开始,应用可以提供可封装或替换应用进程的封装 Shell 脚本。这样一来,可调试的应用就可对其应用启动过程进行自定义,以便在生产设备上使用 ASan。
注意:以下说明将介绍如何将 ASan 与 Android Studio 项目结合使用。对于非 Android Studio 项目,请参阅封装 Shell 脚本文档。
- 将
android:debuggable
添加到应用清单中。 - 在应用的
build.gradle
文件中将 useLegacyPackaging 设置为true
。如需了解详情,请参阅封装 Shell 脚本指南。 - 将 ASan 运行时库添加到应用模块的
jniLibs
中。 -
将包含以下内容的
wrap.sh
文件添加到src/main/resources/lib
目录中的每个目录。#!/system/bin/sh HERE="$(cd "$(dirname "$0")" && pwd)" export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1 ASAN_LIB=$(ls $HERE/libclang_rt.asan-*-android.so) if [ -f "$HERE/libc++_shared.so" ]; then # Workaround for https://github.com/android-ndk/ndk/issues/988. export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so" else export LD_PRELOAD="$ASAN_LIB" fi "$@"
注意:NDK 在此处提供了适用于 ASan 的推荐 wrap.sh 文件。
假设您项目的应用模块的名称为 app
,您的最终目录结构应包含以下内容:
<project root>
└── app
└── src
└── main
├── jniLibs
│ ├── arm64-v8a
│ │ └── libclang_rt.asan-aarch64-android.so
│ ├── armeabi-v7a
│ │ └── libclang_rt.asan-arm-android.so
│ ├── x86
│ │ └── libclang_rt.asan-i686-android.so
│ └── x86_64
│ └── libclang_rt.asan-x86_64-android.so
└── resources
└── lib
├── arm64-v8a
│ └── wrap.sh
├── armeabi-v7a
│ └── wrap.sh
├── x86
│ └── wrap.sh
└── x86_64
└── wrap.sh
堆栈轨迹
Address Sanitizer 需要在每次调用 malloc
/realloc
/free
时都展开堆栈。这里介绍两个选项:
-
基于帧指针的“快速”展开程序。请按照构建部分中的说明使用此展开程序。
-
“慢速”CFI 展开程序。在此模式下,ASan 会使用
_Unwind_Backtrace
。它只需要使用-funwind-tables
(通常默认处于启用状态)。注意:“慢速”展开程序速度缓慢(速度差距达 10 倍或更多,具体取决于您调用
malloc
/free
的频率)。
快速展开程序是 malloc/realloc/free 的默认选项。慢速展开程序是严重异常所对应堆栈轨迹的默认选项。通过将 fast_unwind_on_malloc=0
添加到 wrap.sh 的 ASAN_OPTIONS
变量中,即可为所有堆栈轨迹启用慢速展开程序。