ABI Android

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

  • Il set di istruzioni della CPU (e le estensioni) che possono essere utilizzate.
  • L'endianness degli archivi e dei caricamenti di memoria in fase di runtime. Android è sempre little-endian.
  • Convenzioni per il trasferimento di dati tra applicazioni e sistema, inclusi vincoli di allineamento e modalità di utilizzo dello stack e dei registri da parte del sistema quando chiama le funzioni.
  • Il formato dei file binari eseguibili, come programmi e librerie condivise, e i tipi di contenuti che supportano. Android utilizza sempre ELF. Per ulteriori informazioni, consulta ELF System V Application Binary Interface.
  • Come vengono modificati i nomi C++. Per saperne di più, consulta ABI C++ generica/Itanium.

Questa pagina elenca le ABI supportate dall'NDK e fornisce informazioni sul funzionamento di ciascuna 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, vedi Bug ABI a 32 bit.

ABI supportate

Tabella 1. Interfacce 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
  • LAHF-SAHF
  • Full x86-64-v2.

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

    armeabi-v7a

    Questa ABI è per le CPU ARM a 32 bit. Include Thumb-2 e Neon.

    Per informazioni sulle parti dell'ABI non specifiche di Android, consulta Application Binary Interface (ABI) for the ARM Architecture

    I sistemi di build dell'NDK generano codice Thumb-2 per impostazione predefinita, a meno che tu non utilizzi LOCAL_ARM_MODE in Android.mk per ndk-build o ANDROID_ARM_MODE durante la configurazione di CMake.

    Per saperne di più sulla storia di Neon, consulta la pagina Assistenza Neon.

    Per motivi storici, questa ABI utilizza -mfloat-abi=softfp, il che fa sì che tutti i valori float vengano passati in registri interi e tutti i valori double vengano passati in coppie di registri interi quando vengono effettuate chiamate di funzioni. Nonostante il nome, questa opzione 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 (IEEE binary64, lo stesso di double).

    arm64-v8a

    Questa ABI è per le CPU ARM a 64 bit.

    Per informazioni dettagliate sulle parti dell'ABI non specifiche per Android, consulta la sezione Learn the Architecture di Arm. Arm offre anche alcuni consigli sul porting in Sviluppo di Android a 64 bit.

    Puoi utilizzare gli intrinsics Neon nel codice C e C++ per sfruttare l'estensione Advanced SIMD. La Guida per i programmatori Neon per Armv8-A fornisce maggiori informazioni sulle funzioni intrinseche Neon e sulla programmazione Neon in generale.

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

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

    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 più le estensioni MMX, SSE, SSE2, SSE3 e SSSE3.

    L'ABI non include altre estensioni opzionali del set di istruzioni IA-32, come MOVBE o qualsiasi variante di SSE4. Puoi comunque utilizzare queste estensioni, a condizione che utilizzi il rilevamento delle funzionalità in fase di runtime per abilitarle e fornisca fallback per i dispositivi che non le supportano.

    La toolchain NDK presuppone l'allineamento dello stack a 16 byte prima di una chiamata di funzione. Gli strumenti e le opzioni predefiniti applicano questa regola. Se scrivi codice assembly, devi assicurarti di mantenere l'allineamento dello stack e 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, lo stesso di double e non il long double a 80 bit solo Intel più comune).

    x86_64

    Questa ABI è per le CPU che supportano il set di istruzioni comunemente denominato "x86-64-v2".

    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 opzionali del set di istruzioni x86-64, come MOVBE, SHA o qualsiasi variante di AVX. Puoi comunque utilizzare queste estensioni, a condizione che utilizzi il rilevamento delle funzionalità di runtime per abilitarle e fornisca fallback 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).

    Generare codice per una ABI specifica

    Gradle

    Gradle (utilizzato tramite Android Studio o dalla riga di comando) esegue la build per tutte le ABI non ritirate per impostazione predefinita. Per limitare l'insieme di ABI supportati dalla tua applicazione, utilizza abiFilters. Ad esempio, per eseguire la build solo per ABI a 64 bit, imposta la seguente configurazione in build.gradle:

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

    ndk-build

    ndk-build esegue la compilazione per tutte le ABI non ritirate per impostazione predefinita. Puoi scegliere come target ABI specifici 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 maggiori informazioni sui valori che puoi specificare per APP_ABI, consulta Application.mk.

    CMake

    Con CMake, la compilazione avviene per una singola ABI alla volta e devi specificare esplicitamente l'ABI. Questa operazione viene eseguita con 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 la compilazione con l'NDK, consulta la guida di CMake.

    Il comportamento predefinito del sistema di compilazione è includere i file binari per ogni ABI in un singolo APK, noto anche come APK fat. Un APK fat è molto più grande di uno contenente solo i binari per una singola ABI. Il compromesso è ottenere una compatibilità più ampia, ma a scapito di un APK più grande. Ti consigliamo vivamente di sfruttare gli app bundle o le divisioni degli APK per ridurre le dimensioni degli APK mantenendo al contempo la massima compatibilità con i dispositivi.

    Al momento dell'installazione, il gestore dei pacchetti decomprime solo il codice macchina più appropriato per il dispositivo di destinazione. Per maggiori dettagli, vedi 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 app

    Sia Play Store sia Package Manager si aspettano di trovare le librerie generate dall'NDK nei percorsi dei file all'interno dell'APK che corrispondono al seguente pattern:

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

    In questo caso, <abi> è uno dei nomi ABI elencati in ABI supportate e <name> è il nome della libreria come l'hai definita per la variabile LOCAL_MODULE nel file Android.mk. Poiché i file APK sono semplicemente file zip, è banale aprirli e verificare che le librerie native condivise si trovino nella posizione corretta.

    Se il sistema non trova le librerie condivise native nella posizione prevista, 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-v7a/libfoo.so
    /lib/arm64-v8a/libfoo.so
    /lib/x86/libfoo.so
    /lib/x86_64/libfoo.so
    

    Supporto ABI della 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 per il dispositivo, corrispondente al codice macchina utilizzato nell'immagine di sistema stessa.
    • (Facoltativo) ABI secondarie, corrispondenti ad altre ABI supportate anche dall'immagine di sistema.

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

    Puoi forzare l'installazione di un APK per una specifica ABI. Questa opzione può essere utile per i test sui dispositivi che supportano più di un'ABI. Utilizza il seguente 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 esegue la scansione dell'APK e cerca eventuali librerie condivise nel formato:

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

    Se non viene trovato alcun ABI e hai definito un ABI secondario, il servizio esegue la scansione delle librerie condivise del modulo:

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

    Quando trova le librerie che sta cercando, il gestore dei pacchetti le copia in /lib/lib<name>.so, nella directory delle librerie native 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 oggetto condiviso, l'applicazione viene compilata e installata, ma si arresta in modo anomalo in fase di esecuzione.

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

    L'abilitazione di PAC/BTI fornirà protezione contro alcuni vettori di attacco. PAC protegge gli indirizzi di ritorno firmandoli crittograficamente nel prologo di una funzione e verificando che la firma sia ancora corretta nell'epilogo. BTI impedisce di passare a posizioni arbitrarie nel codice richiedendo che ogni destinazione di ramificazione sia un'istruzione speciale che non fa altro che comunicare al processore che è consentito atterrare 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 sono necessarie più varianti della tua libreria. Anche sui dispositivi ARMv9, PAC/BTI si applica solo al codice a 64 bit.

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

    Consulta la sezione Learn the architecture - Providing protection for complex software (PDF) di Arm per una spiegazione dettagliata dei vettori di attacco PAC/BTI di destinazione e di come funziona la protezione.

    Modifiche alla build

    ndk-build

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

    CMake

    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 quando la compilazione viene eseguita per l'ABI arm64-v8a. Non è necessario utilizzare questo flag durante il 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é il risultato è una libreria in cui la protezione BTI non è 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'assembler scritto a mano che causa errori PAC. Esegui l'upgrade alla versione attuale di OpenSSL.

    • Le versioni precedenti di alcuni sistemi DRM per app generano codice che viola i requisiti PAC/BTI. Se utilizzi la gestione dei diritti digitali (DRM) dell'app e riscontri problemi durante l'attivazione di PAC/BTI, contatta il tuo fornitore di DRM per una versione corretta.