Interfejsy ABI Androida

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

Tabela 1. Interfejsy ABI i obsługiwane zestawy instrukcji.

Interfejs ABI Obsługiwane zestawy instrukcji Uwagi
armeabi-v7a
  • Armeabi
  • Kciuk 2
  • Neon
  • Brak zgodności z urządzeniami z architekturą ARM w wersji 5 lub 6.
    arm64-v8a
  • ARCH64
  • Tylko w wersji Armv8.0.
    x86
  • x86 (IA-32)
  • MMX,
  • SE/2/3
  • SSSE3
  • Brak obsługi MOVBE i SSE4.
    x86_64
  • x86–64
  • MMX,
  • SE/2/3
  • SSSE3
  • SSE4.1, 4.2
  • protokół POPCNT
  • 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:

    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:

    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 aplikacji 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ą algorytmu -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ę.