Address Sanitizer

El NDK de Android admite Address Sanitizer a partir la API nivel 27 (Android O MR 1).

ASan es una herramienta rápida basada en compiladores para detectar errores de memoria en código nativo. ASan detecta:

  • Desabastecimiento de búfer de pila
  • Uso de pila después de liberación
  • Uso de pila fuera del alcance
  • Cierre doble o cierre wild

ASan se ejecuta en ARM de 32 y 64 bits, además de x86 y x86-64. La sobrecarga de la CPU de ASan es de aproximadamente el doble, el tamaño del código aumenta entre el 50% y el doble, y la sobrecarga de la memoria es considerable (depende de tus patrones de asignación, pero es de aproximadamente el doble).

Para ARM de 64 bits, HWASan podría ser una mejor opción.

Compilación

Para compilar el código nativo (JNI) de tu app con Address Sanitizer, haz lo siguiente:

ndk-build

En tu Application.mk:

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

Para cada módulo de tu Android.mk:

LOCAL_ARM_MODE := arm
    

CMake

En build.gradle de tu módulo:

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

Para cada destino de tu CMakeLists.txt:

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

Activo

A partir de Android O MR1 (API nivel 27), una aplicación puede proporcionar una secuencia de comandos wrap.sh que puede unir o reemplazar el proceso de la aplicación. Esto permite que una aplicación depurable personalice el inicio de tu aplicación, lo que permite usar ASan en los dispositivos de producción.

  1. Agrega android:debuggable al manifiesto de la aplicación.
  2. Agrega la biblioteca de tiempo de ejecución ASan a jniLibs del módulo de tu app.
  3. Agrega archivos wrap.sh con los siguientes contenidos a cada uno de los mismos directorios.

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

Suponiendo que el módulo de la aplicación de tu proyecto se llame app, tu estructura de directorio final debe incluir lo siguiente:

<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
    

Seguimiento de pila

Address Sanitizer tiene que desenrollar la pila en cada llamada malloc/realloc/free. En este caso, hay dos opciones:

  1. Un desenredador "rápido" basado en el puntero de marco. Esto es lo que se usa siguiendo las instrucciones de la sección de compilación.

  2. Un desenrollador "lento" de CFI. En este modo, ASan usa _Unwind_Backtrace. Solo requiere -funwind-tables, que normalmente está habilitado de forma predeterminada.

El desenrollador rápido es el predeterminado para malloc/realloc/free. El desenrollador lento es el predeterminado para los seguimientos de pila fatales. Para habilitar el desenrollador lento para todos los seguimientos de la pila, agrega fast_unwind_on_malloc=0 a la variable ASAN_OPTIONS en tu wrap.sh.