Adressdesinfektionsmittel

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.

  1. Fügen Sie dem Anwendungsmanifest android:debuggable hinzu.
  2. Legen Sie useLegacyPackaging in der Datei build.gradle Ihrer App auf true fest. Weitere Informationen finden Sie im Leitfaden zum Umschließen von Shell-Skripts.
  3. Fügen Sie die ASan-Laufzeitbibliothek der Datei jniLibs Ihres App-Moduls hinzu.
  4. Fügen Sie jedem Verzeichnis in Ihrem src/main/resources/lib-Verzeichnis wrap.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:

  1. Ein „schneller“ Frame-Pointer-basierter Unwinder. Dies wird verwendet, wenn Sie der Anleitung im Abschnitt zum Erstellen folgen.

  2. Ein „langsamer“ CFI-Unwinder. In diesem Modus verwendet ASan _Unwind_Backtrace. Dafür ist nur -funwind-tables erforderlich, 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.