Unterschiedliche Android-Geräte verwenden unterschiedliche CPUs, die wiederum unterschiedliche Befehlssätze unterstützen. Jede Kombination aus CPU und Anweisungssatz hat eine eigene Binärschnittstelle (Application Binary Interface, ABI). Ein ABI enthält die folgenden Informationen:
- Der CPU-Befehlssatz (und die Erweiterungen), die verwendet werden können.
- Das Endianness-Format des Arbeitsspeichers, das zur Laufzeit gespeichert und geladen wird. Android ist immer Little-Endian.
- Konventionen für die Weitergabe 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 von ihnen unterstützten Inhaltstypen. Android verwendet immer ELF. Weitere Informationen finden Sie unter ELF System V Application Binary Interface.
- Wie C++-Namen umgeschrieben werden. Weitere Informationen finden Sie unter Generisch/Itanium C++ ABI.
Auf dieser Seite werden die vom NDK unterstützten ABIs aufgelistet und 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 Arten von ABI-Problemen, die sich auf 32-Bit-Systeme auswirken, finden Sie unter 32-Bit-ABI-Fehler.
Unterstützte ABIs
ABI | Unterstützte Befehlssätze | Hinweise |
---|---|---|
armeabi-v7a |
|
Nicht mit ARMv5/v6-Geräten kompatibel. |
arm64-v8a |
Nur Armv8.0. | |
x86 |
Keine Unterstützung für MOVBE oder SSE4. | |
x86_64 |
|
Vollständige x86-64-v1, aber nur teilweise x86-64-v2 (keine LAHF-SAHF). |
Hinweis:Bisher unterstützte das NDK ARMv5 (armeabi) sowie 32-Bit- und 64-Bit-MIPS. Die Unterstützung für diese ABIs wurde jedoch in NDK r17 entfernt.
armeabi-v7a
Diese ABI gilt für 32-Bit-ARM-CPUs. Dazu gehören Thumb-2 und Neon.
Informationen zu den Teilen des ABI, die nicht Android-spezifisch sind, finden Sie 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 Ihrer Android.mk
für ndk-build oder ANDROID_ARM_MODE
beim Konfigurieren von CMake.
Weitere Informationen zur Geschichte von Neon finden Sie im Neon-Support.
Aus historischen Gründen wird in diesem ABI -mfloat-abi=softfp
verwendet, wodurch alle float
-Werte bei Funktionsaufrufen in Ganzzahlregistern und alle double
-Werte in Ganzzahlregisterpaaren übergeben werden. Trotz des Namens wirkt sich dies nur auf die Aufrufkonvention für Gleitkommazahlen aus: Der Compiler verwendet weiterhin Hardware-Gleitkommaanweisungen für die Arithmetik.
Diese ABI verwendet eine 64‑Bit-long double
(IEEE binary64, entspricht double
).
arm64-v8a
Dieses ABI ist für 64-Bit-ARM-CPUs.
Ausführliche Informationen zu den nicht Android-spezifischen Teilen des ABI finden Sie im Arm-Artikel Die Architektur kennenlernen. Arm bietet auch einige Tipps zur Portierung unter 64-Bit-Android-Entwicklung.
Sie können Neon-Intrinsiken in C- und C++-Code verwenden, um die Vorteile der erweiterten SIMD-Erweiterung zu nutzen. Im Neon-Programmierhandbuch für Armv8-A finden Sie weitere Informationen zur Neon-Intrinsik und zur Neon-Programmierung im Allgemeinen.
Unter Android ist das plattformspezifische Register x18 für ShadowCallStack reserviert und darf nicht von Ihrem Code verändert werden. Aktuelle Versionen von Clang verwenden standardmäßig die Option -ffixed-x18
unter Android. Wenn Sie also keinen handgeschriebenen Assembler (oder einen sehr alten Compiler) verwenden, müssen Sie sich darüber keine Gedanken machen.
Diese ABI verwendet eine 128-Bit-long double
(IEEE-Binärprogramm128).
x86
Dieses ABI ist für CPUs gedacht, 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-Instruction-Set-Erweiterungen wie MOVBE oder eine Variante von SSE4. Sie können diese Erweiterungen weiterhin verwenden, sofern Sie sie mithilfe von Laufzeitfunktionstests aktivieren und Fallbacks für Geräte bereitstellen, die sie nicht unterstützen.
Die NDK-Toolchain geht vor einem Funktionsaufruf von einer 16-Byte-Stack-Ausrichtung aus. Diese Regel wird von den Standardtools und -optionen erzwungen. Wenn Sie Assemblycode schreiben, müssen Sie darauf achten, dass der Stack ausgerichtet ist, und dafür sorgen, 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, identisch mit double
, und nicht die gängigere 80‑Bit-long double
von Intel).
x86_64
Dieses ABI ist für CPUs gedacht, 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 Erweiterungen des x86-64-Instruction-Sets wie MOVBE, SHA oder eine Variante von AVX. Sie können diese Erweiterungen weiterhin verwenden, sofern Sie sie mithilfe der Funktion „Runtime-Feature-Probe“ 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 Handbuch, Volume 2: Instruction Set Reference
- Intel64 and IA-32 Intel Architecture Software Developer's Handbuch Volume 3: Systemprogramming
Diese ABI verwendet eine 128-Bit-long double
(IEEE binary128).
Code für ein bestimmtes ABI generieren
Gradle
Gradle (ob über Android Studio oder über die Befehlszeile verwendet) erstellt standardmäßig für alle nicht verworfenen ABIs. Verwenden Sie abiFilters
, um die von Ihrer Anwendung unterstützten ABIs einzuschränken. Wenn Sie beispielsweise nur für 64-Bit-ABIs erstellen möchten, legen Sie in build.gradle
die folgende Konfiguration fest:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
NK-Build
ndk-build erstellt standardmäßig Builds für alle nicht eingestellten ABIs. Sie können eine bestimmte ABI ansteuern, indem Sie APP_ABI
in der Datei Application.mk festlegen. 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 können Sie jeweils nur für ein einzelnes ABI erstellen und müssen es explizit angeben. Dazu verwenden Sie die Variable ANDROID_ABI
, die in der Befehlszeile angegeben werden muss (kann nicht in CMakeLists.txt 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 CMake übergeben werden müssen, um mit dem NDK zu erstellen, finden Sie im CMake-Leitfaden.
Das Standardverhalten des Build-Systems besteht darin, die Binärdateien für jede ABI in ein einzelnes APK aufzunehmen, das auch als Fat-APK bezeichnet wird. Ein Fat-APK ist deutlich größer als ein APK, das nur die Binärdateien für ein einzelnes ABI enthält. Der Vorteil ist eine größere Kompatibilität, aber auf Kosten eines größeren APK. 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 beizubehalten.
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 erfahren Sie, wie die Android-Plattform nativen Code in APKs verwaltet.
Nativer Code in App-Paketen
Sowohl der 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
Hier ist <abi>
einer der ABI-Namen, die unter Unterstützte ABIs aufgeführt sind, und <name>
ist der Name der Bibliothek, den Sie für die Variable LOCAL_MODULE
in der Datei Android.mk
definiert haben. Da APK-Dateien nur ZIP-Dateien sind, können Sie sie ganz einfach öffnen und prüfen, ob sich die freigegebenen nativen Bibliotheken an der richtigen Stelle befinden.
Wenn das System die nativen gemeinsam genutzten Bibliotheken nicht dort findet, wo es erwartet wird, 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 mit einem entsprechenden ABI übereinstimmt. Ein FAT-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:Auf ARMv7-basierten Android-Geräten mit Android 4.0.3 oder niedriger werden native Bibliotheken aus dem Verzeichnis armeabi
anstelle des Verzeichnisses armeabi-v7a
installiert, wenn beide Verzeichnisse vorhanden sind. Das liegt daran, dass /lib/armeabi/
im APK nach /lib/armeabi-v7a/
kommt. Dieses Problem wurde in Version 4.0.4 behoben.
Unterstützung von Android-Plattform-ABIs
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 einem anderen ABI entsprechen, das auch vom System-Image unterstützt wird.
Dieser Mechanismus sorgt dafür, dass das System bei der Installation den besten Maschinencode aus dem Paket extrahiert.
Für eine optimale Leistung sollten Sie direkt für das primäre ABI kompilieren. Ein typisches ARMv5TE-basiertes Gerät würde beispielsweise nur die primäre ABI definieren: armeabi
. Ein typisches ARMv7-basiertes Gerät würde dagegen das primäre ABI als armeabi-v7a
und das sekundäre als armeabi
definieren, da es für jedes von ihnen generierte native Binärdateien ausführen kann.
64-Bit-Geräte unterstützen auch ihre 32-Bit-Varianten. Anhand von ARM64-V8A-Geräten können wir beispielsweise sagen, dass das Gerät auch armeabi- und armeabi-v7a-Code ausführen kann. Die Leistung Ihrer Anwendung auf 64-Bit-Geräten ist jedoch viel besser, wenn sie auf arm64-v8a ausgerichtet ist, anstatt darauf zu vertrauen, dass auf dem Gerät die armeabi-v7a-Version Ihrer Anwendung ausgeführt wird.
Viele x86-basierte Geräte können auch armeabi-v7a
- und armeabi
-NDK-Binärdateien ausführen. Für solche Geräte wäre x86
die primäre ABI und armeabi-v7a
die sekundäre.
Sie können eine APK für eine bestimmte ABI erzwingen. Das ist für Tests nützlich. Verwenden Sie den folgenden Befehl:
adb install --abi abi-identifier path_to_apk
Automatische Extraktion von nativem Code bei der Installation
Bei der Installation einer App scannt der Paketmanagerdienst das APK und sucht nach freigegebenen Bibliotheken in folgendem Format:
lib/<primary-abi>/lib<name>.so
Wenn keine gefunden wird und Sie eine sekundäre ABI definiert haben, sucht der Dienst nach freigegebenen Bibliotheken vom Typ:
lib/<secondary-abi>/lib<name>.so
Wenn die gesuchten Bibliotheken gefunden werden, kopiert der Paketmanager sie in das /lib/lib<name>.so
-Verzeichnis des nativen Bibliotheksverzeichnisses der Anwendung (<nativeLibraryDir>/
). Die folgenden Snippets rufen das nativeLibraryDir
ab:
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 keine Shared Object-Datei vorhanden ist, wird die Anwendung zwar erstellt und installiert, stürzt aber bei der Laufzeit ab.
ARMv9: PAC und BTI für C/C++ aktivieren
Wenn Sie PAC/BTI aktivieren, sind Sie vor einigen Angriffsvektoren geschützt. PAC schützt Rückgabeadressen, indem sie im Prolog einer Funktion kryptografisch signiert und geprüft wird, ob die Rückgabeadresse im Epilog noch korrekt signiert ist. BTI verhindert das Springen zu beliebigen Stellen im Code, da jedes Verzweigungsziel eine spezielle Anweisung ist, die dem Prozessor lediglich mitteilt, dass er dort landen darf.
Android verwendet PAC/BTI-Anweisungen, die auf älteren Prozessoren, die die neuen Anweisungen nicht unterstützen, keine Aktion ausführen. Nur ARMv9-Geräte bieten den PAC-/BTI-Schutz. Sie können denselben Code aber auch auf ARMv8-Geräten ausführen. Es sind also keine mehreren Varianten Ihrer Bibliothek erforderlich. Auch auf ARMv9-Geräten gilt PAC/BTI nur für 64-Bit-Code.
Wenn Sie PAC/BTI aktivieren, erhöht sich die Codegröße in der Regel um 1%.
Eine detaillierte Erklärung der Angriffsvektoren für PAC-/BTI-Ziele und der Funktionsweise des Schutzes finden Sie im Arm-Whitepaper Architektur kennenlernen – Schutz für komplexe Software bereitstellen (PDF).
Änderungen am Build
ndk-build
Legen Sie in jedem Modul Ihrer Android.mk-Datei LOCAL_BRANCH_PROTECTION := standard
fest.
CMake
Verwenden Sie target_compile_options($TARGET PRIVATE -mbranch-protection=standard)
für jedes Ziel in CMakeLists.txt.
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 nicht verwenden, wenn Sie eine Verknüpfung herstellen.
Fehlerbehebung
Uns sind keine Probleme mit der Compilerunterstützung für PAC/BTI bekannt. Beachten Sie jedoch Folgendes:
- Achten Sie beim Verknüpfen darauf, BTI-Code nicht mit Code zu mischen, der nicht BTI-fähig ist. Andernfalls wird eine Bibliothek erstellt, für die der BTI-Schutz nicht aktiviert ist. Mit llvm-readelf können Sie prüfen, ob die resultierende Bibliothek die 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 [...] $
Ältere OpenSSL-Versionen (vor 1.1.1i) enthalten einen Fehler im handgeschriebenen Assembler, der zu PAC-Fehlern führt. Führen Sie ein Upgrade auf die aktuelle OpenSSL-Version durch.
Ältere Versionen einiger DRM-Systeme für Apps generieren Code, der gegen die PAC/BTI-Anforderungen verstößt. Wenn Sie die DRM von Apps verwenden und beim Aktivieren von PAC/BTI Probleme auftreten, wenden Sie sich an Ihren DRM-Anbieter, um eine korrigierte Version zu erhalten.