ABI Android

I diversi dispositivi Android utilizzano CPU diverse, 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) della CPU che è possibile utilizzare.
  • Il livello di sicurezza delle memorie di archiviazione e caricamento in fase di runtime. Android è sempre un piccolo.
  • Convenzioni per il passaggio di dati tra le applicazioni e il sistema, compresi i vincoli di allineamento, e il modo in cui il sistema utilizza lo stack e la registrazione quando chiama le funzioni.
  • Il formato dei file binari eseguibili, come programmi e librerie condivise, e i tipi di contenuti supportati. Android utilizza sempre ELF. Per maggiori informazioni, consulta ELF System V Application Binary Interface.
  • Come vengono modificati i nomi in C++. Per maggiori informazioni, consulta ABI Generico/Itanium C++.

Questa pagina elenca le ABI supportate dall'NDK e fornisce informazioni sul funzionamento di ciascuna.

L'ABI può anche fare riferimento all'API nativa supportata dalla piattaforma. Per un elenco di questi tipi di problemi relativi alle ABI che interessano i sistemi a 32 bit, vedi Bug delle ABI a 32 bit.

ABI supportate

Tabella 1. ABI e set di istruzioni supportati.

ABI Set di istruzioni supportati Notes
armeabi-v7a
  • Armeabi
  • Pollice-2
  • Neon
  • Incompatibile con dispositivi ARMv5/v6.
    arm64-v8a
  • AArch64
  • Solo Armv8.0.
    x86
  • x86 (IA-32)
  • MMX
  • SSE/2/3
  • SSSE3
  • Nessun supporto per MOVBE o SSE4.
    x86_64
  • x86-64
  • MMX
  • SSE/2/3
  • SSSE3
  • SSE4.1, 4.2
  • POPCNT
  • x86-64-v1.

    Nota: in passato, l'NDK supportava ARMv5 (armeabi) e MIPS a 32 e 64 bit, ma il supporto per queste ABI è stato rimosso in NDK r17.

    Armeabi-v7a

    Questa ABI è per le CPU ARM a 32 bit. Sono incluse le icone Pollice-2 e Neon.

    Per informazioni sulle parti dell'ABI che non sono specifiche per Android, consulta ABI (Application Binary Interface) per l'architettura ARM

    I sistemi di build di 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 durante la configurazione di CMake.

    Per ulteriori informazioni sulla cronologia di Neon, vedi Supporto Neon.

    Per motivi storici, questa ABI utilizza -mfloat-abi=softfp, causando il trasferimento di tutti i valori float nei registri interi e di tutti i valori double in coppie di registri interi quando si effettuano chiamate di funzione. Nonostante il nome, questo influisce solo sulla convenzione di chiamata in virgola mobile: il compilatore continuerà a utilizzare istruzioni in virgola mobile hardware per l'aritmetica.

    Questa ABI utilizza un long double a 64 bit (binario IEEE 64 uguale a double).

    Arm64-v8a

    Questa ABI è per le CPU ARM a 64 bit.

    Consulta Imparare l'architettura di Arm per i dettagli completi delle parti dell'ABI non specifiche per Android. Arm offre anche alcuni consigli per il trasferimento nello sviluppo Android a 64 bit.

    Puoi usare le funzionalità intrinseche Neon nel codice C e C++ per sfruttare l'estensione SIMD avanzata. La Neon Programmer's Guide for Armv8-A fornisce ulteriori informazioni sulle funzioni intrinseche e sulla programmazione Neon in generale.

    Su Android, il registro x18 specifico della piattaforma è riservato per ShadowCallStack e non deve essere modificato dal codice. Le versioni attuali di Clang utilizzano per impostazione predefinita l'opzione -ffixed-x18 su Android, quindi, a meno che tu non abbia un assemblatore scritto a mano (o un compilatore molto obsoleto), non dovresti preoccuparti.

    Questa ABI utilizza un long double a 128 bit (binario IEEE128).

    x86

    Questa 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 oltre alle 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 utilizzare queste estensioni, a condizione che tu usi il probe di funzionalità di runtime per abilitarle e fornisci i fallback per i dispositivi che non le supportano.

    La toolchain NDK presuppone un allineamento dello stack di 16 byte prima di una chiamata di funzione. Le opzioni e gli strumenti predefiniti applicano questa regola. Se scrivi codice assembly, devi assicurarti di mantenere l'allineamento dello stack e assicurarti che anche altri compilatori rispettino questa regola.

    Per maggiori dettagli, consulta i seguenti documenti:

    Questa ABI utilizza un long double a 64 bit (binario IEEE 64 uguale a double e non il più comune long double solo per Intel a 80 bit).

    x86_64

    Questa 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 continuare a utilizzare queste estensioni, a condizione che utilizzi il probe di funzionalità di runtime per abilitarle e fornisci i fallback per i dispositivi che non le supportano.

    Per maggiori dettagli, consulta i seguenti documenti:

    Questa ABI utilizza un long double a 128 bit (binario IEEE128).

    Genera il codice per un'ABI specifica

    Gradle

    Per impostazione predefinita, Gradle (utilizzato tramite Android Studio o 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 ABI a 64 bit, imposta la seguente configurazione in build.gradle:

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

    build ndk

    per impostazione predefinita, le build ndk-build per tutte le ABI non deprecate. Puoi scegliere come target ABI specifiche 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.

    CMake

    Con CMake, crei per una singola ABI alla volta e devi specificare esplicitamente l'ABI. Per farlo, utilizza la variabile ANDROID_ABI, che deve essere specificata nella riga di comando (non può essere impostata nel tuo 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 creare con l'NDK, consulta la guida di CMake.

    Il comportamento predefinito del sistema di build è includere i programmi binari di ogni ABI in un singolo APK, noto anche come APK fat. Un APK grasso è molto più grande di uno contenente solo i programmi binari di una singola ABI; il compromesso sta ottenendo una compatibilità più ampia, ma a scapito di un APK più grande. Ti consigliamo vivamente di sfruttare gli app bundle o le suddivisioni degli APK per ridurre le dimensioni degli APK, pur mantenendo la massima compatibilità dei dispositivi.

    Al momento dell'installazione, il gestore di pacchetti decomprime solo il codice macchina più appropriato per il dispositivo di destinazione. Per maggiori dettagli, consulta Estrazione automatica del codice nativo al momento dell'installazione.

    Gestione dell'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 il Play Store che il gestore di pacchetti si aspettano di trovare librerie generate da 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 supportate, mentre <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, è facile aprirli e verificare che le librerie native condivise siano la loro posizione.

    Se il sistema non trova le librerie condivise native dove si aspetta, non può utilizzarle. In questo caso, l'app stessa deve copiare le librerie e poi 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 basati su ARMv7 che eseguono la versione 4.0.3 o versioni precedenti installano librerie native dalla directory armeabi anziché dalla directory armeabi-v7a se esistono entrambe. Questo perché /lib/armeabi/ segue /lib/armeabi-v7a/ nell'APK. Questo problema è stato risolto dalla versione 4.0.4.

    Supporto delle ABI sulla piattaforma Android

    Il sistema Android sa in fase di runtime quali ABI supporta, perché le proprietà di sistema specifiche della build indicano:

    • L'ABI principale del dispositivo, corrispondente al codice della macchina utilizzato nell'immagine di sistema stessa.
    • Facoltativamente, ABI secondarie, corrispondenti ad altre ABI supportate anche dall'immagine di sistema.

    Questo meccanismo garantisce che il sistema estragga il codice macchina migliore dal pacchetto al momento dell'installazione.

    Per ottenere prestazioni ottimali, devi compilare direttamente per l'ABI principale. Ad esempio, un tipico dispositivo basato su ARMv5TE definirebbe solo l'ABI principale: armeabi. Al contrario, un tipico dispositivo basato su ARMv7 definirebbe l'ABI principale come armeabi-v7a e quella secondaria come armeabi, dal momento che può eseguire i programmi binari nativi dell'applicazione generati per ciascuno di essi.

    I dispositivi a 64 bit supportano anche le varianti a 32 bit. Usando come esempio i dispositivi arm64-v8a, il dispositivo può eseguire anche il codice armeabi e armeabi-v7a. Tieni presente, tuttavia, che la tua applicazione avrà prestazioni molto migliori sui dispositivi a 64 bit se ha come target arm64-v8a, anziché doversi affidare al dispositivo che esegue la versione armeabi-v7a della tua applicazione.

    Molti dispositivi basati su x86 possono eseguire anche i programmi binari NDK armeabi-v7a e armeabi. Per questi dispositivi, l'ABI principale è x86 e la seconda, armeabi-v7a.

    Puoi forzare l'installazione di un APK per un'ABI specifica. È utile per i test. Usa questo comando:

    adb install --abi abi-identifier path_to_apk
    

    Estrazione automatica del codice nativo al momento dell'installazione

    Durante l'installazione di un'applicazione, il servizio di gestione 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 secondaria, il servizio analizza le librerie condivise del modulo:

    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 della libreria nativa dell'applicazione (<nativeLibraryDir>/). I seguenti snippet recuperano 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 di oggetti condivisi, l'applicazione viene creata e installata, ma si arresta in modo anomalo in fase di runtime.

    ARMv9: abilitazione di PAC e BTI per C/C++

    L'attivazione di PAC/BTI proteggerà contro alcuni vettori di attacco. PAC protegge gli indirizzi di restituzione firmandoli in modo crittografico nel prologo di una funzione e controllando che l'indirizzo di restituzione sia ancora firmato correttamente nell'epilogo. La BTI impedisce di passare a posizioni arbitrarie nel codice richiedendo che ogni ramo target sia un'istruzione speciale che non fa altro che dire al processore che può arrivare lì.

    Android utilizza istruzioni PAC/BTI che non hanno alcun effetto 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 necessari più varianti della tua libreria. Anche sui dispositivi ARMv9, PAC/BTI si applica solo al codice a 64 bit.

    L'abilitazione di PAC/BTI causerà un leggero aumento delle dimensioni del codice, in genere dell'1%.

    Consulta il documento Impara l'architettura - Fornire protezione per software complessi (PDF) di Arm per una spiegazione dettagliata dei vettori di attacco target PAC/BTI e del funzionamento della protezione.

    Modifiche alla build

    build ndk

    Imposta LOCAL_BRANCH_PROTECTION := standard in ogni modulo del tuo Android.mk.

    CMake

    Utilizza target_compile_options($TARGET PRIVATE -mbranch-protection=standard) per ogni target nel file CMakeLists.txt.

    Altri sistemi di compilazione

    Compila il codice utilizzando -mbranch-protection=standard. Questo flag funziona solo durante la compilazione dell'ABI arm64-v8a. Non è necessario utilizzare questo flag durante il collegamento.

    Risoluzione dei problemi

    Non siamo a conoscenza di eventuali problemi relativi al supporto del compilatore per PAC/BTI, ma:

    • Fai attenzione a non combinare codice BTI e non BTI durante il collegamento, perché ciò determina una libreria che non ha la protezione BTI abilitata. Puoi utilizzare llvm-readelf per verificare se la libreria risultante contiene 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 (precedenti alla 1.1.1i) hanno un bug nell'assemblatore scritto a mano che causa errori PAC. Esegui l'upgrade all'attuale OpenSSL.

    • Le versioni precedenti di alcuni sistemi DRM delle app generano un codice che viola i requisiti PAC/BTI. Se utilizzi la funzionalità DRM dell'app e riscontri problemi durante l'attivazione di PAC/BTI, contatta il tuo fornitore DRM per ricevere una versione corretta.