Administración de ABI

Los distintos teléfonos móviles Android utilizan diferentes CPU, que a su vez son compatibles con diferentes conjuntos de instrucciones. Cada combinación de CPU y conjuntos de instrucciones tiene su propia Interfaz binaria de aplicación, o ABI. La ABI define con gran precisión la manera en que el código máquina de una aplicación debe interactuar con el sistema durante el tiempo de ejecución. Debes especificar una ABI para cada arquitectura de CPU con la cual desees que funcione tu app.

Una ABI típica incluye la siguiente información:

  • Los conjuntos de instrucciones de la CPU que el código máquina debe usar.
  • El formato endian de las cargas y los almacenamientos en memoria durante el tiempo de ejecución.
  • El formato de los archivos ejecutables, como los programas y las bibliotecas compartidas, y los tipos de contenido que admiten.
  • Varias convenciones para transmitir datos entre tu código y el sistema. Estas convenciones incluyen limitaciones de alineación y también la forma en que el sistema usa la pila y los registros al llamar a funciones.
  • La lista de símbolos de funciones disponibles para tu código máquina en el tiempo de ejecución, generalmente de conjuntos de bibliotecas muy específicos.

En esta página, se enumeran las ABI que admite el NDK y se proporciona información sobre cómo funciona cada ABI. Puedes acceder a una lista de problemas de ABI en sistemas de 32 bits en Errores de ABI de 32 bits

ABI compatibles

Cada ABI admite uno o más conjuntos de instrucciones. En la tabla 1, se proporciona una descripción general sobre los conjuntos de instrucciones que admite cada ABI.

Tabla 1: ABI y conjuntos de instrucciones compatibles.

