Estabilidade ABI

A estabilidade da interface binária do aplicativo (ABI) é um pré-requisito para atualizações somente da estrutura porque os módulos do fornecedor podem depender das bibliotecas compartilhadas do Vendor Native Development Kit (VNDK) que residem na partição do sistema. Em uma versão do Android, as bibliotecas compartilhadas do VNDK recém-criadas devem ser compatíveis com ABI com bibliotecas compartilhadas do VNDK lançadas anteriormente, para que os módulos do fornecedor possam trabalhar com essas bibliotecas sem recompilação e sem erros de tempo de execução. Entre as versões do Android, as bibliotecas VNDK podem ser alteradas e não há garantias de ABI.

Para ajudar a garantir a compatibilidade da ABI, o Android 9 inclui um verificador de ABI de cabeçalho, conforme descrito nas seções a seguir.

Sobre conformidade com VNDK e ABI

O VNDK é um conjunto restritivo de bibliotecas às quais os módulos do fornecedor podem se vincular e que permitem atualizações somente da estrutura. Conformidade com ABI refere-se à capacidade de uma versão mais recente de uma biblioteca compartilhada funcionar conforme esperado com um módulo que está vinculado dinamicamente a ela (ou seja, funciona como uma versão mais antiga da biblioteca funcionaria).

Sobre símbolos exportados

Um símbolo exportado (também conhecido como símbolo global ) refere-se a um símbolo que satisfaz todos os seguintes requisitos:

  • Exportado pelos cabeçalhos públicos de uma biblioteca compartilhada.
  • Aparece na tabela .dynsym do arquivo .so correspondente à biblioteca compartilhada.
  • Possui ligação FRACA ou GLOBAL.
  • A visibilidade é PADRÃO ou PROTEGIDA.
  • O índice da seção não está INDEFINIDO.
  • O tipo é FUNC ou OBJECT.

Os cabeçalhos públicos de uma biblioteca compartilhada são definidos como os cabeçalhos disponíveis para outras bibliotecas/binários por meio dos atributos export_include_dirs , export_header_lib_headers , export_static_lib_headers , export_shared_lib_headers e export_generated_headers nas definições Android.bp do módulo correspondente à biblioteca compartilhada.

Sobre tipos acessíveis

Um tipo acessível é qualquer tipo C/C++ integrado ou definido pelo usuário que pode ser acessado direta ou indiretamente por meio de um símbolo exportado E exportado por meio de cabeçalhos públicos. Por exemplo, libfoo.so possui a função Foo , que é um símbolo exportado encontrado na tabela .dynsym . A biblioteca libfoo.so inclui o seguinte:

foo_exportado.h foo.privado.h
typedef struct foo_private foo_private_t;

typedef struct foo {
  int m1;
  int *m2;
  foo_private_t *mPfoo;
} foo_t;

typedef struct bar {
  foo_t mfoo;
} bar_t;

bool Foo(int id, bar_t *bar_ptr);
typedef struct foo_private {
  int m1;
  float mbar;
} foo_private_t;
Android.bp
cc_library {
  name : libfoo,
  vendor_available: true,
  vndk {
    enabled : true,
  }
  srcs : ["src/*.cpp"],
  export_include_dirs : [
    "exported"
  ],
}
tabela .dynsym
Num Value Size Type Bind Vis Ndx Name
1 0 0 FUNC GLOB DEF UND dlerror@libc
2 1ce0 20 FUNC GLOB DEF 12 Foo

Olhando para Foo , os tipos alcançáveis ​​diretos/indiretos incluem:

Tipo Descrição
bool Tipo de retorno de Foo .
int Tipo do primeiro parâmetro Foo .
bar_t * Tipo do segundo parâmetro Foo. Por meio de bar_t * , bar_t é exportado através de foo_exported.h .

bar_t contém um membro mfoo , do tipo foo_t , que é exportado por meio de foo_exported.h , o que resulta na exportação de mais tipos:
  • int : é o tipo de m1 .
  • int * : é o tipo de m2 .
  • foo_private_t * : é o tipo de mPfoo .

No entanto, foo_private_t NÃO está acessível porque não é exportado através de foo_exported.h . ( foo_private_t * é opaco, portanto, alterações feitas em foo_private_t são permitidas.)

Uma explicação semelhante pode ser dada para tipos acessíveis através de especificadores de classe base e parâmetros de modelo também.

