Gerenciamento de ABI

Os dispositivos Android usam CPUs variadas, que são compatíveis com conjuntos de instruções diferentes. Cada combinação de CPU e conjunto de instruções tem a própria interface binária de app, ou ABI, na sigla em inglês. A ABI define, de maneira precisa, como será a interação entre o código de máquina do app e o sistema no momento da execução. Especifique uma ABI para cada arquitetura de CPU com que você quer que o app funcione.

As ABIs típicas contêm as seguintes informações:

  • Um ou mais conjuntos de instrução da CPU que o código de máquina deve usar
  • Ordenação (endianness) de armazenamentos e cargas de memória no momento da execução
  • Formato de binários executáveis, como programas e bibliotecas compartilhadas, e os tipos de conteúdo compatíveis
  • Várias convenções para passar dados entre seu código e o sistema, incluindo restrições de alinhamento, bem como o modo com que o sistema usa pilhas e registros ao chamar funções
  • Lista de símbolos de função disponível para seu código de máquina no momento da execução, geralmente de conjuntos de bibliotecas muito específicos

Esta página enumera as ABIs com que o NDK é compatível e fornece informações sobre como cada ABI funciona. Para ver uma lista de problemas com ABI em sistemas de 32 bits, consulte Bugs da ABI em 32 bits.

ABIs compatíveis

Cada ABI é compatível com um ou mais conjuntos de instrução. A tabela 1 mostra um resumo dos conjuntos de instrução compatíveis com cada ABI.

Tabela 1. ABIs e conjuntos de instruções compatíveis.