ABI Conjuntos de instrucciones compatibles Notas
armeabi
  • ARMV5TE y posteriores
  • Thumb-1
  • Obsoleto a partir de r16. Retirado en r17. Sin cálculo de punto flotante asistido por hardware.
    armeabi-v7a
  • armeabi
  • Thumb-2
  • VFPv3-D16
  • Otro, opcional
  • Incompatible con dispositivos ARMv5, v6.
    arm64-v8a
  • AArch64
  • x86
  • x86 (IA-32)
  • MMX
  • SSE/2/3
  • SSSE3
  • Incompatible con MOVBE o SSE4.
    x86_64
  • x86-64
  • MMX
  • SSE/2/3
  • SSSE3
  • SSE4.1, 4.2
  • POPCNT
  • Nota: Históricamente, el NDK admitía MIPS de 32 y 64 bits, pero esta compatibilidad se retiró en el NDK r17.

    A continuación, se proporciona información detallada sobre cada ABI.

    armeabi

    Nota: Esta ABI se retiró en el NDK r17.

    Esta ABI es para CPU basados en ARM que admiten al menos el conjunto de instrucciones ARMv5TE. Para obtener más información, consulta la siguiente documentación:

    El estándar AAPCS define EABI como una familia de ABI similares, pero diferentes. Asimismo, Android sigue la ABI ARM GNU o Linux little endian.

    Esta ABI no admite cálculos de punto flotante asistidos por hardware. En su lugar, todas las operaciones de punto flotante usan funciones de ayuda de software de la biblioteca estática libgcc.a del compilador.

    La ABI armeabi admite el conjunto de instrucciones Thumb (también conocido como Thumb-1) de ARM. El NDK genera código Thumb de forma predeterminada, a menos que especifiques un comportamiento diferente usando la variable LOCAL_ARM_MODE en tu archivoAndroid.mk .

    armeabi-v7a

    Esta ABI extiende armeabi para incluir varias Extensiones del conjunto de instrucciones de CPU. Las extensiones de instrucciones compatibles con esta ABI específica de Android son las siguientes:

    • La extensión del conjunto de instrucciones Thumb-2, que proporciona un rendimiento similar al de las instrucciones ARM de 32 bits con compactación similar a la de Thumb-1.
    • Las instrucciones hardware-FPU de VFP. Más específicamente, VFPv3-D16, que incluye 16 registros de punto flotante dedicados de 64 bits, además de otros 16 registros de 32 bits del núcleo de ARM.

    Otras extensiones que describe la especificación v7-a ARM, incluida SIMD avanzada (también conocida como NEON), VFPv3-D32 y ThumbEE, son opcionales para esta ABI. Debido a que su presencia no está garantizada, el sistema debe verificar durante el tiempo de ejecución si las extensiones están disponibles. Si no lo están, debes usar rutas de acceso de código alternativas. Esta comprobación es similar a la que el sistema generalmente realiza para verificar o usar MMX, SSE2 y otros conjuntos de instrucciones especializadas en CPU x86.

    Para obtener información sobre cómo realizar estas comprobaciones de tiempo de ejecución, consulta La cpufeatures biblioteca. Además, para obtener información sobre la compatibilidad de NDK con la generación de código máquina para NEON, consulta Compatibilidad con NEON.

    La ABI armeabi-v7a usa el conmutador -mfloat-abi=softfp para aplicar la regla que indica que el compilador debe pasar todos los valores dobles en los pares de registro principales durante las llamadas de función en lugar de pasar los valores de punto flotante dedicados. El sistema puede realizar todos los cálculos internos usando los registros de FP. Esto acelera los cómputos notablemente.

    arm64-v8a

    Esta ABI es para CPU basados en ARMv8 compatibles con AArch64. También incluye los conjuntos de instrucciones NEON y VFPv4.

    Para obtener más información, consulta la Vista previa de la tecnología ARMv8 y comunícate con ARM para obtener más información.

    x86

    Esta ABI es para CPU que por lo general admiten el conjunto de instrucciones comúnmente denominado "x86" o "IA-32". Entre las características de esta ABI se incluyen las siguientes:

    • Instrucciones normalmente generadas por GCC con indicadores de compilador, como las siguientes:
      -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32
      

      Esos indicadores están orientados al conjunto de instrucciones Pentium Pro, junto con las extensiones del conjunto de instrucciones MMX, SSE, SSE2, SSE3 y SSSE3. El código generado es una optimización equilibrada entre las principales CPU Intel de 32 bits.

      Para obtener más información sobre los indicadores del compilador, especialmente los relacionados con la optimización de rendimiento, consulta las sugerencias de rendimiento de x86 de GCC.

    • Uso de la convención de llamada estándar de 32 bits de x86 de Linux, en comparación con la de SVR. Para obtener más información, consulta la sección 6 ("Uso de registros") del documento Convenciones de llamada para diferentes compiladores C++ y sistemas operativos.

    La ABI no incluye ninguna otra extensión opcional del conjunto de instrucciones IA-32, como:

    • MOVBE
    • Cualquier variante de SSE4

    De todos modos, podrás usar estas extensiones siempre que emplees sondeo de funciones en tiempo de ejecución para habilitarlas y proporciones reservas para los dispositivos que no las admitan.

    El conjunto de herramientas del NDK supone una alineación de pila de 16 bytes antes de una llamada a una función. Las herramientas y las opciones predeterminadas aplican esta regla. Si escribes código de ensamblado, debes asegurarte de mantener la alineación de la pila y de que los demás compiladores también obedezcan esta regla.

    Para obtener más información, consulta los siguientes documentos:

    x86_64

    Esta ABI es para CPU compatibles con el conjunto de instrucciones comúnmente denominado "x86-64". Admite instrucciones que GCC generalmente crea con los siguientes indicadores de compilador:

    -march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel
    

    Estos indicadores están orientados al conjunto de instrucciones x86-64, de acuerdo con la documentación de GCC. junto con las extensiones del conjunto de instrucciones MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2 y POPCNT . El código generado es una optimización equilibrada entre las principales CPU Intel de 64 bits.

    Para obtener más información sobre los indicadores de compilador, especialmente los relacionados con la optimización de rendimiento, consulta rendimiento de x86 de GCC.

    Esta ABI no incluye ninguna otra extensión opcional del conjunto de instrucciones x86-64, como las siguientes:

    • MOVBE
    • SHA
    • AVX
    • AVX2

    De todos modos, podrás usar estas extensiones siempre que emplees sondeo de funciones en tiempo de ejecución para habilitarlas y proporciones reservas para los dispositivos que no las admitan.

    Para obtener más información, consulta los siguientes documentos:

    Generación de código para ABI específica

    De manera predeterminada, el NDK está orientado a todas las ABI no obsoletas. Si deseas focalizarte en una sola ABI, establece APP_ABI en tu archivo Application.mk. El fragmento siguiente muestra algunos ejemplos de uso 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.
    

    Para obtener más información sobre los valores que puedes especificar para APP_ABI, consulta Application.mk.

    El comportamiento predeterminado del sistema de compilación es incluir los ejecutables de cada ABI en un solo APK, también conocido como APK multiarquitectura. Un APK multiarquitectura es mucho más grande que uno que solo contiene los binarios para una ABI individual; de este modo, se obtiene una mayor compatibilidad, aunque a costa de un APK más grande. Se recomienda enfáticamente aprovechar los APK de la división para reducir el tamaño de los APK y mantener la máxima compatibilidad de los dispositivos.

    En el momento de la instalación, el administrador de paquetes desempaqueta únicamente el código máquina más adecuado para el dispositivo de destino. Para obtener información detallada, consulta Extracción automática de código nativo en el momento de la instalación.

    Administración de ABI en la plataforma de Android

    En esta sección, se proporciona información detallada sobre la manera en que la plataforma de Android administra código nativo en los APK.

    Código nativo en paquetes de apps

    Tanto Play Store como el administrador de paquetes prevén encontrar bibliotecas generadas por el NDK en rutas de acceso a archivos dentro del APK que coincida con el siguiente patrón:

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

    En este caso, <abi> es uno de los nombres de ABI indicados en ABI compatibles, y <name> es el nombre de la biblioteca que definiste para la variable LOCAL_MODULE en el archivo Android.mk. Debido a que los archivos APK son simplemente archivos zip, es banal que los abras y confirmes que las bibliotecas nativas compartidas estén donde corresponda.

    Si el sistema no encuentra las bibliotecas nativas compartidas donde prevé encontrarlas, no puede usarlas. En este caso, la app debe volver a copiar las bibliotecas y realizar dlopen().

    En un APK multiarquitectura, cada biblioteca reside en un directorio cuyo nombre coincide con una ABI correspondiente. Por ejemplo, un APK multiarquitectura puede contener lo siguiente:

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

    Nota: Dispositivos Android basados en ARMv7 con bibliotecas de instalación nativas 4.0.3 o anteriores del directorio armeabi, en lugar del directorio armeabi-v7a, si existen ambos directorios. Esto ocurre porque /lib/armeabi/ va después de /lib/armeabi-v7a/ en el APK. Este problema está solucionado a partir de la versión 4.0.4.

    Compatibilidad de ABI en la plataforma de Android

    El sistema Android reconoce en el tiempo de ejecución las ABI compatibles, ya que las propiedades del sistema específicas de la compilación indican lo siguiente:

    • La ABI principal para el dispositivo, que corresponde al código máquina empleado en la imagen del sistema.
    • Opcionalmente, ABI secundarias correspondientes a otra ABI que la imagen del sistema también admite.

    Este mecanismo garantiza que el sistema extraiga el mejor código máquina del paquete en el momento de la instalación.

    A fin de lograr el mejor rendimiento, debes realizar compilaciones directamente para la ABI principal. Por ejemplo, un dispositivo típico basado en ARMv5TE solo definiría la ABI principal: armeabi. Por el contrario, un dispositivo típico basado en ARMv7 definiría la ABI principal como armeabi-v7a y la secundaria como armeabi, ya que puede lanzar ejecutables nativos de la aplicación generados para cada uno de ellos.

    Los dispositivos de 64 bits también admiten sus variantes de 32 bits. Si tomamos los dispositivos arm64-v8a como ejemplo, el dispositivo también puede ejecutar un código armeabi y armeabi-v7a. Ten en cuenta, sin embargo, que tu aplicación tendrá un rendimiento mucho mejor en dispositivos de 64 bits si está orientado a arm64-v8a en lugar de depender del dispositivo que ejecuta la versión armeabi-v7a de tu aplicación.

    Muchos dispositivos basados en x86 también pueden lanzar ejecutables armeabi-v7a y armeabi del NDK. Para esos dispositivos, la ABI principal sería x86, y la secundaria, armeabi-v7a.

    Extracción automática de código nativo en el momento de la instalación

    Al instalar una aplicación, el servicio de administrador de paquetes escanea el APK y busca bibliotecas compartidas con el siguiente formato:

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

    Si no encuentra ninguna, y definiste una ABI secundaria, el servicio busca bibliotecas compartidas con el siguiente formato:

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

    Cuando encuentra las bibliotecas que busca, el administrador de paquetes las copia en /lib/lib<name>.so, en el directorio data de la aplicación (data/data/<package_name>/lib/).

    Si no hay archivos de objetos compartidos, la aplicación lo compila y lo instala, pero falla en el tiempo de ejecución.