Address Sanitizer

The Android NDK supports Address Sanitizer beginning with API level 27 (Android O MR 1).

Building

To build your app's native (JNI) code with Address Sanitizer, do the following:

ndk-build

In your Application.mk:

APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address

For each module in your Android.mk:

LOCAL_ARM_MODE := arm

CMake

In your module's build.gradle:

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                # Can also use system or none as ANDROID_STL.
                arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
            }
        }
    }
}

For each target in your CMakeLists.txt:

target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)

Running

Beginning with Android O MR1 (API level 27) an application can provide a wrap.sh script that can wrap or replace the application process. This allows a debuggable application to customize their application startup, which enables using ASan on production devices.

  1. Add android:debuggable to the application manifest.
  2. Add the ASan runtime library to your app module's jniLibs.
  3. Add wrap.sh files with the following contents to each of the same directories.

    #!/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
    "$@"
    

Assuming your project's application module is named app, your final directory structure should include the following:

<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

Stack traces

Address Sanitizer needs to unwind the stack on every malloc/realloc/free call. There are two options here:

  1. A "fast" frame pointer-based unwinder. This is what is used by following the instructions in the building section.

  2. A "slow" CFI unwinder. In this mode ASan uses _Unwind_Backtrace. It requires only -funwind-tables, which is normally enabled by default.

The fast unwinder is the default for malloc/realloc/free. The slow unwinder is the default for fatal stack traces. The slow unwinder can be enabled for all stack traces by adding fast_unwind_on_malloc=0 to the ASAN_OPTIONS variable in your wrap.sh.