AddressSanitizer (ASan) to szybkie narzędzie oparte na kompilatorze, które wykrywa błędy pamięci w kodzie natywnym.
ASan wykrywa:
- Przepełnienie lub niedopełnienie bufora stosu i sterty
- Użycie sterty po zwolnieniu
- Stosowanie poza zakresem
- Podwójny bezpłatny/dziki bezpłatny
ASan działa na 32-bitowych i 64-bitowych urządzeniach z procesorami ARM oraz na urządzeniach x86 i x86-64. 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).
Android 10 i najnowsza gałąź AOSP na AArch64 obsługują AddressSanitizer z pomocą sprzętową (HWASan), podobne narzędzie o mniejszym obciążeniu pamięci RAM i większym zakresie wykrywanych błędów. HWASan wykrywa użycie stosu po powrocie, a także błędy wykrywane przez ASan.
HWASan ma podobny narzut na procesor i rozmiar kodu, ale znacznie mniejszy narzut na pamięć RAM (15%). HWASan jest niedeterministyczny. Istnieje tylko 256 możliwych wartości tagów, więc prawdopodobieństwo pominięcia błędu wynosi 0,4%. HWASan nie ma stref czerwonych o ograniczonej wielkości, które są używane przez ASan do wykrywania przepełnień, ani kwarantanny o ograniczonej pojemności, która służy do wykrywania błędów typu use-after-free. Dlatego w przypadku HWASan nie ma znaczenia, jak duże jest przepełnienie ani jak dawno pamięć została zwolniona. Dzięki temu HWASan jest lepszy od ASan. Możesz dowiedzieć się więcej o architekturze HWASan lub o korzystaniu z HWASan na Androidzie.
ASan wykrywa przepełnienia stosu/globalne oprócz przepełnień sterty i jest szybki przy minimalnym narzucie pamięci.
W tym dokumencie opisujemy, jak skompilować i uruchomić części lub całość Androida z ASan. Jeśli tworzysz aplikację SDK/NDK za pomocą ASan, zamiast tego przeczytaj artykuł Address Sanitizer.
Czyszczenie poszczególnych plików wykonywalnych za pomocą ASan
Dodaj LOCAL_SANITIZE:=address
lub sanitize: { address: true }
do reguły kompilacji pliku wykonywalnego. Możesz wyszukać w kodzie istniejące przykłady lub znaleźć inne dostępne funkcje oczyszczania.
Gdy ASan wykryje błąd, wyświetla szczegółowy raport na standardowym wyjściu i w logcat
, a następnie powoduje awarię procesu.
Sanityzacja bibliotek udostępnionych za pomocą ASan
Ze względu na sposób działania ASan biblioteki utworzonej za pomocą ASan może używać tylko plik wykonywalny utworzony za pomocą ASan.
Aby oczyścić bibliotekę współużytkowaną używaną w wielu plikach wykonywalnych, z których nie wszystkie są tworzone za pomocą ASan, potrzebujesz 2 kopii biblioteki. Zalecany sposób to dodanie do pliku Android.mk
odpowiedniego modułu tego kodu:
LOCAL_SANITIZE:=address LOCAL_MODULE_RELATIVE_PATH := asan
Spowoduje to umieszczenie biblioteki w /system/lib/asan
zamiast w /system/lib
. Następnie uruchom plik wykonywalny za pomocą tego polecenia:
LD_LIBRARY_PATH=/system/lib/asan
W przypadku demonów systemowych dodaj te informacje do odpowiedniej sekcji pliku /init.rc
lub /init.$device$.rc
.
setenv LD_LIBRARY_PATH /system/lib/asan
Sprawdź, czy proces korzysta z bibliotek z /system/lib/asan
w przypadku ich obecności, czytając /proc/$PID/maps
. Jeśli nie, może być konieczne wyłączenie SELinux:
adb root
adb shell setenforce 0
# restart the process with adb shell kill $PID # if it is a system service, or may be adb shell stop; adb shell start.
Lepsze śledzenie stosu
ASan używa szybkiego, opartego na wskaźniku ramki mechanizmu rozwijania stosu do rejestrowania śladu stosu dla każdego zdarzenia przydzielenia i zwolnienia pamięci w programie. Większość Androida jest zbudowana bez wskaźników ramki. W rezultacie często otrzymujesz tylko 1–2 znaczące klatki. Aby to naprawić, ponownie skompiluj bibliotekę za pomocą ASan (zalecane!) lub za pomocą:
LOCAL_CFLAGS:=-fno-omit-frame-pointer LOCAL_ARM_MODE:=arm
Możesz też ustawić ASAN_OPTIONS=fast_unwind_on_malloc=0
w środowisku procesu. To drugie może być bardzo obciążające dla procesora, w zależności od obciążenia.
Symbolizacja
Początkowo raporty ASan zawierają odniesienia do przesunięć w plikach binarnych i bibliotekach współdzielonych. Informacje o pliku źródłowym i wierszu można uzyskać na 2 sposoby:
- Sprawdź, czy plik binarny
llvm-symbolizer
znajduje się w lokalizacji/system/bin
.llvm-symbolizer
jest tworzony na podstawie źródeł wthird_party/llvm/tools/llvm-symbolizer
. - Przefiltruj raport za pomocą
external/compiler-rt/lib/asan/scripts/symbolize.py
skryptu.
Drugie podejście może dostarczać więcej danych (czyli file:line
lokalizacji) ze względu na dostępność bibliotek symboli na hoście.
ASan w aplikacjach
ASan nie widzi kodu Java, ale może wykrywać błędy w bibliotekach JNI. W tym celu musisz skompilować plik wykonywalny za pomocą ASan, czyli w tym przypadku /system/bin/app_process(32|64)
. Umożliwia to włączenie ASan we wszystkich aplikacjach na urządzeniu jednocześnie, co stanowi duże obciążenie, ale urządzenie z 2 GB pamięci RAM powinno sobie z tym poradzić.
Dodaj LOCAL_SANITIZE:=address
do reguły kompilacji app_process
w frameworks/base/cmds/app_process
.
W sekcji service zygote
odpowiedniego pliku system/core/rootdir/init.zygote(32|64).rc
dodaj te wiersze do bloku wciętych wierszy zawierających class main
, również wciętych o tę samą wartość:
setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib setenv ASAN_OPTIONS allow_user_segv_handler=true
Kompilacja, synchronizacja za pomocą adb, flashowanie za pomocą fastboot i ponowne uruchomienie.
Używanie właściwości wrap
Podejście opisane w poprzedniej sekcji powoduje umieszczenie ASan w każdej aplikacji w systemie (a właściwie w każdym procesie potomnym procesu Zygote). Możesz uruchomić tylko jedną (lub kilka) aplikacji z ASan, poświęcając trochę pamięci na wolniejsze uruchamianie aplikacji.
Możesz to zrobić, zaczynając od właściwości wrap.
.
W przykładzie poniżej aplikacja Gmail jest uruchamiana w ASan:
adb root
adb shell setenforce 0 # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"
W tym kontekście asanwrapper
przepisuje /system/bin/app_process
na /system/bin/asan/app_process
, które jest zbudowane za pomocą ASan. Dodaje też /system/lib/asan
na początku ścieżki wyszukiwania biblioteki dynamicznej. Dzięki temu biblioteki z instrumentacją ASan z /system/lib/asan
są preferowane od zwykłych bibliotek z /system/lib
podczas uruchamiania z asanwrapper
.
Jeśli zostanie znaleziony błąd, aplikacja ulegnie awarii, a raport zostanie wydrukowany w dzienniku.
SANITIZE_TARGET
Android 7.0 i nowsze wersje obsługują tworzenie całej platformy Androida za pomocą ASan. (Jeśli tworzysz wersję wyższą niż Android 9, lepszym wyborem będzie HWASan).
Uruchom te polecenia w tym samym drzewie kompilacji.
make -j42
SANITIZE_TARGET=address make -j42
W tym trybie userdata.img
zawiera dodatkowe biblioteki i musi być również wgrany na urządzenie. Użyj tego wiersza poleceń:
fastboot flash userdata && fastboot flashall
Spowoduje to utworzenie 2 zestawów bibliotek współdzielonych: normalnego w /system/lib
(pierwsze wywołanie make) i instrumentowanego przez ASan w /data/asan/lib
(drugie wywołanie make). Pliki wykonywalne z drugiej kompilacji zastępują pliki z pierwszej kompilacji. Wykonywalne pliki z instrumentacją ASan mają inną ścieżkę wyszukiwania bibliotek, która zawiera /data/asan/lib
przed /system/lib
dzięki użyciu /system/bin/linker_asan
w PT_INTERP
.
System kompilacji usuwa katalogi obiektów pośrednich, gdy zmieni się wartość $SANITIZE_TARGET
. Wymusza to ponowne skompilowanie wszystkich elementów docelowych przy zachowaniu zainstalowanych plików binarnych w /system/lib
.
Niektórych elementów docelowych nie można utworzyć za pomocą ASan:
- Statycznie połączone pliki wykonywalne
- Liczba celów:
LOCAL_CLANG:=false
LOCAL_SANITIZE:=false
nie są sprawdzane przez ASan w przypadkuSANITIZE_TARGET=address
Pliki wykonywalne tego typu są pomijane w kompilacji SANITIZE_TARGET
, a wersja z pierwszego wywołania polecenia make pozostaje w /system/bin
.
Takie biblioteki są tworzone bez ASan. Mogą one zawierać kod ASan z zależnych od nich bibliotek statycznych.