Garantindo a conformidade com a ABI

A conformidade com a ABI deve ser garantida para as bibliotecas marcadas como vendor_available: true e vndk.enabled: true nos arquivos Android.bp correspondentes. Por exemplo:

cc_library {
    name: "libvndk_example",
    vendor_available: true,
    vndk: {
        enabled: true,
    }
}

Para tipos de dados acessíveis direta ou indiretamente por uma função exportada, as seguintes alterações em uma biblioteca são classificadas como quebra de ABI:

Tipo de dados Descrição
Estruturas e Classes
  • Altere o tamanho do tipo de classe ou do tipo de estrutura.
  • Classes básicas
    • Adicione ou remova classes base.
    • Adicione ou remova classes base herdadas virtualmente.
    • Altere a ordem das classes base.
  • Funções dos membros
    • Remover funções de membro*.
    • Adicione ou remova argumentos de funções-membro.
    • Altere os tipos de argumento ou os tipos de retorno das funções de membro*.
    • Altere o layout da mesa virtual.
  • Membros de dados
    • Remova membros de dados estáticos.
    • Adicione ou remova membros de dados não estáticos.
    • Altere os tipos de membros de dados.
    • Altere os deslocamentos para membros de dados não estáticos**.
    • Altere os qualificadores const , volatile e/ou restricted dos membros de dados***.
    • Faça downgrade dos especificadores de acesso dos membros de dados***.
  • Altere os argumentos do modelo.
Sindicatos
  • Adicione ou remova membros de dados.
  • Altere o tamanho do tipo de união.
  • Altere os tipos de membros de dados.
Enumerações
  • Altere o tipo subjacente.
  • Altere os nomes dos recenseadores.
  • Altere os valores dos enumeradores.
Símbolos Globais
  • Remova os símbolos exportados pelos cabeçalhos públicos.
  • Para símbolos globais do tipo FUNC
    • Adicione ou remova argumentos.
    • Altere os tipos de argumentos.
    • Altere o tipo de retorno.
    • Faça downgrade do especificador de acesso***.
  • Para símbolos globais do tipo OBJECT
    • Altere o tipo C/C++ correspondente.
    • Faça downgrade do especificador de acesso***.

* As funções-membro públicas e privadas não devem ser alteradas ou removidas porque as funções in-line públicas podem referir-se a funções-membro privadas. As referências de símbolos a funções-membro privadas podem ser mantidas em binários de chamada. Alterar ou remover funções de membros privados de bibliotecas compartilhadas pode resultar em binários incompatíveis com versões anteriores.

** Os deslocamentos para membros de dados públicos ou privados não devem ser alterados porque funções inline podem fazer referência a esses membros de dados em seu corpo de função. A alteração dos deslocamentos dos membros de dados pode resultar em binários incompatíveis com versões anteriores.

*** Embora isso não altere o layout da memória do tipo, existem diferenças semânticas que podem fazer com que as bibliotecas não funcionem conforme o esperado.

Usando ferramentas de conformidade ABI

Quando uma biblioteca VNDK é criada, a ABI da biblioteca é comparada com a referência de ABI correspondente para a versão do VNDK que está sendo criada. Os dumps ABI de referência estão localizados em:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based

Por exemplo, ao construir libfoo para x86 no nível 27 da API, a ABI inferida de libfoo é comparada com sua referência em:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump

Erro de quebra de ABI

Em quebras de ABI, o log de construção exibe avisos com o tipo de aviso e um caminho para o relatório abi-diff. Por exemplo, se a ABI do libbinder tiver uma alteração incompatível, o sistema de compilação gerará um erro com uma mensagem semelhante a esta:

*****************************************************
error: VNDK library: libbinder.so's ABI has INCOMPATIBLE CHANGES
Please check compatibility report at:
out/soong/.intermediates/frameworks/native/libs/binder/libbinder/android_arm64_armv8-a_cortex-a73_vendor_shared/libbinder.so.abidiff
******************************************************
---- Please update abi references by running
platform/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder ----

Criando verificações de ABI da biblioteca VNDK

