A estabilidade da Interface binária do aplicativo (ABI) é um pré-requisito das atualizações somente de framework, 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 precisam ser compatíveis com ABI com as bibliotecas compartilhadas do VNDK lançadas anteriormente para que os módulos do fornecedor funcionem com essas bibliotecas sem recompilação e sem erros de execução. Entre as versões do Android, as bibliotecas do VNDK podem ser mudadas, e não há garantias de ABI.
Para ajudar a garantir a compatibilidade de ABI, o Android 9 inclui um verificador de ABI de cabeçalho, conforme descrito nas seções a seguir.
Sobre o compliance com VNDK e ABI
O VNDK é um conjunto restritivo de bibliotecas que os módulos do fornecedor podem vincular e que permitem atualizações somente do framework. A conformidade com a ABI se refere à capacidade de uma versão mais recente de uma biblioteca compartilhada funcionar como esperado com um módulo vinculado dinamicamente a ela (ou seja, funciona como uma versão mais antiga da biblioteca).
Sobre símbolos exportados
Um símbolo exportado (também conhecido como símbolo global) se refere a um símbolo que atende a todos os seguintes requisitos:
- Exportado pelos cabeçalhos públicos de uma biblioteca compartilhada.
- Aparece na tabela
.dynsymdo arquivo.socorrespondente à biblioteca compartilhada. - Tem vinculação WEAK ou GLOBAL.
- A visibilidade é DEFAULT ou PROTECTED.
- O índice da seção não é UNDEFINED.
- 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 pelos 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 os tipos de alcance
Um tipo acessível é qualquer tipo integrado ou definido pelo usuário em C/C++ que seja
acessível direta ou indiretamente por um símbolo exportado E exportado
por cabeçalhos públicos. Por exemplo, libfoo.so tem a função Foo, que é um símbolo exportado encontrado na tabela .dynsym. A biblioteca libfoo.so inclui o
seguinte:
| foo_exported.h | foo.private.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
|
Analisando Foo, os tipos de alcance direto/indireto 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 por foo_exported.h.
bar_t contém um membro mfoo, do tipo
foo_t, que é exportado por foo_exported.h,
o que resulta na exportação de mais tipos:
No entanto, foo_private_t NÃO pode ser acessado porque não é
exportado por foo_exported.h. (foo_private_t * é opaco. Portanto, as mudanças feitas em foo_private_t são permitidas.)
|
Uma explicação semelhante pode ser dada para tipos acessíveis por especificadores de classe base e parâmetros de modelo também.
Garantir a conformidade com a ABI
A conformidade com a ABI precisa ser garantida para as bibliotecas marcadas
vendor_available: true e vndk.enabled: true nos
arquivos Android.bp correspondentes. 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 mudanças em uma biblioteca são classificadas como incompatíveis com a ABI:
| Tipo de dado | Descrição |
|---|---|
| Estruturas e classes |
|
| União |
|
| Enumerações |
|
| Símbolos globais |
|
* As funções de membro públicas e privadas não podem ser mudadas ou removidas porque as funções inline públicas podem se referir a funções de membro privadas. As referências de símbolos a funções de membros particulares podem ser mantidas em binários de chamadores. Mudar ou remover funções de membros particulares de bibliotecas compartilhadas pode resultar em binários incompatíveis com versões anteriores.
** Os deslocamentos para membros de dados públicos ou particulares não podem ser alterados porque as funções inline podem se referir a esses membros no corpo da função. Mudar os deslocamentos de membros de dados pode resultar em binários incompatíveis com versões anteriores.
*** Embora isso não mude o layout da memória do tipo, há diferenças semânticas que podem fazer com que as bibliotecas não funcionem como esperado.
Usar ferramentas de conformidade com ABI
Quando uma biblioteca VNDK é criada, a ABI dela é comparada com a referência de ABI correspondente para a versão do VNDK que está sendo criada. Os despejos de 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 criar libfoo para x86 no nível 27 da API, a ABI inferida de libfoo é comparada com a referência em:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Erro de quebra de ABI
Em caso de violações de ABI, o registro de build mostra avisos com o tipo e um caminho para o relatório abi-diff. Por exemplo, se a ABI de libbinder tiver
uma mudança incompatível, o sistema de build vai 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 ----
Criar verificações de ABI da biblioteca VNDK
Quando uma biblioteca VNDK é criada:
- O
header-abi-dumperprocessa os arquivos de origem compilados para criar a biblioteca VNDK (os arquivos de origem da própria biblioteca e os arquivos de origem herdados por dependências transitivas estáticas), para produzir arquivos.sdumpque correspondem a cada origem.
Figura 1. Como criar os arquivos .sdump - Em seguida, o
header-abi-linkerprocessa os arquivos.sdump(usando um script de versão fornecido ou o arquivo.socorrespondente à biblioteca compartilhada) para produzir um arquivo.lsdumpque registra todas as informações de ABI correspondentes à biblioteca compartilhada.
Figura 2. Como criar o arquivo .lsdump header-abi-diffcompara o arquivo.lsdumpcom um arquivo.lsdumpde referência para gerar um relatório de diferenças que descreve as diferenças nas ABIs das duas bibliotecas.
Figura 3. Criar o relatório de diff
header-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 build
executa header-abi-dumper em todos os arquivos de origem compilados e
cria uma biblioteca que inclui os arquivos de origem de dependências
transitivas.
| Entradas |
|
|---|---|
| Saída | Um arquivo que descreve a ABI do arquivo de origem. Por exemplo, foo.sdump representa a ABI de foo.cpp.
|
No momento, os arquivos .sdump estão no formato JSON, que não tem garantia de estabilidade em versões futuras. Assim, a formatação de arquivos .sdump
deve ser considerada um detalhe de implementação do sistema de build.
Por exemplo, libfoo.so tem 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++
Esse comando informa ao header-abi-dumper para analisar
foo.cpp com as flags do compilador após -- e
emitir as informações de ABI exportadas pelos cabeçalhos públicos no diretório
exported. Confira a seguir um
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 de ABI exportadas pelo arquivo de origem
foo.cpp e os cabeçalhos públicos, por exemplo,
record_types. Consulte structs, uniões ou classes definidas nos cabeçalhos públicos. Cada tipo de registro tem informações sobre os campos, o tamanho, o especificador de acesso, o arquivo de cabeçalho em que ele é definido e outros atributos.pointer_types. Consulte os tipos de ponteiro referenciados direta/indiretamente pelos registros/funções exportados nos cabeçalhos públicos, junto com o tipo para que o ponteiro aponta (pelo camporeferenced_typeemtype_info). Informações semelhantes são registradas no arquivo.sdumppara tipos qualificados, tipos C/C++ integrados, tipos de matriz e tipos de referência lvalue e rvalue. Essas informações permitem a diferenciação recursiva.functions. Representa funções exportadas por cabeçalhos públicos. Eles também têm informações sobre o nome corrompido da função, o tipo de retorno, os tipos de parâmetros, o especificador de acesso e outros atributos.
header-abi-linker
A ferramenta header-abi-linker usa como entrada os arquivos intermediários produzidos
pelo header-abi-dumper e os vincula:
| Entradas |
|
|---|---|
| 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, considerando as 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 as unidades de tradução. Em seguida, a ferramenta analisa um script de versão ou a tabela .dynsym da biblioteca compartilhada (arquivo .so) para criar 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 despejo 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 do 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
.sdumpfornecidos (foo.sdumpebar.sdump), filtrando as informações de ABI não presentes nos cabeçalhos que estão no diretório:exported. - Analisa
libfoo.soe coleta informações sobre os símbolos exportados pela biblioteca na tabela.dynsym. _Z3FooiP3bare_Z6FooBadiP3fooforam adicionados
libfoo.so.lsdump é o arquivo dump da ABI gerado final de
libfoo.so.
header-abi-diff
A ferramenta header-abi-diff compara dois arquivos .lsdump
que representam a ABI de duas bibliotecas e produz um relatório de diff com as
diferenças entre as duas ABIs.
| Entradas |
|
|---|---|
| Saída | Um relatório de diff que indica as diferenças nas ABIs oferecidas pelas duas bibliotecas compartilhadas comparadas. |
O arquivo de diferenças de ABI está no formato de texto protobuf. O formato está sujeito a mudanças 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, mude o tipo de mfoo de
foo_t para foo_t *. Como bar_t é um
tipo acessível, isso precisa ser sinalizado como uma mudança interruptiva 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 do 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 mudanças
de quebra de ABI em libfoo. A mensagem record_type_diffs indica que um registro mudou e lista as alterações incompatíveis, que incluem:
- O tamanho do registro mudou de
24bytes para8bytes. - O tipo de campo de
mfoomudou defooparafoo *(todos os typedefs foram removidos).
O campo type_stack indica como header-abi-diff
chegou ao tipo que mudou (bar). Esse campo pode ser
interpretado como Foo é uma função exportada que usa
bar * como parâmetro, que aponta para bar, que foi
exportado e mudou.
Aplicar ABI e API
Para aplicar a ABI e a API das bibliotecas compartilhadas do VNDK, as referências de ABI precisam
ser verificadas em ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/.
Para criar essas referências, execute o comando a seguir:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
Depois de criar as referências, qualquer mudança feita no código-fonte que resulte em uma mudança incompatível de ABI/API em uma biblioteca VNDK vai gerar um erro de build.
Para atualizar referências de 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 de ABI libbinder, execute:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder