Estabilidade da ABI

A estabilidade da interface binária do aplicativo (ABI) é um pré-requisito do atualizações somente de framework, porque os módulos do fornecedor podem depender da API Native do fornecedor. Bibliotecas compartilhadas do Kit de Desenvolvimento (VNDK) que residem na partição do sistema. Em uma versão do Android, as bibliotecas compartilhadas VNDK recém-criadas precisam ser É compatível com ABI com as bibliotecas compartilhadas VNDK lançadas anteriormente para que os módulos do fornecedor podem 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 modificadas e não há ABI. garantias.

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 o VNDK e a ABI

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

Sobre os 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 itens a seguir:

  • Exportados pelos cabeçalhos públicos de uma biblioteca compartilhada.
  • Aparece na tabela .dynsym do arquivo .so que correspondem à biblioteca compartilhada.
  • tem vinculação FRACA ou GLOBAL.
  • A visibilidade é DEFAULT ou PROTECTED.
  • O índice da seção não está 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 por meio do export_include_dirs, export_header_lib_headers, export_static_lib_headers, export_shared_lib_headers e Atributos export_generated_headers em Android.bp definições do módulo correspondente à biblioteca compartilhada.

Sobre os tipos acessíveis

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

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. A propósito de bar_t *, bar_t é exportado pelo foo_exported.h.

bar_t contém um membro mfoo, do tipo foo_t, que é exportado pelo método 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 está exportado pelo foo_exported.h. (foo_private_t *) é opaco, então as alterações feitas em foo_private_t são permitidas.

Uma explicação semelhante pode ser dada para tipos acessíveis por meio da classe de base especificadores e parâmetros de modelo.

Garantir a conformidade com a ABI

É preciso garantir a conformidade com a ABI para as bibliotecas marcadas vendor_available: true e vndk.enabled: true na 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, o as seguintes mudanças em uma biblioteca são classificadas como quebras de ABI:

Tipo de dado Descrição
Estruturas e classes
  • Mude o tamanho do tipo de classe ou de struct.
  • Classes de base
    • Adicionar ou remover classes base.
    • Adicione ou remova classes base virtualmente herdadas.
    • Mude a ordem das classes base.
  • Funções dos participantes
    • Remover funções de membro*.
    • Adicione ou remova argumentos de funções de membro.
    • Mudar os tipos de argumento ou os tipos de retorno do membro funções*.
    • Mudar o layout da tabela virtual.
  • Participantes 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**.
    • Mude const, volatile e/ou Qualificadores restricted de membros de dados***.
    • Faça downgrade dos especificadores de acesso dos membros de dados***.
  • Mude os argumentos do modelo.
União
  • Adicione ou remova participantes de dados.
  • Mude o tamanho do tipo de união.
  • Altere os tipos de membros de dados.
Enumerações
  • Mude o tipo.
  • Mude os nomes dos enumeradores.
  • Mude os valores dos enumeradores.
Símbolos globais
  • Remova os símbolos exportados por cabeçalhos públicos.
  • Para símbolos globais do tipo FUNC
    • Adicione ou remova argumentos.
    • Mude os tipos de argumento.
    • Mude o tipo de retorno.
    • Faça downgrade do especificador de acesso***.
  • Para símbolos globais do tipo OBJECT
    • Mude o tipo C/C++ correspondente.
    • Faça downgrade do especificador de acesso***.

* As funções de membros públicas e privadas precisam não podem ser alteradas nem removidas porque as funções inline públicas podem consultar funções de membro privado. As referências de símbolo a funções de membro privado podem sejam mantidos em binários do autor da chamada. Como alterar ou remover funções de membros particulares de bibliotecas compartilhadas podem resultar em binários incompatíveis com versões anteriores.

** As compensações para membros de dados públicos ou privados não podem ser mudou porque as funções em linha podem se referir a esses membros de dados em suas corpo da função. Alterar os deslocamentos dos 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, existem diferenças semânticas que podem fazer com que as bibliotecas funcionando da forma esperada.

Usar ferramentas de conformidade com a ABI

Quando uma biblioteca VNDK é criada, a ABI dela é comparada à referência da ABI correspondente para a versão do VNDK que está sendo compilada. Referência Os despejos da ABI estão localizados em:

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

Por exemplo, na criação de libfoo para x86 no nível 27 da API, A ABI inferida de libfoo é comparada à referência em:

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

Erro de falha da ABI