Quando uma biblioteca VNDK é criada:

  1. header-abi-dumper processa os arquivos de origem compilados para construir a biblioteca VNDK (os próprios arquivos de origem da biblioteca, bem como os arquivos de origem herdados por meio de dependências transitivas estáticas), para produzir arquivos .sdump que correspondem a cada fonte.
    sdump creation
    Figura 1. Criando os arquivos .sdump
  2. header-abi-linker então processa os arquivos .sdump (usando um script de versão fornecido a ele ou o arquivo .so correspondente à biblioteca compartilhada) para produzir um arquivo .lsdump que registra todas as informações ABI correspondentes à biblioteca compartilhada.
    lsdump creation
    Figura 2. Criando o arquivo .lsdump
  3. header-abi-diff compara o arquivo .lsdump com um arquivo .lsdump de referência para produzir um relatório diff que descreve as diferenças nas ABIs das duas bibliotecas.
    abi diff creation
    Figura 3. Criando o relatório de diferenças

cabeçalho-abi-dumper

A ferramenta header-abi-dumper analisa um arquivo de origem C/C++ e despeja a ABI inferida desse arquivo de origem em um arquivo intermediário. O sistema de compilação executa header-abi-dumper em todos os arquivos de origem compilados enquanto também cria uma biblioteca que inclui os arquivos de origem de dependências transitivas.

Entradas
  • Arquivo de origem AC/C++
  • Exportados incluem diretórios
  • Sinalizadores de compilador
Saída Um arquivo que descreve a ABI do arquivo de origem (por exemplo, foo.sdump representa a ABI de foo.cpp ).

Atualmente, os arquivos .sdump estão no formato JSON, o que não garante estabilidade em versões futuras. Como tal, a formatação do arquivo .sdump deve ser considerada um detalhe de implementação do sistema de construção.

Por exemplo, libfoo.so possui o seguinte arquivo de origem foo.cpp :

#include <stdio.h>
#include <foo_exported.h>

bool Foo(int id, bar_t *bar_ptr) {
    if (id > 0 && bar_ptr->mfoo.m1 > 0) {
        return true;
    }
    return false;
}

Você pode usar header-abi-dumper para gerar um arquivo .sdump intermediário que representa a ABI apresentada pelo arquivo de origem usando:

$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++

Este comando diz ao header-abi-dumper para analisar foo.cpp com os sinalizadores do compilador após -- e emitir as informações ABI que são exportadas pelos cabeçalhos públicos no diretório exported . O seguinte é foo.sdump gerado por header-abi-dumper :

{
 "array_types" : [],
 "builtin_types" :
 [
  {
   "alignment" : 4,
   "is_integral" : true,
   "linker_set_key" : "_ZTIi",
   "name" : "int",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIi",
   "size" : 4
  }
 ],
 "elf_functions" : [],
 "elf_objects" : [],
 "enum_types" : [],
 "function_types" : [],
 "functions" :
 [
  {
   "function_name" : "FooBad",
   "linker_set_key" : "_Z6FooBadiP3foo",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3foo"
    }
   ],
   "return_type" : "_ZTI3bar",
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "global_vars" : [],
 "lvalue_reference_types" : [],
 "pointer_types" :
 [
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP11foo_private",
   "name" : "foo_private *",
   "referenced_type" : "_ZTI11foo_private",
   "self_type" : "_ZTIP11foo_private",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3foo",
   "name" : "foo *",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTIP3foo",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIPi",
   "name" : "int *",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIPi",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "qualified_types" : [],
 "record_types" :
 [
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "mfoo",
     "referenced_type" : "_ZTI3foo"
    }
   ],
   "linker_set_key" : "_ZTI3bar",
   "name" : "bar",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTI3bar",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "m1",
     "referenced_type" : "_ZTIi"
    },
    {
     "field_name" : "m2",
     "field_offset" : 64,
     "referenced_type" : "_ZTIPi"
    },
    {
     "field_name" : "mPfoo",
     "field_offset" : 128,
     "referenced_type" : "_ZTIP11foo_private"
    }
   ],
   "linker_set_key" : "_ZTI3foo",
   "name" : "foo",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTI3foo",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "rvalue_reference_types" : []
}

