Administración de ABI

Los diferentes tipos de teléfonos Android usan distintas CPU que, a su vez, admiten 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, cómo se supone que el código máquina de una app interactúa con el sistema durante el tiempo de ejecución. Debes especificar una ABI para cada arquitectura de CPU que quieras que use tu app.

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

  • Los conjuntos de instrucciones del 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 objetos binarios ejecutables, como programas y bibliotecas compartidas, y los tipos de contenido que admiten.
  • Distintas convenciones para transmitir datos entre tu código y el sistema. Estas convenciones incluyen restricciones de alineación y también la manera 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 durante 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 una de ellas. Para ver una lista de los problemas relacionados con las ABI para sistemas de 32 bits, consulta errores de ABI de 32 bits.

ABI admitidas

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

Tabla 1. ABI y conjuntos de instrucciones admitidos

ABI Conjuntos de instrucciones admitidos Notas
armeabi
  • ARMV5TE y posteriores
  • Thumb-1
  • Obsoleto en r16. Quitado en r17. No tiene unidades de puntos flotantes asistidos por hardware.
    armeabi-v7a
  • armeabi
  • Thumb-2
  • VFPv3-D16
  • Otro, opcional
  • No es compatible con dispositivos ARMv5 y v6.
    arm64-v8a
  • AArch64
  • x86
  • x86 (IA-32)
  • MMX
  • SSE/2/3
  • SSSE3
  • No admite 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 bits y de 64 bits, pero dicha compatibilidad se quitó en el NDK r17.

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

    armeabi

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

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

    El estándar de llamada a procedimientos para la arquitectura ARM (AAPCS) define a la EABI como una familia de ABI similares, pero distintas. Además, Android sigue la ABI de GNU y Linux para ARM con sistema little-endian.

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

    La ABI armeabi admite el conjunto de instrucciones de Thumb (conocido como Thumb-1) para ARM. El NDK genera código de Thumb de manera predeterminada, a menos que especifiques otro comportamiento mediante la variable LOCAL_ARM_MODE en tu archivo Android.mk.

    armeabi-v7a

    Esta ABI extiende armeabi para incluir varias extensiones de conjuntos de instrucciones para CPU. A continuación, se muestran las extensiones de instrucciones que admite esta ABI específica de Android:

    • La extensión del conjunto de instrucciones Thumb-2, que proporciona un rendimiento similar al de las instrucciones de ARM de 32 bits con compactación similar a la de Thumb-1.
    • 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 se describen en la especificación de ARM v7-a, incluidas SIMD avanzado (conocida como NEON), VFPv3-D32 y ThumbEE, son opcionales para esta ABI. Debido a que su presencia no está garantizada, el sistema debe comprobar 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 suele realizar el sistema para comprobar o usar MMX, SSE2 y otros conjuntos de instrucciones especializados en CPU x86.

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

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

    arm64-v8a

    Esta ABI es para CPU basadas en ARMv8 que admiten AArch64. Además, incluye conjuntos de instrucciones de NEON y VFPv4.

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

    x86

    Esta ABI se usa para las CPU que 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
          

      Estos indicadores se orientan al conjunto de instrucciones Pentium Pro, junto con las extensiones de conjuntos de instrucciones MMX, SSE, SSE2, SSE3 y SSSE3. El código que se genera 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 relacionados con la optimización del 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 lugar de 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 las siguientes:

    • MOVBE
    • Cualquier variante de SSE4

    De todas maneras, podrás usar estas extensiones, siempre y cuando utilices 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 cumplan esta regla.

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

    x86_64

    Esta ABI es para CPU que admiten el conjunto de instrucciones conocido como "x86-64". Admite instrucciones que GCC suele generar con los siguientes indicadores del compilador:

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

    Estos indicadores se orientan al conjunto de instrucciones x86-64 en conformidad con la documentación de GCC y las extensiones de 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 del compilador (especialmente relacionados con la optimización del 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 todas maneras, podrás usar estas extensiones, siempre y cuando utilices 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íficas

    De forma predeterminada, el NDK se orienta a todas las ABI que no están obsoletas. Puedes orientar a una única ABI si estableces APP_ABI en tu archivo Application.mk. En el siguiente fragmento, se muestran algunos ejemplos del 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 objetos binarios para cada ABI en un solo APK, también conocido como APK multiarquitectura. Este es más grande que el que solo contiene objetos binarios para una sola ABI; de este modo, se obtiene mayor compatibilidad, pero a costa de un APK más grande. Te recomendamos que aproveches los APK divididos para reducir el tamaño de tus APK y mantener la máxima compatibilidad de los dispositivos.

    Durante la instalación, el administrador de paquetes solo debe desempaquetar el código máquina más apropiado para el dispositivo de destino. Para obtener más detalles, consulta Extracción automática de código nativo al momento de la instalación.

    Administración de ABI en la plataforma de Android

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

    Código nativo en paquetes de apps

    Tanto Play Store como el administrador de paquetes esperan encontrar bibliotecas generadas con NDK en las rutas de acceso a archivos dentro del APK que coincidan con el siguiente patrón:

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

    En este caso, <abi> es uno de los nombres de ABI que aparece en ABI admitidas y <name> es el nombre de la biblioteca que definiste para la variable LOCAL_MODULE en el archivo Android.mk. Como los archivos APK son solo archivos ZIP, es muy fácil abrirlos para confirmar que las bibliotecas nativas compartidas están en el lugar correspondiente.

    Si el sistema no encuentra las bibliotecas nativas compartidas donde espera que estén, entonces no podrá usarlas. En ese caso, la app debe volver a copiar las bibliotecas y realizar dlopen().

    En el APK multiarquitectura, cada biblioteca se aloja 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: Los dispositivos Android basados en ARMv7 que ejecutan 4.0.3 o versiones anteriores instalan bibliotecas nativas desde el directorio armeabi, en lugar del armeabi-v7a, en el caso de que existan ambos. Esta acción se debe a que /lib/armeabi/ aparece después de /lib/armeabi-v7a/ en el APK. Este problema se soluciona a partir de la versión 4.0.4.

    Compatibilidad de ABI en la plataforma de Android

    En el momento de la ejecución, el sistema Android sabe cuáles ABI admite, porque las propiedades del sistema específicas de la compilación indican lo siguiente:

    • La ABI principal del dispositivo, que corresponde al código máquina que se usa en la imagen del sistema.
    • De manera opcional, ABI secundarias, que corresponden 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 durante la instalación.

    Para lograr un rendimiento óptimo, debes compilar 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 basado típico en ARMv7 definiría la ABI principal como armeabi-v7a y la secundaria como armeabi, ya que puede ejecutar objetos binarios nativos de la app generados para cada una de ellas.

    Los dispositivos de 64 bits también admiten variantes de 32 bits. Si tomamos dispositivos arm64-v8a como ejemplo, el dispositivo también puede ejecutar código armeabi y armeabi-v7a. Sin embargo, ten en cuenta que tu app tendrá un mejor rendimiento en dispositivos de 64 bits si se orienta a arm64-v8a que si depende de un dispositivo que ejecuta la versión armeabi-v7a de tu app.

    Muchos dispositivos basados en x86 también pueden ejecutar los objetos binarios del NDK armeabi-v7a y armeabi. Para esos dispositivos, la ABI primaria sería x86 y la secundaria, armeabi-v7a.

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

    Cuando instales una app, 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 app (data/data/<package_name>/lib/).

    Si no se encuentra ningún archivo de objetos compartidos, la app se compila y se instala, pero falla en el tiempo de ejecución.