El NDK de Android admite Address Sanitizer (también conocido como ASan) a partir del nivel de API 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
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).
App de ejemplo
En una app de ejemplo, se muestra cómo configurar una variante de compilación para asan.
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)
Ejecución
A partir de Android O MR1 (API nivel 27), una app puede proporcionar una unión de la secuencia de comandos de shell que puede unir o reemplazar el proceso de la app. 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.
- Agrega
android:debuggable
al manifiesto de la aplicación. - Configura
useLegacyPackaging
comotrue
en el archivobuild.gradle
de tu app. Para obtener más información, consulta la guía de unión de la secuencia de comandos de shell. - Agrega la biblioteca de tiempo de ejecución ASan a
jniLibs
del módulo de tu app. Agrega archivos
wrap.sh
con el siguiente contenido a cada directorio en el directoriosrc/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 "$@"
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:
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.
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. A fin de 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.