foo.sdump contém informações ABI exportadas pelo arquivo de origem foo.cpp e pelos cabeçalhos públicos, por exemplo,

  • record_types . Consulte estruturas, uniões ou classes definidas nos cabeçalhos públicos. Cada tipo de registro possui informações sobre seus campos, tamanho, especificador de acesso, arquivo de cabeçalho no qual está definido e outros atributos.
  • pointer_types . Consulte os tipos de ponteiro referenciados direta/indiretamente pelos registros/funções exportados nos cabeçalhos públicos, juntamente com o tipo para o qual o ponteiro aponta (por meio do campo referenced_type em type_info ). Informações semelhantes são registradas no arquivo .sdump para tipos qualificados, tipos C/C++ integrados, tipos de matriz e tipos de referência lvalue e rvalue. Essas informações permitem diferenças recursivas.
  • functions . Representa funções exportadas por cabeçalhos públicos. Eles também possuem informações sobre o nome mutilado da função, o tipo de retorno, os tipos dos parâmetros, o especificador de acesso e outros atributos.

cabeçalho-abi-linker

A ferramenta header-abi-linker pega os arquivos intermediários produzidos pelo header-abi-dumper como entrada e depois vincula esses arquivos:

Entradas
  • Arquivos intermediários produzidos por header-abi-dumper
  • Script de versão/arquivo de mapa (opcional)
  • Arquivo .so da biblioteca compartilhada
  • Exportados incluem diretórios
Saída Um arquivo que descreve a ABI de uma biblioteca compartilhada (por exemplo, libfoo.so.lsdump representa a ABI de libfoo ).

A ferramenta mescla os gráficos de tipo em todos os arquivos intermediários fornecidos a ela, levando em consideração diferenças de uma definição (tipos definidos pelo usuário em diferentes unidades de tradução com o mesmo nome totalmente qualificado, podem ser semanticamente diferentes) entre unidades de tradução. A ferramenta então analisa um script de versão ou a tabela .dynsym da biblioteca compartilhada (arquivo .so ) para fazer uma lista dos símbolos exportados.

Por exemplo, libfoo consiste em foo.cpp e bar.cpp . header-abi-linker pode ser invocado para criar o dump ABI vinculado completo de libfoo da seguinte maneira:

header-abi-linker -I exported foo.sdump bar.sdump \
                  -o libfoo.so.lsdump \
                  -so libfoo.so \
                  -arch arm64 -api current

Exemplo de saída de comando em libfoo.so.lsdump :

{
 "array_types" : [],
 "builtin_types" :
 [
  {
   "alignment" : 1,
   "is_integral" : true,
   "is_unsigned" : true,
   "linker_set_key" : "_ZTIb",
   "name" : "bool",
   "referenced_type" : "_ZTIb",
   "self_type" : "_ZTIb",
   "size" : 1
  },
  {
   "alignment" : 4,
   "is_integral" : true,
   "linker_set_key" : "_ZTIi",
   "name" : "int",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIi",
   "size" : 4
  }
 ],
 "elf_functions" :
 [
  {
   "name" : "_Z3FooiP3bar"
  },
  {
   "name" : "_Z6FooBadiP3foo"
  }
 ],
 "elf_objects" : [],
 "enum_types" : [],
 "function_types" : [],
 "functions" :
 [
  {
   "function_name" : "Foo",
   "linker_set_key" : "_Z3FooiP3bar",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3bar"
    }
   ],
   "return_type" : "_ZTIb",
   "source_file" : "exported/foo_exported.h"
  },
  {
   "function_name" : "FooBad",
   "linker_set_key" : "_Z6FooBadiP3foo",
   "parameters" :
   [
    {
     "referenced_type" : "_ZTIi"
    },
    {
     "referenced_type" : "_ZTIP3foo"
    }
   ],
   "return_type" : "_ZTI3bar",
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "global_vars" : [],
 "lvalue_reference_types" : [],
 "pointer_types" :
 [
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP11foo_private",
   "name" : "foo_private *",
   "referenced_type" : "_ZTI11foo_private",
   "self_type" : "_ZTIP11foo_private",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3bar",
   "name" : "bar *",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTIP3bar",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIP3foo",
   "name" : "foo *",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTIP3foo",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "linker_set_key" : "_ZTIPi",
   "name" : "int *",
   "referenced_type" : "_ZTIi",
   "self_type" : "_ZTIPi",
   "size" : 8,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "qualified_types" : [],
 "record_types" :
 [
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "mfoo",
     "referenced_type" : "_ZTI3foo"
    }
   ],
   "linker_set_key" : "_ZTI3bar",
   "name" : "bar",
   "referenced_type" : "_ZTI3bar",
   "self_type" : "_ZTI3bar",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  },
  {
   "alignment" : 8,
   "fields" :
   [
    {
     "field_name" : "m1",
     "referenced_type" : "_ZTIi"
    },
    {
     "field_name" : "m2",
     "field_offset" : 64,
     "referenced_type" : "_ZTIPi"
    },
    {
     "field_name" : "mPfoo",
     "field_offset" : 128,
     "referenced_type" : "_ZTIP11foo_private"
    }
   ],
   "linker_set_key" : "_ZTI3foo",
   "name" : "foo",
   "referenced_type" : "_ZTI3foo",
   "self_type" : "_ZTI3foo",
   "size" : 24,
   "source_file" : "exported/foo_exported.h"
  }
 ],
 "rvalue_reference_types" : []
}

