Das Android NDK unterstützt Address Sanitizer (auch ASan genannt) ab API-Level 27 (Android O MR1).
ASan ist ein schnelles compilerbasiertes Tool zum Erkennen von Speicherfehlern in nativem Code. ASan erkennt:
- Stack- und Heap-Pufferüberlauf/-unterlauf
- Heap-Speicher nach dem Freigeben verwenden
- Stack-Nutzung außerhalb des Anwendungsbereichs
- Double Free/Wild Free
Der CPU-Aufwand von ASan beträgt etwa das Doppelte, der Code-Aufwand zwischen 50% und dem Doppelten und der Speicher-Aufwand ist hoch (abhängig von Ihren Zuweisungsmustern, aber in der Größenordnung des Doppelten).
Beispiel-App
In einer Beispiel-App wird gezeigt, wie Sie eine Build-Variante für ASan konfigurieren.
Build
So erstellen Sie den nativen (JNI-)Code Ihrer App mit Address Sanitizer:
ndk-build
In Ihrer Application.mk-Datei:
APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address
Für jedes Modul in Ihrer Android.mk-Datei:
LOCAL_ARM_MODE := arm
CMake
In der build.gradle-Datei Ihres Moduls:
android {
defaultConfig {
externalNativeBuild {
cmake {
// Can also use system or none as ANDROID_STL.
arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
}
}
}
}
Für jedes Ziel in Ihrer CMakeLists.txt-Datei:
target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)
Ausführen
Ab Android O MR1 (API-Ebene 27) kann eine Anwendung ein Wrap-Shell-Script bereitstellen, mit dem der Anwendungsprozess umschlossen oder ersetzt werden kann. Dadurch kann eine debugfähige Anwendung ihren Start anpassen, was die Verwendung von ASan auf Produktionsgeräten ermöglicht.
- Fügen Sie dem Anwendungsmanifest
android:debuggablehinzu. - Legen Sie
useLegacyPackagingin der Dateibuild.gradleIhrer App auftruefest. Weitere Informationen finden Sie im Leitfaden zum Umschließen von Shell-Skripts. - Fügen Sie die ASan-Laufzeitbibliothek der Datei
jniLibsIhres App-Moduls hinzu. Fügen Sie jedem Verzeichnis in Ihrem
src/main/resources/lib-Verzeichniswrap.sh-Dateien mit folgendem Inhalt hinzu.#!/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 "$@"
Angenommen, das Anwendungsmodul Ihres Projekts heißt app. Dann sollte die endgültige Verzeichnisstruktur Folgendes enthalten:
<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
Stacktraces
Address Sanitizer muss den Stapel bei jedem malloc-/realloc-/free-Aufruf abwickeln. Hier haben Sie zwei Möglichkeiten:
Ein „schneller“ Frame-Pointer-basierter Unwinder. Dies wird verwendet, wenn Sie der Anleitung im Abschnitt zum Erstellen folgen.
Ein „langsamer“ CFI-Unwinder. In diesem Modus verwendet ASan
_Unwind_Backtrace. Dafür ist nur-funwind-tableserforderlich, das normalerweise standardmäßig aktiviert ist.
Der schnelle Unwinder ist der Standard für malloc/realloc/free. Der langsame Unwinder ist die Standardeinstellung für schwerwiegende Stacktraces. Der langsame Unwinder kann für alle Stacktraces aktiviert werden, indem Sie der Variablen ASAN_OPTIONS in Ihrem wrap.sh fast_unwind_on_malloc=0 hinzufügen.