地址消毒器

Android NDK 从 API 级别 27(Android O MR 1)开始支持 地址消毒器(也称为 ASan)。

ASan 是一种基于编译器的快速工具,用于检测原生代码中的内存错误。ASan 检测

  • 堆栈和堆缓冲区溢出/下溢
  • 释放后使用堆
  • 堆栈在范围之外使用
  • 双重释放/无效释放

ASan 的 CPU 开销大约为 2 倍,代码大小开销在 50% 到 2 倍之间,内存开销很大(取决于您的分配模式,但大约为 2 倍)。

示例应用

一个 示例应用 展示了如何为 asan 配置 构建变体

构建

要使用 地址消毒器 构建应用的原生(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。

  1. android:debuggable 添加到应用清单。
  2. 在应用的 build.gradle 文件中将 useLegacyPackaging 设置为 true。请参阅 包装 shell 脚本 指南以了解更多信息。
  3. 将 ASan 运行时库添加到应用模块的 jniLibs 中。
  4. 将包含以下内容的 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

堆栈跟踪

地址消毒器 需要在每次 malloc/realloc/free 调用时展开堆栈。这里有两个选项

  1. 一种“快速”基于帧指针的展开程序。这是遵循 构建部分 中的说明所使用的。

  2. 一种“缓慢”CFI 展开程序。在此模式下,ASan 使用 _Unwind_Backtrace。它仅需要 -funwind-tables,默认情况下通常启用该选项。

快速解卷器是 malloc/realloc/free 的默认选项。慢速解卷器是致命堆栈跟踪的默认选项。可以通过在 wrap.sh 中的 ASAN_OPTIONS 变量中添加 fast_unwind_on_malloc=0 来为所有堆栈跟踪启用慢速解卷器。