A ferramenta header-abi-linker :

  • Vincula os arquivos .sdump fornecidos a ele ( foo.sdump e bar.sdump ), filtrando as informações ABI não presentes nos cabeçalhos residentes no diretório: exported .
  • Analisa libfoo.so e coleta informações sobre os símbolos exportados pela biblioteca por meio de sua tabela .dynsym .
  • Adiciona _Z3FooiP3bar e _Z6FooBadiP3foo .

libfoo.so.lsdump é o dump ABI final gerado de libfoo.so .

cabeçalho-abi-diff

A ferramenta header-abi-diff compara dois arquivos .lsdump que representam a ABI de duas bibliotecas e produz um relatório diff informando as diferenças entre as duas ABIs.

Entradas
  • Arquivo .lsdump que representa a ABI de uma biblioteca compartilhada antiga.
  • Arquivo .lsdump que representa a ABI de uma nova biblioteca compartilhada.
Saída Um relatório diff indicando as diferenças nas ABIs oferecidas pelas duas bibliotecas compartilhadas comparadas.

O arquivo diff ABI está no formato de texto protobuf . O formato está sujeito a alterações em versões futuras.

Por exemplo, você tem duas versões de libfoo : libfoo_old.so e libfoo_new.so . Em libfoo_new.so , em bar_t , você altera o tipo de mfoo de foo_t para foo_t * . Como bar_t é um tipo acessível, isso deve ser sinalizado como uma alteração significativa da ABI por header-abi-diff .

Para executar header-abi-diff :

header-abi-diff -old libfoo_old.so.lsdump \
                -new libfoo_new.so.lsdump \
                -arch arm64 \
                -o libfoo.so.abidiff \
                -lib libfoo

Exemplo de saída de comando em libfoo.so.abidiff :

lib_name: "libfoo"
arch: "arm64"
record_type_diffs {
  name: "bar"
  type_stack: "Foo-> bar *->bar "
  type_info_diff {
    old_type_info {
      size: 24
      alignment: 8
    }
    new_type_info {
      size: 8
      alignment: 8
    }
  }
  fields_diff {
    old_field {
      referenced_type: "foo"
      field_offset: 0
      field_name: "mfoo"
      access: public_access
    }
    new_field {
      referenced_type: "foo *"
      field_offset: 0
      field_name: "mfoo"
      access: public_access
    }
  }
}

O libfoo.so.abidiff contém um relatório de todas as alterações significativas da ABI em libfoo . A mensagem record_type_diffs indica que um registro foi alterado e lista as alterações incompatíveis, que incluem:

  • O tamanho do registro mudando de 24 bytes para 8 bytes.
  • O tipo de campo mfoo mudando de foo para foo * (todos os typedefs são removidos).

O campo type_stack indica como header-abi-diff atingiu o tipo que mudou ( bar ). Este campo pode ser interpretado como Foo é uma função exportada que leva bar * como parâmetro, que aponta para bar , que foi exportado e alterado.

Aplicação de ABI/API

Para impor a ABI/API de bibliotecas compartilhadas do VNDK, as referências da ABI devem ser verificadas em ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/ . Para criar essas referências, execute o seguinte comando:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py

Depois de criar as referências, qualquer alteração feita no código-fonte que resulte em uma alteração incompatível de ABI/API em uma biblioteca VNDK agora resulta em um erro de compilação.

Para atualizar referências ABI para bibliotecas específicas, execute o seguinte comando:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>

Por exemplo, para atualizar as referências da ABI libbinder , execute:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder