I diversi dispositivi Android usano CPU differenti, che a loro volta supportano set di istruzioni diversi. Ogni combinazione di CPU e set di istruzioni ha la propria Application Binary Interface (ABI). Un'ABI include le seguenti informazioni:
- Il set di istruzioni (e le estensioni) per la CPU utilizzabile.
- L'infinità dell'archiviazione e del caricamento della memoria in fase di esecuzione. Android è sempre piccolo.
- Convenzioni per il trasferimento dei dati tra le applicazioni e il sistema, inclusi i vincoli di allineamento e il modo in cui il sistema utilizza lo stack e si registra quando chiama le funzioni.
- Il formato dei programmi binari eseguibili, come i programmi e le librerie condivise, e i tipi di contenuti che supportano. Android usa sempre ELF. Per ulteriori informazioni, consulta la pagina ELF System V Application Binary Interface.
- Come sono stravolti i nomi di C++. Per ulteriori informazioni, consulta la pagina Generico/Itanium C++ ABI.
Questa pagina elenca gli ABI supportati dall'NDK e fornisce informazioni sul funzionamento di ogni ABI.
ABI può anche fare riferimento all'API nativa supportata dalla piattaforma. Per un elenco di questi tipi di problemi ABI che interessano i sistemi a 32 bit, consulta la pagina sui bug ABI a 32 bit.
ABI supportate
Tabella 1. ABI e set di istruzioni supportati.
ABI | Set di istruzioni supportati | Note |
---|---|---|
armeabi-v7a |
|
Non compatibile con i dispositivi ARMv5/v6. |
arm64-v8a |
Solo Armv8.0. | |
x86 |
Nessun supporto per MOVBE o SSE4. | |
x86_64 |
|
Solo x86-64-v1. |
Nota: in passato l'NDK supportava ARMv5 (armeabi) e MIPS a 32 e 64 bit, ma il supporto per questi ABI era stato rimosso nell'NDK r17.
armeabi-v7a
Questo ABI è per CPU ARM a 32 bit. Include le istruzioni in virgola mobile hardware Thumb-2 e Neon (VFP), in particolare VFPv3-D16 con 16 registri a virgola mobile dedicati a 64 bit.
Per informazioni sulle parti dell'ABI non specifiche per Android, consulta la pagina relativa all'ABI (Application Binary Interface) per l'architettura ARM
I sistemi di build dell'NDK generano il codice Thumb-2 per impostazione predefinita, a meno che non utilizzi LOCAL_ARM_MODE
in Android.mk
per ndk-build o ANDROID_ARM_MODE
quando configuri CMake.
Altre estensioni, tra cui Advanced SIMD (Neon) e VFPv3-D32, sono facoltative. Per ulteriori informazioni, vedi Assistenza Neon.
Questa ABI utilizza -mfloat-abi=softfp
per applicare la regola che il compilatore deve passare tutti i valori float
nei registri interi e tutti i valori double
nelle coppie di registri interi quando effettuano chiamate a funzioni. Questo influisce solo sulla convenzione di chiamata. Il compilatore continuerà a utilizzare le istruzioni in virgola mobile dell'hardware.
Questa ABI utilizza una long double
a 64 bit (IEEE binario64 come double
).
arm64-v8a
Questo ABI è per CPU ARM a 64 bit.
Consulta il corso Impara l'architettura di Arm per dettagli completi sulle parti dell'ABI che non sono specifiche di Android. Arm offre anche alcuni consigli di trasferimento sullo sviluppo Android a 64 bit.
Puoi utilizzare Neon Intrinsics nel codice C e C++ per sfruttare l'estensione SIMD avanzata. La Guida ai programmi per neon per Armv8-A fornisce ulteriori informazioni sugli aspetti al neon e sulla programmazione al neon in generale.
Su Android, il registro x18 specifico della piattaforma è riservato a ShadowCallStack e non deve essere toccato dal codice. Le versioni attuali di Clang utilizzano per impostazione predefinita l'opzione -ffixed-x18
su Android, quindi non devi preoccuparti di questo assemblatore scritto a mano (o di un compilatore precedente).
Questa ABI utilizza una long double
a 128 bit (IEEE binario128).
x86
Questo ABI è per le CPU che supportano il set di istruzioni comunemente noto come "x86", "i386" o "IA-32".
L'ABI di Android include il set di istruzioni di base più le estensioni MMX, SSE, SSE2, SSE3 e SSSE3.
L'ABI non include altre estensioni facoltative del set di istruzioni IA-32, come MOVBE o qualsiasi variante di SSE4. Puoi comunque usare queste estensioni, purché tu voglia usare le funzionalità di runtime del runtime per attivarle e fornire funzionalità di riserva per i dispositivi che non le supportano.
La toolchain NDK presuppone l'allineamento dello stack a 16 byte prima di una chiamata funzione. Gli strumenti e le opzioni predefiniti applicano questa regola. Se stai scrivendo codice di assemblaggio, devi assicurarti di mantenere l'allineamento dello stack e assicurarti che anche altri compilatori rispettino questa regola.
Per maggiori dettagli, consulta i seguenti documenti:
- Chiamate per le chiamate a diversi sistemi operativi e compilatori C++
- Manuale dello sviluppatore del software Intel Architecture IA-32, Volume 2: Riferimenti del set di istruzioni
- Manuale per gli sviluppatori di software per l'architettura Intel Intel-IA-32, Volume 3: Guida alla programmazione del sistema
- Interfaccia binaria dell'applicazione V di sistema: integrazione dell'architettura processore Intel386
Questa ABI utilizza una long double
a 64 bit (IEEE binario64 uguale a double
e non il più comune
long double
a 80 bit solo Intel).
x86_64
Questo ABI è per le CPU che supportano il set di istruzioni comunemente indicato come "x86-64".
L'ABI di Android include il set di istruzioni di base più MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2 e l'istruzione POPCNT.
L'ABI non include altre estensioni facoltative del set di istruzioni x86-64, come MOVBE, SHA o qualsiasi variante di AVX. Puoi comunque usare queste estensioni, a condizione che usi la verifica delle funzionalità di runtime per attivarle e fornire risorse di riserva per i dispositivi che non le supportano.
Per maggiori dettagli, consulta i seguenti documenti:
- Convenzioni di chiamata per compilatori C++ e sistemi operativi diversi
- Manuale per gli sviluppatori di architetture software di Intel64 e IA-32, volume 2: set di istruzioni Riferimento
- Manuale per gli sviluppatori di software per architettura Intel64 e IA-32 Volume 3: Programmazione del sistema
Questa ABI utilizza una long double
a 128 bit (IEEE binario128).
Genera il codice per una specifica ABI
Gradle
Per impostazione predefinita, Gradle (sia utilizzato tramite Android Studio sia dalla riga di comando) crea per tutte le ABI non deprecate. Per limitare l'insieme di ABI supportate dalla tua applicazione, utilizza abiFilters
. Ad esempio, per creare solo per le ABI a 64 bit, imposta la seguente configurazione in build.gradle
:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'x86_64'
}
}
}
build-ndk
Per impostazione predefinita, crea ndk-build per tutti gli ABI non deprecati. Puoi scegliere come target uno specifico ABI impostando APP_ABI
nel file Application.mk. Il seguente snippet mostra alcuni esempi di utilizzo di 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.
Per ulteriori informazioni sui valori che puoi specificare per APP_ABI
, consulta
Application.mk.
C-Make
Con CMake, crei una ABI alla volta e devi specificare esplicitamente l'ABI. A tale scopo, utilizza la variabile ANDROID_ABI
, che deve essere specificata nella riga di comando (non può essere impostata in CMakeLists.txt). Ad esempio:
$ cmake -DANDROID_ABI=arm64-v8a ...
$ cmake -DANDROID_ABI=armeabi-v7a ...
$ cmake -DANDROID_ABI=x86 ...
$ cmake -DANDROID_ABI=x86_64 ...
Per gli altri flag che devono essere passati a CMake per essere creati con NDK, consulta la guida a CMake.
Il comportamento predefinito del sistema di build consiste nell'includere i programmi binari per ogni ABI in un singolo APK, noto anche come APK grasso. Un APK grasso è significativamente più grande di uno contenente solo i programmi binari per una singola ABI; il compromesso sta ottenendo una compatibilità più ampia, ma a scapito di un APK più grande. Ti consigliamo vivamente di utilizzare gli app bundle o le suddivisioni dell'APK per ridurre le dimensioni degli APK pur mantenendo la massima compatibilità del dispositivo.
Al momento dell'installazione, il gestore di pacchetti decompone solo il codice della macchina più appropriato per il dispositivo di destinazione. Per maggiori dettagli, consulta Estrazione automatica di codice nativo al momento dell'installazione.
Gestione ABI sulla piattaforma Android
Questa sezione fornisce dettagli su come la piattaforma Android gestisce il codice nativo negli APK.
Codice nativo nei pacchetti di app
Sia Play Store sia Package Manager prevedono di trovare librerie generate dall'NDK sui percorsi dei file all'interno dell'APK che corrispondono al seguente pattern:
/lib/<abi>/lib<name>.so
Qui, <abi>
è uno dei nomi ABI elencati in ABI supportati,
e <name>
è il nome della libreria come lo hai definito per la variabile LOCAL_MODULE
nel file Android.mk
. Poiché i file APK sono solo file ZIP, è banale aprirli e confermare che le librerie native condivise sono la loro posizione.
Se il sistema non trova le librerie condivise native dove previsto, non può utilizzarle. In questo caso, l'app deve copiare le librerie e quindi eseguire dlopen()
.
In un APK grasso, ogni libreria si trova in una directory il cui nome corrisponde a un ABI corrispondente. Ad esempio, un APK grasso può contenere:
/lib/armeabi/libfoo.so /lib/armeabi-v7a/libfoo.so /lib/arm64-v8a/libfoo.so /lib/x86/libfoo.so /lib/x86_64/libfoo.so
Nota: i dispositivi Android con ARMv7 che eseguono la versione 4.0.3 o versioni precedenti installano le librerie native dalla directory armeabi
anziché dalla directory armeabi-v7a
, se esistono entrambe le directory. Questo perché la proprietà /lib/armeabi/
è successiva a /lib/armeabi-v7a/
nell'APK. Questo problema è stato risolto dalla 4.0.4.
Supporto ABI della piattaforma Android
Il sistema Android riconosce in fase di esecuzione quali ABI supporta, perché le proprietà di sistema specifiche della build indicano:
- L'ABI principale del dispositivo, corrispondente al codice macchina utilizzato nell'immagine di sistema stessa.
- Facoltativamente, le ABI secondarie, corrispondenti ad altre ABI supportate anche dall'immagine di sistema.
Questo meccanismo assicura che il sistema estragga il codice macchina migliore dal pacchetto al momento dell'installazione.
Per ottenere prestazioni ottimali, devi compilare direttamente per la ABI principale. Ad esempio, un dispositivo tipico basato su ARMv5TE definisce solo l'ABI principale: armeabi
. Al contrario, un dispositivo tipico basato su ARMv7 definirà l'ABI principale come armeabi-v7a
e quello secondario come armeabi
, poiché può eseguire programmi binari dell'applicazione generati per ognuno di essi.
I dispositivi a 64 bit supportano anche le varianti a 32 bit. Utilizzando i dispositivi arm64-v8a, ad esempio, può eseguire anche il codice armeabi e armeabi-v7a. Tieni presente che l'applicazione avrà prestazioni migliori sui dispositivi a 64 bit se ha come target arm64-v8a anziché affidarsi alla versione armeabi-v7a dell'applicazione.
Molti dispositivi basati su x86 possono anche eseguire programmi binari NDK armeabi-v7a
e armeabi
. Per tali dispositivi, l'ABI principale sarebbe x86
e il secondo armeabi-v7a
.
Puoi forzare l'installazione di un APK per una specifica ABI. È utile per i test. Utilizza il comando seguente:
adb install --abi abi-identifier path_to_apk
Estrazione automatica del codice nativo al momento dell'installazione
Quando installi un'applicazione, il servizio del gestore di pacchetti analizza l'APK e cerca eventuali librerie condivise nel formato:
lib/<primary-abi>/lib<name>.so
Se non ne trova nessuno e hai definito un ABI secondario, il servizio cerca le librerie condivise nel formato:
lib/<secondary-abi>/lib<name>.so
Quando trova le librerie che sta cercando, il gestore di pacchetti le copia in /lib/lib<name>.so
, nella directory delle librerie native dell'applicazione (<nativeLibraryDir>/
). I seguenti snippet recuperano le 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 );
Se non è presente alcun file oggetto condiviso, l'applicazione crea e installa, ma si arresta in modo anomalo in fase di esecuzione.
ARMv9: abilitazione PAC e BTI per C/C++
L'attivazione di PAC/BTI fornirà protezione contro alcuni vettori di attacco. PAC protegge gli indirizzi di ritorno registrandoli crittograficamente nel prolog di una funzione e verificando che l'indirizzo di restituzione sia ancora correttamente inserito nell'epilog. BTI impedisce di passare a posizioni arbitrarie nel codice richiedendo che ogni ramo target sia un'istruzione speciale che non fa altro che comunicare al processore che è possibile accedervi.
Android utilizza le istruzioni PAC/BTI che non fanno nulla sui processori meno recenti che non supportano le nuove istruzioni. Solo i dispositivi ARMv9 avranno la protezione PAC/BTI, ma puoi eseguire lo stesso codice anche sui dispositivi ARMv8: non sono necessarie più varianti della libreria. Anche su dispositivi ARMv9, PAC/BTI si applica solo al codice a 64 bit.
L'attivazione di PAC/BTI causerà un leggero aumento delle dimensioni del codice, in genere dell'1%.
Consulta Impara l'architettura - Fornire la protezione per il software complesso (PDF) di Arm per una spiegazione dettagliata del target PAC/BTI dei vettori di attacco e come funziona la protezione.
Modifiche alla build
build-ndk
Imposta LOCAL_BRANCH_PROTECTION := standard
in ogni modulo del tuo file Android.mk.
C-Make
Utilizza target_compile_options($TARGET PRIVATE -mbranch-protection=standard)
per ogni destinazione nel file CMakeLists.txt.
Altri sistemi di compilazione
Compila il tuo codice utilizzando -mbranch-protection=standard
. Questo flag funziona solo durante la compilazione per l'ABI arm64-v8a. Non è necessario utilizzare questo flag
al momento del collegamento.
Risoluzione dei problemi
Non siamo a conoscenza di problemi con il supporto del compilatore per PAC/BTI, ma:
- Fai attenzione a non combinare codice BTI e non BTI durante il collegamento, perché viene creata una libreria per cui non è abilitata la protezione BTI. Puoi utilizzare llvm-readelf per verificare se la tua libreria risultante ha o meno la nota 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 [...] $
Le versioni precedenti di OpenSSL (prima della 1.1.1i) presentano un bug nell'assemblatore scritto a mano che causa errori PAC. Esegui l'upgrade al sistema OpenSSL attuale.
Le versioni precedenti di alcuni sistemi DRM per app generano codice che viola i requisiti PAC/BTI. Se utilizzi l'app DRM e riscontri problemi quando attivi PAC/BTI, contatta il tuo fornitore DRM per una versione corretta.