Em falhas de ABI, o registro do build exibe avisos com o tipo de aviso e uma para o relatório abi-diff. Por exemplo, se a ABI do libbinder tiver uma alteração incompatível, o sistema de compilação gera um erro com uma mensagem semelhante a:

*****************************************************
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:

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

cabeçalho-abi-dumper

A ferramenta header-abi-dumper analisa um arquivo de origem C/C++ e faz o despejo a ABI inferida desse arquivo de origem para um arquivo intermediário. O build o sistema executa header-abi-dumper em todos os arquivos de origem compilados enquanto criar uma biblioteca que inclua os arquivos de origem de arquivos dependências.

Entradas
  • Um arquivo de origem C/C++
  • Diretórios "include" exportados
  • Sinalizações do compilador
Saída Um arquivo que descreve a ABI do arquivo de origem (por exemplo, foo.sdump representa a ABI do foo.cpp.

No momento, os arquivos .sdump estão no formato JSON, que não está com garantia de estabilidade em versões futuras. Por isso, .sdump a formatação do arquivo 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;
}

É possível usar header-abi-dumper para gerar um intermediário Arquivo .sdump 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 instrui o header-abi-dumper a analisar foo.cpp com as sinalizações do compilador após --; e emitem as informações da ABI que são exportadas pelos cabeçalhos públicos nas exported. Confira a seguir foo.sdump geradas 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 da ABI exportadas pelo arquivo de origem. foo.cpp e os cabeçalhos públicos, por exemplo,

  • record_types: Consultar structs, uniões ou classes definidas nos cabeçalhos públicos. Cada tipo de registro tem informações sobre os campos, tamanho, especificador de acesso, o arquivo de cabeçalho em que está definido e outros atributos.
  • pointer_types: Consultar tipos de ponteiro direta/indiretamente referenciadas pelos registros/funções exportados nos cabeçalhos públicos, junto com com o tipo para o qual o ponteiro aponta (por meio do botão referenced_type) em type_info). Informações semelhantes são registradas no Arquivo .sdump para tipos qualificados, tipos C/C++ integrados, matriz e os tipos de referência lvalue e rvalue. Essas informações permitem diferenciação recursiva.
  • functions: Representa as funções exportadas por cabeçalhos públicos. Elas também têm informações sobre o nome corrompido 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 usa os arquivos intermediários produzidos por header-abi-dumper como entrada e, em seguida, vincula esses arquivos:

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

A ferramenta mescla os gráficos de tipo em todos os arquivos intermediários fornecidos a ela, considerando uma definição única (tipos definidos pelo usuário em diferentes 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 crie o despejo completo da ABI vinculado 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 resposta ao 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 da ABI não presentes no os cabeçalhos que residem no diretório: exported.
  • Analisa libfoo.so e coleta informações sobre os símbolos exportado pela biblioteca usando a tabela .dynsym.
  • _Z3FooiP3bar e _Z6FooBadiP3foo foram adicionados

libfoo.so.lsdump é o despejo de ABI gerado final 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 de diferenças indicando a diferenças entre as duas ABIs.

Entradas
  • Arquivo .lsdump que representa a ABI de um antigo arquivo compartilhado biblioteca.
  • Arquivo .lsdump que representa a ABI de uma nova biblioteca compartilhada.
Saída Um relatório de diferenças que indica as diferenças nas ABIs oferecidas pelos dois bibliotecas compartilhadas.

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

Por exemplo, você tem duas versões libfoo: libfoo_old.so e libfoo_new.so Em libfoo_new.so, em bar_t, você muda o tipo de mfoo de foo_t para foo_t *. Como bar_t é um acessível, isso deve ser sinalizado como uma alteração interruptiva de ABI 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 resposta ao 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 falhas da ABI mudanças 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 que muda de 24 bytes para 8 bytes.
  • O tipo de campo de mfoo muda de foo para foo * (todos os typedefs são removidos).

O campo type_stack indica como header-abi-diff atingiu o tipo de alteração (bar). Este campo pode ser interpretada como Foo é uma função exportada que toma bar * como parâmetro, que aponta para bar, que foi exportados e alterados.

Aplicar ABI e API

Para aplicar a ABI e a API de bibliotecas compartilhadas do VNDK, as referências da ABI precisam fazer check-in 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, as mudanças feitas no código-fonte que resultam em uma mudança de ABI/API incompatível em uma biblioteca VNDK agora resulta em um erro de compilação.

Para atualizar as referências de ABI de 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