ABI Conjuntos de instrução compatíveis Notas
armeabi
  • ARMV5TE e posterior
  • Thumb-1
  • Obsoleto na versão r16. Removido na versão r17. Sem flutuação de hardware.
    armeabi-v7a
  • armeabi
  • Thumb-2
  • VFPv3-D16
  • Outro, opcional
  • Incompatível com dispositivos ARMv5, v6.
    arm64-v8a
  • AArch64
  • x86
  • x86 (IA-32)
  • MMX
  • SSE/2/3
  • SSSE3
  • Sem compatibilidade com MOVBE ou SSE4.
    x86_64
  • x86-64
  • MMX
  • SSE/2/3
  • SSSE3
  • SSE4.1, 4.2
  • POPCNT
  • Observação: anteriormente, o NDK era compatível com MIPS de 32 e 64 bits, mas essa compatibilidade foi removida no NDK r17.

    Veja informações mais detalhadas sobre cada ABI abaixo.

    armeabi

    Observação: esta ABI foi removida no NDK r17.

    Essa ABI destina-se a CPUs baseadas em ARM compatíveis pelo menos com o conjunto de instruções ARMv5TE. Consulte a documentação a seguir para mais detalhes:

    O padrão AAPCS define EABI como uma família de ABIs parecidas, mas distintas. Além disso, o Android segue a ARM GNU/ABI do Linux little-endian.

    Essa ABI não é compatível com cálculos de ponto flutuante assistido por hardware. Todas as operações de ponto flutuante usam funções auxiliares de software da biblioteca estática libgcc.a do compilador.

    A ABI armeabi é compatível com o conjunto de instruções Thumb (conhecido também como Thumb-1) do ARM. O NDK gera um código Thumb por padrão, a menos que você especifique um comportamento diferente usando a variável LOCAL_ARM_MODE no arquivo Android.mk.

    armeabi-v7a

    Essa ABI estende a armeabi para incluir várias extensões de conjunto de instruções da CPU. As extensões de instrução compatíveis com essa ABI específica do Android são:

    • Extensão do conjunto de instruções Thumb-2, que fornece desempenho semelhante ao das instruções de ARM de 32 bits, com compactação parecida com a do Thumb-1.
    • Instruções VFP da FPU de hardware. Mais especificamente, VFPv3-D16, que contém 16 registros de ponto flutuante de 64 bits dedicados, além de outros 16 registros de 32 bits do núcleo do ARM.

    Outras extensões que a especificação do ARM v7-a descreve, inclusive Advanced SIMD (conhecido também como NEON), VFPv3-D32 e ThumbEE, são opcionais para esta ABI. Como a presença delas não é garantida, o sistema deve verificar, no momento da execução, se as extensões estão disponíveis. Caso não estejam, será necessário usar caminhos de código alternativos. Essa verificação é semelhante àquela que o sistema normalmente faz para conferir ou usar MMX, SSE2 e outros conjuntos de instrução especializados em CPUs de x86.

    Para mais informações sobre como realizar essas verificações no ambiente de execução, consulte a biblioteca cpufeatures. Além disso, para mais informações sobre a compatibilidade do NDK para a compilação de códigos de máquina para NEON, consulte Compatibilidade NEON.

    A ABI armeabi-v7a usa a chave -mfloat-abi=softfp para aplicar a regra que determina que o compilador precisa passar todos os valores duplos em pares de registro principais durante chamadas de função, em vez de pontos flutuantes dedicados. O sistema pode fazer todos os cálculos internos usando os registros de FP. Assim, a velocidade do cálculo aumenta consideravelmente.

    arm64-v8a

    Essa ABI é destinada a CPUs com base em ARMv8 compatíveis com Aarch64. Ela também inclui os conjuntos de instrução NEON e VFPv4.

    Para saber mais, consulte a Prévia da tecnologia ARMv8 e entre em contato com o ARM para saber mais detalhes.

    x86

    Essa ABI é para CPUs compatíveis com o conjunto de instruções conhecido como “x86" ou "IA-32". São caraterísticas dessa ABI:

    • Instruções normalmente geradas por GCC com sinalizações de compilador, por exemplo:
          -march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32
          

      Essas sinalizações têm como alvo o conjunto de instruções Pentium Pro, assim como as extensões de conjunto de instruções MMX, SSE, SSE2, SSE3 e SSSE3. O código gerado é uma otimização balanceada na parte superior das CPUs Intel de 32 bits.

      Para saber mais sobre sinalizações do compilador, especialmente aquelas relacionadas à otimização de desempenho, consulte Dicas de desempenho do GCC x86.

    • Use a convenção de chamada padrão do Linux x86 de 32 bits, em vez daquela do SVR. Para mais informações, consulte a seção 6, "Uso de registros", de Chamar convenções de diferentes compiladores C++ e sistemas operacionais.

    A ABI não contém nenhuma outra extensão de conjunto de instruções IA-32 opcional, como:

    • MOVBE
    • Qualquer variante do SSE4

    Você ainda pode usar essas extensões, desde que use testes de recursos no ambiente de execução para ativá-las, além de fornecer alternativas para dispositivos incompatíveis com elas.

    O conjunto de ferramentas do NDK presume um alinhamento de pilha de 16 bytes antes de uma chamada de função. As ferramentas e opções padrão aplicam essa regra. Se estiver escrevendo código Assembly, será preciso manter o alinhamento de pilhas, além de garantir que outros compiladores também obedeçam a essa regra.

    Consulte os documentos a seguir para saber mais:

    x86_64

    Essa ABI destina-se a CPUs compatíveis com o conjunto de instruções conhecido como “x86-64”. Ela é compatível com instruções que o GCC costuma gerar com as seguintes sinalizações de compilação:

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

    Essas sinalizações têm como alvo o conjunto de instruções x86-64, de acordo com a documentação GCC, assim como as extensões de conjunto de instruções MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2 e POPCNT. O código gerado é uma otimização balanceada na parte superior das CPUs Intel de 64 bits.

    Para saber mais sobre sinalizações de compilador, especialmente aquelas relacionadas à otimização de desempenho, consulte Desempenho do GCC x86.

    Essa ABI não inclui nenhuma extensão de conjunto de instruções x86-64 opcional, como:

    • MOVBE
    • SHA
    • AVX
    • AVX2

    Você ainda pode usar essas extensões, desde que use testes de recursos no ambiente de execução para ativá-las, além de fornecer alternativas para dispositivos incompatíveis com elas.

    Consulte os documentos a seguir para saber mais:

    Gerar código para uma ABI específica

    Por padrão, o NDK destina-se a todas as ABIs não obsoletas. É possível segmentar uma única ABI configurando a APP_ABI no seu arquivo Application.mk. O snippet a seguir mostra alguns exemplos de como usar 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 mais informações sobre os valores que você pode especificar para APP_ABI, consulte Application.mk.

    O comportamento padrão do sistema de compilação é incluir os binários para cada ABI em um único APK, também conhecido como APK multiarquitetura. Um APK multiarquitetura é muito maior do que um que tem somente os binários para uma única ABI. A vantagem dele é o aumento da compatibilidade, embora o APK seja maior. Recomendamos que você aproveite os APKs divididos para reduzir o tamanho dos seus APKs ao mesmo tempo em que mantém o máximo da compatibilidade de dispositivo.

    No momento da instalação, o gerenciador de pacotes descompacta somente o código de máquina mais adequado para o dispositivo em questão. Para saber mais, consulte Extração automática de código nativo em tempo de instalação.

    Gerenciamento de ABI na plataforma Android

    Esta seção traz detalhes sobre como a plataforma Android gerencia código nativo nos APKs.

    Código nativos em pacotes de apps

    Tanto a Play Store quanto o Gerenciador de pacotes esperam encontrar bibliotecas geradas pelo NDK em caminhos de arquivo dentro do APK e que correspondam ao seguinte padrão:

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

    Neste caso, <abi> é um dos nomes de ABI listados em ABIs compatíveis, e <name> é o nome da biblioteca que você definiu para a variável LOCAL_MODULE no arquivo Android.mk. Como os arquivos do APK são do tipo ZIP, é importante abri-los para verificar se as bibliotecas nativas compartilhadas estão no lugar certo.

    Se o sistema não encontrar as bibliotecas nativas compartilhadas no local esperado, não será possível usá-las. Nesse caso, o próprio app tem que copiar as bibliotecas e depois executar dlopen().

    Em APKs multiarquitetura, cada biblioteca reside em um diretório cujo nome corresponde a uma ABI relevante. Por exemplo, um APK multiarquitetura pode conter:

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

    Observação: dispositivos Android baseados em ARMv7 com a versão 4.0.3 ou anterior instalarão bibliotecas nativas do diretório armeabi, em vez de armeabi-v7a, se os dois diretórios existirem. Isso ocorre porque /lib/armeabi/ vem depois de /lib/armeabi-v7a/ no APK. Esse problema foi corrigido a partir da versão 4.0.4.

    Compatibilidade com a ABI da plataforma Android

    O sistema Android descobre as ABIs compatíveis no momento da execução, porque as propriedades do sistema específicas da versão indicam:

    • A ABI principal do dispositivo, correspondente ao código de máquina usado na própria imagem do sistema
    • ABIs secundárias opcionais, correspondentes a outra ABI também compatível com a imagem do sistema

    Esse mecanismo garante que o sistema extraia o melhor código de máquina do pacote no momento de instalação.

    Para o melhor desempenho possível, compile diretamente para a ABI principal. Por exemplo, um dispositivo baseado em ARMv5TE definiria somente a ABI principal: armeabi. Por outro lado, um dispositivo baseado em ARMv7 definiria a ABI principal como armeabi-v7a, e a secundária como armeabi, já que ele pode executar binários nativos do app gerados para cada uma delas.

    Os dispositivos de 64 bits também são compatíveis com as variantes de 32 bits. Por exemplo, dispositivos arm64-v8a também podem executar código armeabi e armeabi-v7a. No entanto, o app terá um desempenho muito melhor em dispositivos de 64 bits se ele for destinado a arm64-v8a, em vez de esperar o dispositivo executar a versão armeabi-v7a do app.

    Muitos dispositivos baseados em x86 também podem executar binários armeabi-v7a e armeabi do NDK. Para esses dispositivos, a ABI principal seria x86, e a secundária seria armeabi-v7a.

    Extração automática de código nativo em tempo de instalação

    Ao instalar um app, o serviço do gerenciador de pacotes verifica o APK e busca bibliotecas compartilhadas com o seguinte formato:

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

    Se nenhuma for encontrada e você tiver definido uma ABI secundária, o serviço procurará bibliotecas compartilhadas com o seguinte formato:

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

    Quando encontra as bibliotecas que está procurando, o gerenciador de pacotes as copia para /lib/lib<name>.so, no diretório data do app (data/data/<package_name>/lib/).

    Se não houver arquivos de objeto compartilhado, o app será compilado e instalado, mas falhará no momento da execução.