Address Sanitizer

从 API 级别 27 (Android O MR 1) 开始,Android NDK 均支持 Address Sanitizer

编译

要使用 Address Sanitizer 编译应用原生 (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)开始,应用能够提供可封装或替换应用进程的 wrap.sh 脚本。这样一来,可调试的应用就可对其应用启动过程进行自定义,以便在生产设备上使用 ASan。

  1. android:debuggable 添加到应用清单。
  2. 将 ASan 运行时库添加到应用模块的 jniLibs 中。
  3. 将包含以下内容的 wrap.sh 文件添加到每个相同的目录中。

    #!/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 时都展开堆栈。这里介绍两个选项:

  1. 基于帧指针的“快速”展开程序。请按照编译部分中的说明使用此展开程序。

  2. “慢速”CFI 展开程序。在此模式下,ASan 会使用 _Unwind_Backtrace。它只需要使用 -funwind-tables(通常默认处于启用状态)。

快速展开程序是 malloc/realloc/free 的默认选项。慢速展开程序是严重异常所对应堆栈轨迹的默认选项。通过将 fast_unwind_on_malloc=0 添加到 wrap.sh 的 ASAN_OPTIONS 变量中,即可为所有堆栈轨迹启用慢速展开程序。