Android-ABIs

Verschiedene Android-Geräte verwenden unterschiedliche CPUs, die wiederum unterschiedliche Anweisungssätze unterstützen. Jede Kombination aus CPU und Befehlssatz hat eine eigene Binärschnittstelle (Application Binary Interface, ABI). Ein ABI enthält die folgenden Informationen:

  • Der CPU-Befehlssatz (und die Erweiterungen), der verwendet werden kann.
  • Die Endianness von Arbeitsspeicherspeichern und -ladevorgängen zur Laufzeit. Android ist immer „Little Endian“.
  • Konventionen für die Weitergabe von Daten zwischen Anwendungen und dem System, einschließlich Ausrichtungseinschränkungen und der Verwendung des Stacks und der Registrierung beim Aufrufen von Funktionen.
  • Das Format ausführbarer Binärdateien, z. B. Programme und gemeinsam genutzte Bibliotheken, und die unterstützten Inhaltstypen. Android verwendet immer ELF. Weitere Informationen finden Sie unter Binärschnittstelle „ELF System V“.
  • Wie C++-Namen manipuliert werden. Weitere Informationen finden Sie unter Generic/Itanium C++ ABI.

Auf dieser Seite sind die vom NDK unterstützten ABIs aufgeführt und es werden Informationen zur Funktionsweise der einzelnen ABIs bereitgestellt.

ABI kann sich auch auf die native API beziehen, die von der Plattform unterstützt wird. Eine Liste dieser ABI-Probleme, die sich auf 32-Bit-Systeme auswirken, finden Sie unter 32-Bit-ABI-Fehler.

Unterstützte ABIs

Tabelle 1 ABIs und unterstützte Befehlssätze.

