Różne urządzenia z Androidem korzystają z różnych procesorów, które z kolei obsługują różne zestawów instrukcji. Każda kombinacja procesora i instrukcji ma własne Application Binary Interface (ABI). Interfejs ABI zawiera te informacje:
- Zestaw instrukcji dotyczących procesora (i rozszerzenia), których można używać.
- Koniec pamięci zapisywanej i wczytywanej w czasie działania. Android jest zawsze Little-endian.
- Konwencje dotyczące przekazywania danych między aplikacjami a systemem, w tym ograniczenia wyrównywania oraz to, jak system używa stosu jest rejestrowane podczas wywoływania funkcji.
- Format plików binarnych wykonywalnych, takich jak programy i biblioteki udostępnione i obsługiwane przez nie typy treści. Android zawsze używa ELF. Więcej Więcej informacji zawiera Interfejs binarny aplikacji ELF V.
- Jak nazwy w C++ są zniekształcane. Więcej informacji: Ogólny/Itanium C++ ABI:
Na tej stronie wymieniono interfejsy ABI obsługiwane przez NDK oraz podano informacje jak działa każdy z interfejsów ABI.
Interfejs ABI może też odnosić się do natywnego interfejsu API obsługiwanego przez platformę. Dla listę tego typu problemów z interfejsem ABI wpływających na systemy 32-bitowe, zobacz Błędy ABI 32-bitowe.
Obsługiwane interfejsy ABI
Interfejs ABI | Obsługiwane zestawy instrukcji | Uwagi |
---|---|---|
armeabi-v7a |
|
Brak zgodności z urządzeniami z architekturą ARM w wersji 5 lub 6. |
arm64-v8a |
Tylko w wersji Armv8.0. | |
x86 |
Brak obsługi MOVBE i SSE4. | |
x86_64 |
|
Pełny format x86-64-v1, ale tylko częściowe x86-64-v2 (bez CMPXCHG16B ani LAHF-SAHF). |
Uwaga: w przeszłości model ARMv5 obsługiwany przez NDK (armeabi) oraz 32- i 64-bitowego MIPS, ale obsługa tych interfejsów ABI została usunięta w NDK r17.
Armeabi-V7a
Ten interfejs ABI jest przeznaczony dla 32-bitowych procesorów ARM. Są to Thumb-2 i Neon.
Informacje o częściach interfejsu ABI, które nie są typowe dla Androida, znajdziesz w artykule Application Binary Interface (ABI) dla architektury ARM
Systemy kompilacji NDK domyślnie generują kod Thumb-2, chyba że
LOCAL_ARM_MODE
w: Android.mk
na
ndk-build lub ANDROID_ARM_MODE
podczas konfigurowania narzędzia CMake.
Aby dowiedzieć się więcej o historii neonów, odwiedź stronę pomocy Neo.
Ze względów historycznych ten interfejs ABI używa parametru -mfloat-abi=softfp
powodującego wszystkie float
wartości do przekazania w rejestrach całkowitych i wszystkie wartości double
do przekazania
w parach rejestrów całkowitych przy wywoływaniu funkcji. Pomimo nazwy ten element
wpływa tylko na konwencję wywołań zmiennoprzecinkowych: kompilator
używać sprzętowych instrukcji zmiennoprzecinkowych na potrzeby arytmetyki.
Ten interfejs ABI używa 64-bitowego kodu long double
(IEEE binarnemu64 to samo co double
).
Arm64-V8a
Ten interfejs ABI jest przeznaczony dla 64-bitowych procesorów ARM.
Zobacz Arm's Poznaj architekturę aby uzyskać szczegółowe informacje o częściach interfejsu ABI, które nie są związane z Androidem. Odmiana oferuje też porady dotyczące przenoszenia Programowanie w wersji na Androida 64-bitowej.
Możesz użyć neonów neonowych w języku C i C++, aby korzystać z rozszerzenia Advanced SIMD. Neon Programmer's Guide for Armv8-A zawiera więcej informacji o neonowej istocie oraz o programowaniu neonów.
W systemie Android rejestr x18 związany z platformą jest zarezerwowany dla
ShadowCallStack
i nie powinien być dotknięty kodem. Aktualne wersje języka Clang domyślnie to
za pomocą aplikacji -ffixed-x18
na urządzeniach z Androidem, jeśli więc nie masz
ale nie musisz się tym przejmować.
Ten interfejs ABI używa 128-bitowego kodu long double
(IEEE binarnego128).
x86
Ten interfejs ABI jest przeznaczony dla procesorów obsługujących zestaw instrukcji znany pod nazwą „x86”, „i386” lub „IA-32”.
Interfejs ABI Androida obejmuje podstawowy zestaw instrukcji plus MMX, SSE SSE2, SSE3 oraz SSSE3.
Interfejs ABI nie zawiera żadnego innego opcjonalnego zestawu instrukcji IA-32 takich jak MOVBE lub dowolny wariant SSE4. Nadal możesz używać tych rozszerzeń, o ile używasz sondowania funkcji w czasie działania i udostępniać opcje zastępcze na urządzeniach, które ich nie obsługują.
Łańcuch narzędzi NDK zakłada wyrównanie stosu 16-bajtowego przed wywołaniem funkcji. Domyślne narzędzia opcje wymuszają tę regułę. Jeśli piszesz kod asemmentu, musisz pamiętać, i upewnić się, że innych kompilatorów przestrzegają tej zasady.
Więcej informacji znajdziesz w tych dokumentach:
- Połączenia i w przypadku różnych kompilatorów i systemów operacyjnych C++
- Intel IA-32 Intel Architecture Software Developer's Instrukcja dla programistów, część 2: Odniesienie do zestawu instrukcji
- Intel Instrukcja dla programistów oprogramowania architektury Intel IA-32, część 3: System Przewodnik po programach
- Plik binarny aplikacji System V Interfejs: dodatek do architektury procesora Intel386
Ten interfejs ABI używa 64-bitowego kodu long double
(IEEE binarnemu64 to samo co double
, a nie więcej
typowy long double
-bitowy 80-bitowy firmy Intel).
x86_64
Ten interfejs ABI jest przeznaczony dla procesorów obsługujących zbiór instrukcji nazywanych potocznie „x86-64”.
Interfejs ABI Androida obejmuje podstawowy zestaw instrukcji plus MMX, SSE SSE2, SSE3, SSSE3, SSE4.1. SSE4.2 i z instrukcją POPCNT.
Interfejs ABI nie zawiera żadnego innego opcjonalnego zestawu instrukcji x86-64. takich jak MOVBE, SHA i dowolny wariant AVX. Nadal możesz używać tych rozszerzeń, pod warunkiem że będziesz używać sondowania funkcji w czasie działania i udostępniać opcje zastępcze na urządzeniach, które ich nie obsługują.
Więcej informacji znajdziesz w tych dokumentach:
- Konwencje wywołań dla różne kompilatory i systemy operacyjne C++
- Instrukcja dla programistów oprogramowania Intel64 i IA-32 Architectures Architectures, część 2: zestaw instrukcji Źródła wiedzy
- Instrukcja dla deweloperów oprogramowania Intel64 i IA-32 dla programistów oprogramowania architektury Intel, część 3: programowanie systemu
Ten interfejs ABI używa 128-bitowego kodu long double
(IEEE binarnego128).
Generowanie kodu dla określonego interfejsu ABI
Gradle
Kompilacje Gradle (używane za pomocą Android Studio lub z wiersza poleceń)
domyślnie dla wszystkich niewycofanych interfejsów ABI. Aby ograniczyć zestaw interfejsów ABI,
obsługuje aplikację, użyj funkcji abiFilters
. Na przykład, aby tworzyć tylko
64-bitowe interfejsy ABI, ustaw w build.gradle
tę konfigurację:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
ndk-build
domyślnie ndk-build dla wszystkich niewycofanych interfejsów ABI. Możesz kierować reklamy na
za pomocą określonych interfejsów ABI, ustawiając APP_ABI
w pliku Application.mk.
ten fragment kodu zawiera kilka przykładów użycia atrybutu APP_ABI
:
APP_ABI := arm64-v8a # Target only arm64-v8a
APP_ABI := all # Target all ABIs, including those that are deprecated.
APP_ABI := armeabi-v7a x86_64 # Target only armeabi-v7a and x86_64.
Więcej informacji o wartościach, które można określić dla atrybutu APP_ABI
, znajdziesz tutaj:
Plik Application.mk.
CMake
Dzięki CMake możesz kompilować jednocześnie 1 interfejs ABI i określić własny interfejs ABI
bezpośrednio. Służy do tego zmienna ANDROID_ABI
, która musi być
określone w wierszu poleceń (nie można go ustawić w pliku CMakeLists.txt). Dla:
przykład:
$ cmake -DANDROID_ABI=arm64-v8a ...
$ cmake -DANDROID_ABI=armeabi-v7a ...
$ cmake -DANDROID_ABI=x86 ...
$ cmake -DANDROID_ABI=x86_64 ...
Inne flagi, które muszą być przekazywane do CMake w celu kompilacji za pomocą NDK, znajdziesz w artykule przewodnika CMake.
Domyślnym działaniem systemu kompilacji jest uwzględnianie plików binarnych dla każdego interfejsu ABI w pojedynczym pliku APK, czyli tzw. grubem pliku APK. Gruby plik APK jest znacznie większy. niż jeden zawierający tylko pliki binarne dla pojedynczego interfejsu ABI; kompromis przynosi korzyści większą zgodność, ale kosztem większego pakietu APK. Silnie zalecamy korzystanie z pakietów aplikacji lub podziałów plików APK, zmniejsz rozmiar plików APK przy zachowaniu maksymalnej liczby zgodność.
Podczas instalacji menedżer pakietów rozpakowuje tylko najbardziej odpowiednie dla urządzenia docelowego. Szczegółowe informacje znajdziesz w sekcji Automatyczne wyodrębnianie podczas instalacji.
Zarządzanie interfejsami ABI na platformie Androida
Ta sekcja zawiera szczegółowe informacje o tym, jak platforma Androida zarządza natywnymi w plikach APK.
Kod natywny w pakietach aplikacji
Zarówno Sklep Play, jak i Menedżer pakietów znajdą wygenerowane przez NDK biblioteki w ścieżkach plików w pakiecie APK pasujące do tego wzorca:
/lib/<abi>/lib<name>.so
<abi>
jest jedną z nazw interfejsu ABI wymienionych w sekcji Obsługiwane interfejsy ABI,
a <name>
to nazwa biblioteki zdefiniowana przez Ciebie dla pola LOCAL_MODULE
.
w pliku Android.mk
. Od
Pliki APK to po prostu pliki ZIP. Można je łatwo otworzyć i potwierdzić, że udostępniane pliki natywne
do swoich bibliotek.
Jeśli system nie znajdzie natywnych bibliotek udostępnionych tam, gdzie ich wymaga, nie będzie mógł użyć
. W takim przypadku sama aplikacja musi skopiować biblioteki, a potem
wykonaj dlopen()
.
W przypadku grubych plików APK każda biblioteka znajduje się w katalogu, którego nazwa odpowiada odpowiedniemu interfejsowi ABI. Na przykład gruby plik APK może zawierać:
/lib/armeabi/libfoo.so /lib/armeabi-v7a/libfoo.so /lib/arm64-v8a/libfoo.so /lib/x86/libfoo.so /lib/x86_64/libfoo.so
Uwaga: urządzenia z Androidem w wersji 4.0.3 lub starszej i z procesorami ARMv7
zainstaluj biblioteki natywne z katalogu armeabi
zamiast z biblioteki armeabi-v7a
, jeśli oba katalogi istnieją. Dzieje się tak, ponieważ /lib/armeabi/
pojawia się po
/lib/armeabi-v7a/
w pliku APK. Ten problem został rozwiązany od wersji 4.0.4.
Obsługa interfejsu ABI platformy Androida
System Android wie w czasie działania, który interfejs ABI obsługuje, ponieważ w przypadku konkretnej kompilacji właściwości wskazują:
- Podstawowy interfejs ABI urządzenia odpowiadający kodowi maszyny używanemu w obrazu systemu.
- Opcjonalnie dodatkowe interfejsy ABI odpowiadające innemu interfejsowi ABI używanym przez obraz systemu który obsługuje.
Mechanizm ten gwarantuje, że system będzie wyodrębniać najlepszy kod maszynowy z pakietu w momencie instalacji.
Aby uzyskać najlepszą wydajność, skompiluj ją bezpośrednio pod kątem podstawowego interfejsu ABI. Na przykład plik
typowe urządzenie z architekturą ARMv5TE zdefiniuje tylko podstawowy interfejs ABI: armeabi
. Z kolei
typowe urządzenie z architekturą ARMv7 zdefiniuje podstawowy interfejs ABI jako armeabi-v7a
, a dodatkowy
jeden jako armeabi
, ponieważ może on uruchamiać natywne pliki binarne aplikacji wygenerowane dla każdego z nich.
Urządzenia 64-bitowe obsługują też ich warianty 32-bitowe. Korzystanie z urządzeń ze standardem arm64-v8a Przykład: urządzenie może również uruchamiać kod Armeabi i Armeabi-v7a. Uwaga: jednak na urządzeniach 64-bitowych aplikacja będzie działać znacznie lepiej, jest kierowana na wersję armeabi-v8a, zamiast polegać na urządzeniu wersji Twojej aplikacji.
Wiele urządzeń x86 może również uruchamiać pliki binarne NDK armeabi-v7a
i armeabi
. Dla:
takich urządzeń, podstawowym interfejsem ABI byłoby x86
, a drugim – armeabi-v7a
.
Możesz wymusić instalację pakietu apk dla określonego interfejsu ABI. Jest to przydatne podczas testowania. Użyj tego polecenia:
adb install --abi abi-identifier path_to_apk
Automatyczne wyodrębnianie kodu natywnego podczas instalacji
Podczas instalowania aplikacji usługa menedżera pakietów skanuje plik APK i szuka biblioteki udostępnione w formie:
lib/<primary-abi>/lib<name>.so
Jeśli nie zostanie znaleziona żadna z nich i masz zdefiniowany dodatkowy interfejs ABI, usługa przeskanuje w poszukiwaniu bibliotek udostępnionych formularz:
lib/<secondary-abi>/lib<name>.so
Po znalezieniu bibliotek, których szuka, menedżer pakietów kopiuje je
je do katalogu /lib/lib<name>.so
, w natywnym katalogu biblioteki aplikacji
(<nativeLibraryDir>/
). Te fragmenty kodu pobierają nativeLibraryDir
:
Kotlin
import android.content.pm.PackageInfo import android.content.pm.ApplicationInfo import android.content.pm.PackageManager ... val ainfo = this.applicationContext.packageManager.getApplicationInfo( "com.domain.app", PackageManager.GET_SHARED_LIBRARY_FILES ) Log.v(TAG, "native library dir ${ainfo.nativeLibraryDir}")
Java
import android.content.pm.PackageInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; ... ApplicationInfo ainfo = this.getApplicationContext().getPackageManager().getApplicationInfo ( "com.domain.app", PackageManager.GET_SHARED_LIBRARY_FILES ); Log.v( TAG, "native library dir " + ainfo.nativeLibraryDir );
Jeśli nie ma pliku shared-object, aplikacja skompiluje i zainstaluje się, ale ulega awarii przy w środowisku wykonawczym.
ARMv9: włączanie PAC i BTI w C/C++
Włączenie plików PAC/BTI zapewni ochronę przed niektórymi wektorami ataku. PAC chroni adresy zwrotne, podpisując je kryptograficznie w funkcji prolog i sprawdzenie, czy adres zwrotny jest nadal poprawnie zalogowany (epilog). BTI zapobiega przeskakiwaniu do dowolnych lokalizacji w kodzie, ponieważ wymaga że każda gałąź to specjalna instrukcja, która nie informuje o tym, i ustalić, że można tam trafić.
Android używa instrukcji PAC/BTI, które nie wpływają na starsze procesory, nie obsługują nowych instrukcji. Tylko urządzenia z architekturą ARMv9 mają kod PAC/BTI zabezpieczeń, ale ten sam kod można uruchamiać także na urządzeniach ARMv8. Nie ma potrzeby kilka wariantów swojej biblioteki. Nawet w przypadku urządzeń z architekturą ARMv9 obowiązują tylko zasady PAC/BTI do kodu 64-bitowego.
Włączenie PAC/BTI spowoduje niewielki wzrost rozmiaru kodu, zwykle o 1%.
Obejrzyj film Arm: Poznaj architekturę – zapewnienie ochrony złożone oprogramowanie (PDF) znajdziesz szczegółowe objaśnienie wektorów ataku PAC/BTI oraz sposobów to ochrona.
Wprowadź zmiany
ndk-build
Ustaw LOCAL_BRANCH_PROTECTION := standard
w każdym module w Android.mk.
CMake
Użyj formatu: target_compile_options($TARGET PRIVATE -mbranch-protection=standard)
dla każdego elementu docelowego w pliku CMakeLists.txt.
Inne systemy kompilacji
Skompiluj kod za pomocą polecenia -mbranch-protection=standard
. Ta flaga działa tylko
podczas kompilacji pod kątem interfejsu ABI arm64-v8a. Nie musisz używać tej flagi, gdy
Google Analytics.
Rozwiązywanie problemów
Nie wiemy o żadnych problemach z obsługą kompilatora dla plików PAC/BTI, ale:
- Podczas łączenia uważaj, by nie łączyć kodów BTI i innych niż BTI, ponieważ spowoduje wyświetlenie biblioteki, w której nie włączono ochrony BTI. Za pomocą llvm-readelf, aby sprawdzić, czy biblioteka wynikowa zawiera notatkę BTI.
$ llvm-readelf --notes LIBRARY.so [...] Displaying notes found in: .note.gnu.property Owner Data size Description GNU 0x00000010 NT_GNU_PROPERTY_TYPE_0 (property note) Properties: aarch64 feature: BTI, PAC [...] $
Starsze wersje OpenSSL (sprzed wersji 1.1.1i) zawierają błąd w odręcznym narzędziu Assemler który powoduje błędy PAC. Uaktualnij OpenSSL do bieżącej wersji.
Stare wersje niektórych systemów DRM aplikacji generują kod, który narusza PAC/BTI . Jeśli korzystasz z DRM aplikacji i masz problemy z włączeniem PAC/BTI, skontaktować się z dostawcą DRM, by uzyskać poprawioną wersję.