Android NDK 从 API 级别 27(Android O MR 1)开始支持 地址消毒程序(也称为 ASan)。
ASan 是一种基于编译器的快速工具,用于检测原生代码中的内存错误。ASan 可检测
- 堆栈和堆缓冲区溢出/下溢
- 释放后使用堆
- 堆栈在作用域外使用
- 双重释放/无效释放
ASan 的 CPU 开销大约是 2 倍,代码大小开销在 50% 到 2 倍之间,内存开销很大(取决于您的分配模式,但大约是 2 倍)。
示例应用
构建
要使用 地址消毒程序 构建您的应用的原生 (JNI) 代码,请执行以下操作
ndk-build
在您的 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
CMake
在您的模块的 build.gradle 中
android {
defaultConfig {
externalNativeBuild {
cmake {
// Can also use system or none as ANDROID_STL.
arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
}
}
}
}
对于 CMakeLists.txt 中的每个目标
target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)
运行
从 Android O MR1(API 级别 27)开始,应用程序可以提供一个包装 shell 脚本 来包装或替换应用程序进程。这允许可调试的应用程序自定义其应用程序启动,从而能够在生产设备上使用 ASan。
- 在应用程序清单中添加
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 "$@"
假设您的项目的应用程序模块名为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
,该选项通常默认启用。
快速展开程序是 malloc/realloc/free 的默认选项。慢速展开程序是致命堆栈跟踪的默认选项。可以通过在wrap.sh
中将fast_unwind_on_malloc=0
添加到ASAN_OPTIONS
变量来为所有堆栈跟踪启用慢速展开程序。