ABI Android

Chaque CPU utilise différents processeurs, qui sont eux-mêmes compatibles avec différents ensembles d'instructions. Chaque combinaison de processeur et d'ensemble d'instructions possède sa propre interface binaire d'application (ABI). Une ABI inclut les informations suivantes :

  • Ensemble d'instructions processeur (et extensions) pouvant être utilisé.
  • Mode Endian du stockage et du chargement de la mémoire au moment de l'exécution. Android est toujours en mode Little Endian.
  • Conventions pour la transmission de données entre les applications et le système, y compris les contraintes d'alignement, ainsi que la manière dont le système utilise la pile et s'enregistre lorsqu'il appelle des fonctions.
  • Format des binaires exécutables, tels que les programmes et les bibliothèques partagées, ainsi que les types de contenus acceptés. Android utilise toujours ELF. Pour en savoir plus, consultez la page sur l'interface binaire d'application V du système ELF.
  • Façon dont les noms C++ sont tronqués. Pour en savoir plus, consultez la page sur l'ABI C++ Generic/Itanium.

Cette page énumère les ABI compatibles avec le NDK et fournit des informations sur le fonctionnement de chacune d'elles.

L'ABI peut également faire référence à l'API native compatible avec la plate-forme. Pour obtenir la liste des types d'erreurs d'ABI affectant les systèmes 32 bits, consultez la page sur les bugs d'ABI 32 bits.

ABI compatibles

Tableau 1. ABI et ensembles d'instructions compatibles.

