Gerenciamento de ABI

Diferentes celulares Android usam diferentes CPUs que, por sua vez, oferecem compatibilidade com diferentes conjuntos de instrução. Cada combinação de CPU e conjuntos de instrução tem sua própria Interface Binária do Aplicativo, ou ABI. A ABI define, com muita precisão, como o código de máquina de um aplicativo deve interagir com o sistema em tempo de execução. É necessário especificar uma ABI para cada arquitetura de CPU com que você quer que o aplicativo trabalhe.

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.
  • A ordenação (endianness) de armazenamentos e cargas de memória em tempo de execução.
  • O formato de binários executáveis, como programas e bibliotecas compartilhadas, e os tipos de conteúdo que suportam.
  • Diversas convenções para passar dados entre seu código e o sistema. Essas convenções incluem restrições de alinhamento e como o sistema usa a pilha e faz registros quando ela chama funções.
  • A lista de símbolos de função disponibilizada ao seu código de máquina em tempo de execução, geralmente de conjuntos de bibliotecas muito específicos.

Esta página enumera as ABIs com que o NDK tem compatibilidade e fornece informações sobre como cada ABI funciona.

ABIs compatíveis

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

Tabela 1. ABIs e conjuntos de instrução compatíveis.

ABI Conjuntos de instrução compatíveis Observações
armeabi
  • ARMV5TE e posterior
  • Thumb-1
  • Sem flutuação de hardware.
    armeabi-v7a
  • armeabi
  • Thumb-2
  • VFPv3-D16
  • Outro, opcional
  • Incompatível com dispositivos ARMv5, v6.
    arm64-v8a
  • AArch-64
  • 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
  • mips
  • MIPS32r1 e posteriores
  • Usa flutuação de hardware assume uma taxa de clock de CPU:FPU de 2:1 para máxima compatibilidade. Não fornece micromips nem MIPS16.
    mips64
  • MIPS64r6
  • Veja mais informações detalhadas sobre cada ABI abaixo.

    armeabi

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

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

    Essa ABI não é compatível com computações de ponto flutuante assistido por hardware. Em vez disso, 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 da ARM (conhecido como Thumb-1). O NDK gera o código do Thumb por padrão, a menos que você especifique um comportamento diferente com a variável LOCAL_ARM_MODE no arquivo Android.mk .

    armeabi-v7a

    Essa ABI estende o armeabi para incluir diversas extensões ao conjunto de instruções do CPU. As extensões de instrução que essa ABI específica do Android suporta são:

    • A extensão do conjunto de instruções Thumb-2, que fornece desempenho comparável às instruções de ARM de 32 bits, com compactação similar ao Thumb-1.
    • As instruções VFP de hardware FPU. Mais especificamente, o 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 da ARM.

    Outras extensões que a especificação ARM v7-a descreve, inclusive SIMD avançado (conhecido como NEON), VFPv3-D32 e ThumbEE, opcionais a essa ABI. Como a presença deles não é garantida, o sistema deve verificar, em tempo de execução, se as extensões estão disponíveis. Se não, você deve usar caminhos de código alternativos. Essa verificação é semelhante à que o sistema normalmente faz para conferir ou usar MMX, SSE2 e outros conjuntos de instrução especializados em CPUs de x86.

    Para obter informações sobre a realização dessas verificações em tempo de execução, consulte A biblioteca cpufeatures. Além disso, para saber mais sobre o suporte do NDK para compilação de código de máquina com NEON, consulte Compatibilidade com NEON.

    A ABI armeabi-v7a usa a chave -mfloat-abi=softfp para impor a regra de que o compilador deve passar todos os valores duplos em pares de registro principais durante chamadas de função, em vez de pontos flutuantes dedicados. O sistema pode realizar todas as computações internas usando os registros de FP. Assim, a velocidade da computação 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 contate a ARM para obter mais detalhes.

    x86

    Essa ABI destina-se a CPUs compatíveis com conjuntos de instrução comumente chamados de “x86” ou “IA-32”. São caraterísticas dessa ABI:

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

      Esses sinalizadores visam o conjunto de instruções Pentium Pro, junto com as extensões MMX, SSE, SSE2, SSE3 e SSSE5. O código gerado é uma otimização balanceada na parte superior dos CPUs Intel de 32 bits.

      Para saber mais sobre sinalizadores de compilador, especialmente os relacionados a otimização de desempenho, consulte Dicas de desempenho com GCC x86.

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

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

    • MOVBE
    • Qualquer variante do SSE4.

    Ainda é possível usar essas extensões, desde que use testes de recursos em tempo de execução para ativá-las, além de fornecer fallbacks para dispositivos incompatíveis com elas.

    A cadeia de ferramentas do NDK assume alinhamento de pilha de 16 bytes antes de uma chamada de função. As ferramentas e opções padrão impõem essa regra. Se estiver escrevendo código de assembly, você não pode deixar de 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 é para CPUs compatíveis com o conjunto de instruções comumente conhecido como “x86-64”. Ela é compatível com instruções que o GCC normalmente gera com os seguintes sinalizadores de compilador:

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

    Esses sinalizadores visam o conjunto de instruções x86-64, de acordo com a documentação do GCC, junto com as extensões MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, e POPCNT . O código gerado é uma otimização balanceada na parte superior dos CPUs Intel de 64 bits.

    Para saber mais sobre sinalizadores de compilador, especialmente os relacionados a 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 em tempo de execução para ativá-las, além de fornecer fallbacks para dispositivos não compatíveis com elas.

    Consulte os documentos a seguir para saber mais:

    mips

    Essa ABI é direcionada a CPUs baseados em MIPS compatíveis com, no mínimo, o conjunto de instruções MIPS32r1. Ela contém os seguintes recursos:

    • Revisão 1 de ISA do MIPS32
    • Little-endian
    • O32
    • Flutuação de hardware
    • Não há extensões DSP específicas de aplicativos

    Para saber mais, consulte a documentação a seguir:

    Para obter detalhes mais específicos, consulte Arquitetura MIPS32. Você pode encontrar respostas de perguntas comuns em Perguntas frequentes sobre MIPS.

    mips64

    Essa ABI é para MIPS64 R6. Para saber mais, consulte Arquitetura MIPS64.

    Geração de código para uma ABI específica

    Por padrão, o NDK gera código de máquina para a ABI armeabi. Em vez disso, você pode gerar código de máquina compatível com ARMv7-a adicionando a seguinte linha ao arquivo Application.mk.

    APP_ABI := armeabi-v7a
    

    Para criar código de máquina para duas ou mais ABIs distintas usando espaços como delimitadores. Por exemplo:

    APP_ABI := armeabi armeabi-v7a
    

    Essa configuração diz ao NDK para compilar duas versões do seu código de máquina: uma para cada ABI listada nesta linha. Para saber mais sobre os valores que você pode especificar para a variável APP_ABI, consulte Android.mk.

    Ao compilar diversas versões do código de máquina, o sistema de compilação copia a biblioteca para o caminho do projeto do seu aplicativo e, por fim, empacota-as no APK, criando, assim, um binário fat. Um binário fat é maior do que um que contém somente o código de máquina de um único sistema. A vantagem é ganhar maior compatibilidade, mas gerando um APK maior.

    No tempo de instalação, o gerente do pacote desempacota 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 fornece detalhes sobre como a plataforma Android gerencia código nativo nos APKs.

    Código nativos em pacotes de aplicativo

    Tanto a Play Store quanto o Package Manager esperam encontrar bibliotecas geradas pelo NDK em caminhos de arquivo dentro da APK correspondentes ao seguinte padrão:

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

    Aqui, <abi> é o nome de uma das ABIs listadas 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 APK são apenas arquivos compactados, é normal abri-los para confirmar que as bibliotecas nativas compartilhadas estão no lugar certo.

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

    Em um binário fat, cada biblioteca reside em uma pasta cujo nome corresponde a uma ABI. Por exemplo, um binário fat 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
    /lib/mips/libfoo.so
    /lib/mips64/libfoo.so
    

    Observação: dispositivos Android baseados em ARMv7 com a versão 4.0.3 ou anterior instalam bibliotecas nativas no diretório armeabi em vez de armeabi-v7a, se os dois diretórios existirem. Isso 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 ABI da plataforma Android

    O sistema Android sabe, em tempo de execução, que ABIs aceita 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.
    • Uma ABI secundária opcional, correspondente a outra ABI que a imagem do sistema também suporta.

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

    Para máximo desempenho, você deve compilar diretamente para a ABI principal. Por exemplo, um dispositivo baseado em ARMv5TE comum só poderia definir a ABI principal: armeabi. Por outro lado, um dispositivo baseado em ARMv7 comum definiria a ABI principal como armeabi-v7a e a secundária como armeabi, já que pode executar binários nativos do aplicativo gerados para cada uma delas.

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

    Um dispositivo comum baseado em MIPS só define uma ABI principal: mips.

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

    Ao instalar um aplicativo, o serviço de gerenciador de pacotes examina o APK e busca bibliotecas compartilhadas com a seguinte forma:

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

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

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

    Quando encontra as bibliotecas que está procurando, o gerenciador de pacotes copia-as para /lib/lib<name>.so, na pasta data do aplicativo (data/data/<package_name>/lib/).

    Se não houver nenhum arquivo de objeto compartilhado, o aplicativo compila e instala, mas falha em tempo de execução.