ABI Android

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
  • Armeabi
  • Mi piace-2
  • VFPv3-D16
  • Non compatibile con i dispositivi ARMv5/v6.
    arm64-v8a
  • AArch64
  • Solo Armv8.0.
    x86
  • x86 (IA-32)
  • MXP
  • 2/SSE
  • SS3
  • Nessun supporto per MOVBE o SSE4.
    x86_64
  • x86-64
  • MXP
  • 2/SSE
  • SS3
  • SSE4.1, 4.2
  • POPCNT
  • 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:

    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:

    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.