从 API 级别 27 (Android O MR 1) 开始,Android NDK 支持 Address Sanitizer(也称为 ASan)。ASan 是一种基于编译器的快速检测工具,用于检测C/C++代码中的内存错误。ASan 的 CPU 开销约为 2 倍,代码大小开销在 50% 到 2 倍之间,并且内存开销很大。
注意: ASan在Android 平台上不支持检测内存泄漏!!!
ASan 可以检测以下问题:
- 堆栈和堆缓冲区上溢/下溢
- 释放之后的堆使用情况
- 超出范围的堆栈使用情况
- 重复释放/错误释放
ASan 可在 32 位和 64 位 ARM 以及 x86 和 x86-64 上运行。Android NDK 也支持 HWAddress Sanitizer(也称为 HWASan)。与ASan 相比,HWASan功能类似,开销较小,但是使用麻烦。
- NDK r21 和 Android 10(API 级别 29)以上
- HWASan 仅适用于 64 位 Arm 设备
- HWASan 应用需要将预构建的映像刷写到支持的 Pixel 手机上。
Build
如需使用 Address Sanitizer 构建应用的原生 (JNI) 代码,请执行以下操作:
Quote from: https://developer.android.com/ndk/guides/asan#building
1.在模块的
build.gradle
中:
1
2
3
4
5
6
7
8
9
10 android {
defaultConfig {
externalNativeBuild {
cmake {
# Can also use system or none as ANDROID_STL.
arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
}
}
}
}2.对于
CMakeLists.txt
中的每个目标:
1
2 target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)
第一步
还可以添加开关,只在debug版本中开启该功能:
在模块的 build.gradle
中:
1 | cmake { |
在模块的 CMakeLists.txt
中添加:
1 | # Enable address sanitizer only for debug builds |
第二步
还可以使用如下配置:
1 | SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") |
Run
ASan需要在进程启动时启用,要求以全新的进程来运行应用,而不是从 zygote 克隆。从 Android O MR1(API 级别 27)开始,可替换应用进程的封装 Shell 脚本。这样可调试的应用就可对其应用启动过程进行自定义,以便在生产设备上使用 ASan。
Quote from: https://developer.android.com/ndk/guides/asan#running
将
android:debuggable
和android:extractNativeLibs=true
添加到应用清单。请注意,后者是某些配置的默认设置。如需了解详情,请参阅封装 Shell 脚本。将 ASan 运行时库添加到应用模块的
jniLibs
中。将包含以下内容的
wrap.sh
文件添加到每个相同的目录中。
1
2
3
4
5
6
7
8
9
10
11
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
"$@"
第一步
目前发现, android:debuggable
默认是 "false"
,即使不设置为 "true"
,也不影响结果。
如果没有在 AndroidManifest.xml
配置, android:extractNativeLibs
默认值是 "true"
。
注意:如果使用app bundle, Android Gradle 插件从3.6.0 默认会将 extractNativeLibs
设置为 "false"
。也就是说,原生库将保持页面对齐状态并以未压缩的形式打包。
第二步
除了手动拷贝,还可以使用下面的两种方法:
(1)在CMakeLists.txt
中添加如下配置, 自动拷贝*.asan*${ARCH}*-android.so
, 需要设置库的名称LibraryName:
1 | set(LIBNAME "LibraryName") |
(2)使用asan_device_setup
,将*.asan*${ARCH}*-android.so
添加到设备,设备需要root。
Mac 系统下,其路径为ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/lib64/clang/9.0.9/bin
1 | ❯ ./asan_device_setup --help |
如果执行成功,那么设备就会自动重启,测试完成最好移除这些库。
第三步
NDK包含一个推荐的针对ASan的wrap.sh文件。
Mac 系统下,其路径为ANDROID_NDK_HOME/wrap.sh/asan.sh
将asan.sh
重命名为wrap.sh
,在模块的跟目录下添加 wrap
, 目录结构应包含以下内容 :
1 | <project root> |
在模块的 build.gradle
中添加:
1 | android { |
Test Case
heap-use-after-free error
1 | IMPL_FUNC(void,doHeapUseAfterFree) { |
heap-buffer-overflow error
1 | IMPL_FUNC(void,doHeapBufferOverflow) { |
stack-buffer-overflow error
1 | IMPL_FUNC(void,doStackBufferOverflow) { |
stack-buffer-underflow error
1 | IMPL_FUNC(void,doStackBufferUnderflow) { |
stack-use-after-return error
1 | char* x; |
double-free error
1 | IMPL_FUNC(void,doDoubleFree) { |
null-pointer dereference error
1 | IMPL_FUNC(void,doNullpointerDereference) { |
memcpy-param-overlap
1 | IMPL_FUNC(void,doMemcpyParamOverlap) { |
strncat-param-overlap
1 | IMPL_FUNC(void,doStrncatParamOverlap) { |
Sample
https://github.com/rolyyu/AndroidASan
Reference
https://developer.android.com/ndk/guides/asan
https://developer.android.com/ndk/guides/wrap-script
https://github.com/google/sanitizers
https://stackoverflow.com/questions/41552966/getting-new-delete-type-mismatch-from-asan
https://docs.microsoft.com/en-us/cpp/sanitizers/asan-error-examples?view=msvc-160
https://docs.microsoft.com/en-us/cpp/sanitizers/error-new-delete-type-mismatch?view=msvc-160