Le NDK Android est compatible avec Address Sanitizer (également appelé ASan) à partir du niveau d'API 27 (Android O MR 1).
ASan est un outil rapide basé sur un compilateur qui permet de détecter les bugs liés à la mémoire dans le code natif. ASan détecte les bugs suivants :
- Débordement positif/négatif de la pile et du tampon de tas de mémoire
- Bugs "use-after-free" au niveau des tas de mémoire
- Utilisation de la pile en dehors du champ d'application
- Bugs de type "double free"/"wild free"
La surcharge processeur d'ASan est plus ou moins multipliée par deux, la surcharge de la taille du code est comprise entre 50 % et deux fois supérieure, et la surcharge de la mémoire est élevée (en fonction de vos modèles d'allocation, mais de l'ordre de deux fois supérieure).
Application exemple
Une application exemple indique comment configurer une variante de compilation pour asan.
Compiler
Pour compiler le code natif (JNI) de votre application avec Address Sanitizer, procédez comme suit :
ndk-build
Dans votre fichier Application.mk :
APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address
Pour chaque module de votre fichier Android.mk :
LOCAL_ARM_MODE := arm
CMake
Dans le fichier build.gradle de votre module :
android {
defaultConfig {
externalNativeBuild {
cmake {
// Can also use system or none as ANDROID_STL.
arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
}
}
}
}
Pour chaque cible de votre fichier CMakeLists.txt :
target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)
Exécuter
À partir d'Android O MR1 (niveau d'API 27), une application peut fournir un script shell qui peut encapsuler ou remplacer le processus de l'application. Une application débogable peut ainsi personnaliser le démarrage de son application, ce qui permet d'utiliser ASan sur des appareils de production.
- Ajoutez
android:debuggable
au fichier manifeste de l'application. - Définissez
useLegacyPackaging
surtrue
dans le fichierbuild.gradle
de votre application. Pour en savoir plus, consultez le guide sur l'encapsulation du script shell. - Ajoutez la bibliothèque d'exécution ASan dans le répertoire
jniLibs
de votre module d'application. Ajoutez les fichiers
wrap.sh
avec le contenu suivant à chaque répertoire du répertoiresrc/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 "$@"
En supposant que le module d'application de votre projet s'appelle app
, votre structure de répertoires finale doit inclure les éléments suivants :
<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
Traces de la pile
Address Sanitizer doit dérouler la pile à chaque appel malloc
/realloc
/free
. Deux possibilités s'offrent à vous :
Un dérouleur "rapide" basé sur un pointeur de frame. Pour cela, suivez les instructions de la section sur la compilation.
Un dérouleur CFI "lent". Dans ce mode, ASan utilise
_Unwind_Backtrace
. Il ne nécessite que-funwind-tables
, qui est normalement activé par défaut.
Le dérouleur rapide est l'option par défaut pour malloc/realloc/free. Le dérouleur lent est l'option par défaut pour les traces de pile fatales. Vous pouvez activer le dérouleur lent pour toutes les traces de la pile en ajoutant fast_unwind_on_malloc=0
à la variable ASAN_OPTIONS
dans wrap.sh.