ABI Android

I diversi dispositivi Android utilizzano CPU diverse, che a loro volta supportano diversi set di istruzioni. Ogni combinazione di CPU e set di istruzioni ha la propria ABI (Application Binary Interface). Un ABI include le seguenti informazioni:

  • Il set di istruzioni della CPU (e le estensioni) che è possibile utilizzare.
  • L'endianità degli archivi e dei caricamenti della memoria in fase di runtime. Android è sempre little-endian.
  • Convenzioni per il passaggio di dati tra le applicazioni e il sistema, inclusi i vincoli di allineamento e il modo in cui il sistema utilizza lo stack e le registrazioni quando chiama le funzioni.
  • Il formato dei file eseguibili binari, come programmi e librerie condivise, e i tipi di contenuti supportati. Android utilizza sempre ELF. Per ulteriori informazioni, consulta la sezione ELF System V Application Binary Interface.
  • Come vengono alterati i nomi C++. Per ulteriori informazioni, consulta Generic/Itanium C++ ABI.

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

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

ABI supportate

Tabella 1. ABI e set di istruzioni supportati.

ABI Set di istruzioni supportati Note
armeabi-v7a
  • Armeabi
  • Pollice-2
  • Neon
  • Non compatibile con i 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
  • CMPXCHG16B
  • x86-64-v1 completa, ma solo x86-64-v2 parziale (non LAHF-SAHF).

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

    Armeabi-V7a

    Questo ABI è destinato alle CPU ARM a 32 bit. Sono inclusi Pollice-2 e Neon.

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

    I sistemi di compilazione dell'NDK generano 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, visita la pagina Assistenza Neon.

    Per motivi storici, questa ABI utilizza -mfloat-abi=softfp e causa il passaggio di tutti i valori float in 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 userà comunque le istruzioni hardware in virgola mobile per l'aritmetica.

    Questa ABI utilizza un long double a 64 bit (IEEE binary64, lo stesso di double).

    arm64-v8a

    Questo ABI è destinato alle CPU ARM a 64 bit.

    Consulta la pagina Scopri l'architettura di Arm per informazioni complete sulle parti dell'ABI non specifiche per Android. Arm offre anche alcuni consigli per il porting in Sviluppo Android a 64 bit.

    Puoi utilizzare le istruzioni intrinseche Neon nel codice C e C++ per sfruttare l'estensione SIMD avanzata. La Guida per programmatori Neon per 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. Per impostazione predefinita, le versioni attuali di Clang utilizzano l'opzione -ffixed-x18 su Android, quindi, a meno che tu non abbia assemblato manualmente (o un compilatore molto vecchio), non dovresti preoccuparti di questo.

    Questa ABI utilizza un long double a 128 bit (IEEE binary128).

    x86

    Questo ABI è destinato alle CPU che supportano l'insieme di istruzioni comunemente noto come "x86", "i386" o "IA-32".

    L'ABI di Android include l'insieme 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 utilizzare queste estensioni, a condizione di utilizzare il rilevamento delle funzionalità di runtime per attivarle e di fornire soluzioni alternative per i dispositivi che non le supportano.

    La toolchain NDK presuppone l'allineamento dello stack di 16 byte prima di una chiamata di funzione. Gli strumenti e le opzioni predefinite applicano questa regola. Se stai scrivendo codice assembly, devi assicurarti di mantenere l'allineamento della pila e di fare in modo che anche gli altri compilatori rispettino questa regola.

    Per ulteriori dettagli, consulta i seguenti documenti:

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

    x86_64

    Questo ABI è destinato alle CPU che supportano l'insieme di istruzioni comunemente noto come "x86-64".

    L'ABI di Android include l'insieme 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 utilizzare queste estensioni, a condizione di utilizzare il rilevamento delle funzionalità di runtime per attivarle e di fornire soluzioni alternative per i dispositivi che non le supportano.

    Per ulteriori dettagli, consulta i seguenti documenti:

    Questa ABI utilizza un long double a 128 bit (IEEE binary128).

    Genera codice per un ABI specifico

    Gradle

    Per impostazione predefinita, Gradle (utilizzato tramite Android Studio o dalla riga di comando) esegue build per tutti gli ABI non ritirati. Per limitare l'insieme di ABI supportati dalla tua applicazione, utilizza abiFilters. Ad esempio, per eseguire la compilazione 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, ndk-build esegue build per tutte le ABI non ritirate. Puoi scegliere come target ABI specifici impostando APP_ABI nel file Application.mk. Lo snippet seguente 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.

    Marca

    Con CMake, esegui la compilazione per un'unica ABI alla volta e devi specificare esplicitamente l'ABI. A tale scopo, utilizza la variabile ANDROID_ABI, che deve essere specificata sulla riga di comando (non può essere impostata in CMakeLists.txt). Per 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 la compilazione con l'NDK, consulta la guida di CMake.

    Il comportamento predefinito del sistema di compilazione è includere i binari per ogni ABI in un singolo APK, noto anche come APK fat. Un APK fat è molto più grande di uno che contiene solo i binari per un singolo ABI. Il compromesso è una maggiore compatibilità, ma a spese di un APK più grande. Ti consigliamo vivamente di utilizzare gli app bundle o le divisioni dell'APK per ridurre le dimensioni degli APK mantenendo comunque la massima compatibilità dei dispositivi.

    Al momento dell'installazione, il gestore dei pacchetti estrae 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 delle 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 sia il gestore di pacchetti si aspettano di trovare le librerie generate da NDK nei percorsi dei file all'interno dell'APK corrispondenti al seguente pattern:

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

    Qui, <abi> è uno dei nomi ABI elencati in ABI supportati e <name> è il nome della libreria così come 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 al loro posto.

    Se il sistema non trova le librerie condivise native dove si aspetta, non può usarle. In questo caso, l'app stessa deve copiare le librerie e poi eseguire dlopen().

    In un APK fat, ogni libreria si trova in una directory il cui nome corrisponde a un ABI corrispondente. Ad esempio, un APK grande 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 con la versione 4.0.3 o precedente installano le librerie native dalla directory armeabi anziché dalla directory armeabi-v7a se esistono entrambe le directory. Questo perché /lib/armeabi/ segue /lib/armeabi-v7a/ nell'APK. Il problema è stato risolto dalla versione 4.0.4.

    Supporto ABI della piattaforma Android

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

    • L'ABI principale per il dispositivo, corrispondente al codice macchina utilizzato nell'immagine di sistema stessa.
    • Facoltativamente, ABI secondari corrispondenti ad altri ABI supportati anche dall'immagine di sistema.

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

    Per ottenere le migliori prestazioni, devi compilare direttamente per l'ABI principale. Ad esempio, un tipico dispositivo basato su ARMv5TE definisce solo l'ABI principale: armeabi. Al contrario, un tipico dispositivo basato su ARMv7 definirebbe l'ABI principale come armeabi-v7a e quello secondario come armeabi, poiché può eseguire i binari nativi dell'applicazione generati per ciascuno di essi.

    I dispositivi a 64 bit supportano anche le varianti a 32 bit. Utilizzando i dispositivi arm64-v8a come esempio, il dispositivo può anche eseguire codice armeabi e armeabi-v7a. Tieni tuttavia presente che l'applicazione avrà un rendimento molto migliore sui dispositivi a 64 bit se ha come target arm64-v8a anziché fare affidamento sul dispositivo che esegue la versione armeabi-v7a della tua applicazione.

    Molti dispositivi basati su x86 possono anche eseguire i binari NDK armeabi-v7a e armeabi. Per questi dispositivi, l'ABI principale sarà x86 e il secondo armeabi-v7a.

    Puoi forzare l'installazione di un APK per un ABI specifico. Questa opzione è utile per i test. Utilizza 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 dei pacchetti analizza l'APK e cerca eventuali librerie condivise del modulo:

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

    Se non ne viene trovata alcuna e hai definito un'ABI secondaria, il servizio analizza le librerie condivise nel formato:

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

    Quando trova le librerie che cerca, il gestore del pacchetto 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 compilata e installata, ma si arresta in modo anomalo in fase di esecuzione.

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

    L'attivazione di PAC/BTI fornisce protezione contro alcuni vettori di attacco. Il PAC protegge gli indirizzi di ritorno sottoscrivendoli in modo crittografico nel prelogo di una funzione e controllando che l'indirizzo di ritorno sia ancora correttamente firmato nel post-epilogo. BTI impedisce di saltare a posizioni arbitrarie nel codice richiedendo che ogni target di ramo sia un'istruzione speciale che non fa altro che dire al processore che è consentito eseguire il salto lì.

    Android utilizza 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 è necessaria la presenza di più varianti della raccolta. Anche sui 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 la pagina Scopri l'architettura: fornire protezione per software complesso (PDF) di Arm per una spiegazione dettagliata dei vettori di attacco target PAC/BTI e di come funziona la protezione.

    Modifiche alla compilazione

    ndk-build

    Imposta LOCAL_BRANCH_PROTECTION := standard in ogni modulo di Android.mk.

    Marca

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

    Altri sistemi di compilazione

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

    Risoluzione dei problemi

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

    • Fai attenzione a non combinare il codice BTI con quello non BTI durante il collegamento, perché in questo modo la libreria non avrà 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) presentano un bug nell'assembler scritto a mano che causa errori PAC. Esegui l'upgrade all'attuale versione di OpenSSL.

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