Administración de ABI

Los diferentes dispositivos portátiles con Android incorporan diferentes 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 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 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 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 la registra 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 el la forma exacta en que funciona cada ABI.

ABI admitidas

Cada ABI admite uno o más conjuntos de instrucciones. La tabla 1 proporciona información general sobre 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
  • 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
  • AArch-64
  • 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
  • mips
  • MIPS32r1 y posteriores
  • Usa cálculo de punto flotante asistido por hardware y supone una relación de reloj CPU:FPU de 2:1 para obtener la máxima compatibilidad. No proporciona micromips ni MIPS16.
    mips64
  • MIPS64r6
  • A continuación, se proporciona información detallada sobre cada ABI.

    armeabi

    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 archivo Android.mk.

    armeabi-v7a

    Esta ABI extiende armeabi para incluir varias extensiones del conjunto de instrucciones de CPU. Las extensiones de instrucciones que admite 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.
    • 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 (antes 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 acerca de cómo realizar estas comprobaciones en el tiempo de ejecución, consulta la biblioteca cpufeatures. Asimismo, si deseas obtener información de compatibilidad del NDK para compilar 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 valores dobles en los pares de registro principales durante las llamadas de función en lugar de pasar 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 que admiten 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 generalmente 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 que admiten 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:

    mips

    Esta ABI es para CPU basadas en MIPS que admiten, al menos, el conjunto de instrucciones MIPS32r1. Incluye las siguientes funciones:

    • revisión 1 de la ISA de MIPS32;
    • little endian;
    • O32;
    • cálculo de punto flotante asistido por hardware;
    • ausencia de extensiones DSP específicas de la aplicación.

    Para obtener más información, consulta la siguiente documentación:

    Para obtener información detallada, consulta MIPS32 Architecture (arquitectura MIPS32). Puedes encontrar respuestas a preguntas frecuentes en MIPS FAQ (preguntas frecuentes sobre MIPS).

    mips64

    Esta ABI es para MIPS64 R6. Para obtener más información, consulta MIPS64 Architecture (arquitectura MIPS64).

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

    De forma predeterminada, el NDG genera código máquina para la ABI armeabi. Como alternativa, puedes generar código máquina compatible con ARMv7-a agregando la siguiente línea a tu archivo Application.mk.

    APP_ABI := armeabi-v7a
    

    Para compilar código máquina para dos o más ABI diferentes, usando espacios como delimitadores. Por ejemplo:

    APP_ABI := armeabi armeabi-v7a
    

    Esta configuración indica al NDK que compile dos versiones de tu código máquina: una para cada ABI indicada en esta línea. Para obtener más información sobre los valores que puedes especificar para la variable APP_ABI, consulta Android.mk.

    Cuando compilas varias versiones de código máquina, el sistema de compilación copia las bibliotecas en la ruta de acceso de tu proyecto de aplicación y las empaqueta en tu APK creando un ejecutable multiarquitectura. Un ejecutable multiarquitectura es más grande que uno que solo contiene el código máquina para un sistema individual; de este modo se obtiene una mayor compatibilidad, aunque a costa de un APK más grande.

    En el momento de la instalación, el administrador de paquetes desempaqueta únicamente el código de 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 admitidas, 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 fundamental que los abras y confirmes que las bibliotecas nativas 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 ejecutable multiarquitectura, cada biblioteca reside en un directorio cuyo nombre coincida con una ABI correspondiente. Por ejemplo, un ejecutable multiarquitectura puede contener:

    /lib/armeabi/libfoo.so
    /lib/armeabi-v7a/libfoo.so
    /lib/arm64-v8a/libfoo.so
    /lib/x86/libfoo.so
    /lib/x86_64/libfoo.so
    /lib/mips/libfoo.so
    /lib/mips64/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 se soluciona 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 que admite, 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.
    • Una segunda ABI opcional correspondiente 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.

    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.

    Un dispositivo típico basado en MIPS solo define una ABI principal: mips.

    Extracción automática de código nativo al 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.