ABI Unterstützte Anweisungen Hinweise
armeabi-v7a
  • Armeabi
  • Daumen-2
  • Neon
  • Nicht kompatibel mit ARMv5/v6-Geräten.
    arm64-v8a
  • AArch64
  • Nur Armv8.0.
    x86
  • x86 (IA-32)
  • MMX
  • SSE/2/3
  • Logo: SSSE3
  • Keine Unterstützung für MOVBE oder SSE4.
    x86_64
  • x86–64
  • MMX
  • SSE/2/3
  • Logo: SSSE3
  • SSE4.1, 4.2
  • Logo: POPCNT
  • Nur x86-64-v1.

    Hinweis:In der Vergangenheit unterstützte der NDK ARMv5 (armeabi) sowie 32- und 64-Bit-MIPS, aber die Unterstützung für diese ABIs wurde in NDK r17 entfernt.

    Armeabi-V7a

    Dieses ABI ist für 32-Bit-ARM-CPUs vorgesehen. Es enthält Thumb-2 und Neon.

    Informationen zu den Teilen des ABI, die nicht Android-spezifisch sind, findest du unter Application Binary Interface (ABI) für die ARM-Architektur.

    Die Build-Systeme des NDK generieren standardmäßig Thumb-2-Code, es sei denn, Sie verwenden LOCAL_ARM_MODE in Ihrem Android.mk für ndk-build oder ANDROID_ARM_MODE beim Konfigurieren von CMake.

    Weitere Informationen zur Geschichte von Neon finden Sie unter Neon-Support.

    Aus historischen Gründen verwendet dieses ABI -mfloat-abi=softfp. Dadurch werden bei Funktionsaufrufen alle float-Werte in Ganzzahlregistern und alle double-Werte in Ganzzahl-Registerpaaren übergeben. Trotz des Namens wirkt sich dies nur auf die Gleitkomma-Aufrufkonvention aus: Der Compiler verwendet weiterhin Hardware-Gleitkommaanweisungen für die Arithmetik.

    Dieses ABI verwendet eine 64-Bit-long double (IEEE binary64 identisch mit double).

    ARM64-V8a

    Dieses ABI ist für 64-Bit-ARM-CPUs vorgesehen.

    Vollständige Details zu den Teilen des ABI, die nicht Android-spezifisch sind, finden Sie in Learn the Architecture von Arm. Arm bietet auch einige Tipps zur Portierung in der 64-Bit-Android-Entwicklung.

    Sie können die integrierten Neon-Elemente in C- und C++-Code verwenden, um die Advanced SIMD-Erweiterung zu nutzen. Weitere Informationen zur Neon-Programmierung und zur Neon-Programmierung im Allgemeinen finden Sie im Neon Programmer's Guide für Armv8-A.

    Unter Android ist das plattformspezifische x18-Register für ShadowCallStack reserviert und sollte nicht von Ihrem Code betroffen sein. Aktuelle Versionen von Clang verwenden standardmäßig die Option -ffixed-x18 unter Android. Sofern Sie keinen handschriftlichen Assembr (oder einen sehr alten Compiler) verwenden, sollten Sie sich darüber keine Gedanken machen.

    Dieses ABI verwendet eine 128-Bit-long double (IEEE binary128).

    x86

    Dieses ABI ist für CPUs vorgesehen, die den allgemein als „x86“, „i386“ oder „IA-32“ bekannten Befehlssatz unterstützen.

    ABI von Android enthält den Basisbefehlssatz sowie die Erweiterungen MMX, SSE, SSE2, SSE3 und SSSE3.

    Das ABI enthält keine anderen optionalen IA-32-Befehlssatzerweiterungen wie MOVBE oder eine Variante von SSE4. Sie können diese Erweiterungen trotzdem verwenden, solange Sie sie mithilfe von Laufzeitprüfung aktivieren und Fallbacks für Geräte bereitstellen, die sie nicht unterstützen.

    Die NDK-Toolchain setzt vor einem Funktionsaufruf von einer 16-Byte-Stack-Ausrichtung aus. Diese Regel wird durch die Standardtools und -optionen erzwungen. Wenn Sie Assembly-Code schreiben, müssen Sie die Stackausrichtung beibehalten und dafür sorgen, dass andere Compiler ebenfalls diese Regel befolgen.

    Weitere Informationen finden Sie in den folgenden Dokumenten:

    Dieses ABI verwendet eine 64-Bit-long double (IEEE binary64 ist mit double identisch und nicht die gängigere 80-Bit-Intel-long double).

    x86_64

    Dieses ABI ist für CPUs vorgesehen, die den Befehlssatz unterstützen, der allgemein als "x86-64" bezeichnet wird.

    Das ABI von Android umfasst den Basisbefehlssatz sowie MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2 und die POPCNT-Anweisung.

    Das ABI enthält keine anderen optionalen x86-64-Befehlssatzerweiterungen wie MOVBE, SHA oder eine Variante von AVX. Sie können diese Erweiterungen trotzdem verwenden, solange Sie sie mithilfe von Runtime-Feature-Tests aktivieren und Fallbacks für Geräte bereitstellen, die sie nicht unterstützen.

    Weitere Informationen finden Sie in den folgenden Dokumenten:

    Dieses ABI verwendet eine 128-Bit-long double (IEEE binary128).

    Code für ein bestimmtes ABI generieren

    Gradle

    Gradle-Builds werden standardmäßig für alle nicht verworfenen ABIs erstellt, unabhängig davon, ob sie über Android Studio oder die Befehlszeile verwendet werden. Verwenden Sie abiFilters, um die Gruppe von ABIs einzuschränken, die von Ihrer Anwendung unterstützt werden. Wenn Sie beispielsweise nur für 64-Bit-ABIs erstellen möchten, legen Sie die folgende Konfiguration in Ihrem build.gradle fest:

    android {
        defaultConfig {
            ndk {
                abiFilters 'arm64-v8a', 'x86_64'
            }
        }
    }
    

    NDK-Build

    ndk-build-Builds standardmäßig für alle nicht verworfenen ABIs. Du kannst ein Targeting auf bestimmte ABIs vornehmen, indem du APP_ABI in der Datei Application.mk festlegst. Das folgende Snippet zeigt einige Beispiele für die Verwendung von 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.
    

    Weitere Informationen zu den Werten, die Sie für APP_ABI angeben können, finden Sie unter Application.mk.

    CMake

    Mit CMake erstellen Sie jeweils für ein einzelnes ABI und müssen Ihre ABI explizit angeben. Dazu verwenden Sie die Variable ANDROID_ABI, die in der Befehlszeile angegeben werden muss und nicht in der Datei CMakeLists.txt festgelegt werden kann. Beispiel:

    $ cmake -DANDROID_ABI=arm64-v8a ...
    $ cmake -DANDROID_ABI=armeabi-v7a ...
    $ cmake -DANDROID_ABI=x86 ...
    $ cmake -DANDROID_ABI=x86_64 ...
    

    Informationen zu den anderen Flags, die an CMake übergeben werden müssen, um mit dem NDK zu erstellen, finden Sie in der CMake-Anleitung.

    Das Standardverhalten des Build-Systems besteht darin, die Binärdateien für jede ABI in einem einzelnen APK zu speichern, das auch als Fat APK bezeichnet wird. Ein fettes APK ist deutlich größer als ein APK, das nur die Binärprogramme für ein einzelnes ABI enthält. Der Kompromiss besteht darin, die Kompatibilität zu erhöhen, aber auf Kosten eines größeren APKs. Wir empfehlen Ihnen dringend, entweder App Bundles oder APK-Splits zu verwenden, um die Größe Ihrer APKs zu reduzieren und gleichzeitig die maximale Gerätekompatibilität zu gewährleisten.

    Bei der Installation entpackt der Paketmanager nur den am besten geeigneten Maschinencode für das Zielgerät. Weitere Informationen finden Sie unter Automatische Extraktion von nativem Code bei der Installation.

    ABI-Verwaltung auf der Android-Plattform

    In diesem Abschnitt wird beschrieben, wie die Android-Plattform nativen Code in APKs verwaltet.

    Nativer Code in App-Paketen

    Sowohl der Play Store als auch der Paketmanager erwarten NDK-generierte Bibliotheken in Dateipfaden im APK, die dem folgenden Muster entsprechen:

    /lib/<abi>/lib<name>.so
    

    Hier ist <abi> einer der ABI-Namen, die unter Unterstützte ABIs aufgeführt sind, und <name> der Name der Bibliothek, wie Sie ihn für die Variable LOCAL_MODULE in der Datei Android.mk definiert haben. Da es sich bei APK-Dateien nur um ZIP-Dateien handelt, ist es einfach, sie zu öffnen und zu prüfen, ob sich die gemeinsam genutzten nativen Bibliotheken am richtigen Ort befinden.

    Wenn das System die nativen freigegebenen Bibliotheken nicht dort findet, wo sie erwartet werden, können sie nicht verwendet werden. In einem solchen Fall muss die Anwendung die Bibliotheken kopieren und dann dlopen() ausführen.

    In einem fetten APK befindet sich jede Bibliothek in einem Verzeichnis, dessen Name mit dem entsprechenden ABI übereinstimmt. Ein fett formatiertes APK kann beispielsweise Folgendes enthalten:

    /lib/armeabi/libfoo.so
    /lib/armeabi-v7a/libfoo.so
    /lib/arm64-v8a/libfoo.so
    /lib/x86/libfoo.so
    /lib/x86_64/libfoo.so
    

    Hinweis:ARMv7-basierte Android-Geräte mit Android 4.0.3 oder niedriger installieren native Bibliotheken aus dem Verzeichnis armeabi statt aus dem Verzeichnis armeabi-v7a, wenn beide Verzeichnisse vorhanden sind. Das liegt daran, dass /lib/armeabi/ im APK nach /lib/armeabi-v7a/ steht. Dieses Problem wurde ab Version 4.0.4 behoben.

    ABI-Unterstützung für Android-Plattform

    Das Android-System weiß zur Laufzeit, welche ABIs unterstützt werden, da Build-spezifische Systemeigenschaften Folgendes anzeigen:

    • Die primäre ABI für das Gerät, die dem Maschinencode entspricht, der im System-Image selbst verwendet wird.
    • Optional: sekundäre ABIs, die anderen vom System-Image unterstützten ABIs entsprechen.

    Dieser Mechanismus sorgt dafür, dass das System bei der Installation den besten Maschinencode aus dem Paket extrahiert.

    Für eine optimale Leistung solltest du sie direkt für das primäre ABI kompilieren. Ein typisches ARMv5TE-basiertes Gerät würde beispielsweise nur das primäre ABI definieren: armeabi. Im Gegensatz dazu definiert ein typisches ARMv7-basiertes Gerät die primäre ABI als armeabi-v7a und die sekundäre ABI als armeabi, da es anwendungsnative Binärprogramme ausführen kann, die für jedes von ihnen generiert wurden.

    64-Bit-Geräte unterstützen auch ihre 32-Bit-Varianten. Am Beispiel von „arm64-v8a“-Geräten kann das Gerät auch die Codes „armeabi“ und „armeabi-v7a“ ausführen. Beachten Sie jedoch, dass Ihre Anwendung auf 64-Bit-Geräten viel besser funktioniert, wenn sie auf arm64-v8a ausgerichtet ist, anstatt sich auf das Gerät zu verlassen, auf dem die Version armeabi-v7a Ihrer Anwendung ausgeführt wird.

    Auf vielen x86-basierten Geräten können auch die NDK-Binärdateien armeabi-v7a und armeabi ausgeführt werden. Für solche Geräte wäre das primäre ABI x86 und das zweite armeabi-v7a.

    Sie können die Installation eines APK für ein bestimmtes ABI erzwingen. Dies ist für Tests nützlich. Verwenden Sie den folgenden Befehl:

    adb install --abi abi-identifier path_to_apk
    

    Automatisches Extrahieren von nativem Code bei der Installation

    Beim Installieren einer Anwendung scannt der Paketmanager das APK und sucht nach freigegebenen Bibliotheken in folgendem Format:

    lib/<primary-abi>/lib<name>.so
    

    Wenn keine gefunden wird und Sie ein sekundäres ABI definiert haben, sucht der Dienst nach gemeinsam genutzten Bibliotheken in der folgenden Form:

    lib/<secondary-abi>/lib<name>.so
    

    Wenn die gesuchten Bibliotheken gefunden werden, kopiert der Paketmanager sie nach /lib/lib<name>.so im nativen Bibliotheksverzeichnis der Anwendung (<nativeLibraryDir>/). Mit den folgenden Snippets wird das nativeLibraryDir abgerufen:

    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 );
    

    Ist keine Datei mit gemeinsam genutzten Objekten vorhanden, wird die Anwendung erstellt und installiert, stürzt jedoch während der Laufzeit ab.

    ARMv9: PAC und BTI für C/C++ aktivieren

    Die Aktivierung von PAC/BTI bietet Schutz vor einigen Angriffsvektoren. PAC schützt Rücksendeadressen, indem sie diese kryptografisch im Prolog einer Funktion signiert und überprüft, ob die Rücksendeadresse im Epilog noch korrekt signiert ist. BTI verhindert das Wechseln zu beliebigen Stellen im Code, da jedes Zweigziel eine spezielle Anweisung ist, die nichts weiter tut, als dem Prozessor mitzuteilen, dass es in Ordnung ist, dort zu landen.

    Android verwendet PAC/BTI-Anweisungen, die auf älteren Prozessoren, die die neuen Anweisungen nicht unterstützen, nicht funktionieren. Nur ARMv9-Geräte haben den PAC/BTI-Schutz. Sie können den gleichen Code aber auch auf ARMv8-Geräten ausführen, sodass mehrere Varianten Ihrer Bibliothek nicht erforderlich sind. Selbst auf ARMv9-Geräten gilt PAC/BTI nur für 64-Bit-Code.

    Das Aktivieren von PAC/BTI führt zu einer leichten Erhöhung der Codegröße, in der Regel um 1%.

    Eine detaillierte Erläuterung der PAC-/BTI-Zielvektoren und der Funktionsweise des Schutzes finden Sie in Arms Learn the Architecture – Approval Protection for Complex Software (PDF).

    Änderungen erstellen

    NDK-Build

    Lege LOCAL_BRANCH_PROTECTION := standard in jedem Modul deiner Android.mk-Datei fest.

    CMake

    Verwenden Sie target_compile_options($TARGET PRIVATE -mbranch-protection=standard) für jedes Ziel in Ihrer CMakeLists.txt-Datei.

    Andere Build-Systeme

    Kompilieren Sie Ihren Code mit -mbranch-protection=standard. Dieses Flag funktioniert nur bei der Kompilierung für das ABI „arm64-v8a“. Sie müssen dieses Flag bei der Verknüpfung nicht verwenden.

    Fehlerbehebung

    Uns sind keine Probleme mit der Compiler-Unterstützung für PAC/BTI bekannt, aber:

    • Achten Sie beim Verknüpfen darauf, BTI- und Nicht-BTI-Code nicht zu vermischen, da dies zu einer Bibliothek führt, für die der BTI-Schutz nicht aktiviert ist. Mit llvm-readelf können Sie prüfen, ob die resultierende Bibliothek einen BTI-Hinweis enthält.
    $ 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
    [...]
    $
    
    • In älteren Versionen von OpenSSL (vor 1.1.1i) gibt es einen Fehler im handgeschriebenen Assembler, der PAC-Fehler verursacht. Führen Sie ein Upgrade auf das aktuelle OpenSSL durch.

    • Alte Versionen einiger App-DRM-Systeme generieren Code, der gegen die PAC/BTI-Anforderungen verstößt. Wenn Sie die digitale Rechteverwaltung für Apps verwenden und beim Aktivieren von PAC/BTI Probleme auftreten, wenden Sie sich an Ihren DRM-Anbieter, um eine korrigierte Version zu erhalten.