Auf verschiedenen Android-Geräten werden unterschiedliche CPUs verwendet, die wiederum unterschiedliche Befehlssätze unterstützen. Jede Kombination aus CPU und Befehlssatz hat eine eigene Binärschnittstelle (ABI). Ein ABI enthält die folgenden Informationen:
- Der CPU-Befehlssatz (und die Erweiterungen), der verwendet werden kann.
- Die Endianness von Speicheroperationen und ‑ladevorgängen zur Laufzeit. Android ist immer Little-Endian.
- Konventionen für die Übergabe von Daten zwischen Anwendungen und dem System, einschließlich Ausrichtungsbeschränkungen und der Verwendung des Stacks und der Register durch das System beim Aufrufen von Funktionen.
- Das Format ausführbarer Binärdateien wie Programme und gemeinsam genutzte Bibliotheken sowie die unterstützten Inhaltstypen. Unter Android wird immer ELF verwendet. Weitere Informationen finden Sie unter ELF System V Binärschnittstelle.
- Wie C++-Namen geändert werden. Weitere Informationen finden Sie unter Generic/Itanium C++ ABI.
Auf dieser Seite werden die vom NDK unterstützten ABIs aufgeführt und es wird beschrieben, wie die einzelnen ABIs funktionieren.
ABI kann sich auch auf die native API beziehen, die von der Plattform unterstützt wird. Eine Liste der ABI-Probleme, die sich auf 32-Bit-Systeme auswirken, finden Sie unter 32-bit ABI bugs.
Unterstützte ABIs
Tabelle 1. ABIs und unterstützte Befehlssätze.
| ABI | Unterstützte Befehlssätze | Hinweise |
|---|---|---|
armeabi-v7a |
|
Nicht kompatibel mit ARMv5/v6-Geräten. |
arm64-v8a |
Nur Armv8.0. | |
x86 |
Keine Unterstützung für MOVBE oder SSE4. | |
x86_64 |
|
Vollständiges x86-64-v2. |
Hinweis:Früher unterstützte das NDK ARMv5 (armeabi) sowie 32-Bit- 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. Dazu gehören Thumb-2 und Neon.
Informationen zu den Teilen der ABI, die nicht Android-spezifisch sind, finden Sie unter Binärschnittstelle (ABI) für die ARM-Architektur.
Die Build-Systeme des NDK generieren standardmäßig Thumb-2-Code, sofern Sie nicht LOCAL_ARM_MODE in Ihrem Android.mk für ndk-build oder ANDROID_ARM_MODE bei der Konfiguration von CMake verwenden.
Weitere Informationen zur Geschichte von Neon finden Sie im Neon-Support.
Aus historischen Gründen wird in diesem ABI -mfloat-abi=softfp verwendet. Daher werden bei Funktionsaufrufen alle float-Werte in Ganzzahlregistern und alle double-Werte in Ganzzahlregisterpaaren übergeben. Trotz des Namens wirkt sich dies nur auf die Aufrufkonvention für Gleitkommazahlen aus: Der Compiler verwendet weiterhin Hardware-Gleitkomma-Befehle für die Arithmetik.
Diese ABI verwendet eine 64-Bit-long double (IEEE binary64, dieselbe wie double).
arm64-v8a
Dieses ABI ist für 64-Bit-ARM-CPUs vorgesehen.
Ausführliche Informationen zu den Teilen der ABI, die nicht Android-spezifisch sind, finden Sie unter Learn the Architecture von Arm. Arm bietet auch einige Portierungstipps unter 64-bit Android Development.
Sie können Neon-Intrinsics in C- und C++-Code verwenden, um die Advanced SIMD-Erweiterung zu nutzen. Weitere Informationen zu Neon-Intrinsics und zur Neon-Programmierung im Allgemeinen finden Sie im Neon Programmer's Guide for Armv8-A.
Unter Android ist das plattformspezifische x18-Register für ShadowCallStack reserviert und sollte nicht von Ihrem Code verwendet werden. In aktuellen Versionen von Clang wird standardmäßig die Option -ffixed-x18 unter Android verwendet. Wenn Sie also keinen handgeschriebenen Assembler (oder einen sehr alten Compiler) haben, müssen Sie sich darüber keine Gedanken machen.
Diese ABI verwendet eine 128‑Bit-long double (IEEE binary128).
x86
Dieses ABI ist für CPUs, die den Befehlssatz unterstützen, der allgemein als „x86“, „i386“ oder „IA-32“ bezeichnet wird.
Das ABI von Android umfasst 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 weiterhin verwenden, sofern Sie sie mithilfe der Laufzeit-Funktionserkennung aktivieren und Fallbacks für Geräte bereitstellen, die sie nicht unterstützen.
Die NDK-Toolchain geht vor einem Funktionsaufruf von einer 16‑Byte-Stapelausrichtung aus. Die Standardtools und ‑optionen erzwingen diese Regel. Wenn Sie Assembly-Code schreiben, müssen Sie darauf achten, dass die Ausrichtung des Stacks beibehalten wird und dass auch andere Compiler diese Regel einhalten.
Weitere Informationen finden Sie in den folgenden Dokumenten:
- Aufrufkonventionen für verschiedene C++-Compiler und Betriebssysteme
- Intel IA-32 Intel Architecture Software Developer's Manual, Volume 2: Instruction Set Reference
- Intel IA-32 Intel Architecture Software Developer's Manual, Volume 3: System Programming Guide
- System V Application Binary Interface: Intel386 Processor Architecture Supplement
Diese ABI verwendet eine 64-Bit-long double (IEEE binary64, dieselbe wie double und nicht die häufigere 80-Bit-long double von Intel).
x86_64
Diese ABI ist für CPUs, die den Befehlssatz unterstützen, der allgemein als „x86-64-v2“ bezeichnet wird.
Die ABI von Android umfasst den Basisbefehlssatz sowie MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2 und den POPCNT-Befehl.
Das ABI enthält keine anderen optionalen x86-64-Befehlssatzerweiterungen wie MOVBE, SHA oder eine Variante von AVX. Sie können diese Erweiterungen weiterhin verwenden, sofern Sie sie mit der Laufzeit-Funktionserkennung aktivieren und Fallbacks für Geräte bereitstellen, die sie nicht unterstützen.
Weitere Informationen finden Sie in den folgenden Dokumenten:
- Aufrufkonventionen für verschiedene C++-Compiler und Betriebssysteme
- Intel64 and IA-32 Architectures Software Developer's Manual, Volume 2: Instruction Set Reference
- Intel64 and IA-32 Intel Architecture Software Developer's Manual Volume 3: System Programming
Diese ABI verwendet eine 128‑Bit-long double (IEEE binary128).
Code für eine bestimmte ABI generieren
Gradle
Gradle erstellt standardmäßig Builds für alle nicht mehr eingestellten ABIs, unabhängig davon, ob es über Android Studio oder über die Befehlszeile verwendet wird. Verwenden Sie abiFilters, um die Menge der 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 Ihrer build.gradle fest:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
ndk-build
Mit ndk-build werden standardmäßig Builds für alle nicht mehr unterstützten ABIs erstellt. Sie können bestimmte ABIs als Ziel festlegen, indem Sie APP_ABI in der Datei Application.mk festlegen. Das folgende Snippet enthält 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 nur für eine ABI und müssen die ABI explizit angeben. Dazu verwenden Sie die Variable ANDROID_ABI, die in der Befehlszeile angegeben werden muss (sie kann nicht in Ihrer CMakeLists.txt-Datei festgelegt werden). 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 im CMake-Leitfaden.
Standardmäßig enthält das Build-System die Binärdateien für jede ABI in einem einzelnen APK, auch als Fat APK bezeichnet. Ein Fat-APK ist deutlich größer als ein APK, das nur die Binärdateien für eine einzelne ABI enthält. Der Vorteil ist eine breitere Kompatibilität, der Nachteil ein größeres APK. Es wird dringend empfohlen, entweder App Bundles oder APK-Splits zu verwenden, um die Größe Ihrer APKs zu reduzieren und gleichzeitig die maximale Gerätekompatibilität beizubehalten.
Bei der Installation entpackt der Paketmanager nur den am besten geeigneten Maschinencode für das Zielgerät. Weitere Informationen finden Sie unter Automatisches Extrahieren 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 Google Play Store als auch der Paketmanager erwarten, dass NDK-generierte Bibliotheken in Dateipfaden innerhalb der APK gefunden werden, die dem folgenden Muster entsprechen:
/lib/<abi>/lib<name>.so
Dabei ist <abi> einer der ABI-Namen, die unter Unterstützte ABIs aufgeführt sind, und <name> ist der Name der Bibliothek, wie Sie ihn für die Variable LOCAL_MODULE in der Datei Android.mk definiert haben. Da APK-Dateien nur ZIP-Dateien sind, lassen sie sich ganz einfach öffnen, um zu prüfen, ob sich die freigegebenen nativen Bibliotheken am richtigen Ort befinden.
Wenn das System die nativen gemeinsam genutzten Bibliotheken nicht dort findet, wo es sie erwartet, kann es sie nicht verwenden. In diesem Fall muss die App selbst die Bibliotheken kopieren und dann dlopen() ausführen.
In einem Fat-APK befindet sich jede Bibliothek in einem Verzeichnis, dessen Name einer entsprechenden ABI entspricht. Eine Fat-APK kann beispielsweise Folgendes enthalten:
/lib/armeabi-v7a/libfoo.so /lib/arm64-v8a/libfoo.so /lib/x86/libfoo.so /lib/x86_64/libfoo.so
Unterstützung von ABIs auf der Android-Plattform
Das Android-System weiß zur Laufzeit, welche ABIs es unterstützt, da buildspezifische Systemeigenschaften Folgendes angeben:
- Die primäre ABI für das Gerät, die dem im System-Image verwendeten Maschinencode entspricht.
- Optional: sekundäre ABIs, die anderen ABIs entsprechen, die das System-Image ebenfalls unterstützt.
Dieser Mechanismus sorgt dafür, dass das System bei der Installation den besten Maschinencode aus dem Paket extrahiert.
Sie können die Installation einer APK-Datei für eine bestimmte ABI erzwingen. Dies kann beim Testen auf Geräten hilfreich sein, die mehr als eine ABI unterstützen. Verwenden Sie den folgenden Befehl:
adb install --abi abi-identifier path_to_apk
Automatisches Extrahieren von nativem Code bei der Installation
Bei der Installation einer Anwendung scannt der Paketmanagerdienst die 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 freigegebenen Bibliotheken in folgendem Format:
lib/<secondary-abi>/lib<name>.so
Wenn der Paketmanager die gesuchten Bibliotheken findet, kopiert er sie in /lib/lib<name>.so unter dem nativen Bibliotheksverzeichnis der Anwendung (<nativeLibraryDir>/). In den folgenden Snippets wird 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 );
Wenn überhaupt keine Shared-Object-Datei vorhanden ist, wird die Anwendung erstellt und installiert, stürzt aber zur Laufzeit ab.
ARMv9: PAC und BTI für C/C++ aktivieren
Durch die Aktivierung von PAC/BTI wird Schutz vor einigen Angriffsvektoren geboten. PAC schützt Rücksprungadressen, indem sie im Prolog einer Funktion kryptografisch signiert und im Epilog geprüft werden, ob die Rücksprungadresse weiterhin korrekt signiert ist. BTI verhindert das Springen zu beliebigen Stellen in Ihrem Code, indem es erfordert, dass jedes Sprungziel eine spezielle Anweisung ist, die dem Prozessor lediglich mitteilt, dass es in Ordnung ist, dort zu landen.
Android verwendet PAC/BTI-Anweisungen, die auf älteren Prozessoren, die die neuen Anweisungen nicht unterstützen, nichts bewirken. Nur ARMv9-Geräte haben den PAC/BTI-Schutz, aber Sie können denselben Code auch auf ARMv8-Geräten ausführen. Sie benötigen also keine mehreren Varianten Ihrer Bibliothek. Auch auf ARMv9-Geräten gilt PAC/BTI nur für 64-Bit-Code.
Durch die Aktivierung von PAC/BTI erhöht sich die Codegröße geringfügig, in der Regel um 1%.
Eine detaillierte Erklärung der Angriffsvektoren, auf die PAC/BTI abzielen, und der Funktionsweise des Schutzes finden Sie im Arm-Dokument Learn the architecture - Providing protection for complex software (PDF).
Build-Änderungen
ndk-build
Legen Sie LOCAL_BRANCH_PROTECTION := standard in jedem Modul von Android.mk 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, wenn Sie für das ABI „arm64-v8a“ kompilieren. Sie müssen dieses Flag beim Verknüpfen nicht verwenden.
Fehlerbehebung
Uns sind keine Probleme mit der Compilerunterstützung für PAC/BTI bekannt. Allerdings gilt Folgendes:
- Achten Sie darauf, beim Verknüpfen keinen BTI- und Nicht-BTI-Code zu mischen, da dies zu einer Bibliothek führt, in der der BTI-Schutz nicht aktiviert ist. Mit llvm-readelf können Sie prüfen, ob die resultierende Bibliothek die BTI-Anmerkung 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
[...]
$
Ältere Versionen von OpenSSL (vor 1.1.1i) haben einen Fehler im handgeschriebenen Assembler, der PAC-Fehler verursacht. Führen Sie ein Upgrade auf die aktuelle OpenSSL-Version durch.
Bei alten Versionen einiger App-DRM-Systeme wird Code generiert, der gegen die PAC/BTI-Anforderungen verstößt. Wenn Sie App-DRM verwenden und Probleme beim Aktivieren von PAC/BTI auftreten, wenden Sie sich an Ihren DRM-Anbieter, um eine korrigierte Version zu erhalten.