ABI Ensembles d'instructions compatibles Notes
armeabi-v7a
  • armeabi
  • Thumb-2
  • Néon
  • Incompatible avec les appareils ARMv5/v6.
    arm64-v8a
  • AArch64
  • Armv8.0 uniquement.
    x86
  • x86 (IA-32)
  • MMX
  • SSE/2/3
  • SSSE3
  • Non compatibles avec MOVBE ou SSE4.
    x86_64
  • x86-64
  • MMX
  • SSE/2/3
  • SSSE3
  • SSE4.1, 4.2
  • POPCNT
  • x86-64-v1 uniquement.

    Remarque : À l'origine, le NDK était compatible avec ARMv5 (armeabi) et MIPS 32 bits et 64 bits, mais la prise en charge de ces ABI n'est plus assurée depuis le NDK r17.

    armeabi-v7a

    Cette ABI est destinée aux processeurs ARM 32 bits. Elle inclut Thumb-2 et Neon.

    Pour en savoir plus sur les parties de l'ABI qui ne sont pas spécifiques à Android, consultez la page Interface binaire d'application (ABI) pour l'architecture ARM.

    Les systèmes de compilation du NDK génèrent le code Thumb-2 par défaut, sauf si vous utilisez LOCAL_ARM_MODE dans Android.mk pour ndk-build ou ANDROID_ARM_MODE lorsque vous configurez CMake.

    Pour en savoir plus sur l'historique de Neon, consultez la page Compatibilité avec Neon.

    Pour des raisons historiques, cette ABI utilise -mfloat-abi=softfp. Par conséquent, toutes les valeurs float sont transmises dans des registres d'entiers et toutes les valeurs double par paires de registres d'entiers lors des appels de fonction. Malgré son nom, cela n'affecte que la convention d'appel à virgule flottante: le compilateur utilisera toujours les instructions à virgule flottante matérielle pour l'arithmétique.

    Cette ABI utilise un long double 64 bits (IEEE binary64 identique à double).

    arm64-v8a

    Cette ABI est destinée aux processeurs ARM 64 bits.

    Consultez la section sur la découverte de l'architecture d'Arm pour obtenir des informations complètes sur les parties de l'ABI qui ne sont pas spécifiques à Android. Arm fournit également des conseils pour le portage dans le cadre du développement Android 64 bits.

    Vous pouvez utiliser les fonctionnalités intrinsèques de Neon dans le code C et C++ pour tirer parti de l'extension Advanced SIMD. Le document Neon Programmer's Guide for Armv8-A (Guide du programmeur Neon pour Armv8-A) fournit plus d'informations sur les fonctionnalités intrinsèques de Neon et la programmation Neon en général.

    Sur Android, le registre x18 propre à la plate-forme est réservé à ShadowCallStack et ne doit pas être modifié par votre code. Les versions actuelles de Clang utilisent l'option -ffixed-x18 par défaut sur Android. Par conséquent, vous n'avez pas à vous soucier de ce point, sauf si vous disposez d'un compilateur écrit à la main (ou d'un très ancien compilateur).

    Cette ABI utilise un long double 128 bits (IEEE binary128).

    x86

    Cette ABI est destinée aux processeurs compatibles avec l'ensemble d'instructions communément appelé "x86", "i386" ou "IA-32".

    L'ABI d'Android inclut l'ensemble d'instructions de base, ainsi que les extensions MMX, SSE, SSE2, SSE3 et SSSE3.

    L'ABI n'inclut pas d'autres extensions d'ensemble d'instructions IA-32 facultatives, comme MOVBE ou toute variante de SSE4. Vous pouvez continuer à utiliser ces extensions, à condition de recourir à la vérification des fonctionnalités lors de l'exécution pour les activer, et de fournir des alternatives pour les appareils qui ne les acceptent pas.

    La chaîne d'outils du NDK suppose un alignement de la pile de 16 octets avant un appel de fonction. Les outils et options par défaut appliquent cette règle. Si vous écrivez du code d'assemblage, veillez à assurer l'alignement de la pile et à ce que les autres compilateurs respectent également cette règle.

    Pour en savoir plus, consultez les documents suivants :

    Cette ABI utilise un long double 64 bits (IEEE binary64 identique à double, et non le long double 80 bits Intel le plus courant).

    x86_64

    Cette ABI est destinée aux processeurs compatibles avec l'ensemble d'instructions communément appelé "x86-64".

    L'ABI d'Android inclut l'ensemble d'instructions de base, ainsi que les extensions MMX, SSE, SSE2, . SSE3, SSSE3, SSE4.1, SSE4.2 et l'instruction POPCNT.

    L'ABI n'inclut aucune autre extension d'ensemble d'instructions x86-64 facultative, comme MOVBE, SHA ou toute variante d'AVX. Vous pouvez continuer à utiliser ces extensions, à condition de recourir à la vérification des fonctionnalités lors de l'exécution pour les activer, et de fournir des alternatives pour les appareils qui ne les acceptent pas.

    Pour en savoir plus, consultez les documents suivants :

    Cette ABI utilise un long double 128 bits (IEEE binary128).

    Générer le code d'une ABI spécifique

    Gradle

    Gradle (qu'il soit utilisé via Android Studio ou à partir de la ligne de commande) compile toutes les ABI non obsolètes par défaut. Pour limiter l'ensemble d'ABI compatibles avec votre application, utilisez abiFilters. Par exemple, pour créer des ABI 64 bits uniquement, définissez la configuration suivante dans build.gradle :

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

    ndk-build

    ndk-build est créé par défaut pour toutes les ABI non obsolètes. Pour cibler des ABI spécifiques, définissez APP_ABI dans le fichier Application.mk. L'extrait de code suivant montre quelques exemples d'utilisation de 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.
    

    Pour en savoir plus sur les valeurs que vous pouvez spécifier pour APP_ABI, consultez Application.mk.

    CMake

    Avec CMake, vous créez une seule ABI à la fois et devez la spécifier explicitement. Pour ce faire, vous devez utiliser la variable ANDROID_ABI, qui doit être spécifiée dans la ligne de commande (elle ne peut pas être définie dans le fichier CMakeLists.txt). Exemple :

    $ cmake -DANDROID_ABI=arm64-v8a ...
    $ cmake -DANDROID_ABI=armeabi-v7a ...
    $ cmake -DANDROID_ABI=x86 ...
    $ cmake -DANDROID_ABI=x86_64 ...
    

    Pour connaître les autres indicateurs que vous devez transmettre à CMake pour qu'il exécute le build à l'aide du NDK, consultez le guide CMake.

    Le comportement par défaut du système de compilation consiste à inclure les binaires de chaque ABI dans un seul APK, également appelé APK "fat". Un fichier APK "fat" est nettement plus volumineux qu'un fichier APK unique qui ne contient que les binaires d'une seule ABI. L'avantage est une plus grande compatibilité, mais au détriment de la taille élevée du fichier APK. Nous vous recommandons vivement de tirer parti des app bundles ou des fractionnements de fichiers APK pour réduire la taille de vos fichiers APK, tout en assurant une compatibilité optimale avec les appareils.

    Au moment de l'installation, le gestionnaire de packages décompresse uniquement le code machine le plus approprié pour l'appareil cible. Pour en savoir plus, consultez la section Extraction automatique du code natif au moment de l'installation.

    Gestion des ABI sur la plate-forme Android

    Cette section explique comment la plate-forme Android gère le code natif dans les APK.

    Code natif dans les packages d'applications

    Le Play Store et le gestionnaire de packages s'attendent à trouver des bibliothèques générées par le NDK sur les chemins de fichiers de l'APK, avec le schéma suivant :

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

    Ici, <abi> correspond à l'un des noms d'ABI indiqués sous ABI compatibles, et <name> correspond au nom de la bibliothèque que vous avez définie pour la variable LOCAL_MODULE dans le fichier Android.mk. Les fichiers APK ne sont que des fichiers ZIP. Il est donc facile de les ouvrir et de confirmer que les bibliothèques natives partagées sont à leur place.

    Si le système ne trouve pas les bibliothèques partagées natives à l'emplacement attendu, il ne peut pas les utiliser. Dans ce cas, l'application elle-même doit copier les bibliothèques, puis exécuter dlopen().

    Dans un fichier APK "fat", chaque bibliothèque se trouve dans un répertoire dont le nom correspond à une ABI spécifique. Par exemple, un fichier APK peut contenir les éléments suivants :

    /lib/armeabi/libfoo.so
    /lib/armeabi-v7a/libfoo.so
    /lib/arm64-v8a/libfoo.so
    /lib/x86/libfoo.so
    /lib/x86_64/libfoo.so
    

    Remarque : Les appareils Android basés sur ARMv7 et exécutant la version 4.0.3 ou une version antérieure installent des bibliothèques natives à partir du répertoire armeabi au lieu de armeabi-v7a si ces deux répertoires existent. Cela s'explique par le fait que /lib/armeabi/ vient après /lib/armeabi-v7a/ dans le fichier APK. Ce problème a été résolu à partir de la version 4.0.4.

    Compatibilité des ABI avec la plate-forme Android

    Le système Android sait, au moment de l'exécution, quelles sont les ABI compatibles, car les propriétés système spécifiques à la compilation indiquent les éléments suivants :

    • ABI principale de l'appareil, correspondant au code machine utilisé dans l'image système
    • Éventuellement, les ABI secondaires, correspondant à d'autres ABI compatibles avec l'image système

    Ce mécanisme garantit que le système extrait le code machine le plus approprié du package au moment de l'installation.

    Pour optimiser les performances, vous devez directement effectuer la compilation pour l'ABI principale. Par exemple, un appareil classique basé sur ARMv5TE ne définit que l'ABI principale : armeabi. En revanche, un appareil classique basé sur ARMv7 définit l'ABI principale comme armeabi-v7a et l'ABI secondaire comme armeabi, car il peut exécuter les binaires natifs d'application générés pour chacun d'eux.

    Les appareils 64 bits sont également compatibles avec leurs variantes 32 bits. À l'aide des appareils arm64-v8a, par exemple, ils peuvent également exécuter le code armeabi et armeabi-v7a. Notez toutefois que votre application fonctionnera beaucoup mieux sur les appareils 64 bits si elle cible arm64-v8a plutôt que sur l'appareil exécutant la version armeabi-v7a de votre application.

    De nombreux appareils basés sur x86 peuvent également exécuter des binaires NDK armeabi-v7a et armeabi. Pour ces appareils, l'ABI principale est x86, et la deuxième, armeabi-v7a.

    Vous pouvez installer d'office un fichier APK pour une ABI spécifique. Cette approche est utile pour les tests. Exécutez la commande ci-dessous :

    adb install --abi abi-identifier path_to_apk
    

    Extraction automatique du code natif au moment de l'installation

    Lors de l'installation d'une application, le service de gestion des packages analyse le fichier APK et recherche les bibliothèques partagées sous la forme suivante :

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

    Si aucune bibliothèque n'est identifiée et que vous avez défini une ABI secondaire, le service recherche des bibliothèques partagées sous la forme suivante :

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

    Une fois qu'il a trouvé les bibliothèques qu'il recherche, le gestionnaire de packages les copie dans /lib/lib<name>.so, sous le répertoire de bibliothèques natives de l'application (<nativeLibraryDir>/). Les extraits de code suivants récupèrent 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 );
    

    En l'absence de fichier d'objet partagé, l'application effectue la compilation et l'installation, mais plante au moment de l'exécution.

    ARMv9 : activer PAC et BTI pour C/C++

    L'activation de PAC/BTI assure une protection contre certains vecteurs d'attaque. PAC protège les adresses de retour en les signant de manière cryptographique dans le prologue d'une fonction et en vérifiant que l'adresse de retour est toujours correctement signée dans l'épilogue. BTI empêche le renvoi vers des emplacements arbitraires du code en exigeant que chaque branche cible soit une instruction spéciale dont la seule fonction est d'indiquer que l'emplacement de destination est correct.

    Android utilise des instructions PAC/BTI qui n'ont aucun effet sur les processeurs plus anciens qui ne sont pas compatibles avec les nouvelles instructions. Seuls les appareils ARMv9 bénéficient de la protection PAC/BTI, mais vous pouvez exécuter le même code sur des appareils ARMv8 : il n'est pas nécessaire d'avoir plusieurs variantes de votre bibliothèque. Même sur les appareils ARMv9, PAC/BTI ne s'applique qu'au code 64 bits.

    L'activation de PAC/BTI entraîne une légère augmentation de la taille du code, généralement de 1 %.

    Pour en savoir plus sur la cible PAC/BTI des vecteurs d'attaque et sur le fonctionnement de la protection, consultez la section sur la protection des logiciels complexes dans le document PDF d'Arm.

    Modifications de la compilation

    ndk-build

    Définissez LOCAL_BRANCH_PROTECTION := standard dans chaque module du fichier Android.mk.

    CMake

    Utilisez target_compile_options($TARGET PRIVATE -mbranch-protection=standard) pour chaque cible dans le fichier CMakeLists.txt.

    Autres systèmes de compilation

    Compilez le code avec -mbranch-protection=standard. Cet indicateur ne fonctionne que lors de la compilation pour l'ABI arm64-v8a. Vous n'avez pas besoin de l'utiliser lors de l'association.

    Dépannage

    Nous n'avons connaissance d'aucun problème lié à la compatibilité du compilateur avec PAC/BTI. Toutefois, gardez à l'esprit les points suivants :

    • Veillez à ne pas mélanger le code BTI et un autre type de code lorsque vous effectuez l'association, car cela générerait une bibliothèque pour laquelle la protection BTI ne serait pas activée. Vous pouvez utiliser l'outil llvm-readelf pour vérifier si la bibliothèque générée comporte la note BTI ou non.
    $ 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
    [...]
    $
    
    • Les anciennes versions d'OpenSSL (antérieures à la version 1.1.1i) comportent un bug dans l'outil d'assemblage manuel, lequel entraîne des échecs de PAC. Pour résoudre ce problème, installez la version actuelle d'OpenSSL.

    • Les anciennes versions de certains systèmes DRM d'application génèrent un code qui ne respecte pas les exigences PAC/BTI. Si vous utilisez une DRM d'application et que vous rencontrez des problèmes lors de l'activation de PAC/BTI, demandez à votre fournisseur DRM de vous fournir une version corrigée.