Android NDK obsługuje Address Sanitizer (znany też jako ASan) od poziomu interfejsu API 27 (Android O MR1).
ASan to szybkie narzędzie oparte na kompilatorze, które służy do wykrywania błędów pamięci w kodzie natywnym. ASan wykrywa:
- Przepełnienie lub niedopełnienie bufora stosu i sterty
- Wykorzystanie sterty po zwolnieniu
- Użycie stosu poza zakresem
- Podwójny bezpłatny/dziki bezpłatny
Narzędzie ASan powoduje około 2-krotny narzut na procesor, 50–200% narzut na rozmiar kodu i duży narzut na pamięć (zależny od wzorców alokacji, ale rzędu 2x).
Przykładowa aplikacja
Przykładowa aplikacja pokazuje, jak skonfigurować wariant kompilacji dla ASan.
Kompilacja
Aby skompilować kod natywny (JNI) aplikacji za pomocą Address Sanitizer, wykonaj te czynności:
ndk-build
W pliku Application.mk:
APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address
W przypadku każdego modułu w pliku Android.mk:
LOCAL_ARM_MODE := arm
CMake
W pliku build.gradle modułu:
android {
defaultConfig {
externalNativeBuild {
cmake {
// Can also use system or none as ANDROID_STL.
arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
}
}
}
}
W przypadku każdego celu w pliku CMakeLists.txt:
target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)
Uruchom
Od Androida O MR1 (API w wersji 27) aplikacja może udostępniać skrypt powłoki opakowującej, który może opakowywać lub zastępować proces aplikacji. Umożliwia to aplikacji z możliwością debugowania dostosowywanie uruchamiania, co pozwala na używanie ASan na urządzeniach produkcyjnych.
- Dodaj
android:debuggabledo pliku manifestu aplikacji. - W pliku
build.gradleaplikacji ustaw wartośćuseLegacyPackagingnatrue. Więcej informacji znajdziesz w przewodniku dotyczącym skryptu powłoki opakowującej. - Dodaj bibliotekę środowiska wykonawczego ASan do pliku
jniLibsmodułu aplikacji. Dodaj do każdego katalogu w katalogu
src/main/resources/libplikiwrap.shz tą treścią:#!/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 "$@"
Jeśli moduł aplikacji projektu ma nazwę app, ostateczna struktura katalogu powinna zawierać te elementy:
<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
Śledzenie stosu
Address Sanitizer musi cofnąć stos przy każdym wywołaniu malloc/realloc/free. Masz 2 możliwości:
„Szybki” mechanizm rozwijania stosu oparty na wskaźnikach ramki. Jest on używany zgodnie z instrukcjami w sekcji dotyczącej budowania.
„Wolny” dekompilator CFI. W tym trybie ASan używa
_Unwind_Backtrace. Wymaga tylko-funwind-tables, która jest zwykle domyślnie włączona.
Szybkie rozwijanie stosu jest domyślnym ustawieniem w przypadku funkcji malloc/realloc/free. Powolne rozwijanie stosu jest domyślnym ustawieniem w przypadku zrzutów stosu błędów krytycznych. Powolne rozwijanie można włączyć dla wszystkich śladów stosu, dodając fast_unwind_on_malloc=0 do zmiennej ASAN_OPTIONS w pliku wrap.sh.