A estabilidade da ABI (Application Binary Interface) é um pré-requisito das atualizações somente de 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 VNDK recém-criadas devem ser compatíveis com ABI para bibliotecas compartilhadas 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 do VNDK podem ser alteradas e não há garantias de ABI.
Para ajudar a garantir a compatibilidade com ABI, o Android 9 inclui um verificador de ABI de cabeçalho, conforme descrito nas seções a seguir.
Sobre a 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. A conformidade com ABI refere-se à capacidade de uma versão mais recente de uma biblioteca compartilhada funcionar conforme o esperado com um módulo que está dinamicamente vinculado 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 ) refere-se a um símbolo que satisfaça todos os requisitos a seguir:
- Exportado pelos cabeçalhos públicos de uma biblioteca compartilhada.
- Aparece na tabela
.dynsymdo arquivo.socorrespondente à biblioteca compartilhada. - Tem encadernação FRACA ou GLOBAL.
- A visibilidade é PADRÃO ou PROTEGIDA.
- O índice de seção não é 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 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 alcançáveis
Um tipo alcançável é qualquer tipo interno ou definido pelo usuário do C/C++ que pode ser alcançado direta ou indiretamente por meio de um símbolo exportado E exportado por meio de 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 : [
"include"
],
}
|
| 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 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 através 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:
No entanto, foo_private_t NÃO é alcançável porque não é exportado através foo_exported.h . ( foot_private_t * é opaco, portanto, as alterações feitas em foo_private_t são permitidas.) |
Uma explicação semelhante pode ser dada para tipos alcançáveis por meio de especificadores de classe base e parâmetros de modelo também.
Garantindo a conformidade com a ABI
A conformidade com 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 |
|
| Sindicatos |
|
| Enumerações |
|
| Símbolos Globais |
|
* As funções de membro públicas e privadas não devem ser alteradas ou removidas porque as funções inline públicas podem se referir a funções de membro privadas. As referências de símbolo para funções de membro privadas podem ser mantidas em binários do chamador. Alterar ou remover funções de membro privado 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 se referir a esses membros de dados em seu corpo de função. Alterar os deslocamentos dos membros de dados pode resultar em binários incompatíveis com versões anteriores.
*** Embora isso não altere o layout de 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 é compilada, a ABI da biblioteca é comparada com a referência ABI correspondente para a versão do VNDK que está sendo compilada. Os dumps da ABI de referência estão localizados em:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based
Por exemplo, ao construir libfoo para a API nível 27 do VNDK, a ABI inferida de libfoo é comparada com sua referência em:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/source-based/libfoo.so.lsdump
Erro de quebra ABI
Em quebras de ABI, o log de compilaçã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 lançará um erro com uma mensagem semelhante à seguinte:
***************************************************** 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 ----
Como criar verificações de ABI da biblioteca VNDK
Quando uma biblioteca VNDK é criada:
-
header-abi-dumperprocessa 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.sdumpque correspondem a cada origem.
Figura 1. Criando os arquivos .sdump -
header-abi-linkerentão processa os arquivos.sdump(usando um script de versão fornecido a ele ou o arquivo.socorrespondente à biblioteca compartilhada) para produzir um arquivo.lsdumpque registra todas as informações de ABI correspondentes à biblioteca compartilhada.
Figura 2. Criando o arquivo .lsdump -
header-abi-diffcompara o arquivo.lsdumpcom um arquivo.lsdumpde referência para produzir um relatório de comparação que descreve as diferenças nas ABIs das duas bibliotecas.
Figura 3. Criando o relatório de comparação
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 das dependências transitivas.
Atualmente, os arquivos .sdump são formatados como Protobuf TextFormatted , 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 compilação.
Por exemplo, libfoo.so tem o seguinte arquivo fonte 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 -- -x c++
Este comando diz header-abi-dumper para analisar foo.cpp e emitir as informações de ABI que são expostas nos cabeçalhos públicos no diretório exported . Este é um trecho (não uma representação completa) de foo.sdump gerado por header-abi-dumper :
record_types {
type_info {
name: "foo"
size: 12
alignment: 4
referenced_type: "type-1"
source_file: "foo/include/foo_exported.h"
linker_set_key: "foo"
self_type: "type-1"
}
fields {
referenced_type: "type-2"
field_offset: 0
field_name: "m1"
access: public_access
}
fields {
referenced_type: "type-3"
field_offset: 32
field_name: "m2"
access: public_access
}
fields {
referenced_type: "type-5"
field_offset: 64
field_name: "mPfoo"
access: public_access
}
access: public_access
record_kind: struct_kind
tag_info {
unique_id: "_ZTS3foo"
}
}
record_types {
type_info {
name: "bar"
size: 12
alignment: 4
referenced_type: "type-6"
…
pointer_types {
type_info {
name: "bar *"
size: 4
alignment: 4
referenced_type: "type-6"
source_file: "foo/include/foo_exported.h"
linker_set_key: "bar *"
self_type: "type-8"
}
}
builtin_types {
type_info {
name: "int"
size: 4
alignment: 4
referenced_type: "type-2"
source_file: ""
linker_set_key: "int"
self_type: "type-2"
}
is_unsigned: false
is_integral: true
}
functions {
return_type: "type-7"
function_name: "Foo"
source_file: "foo/include/foo_exported.h"
parameters {
referenced_type: "type-2"
default_arg: false
}
parameters {
referenced_type: "type-8"
default_arg: false
}
linker_set_key: "_Z3FooiP3bar"
access: public_access
}
foo.sdump contém informações de ABI expostas pelo arquivo fonte foo.cpp , por exemplo:
-
record_types. Consulte estruturas, uniões ou classes expostas pelos cabeçalhos públicos. Cada tipo de registro possui informações sobre seus campos, seu tamanho, especificador de acesso, o arquivo de cabeçalho em que foi exposto, etc. -
pointer_types. Consulte os tipos de ponteiro referenciados direta/indiretamente por registros/funções expostos por cabeçalhos públicos, juntamente com o tipo para o qual o ponteiro aponta (por meio do camporeferenced_typeemtype_info). Informações semelhantes são registradas no arquivo.sdumppara tipos qualificados, tipos C/C++ internos, tipos de matriz e tipos de referência lvalue e rvalue (essas informações de log sobre tipos permitem diferenciação recursiva). -
functions. Representam funções expostas por cabeçalhos públicos. Eles também têm informações sobre o nome desconfigurado da função, o tipo de retorno, os tipos dos parâmetros, o especificador de acesso, etc.
header-abi-linker
A ferramenta header-abi-linker pega os arquivos intermediários produzidos pelo header-abi-dumper como entrada e vincula esses arquivos:
| Entradas |
|
|---|---|
| Saída | Um arquivo que registra a ABI de uma biblioteca compartilhada (por exemplo, libfoo.so.lsdump representa a ABI da libfoo ). |
A ferramenta mescla os gráficos de tipos em todos os arquivos intermediários fornecidos a ela, levando em consideração 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. 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, quando libfoo adiciona o arquivo bar.cpp (que expõe uma bar de função C ) à sua compilação, header-abi-linker pode ser invocado para criar o dump ABI vinculado completo de libfoo da seguinte forma:
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 :
record_types {
type_info {
name: "foo"
size: 24
alignment: 8
referenced_type: "type-1"
source_file: "foo/include/foo_exported.h"
linker_set_key: "foo"
self_type: "type-1"
}
fields {
referenced_type: "type-2"
field_offset: 0
field_name: "m1"
access: public_access
}
fields {
referenced_type: "type-3"
field_offset: 64
field_name: "m2"
access: public_access
}
fields {
referenced_type: "type-4"
field_offset: 128
field_name: "mPfoo"
access: public_access
}
access: public_access
record_kind: struct_kind
tag_info {
unique_id: "_ZTS3foo"
}
}
record_types {
type_info {
name: "bar"
size: 24
alignment: 8
...
builtin_types {
type_info {
name: "void"
size: 0
alignment: 0
referenced_type: "type-6"
source_file: ""
linker_set_key: "void"
self_type: "type-6"
}
is_unsigned: false
is_integral: false
}
functions {
return_type: "type-19"
function_name: "Foo"
source_file: "foo/include/foo_exported.h"
parameters {
referenced_type: "type-2"
default_arg: false
}
parameters {
referenced_type: "type-20"
default_arg: false
}
linker_set_key: "_Z3FooiP3bar"
access: public_access
}
functions {
return_type: "type-6"
function_name: "FooBad"
source_file: "foo/include/foo_exported_bad.h"
parameters {
referenced_type: "type-2"
default_arg: false
}
parameters {
referenced_type: "type-7"
default_arg: false
}
linker_set_key: "_Z6FooBadiP3foo"
access: public_access
}
elf_functions {
name: "_Z3FooiP3bar"
}
elf_functions {
name: "_Z6FooBadiP3foo"
}
A ferramenta header-abi-linker :
- Vincula os arquivos
.sdumpfornecidos a ele (foo.sdumpebar.sdump), filtrando as informações ABI não presentes nos cabeçalhos que residem no diretório:exported. - Analisa
libfoo.soe coleta informações sobre os símbolos exportados pela biblioteca por meio de sua tabela.dynsym. - Adiciona
_Z3FooiP3bareBar.
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 |
|
|---|---|
| Saída | Um relatório de diferenças informando as diferenças nas ABIs oferecidas pelas duas bibliotecas compartilhadas comparadas. |
O arquivo diff ABI foi projetado para ser o mais detalhado e legível possível. 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 diretamente alcançável, isso deve ser sinalizado como uma alteração de quebra de 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 de quebra de ABI no 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
24bytes para8bytes. - O tipo de campo de
mfoomudando defooparafoo *(todos os typedefs são removidos).
O campo type_stack indica como header-abi-diff alcançou o tipo que mudou ( bar ). Este campo pode ser interpretado como Foo é uma função exportada que recebe como parâmetro bar * , que aponta para bar , que foi exportado e alterado.
Aplicação de ABI/API
Para impor a ABI/API das bibliotecas compartilhadas VNDK e LLNDK, as referências ABI devem ser verificadas em ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/ . 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 de ABI/API incompatível em uma biblioteca VNDK ou LLNDK agora resulta em um erro de compilação.
Para atualizar as referências de ABI para bibliotecas principais específicas do VNDK, 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 do libbinder , execute:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder
Para atualizar as referências de ABI para bibliotecas LLNDK específicas, execute o seguinte comando:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk
Por exemplo, para atualizar as referências da libm ABI, execute:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libm --llndk