La stabilità dell'interfaccia binaria dell'applicazione (ABI) è un prerequisito degli aggiornamenti solo del framework, perché i moduli del fornitore potrebbero dipendere dalle librerie condivise del Vendor Native Development Kit (VNDK) che si trovano nella partizione di sistema. All'interno di una release Android, le librerie condivise VNDK appena create devono essere compatibili con l'ABI delle librerie condivise VNDK rilasciate in precedenza, in modo che i moduli del fornitore possano funzionare con queste librerie senza ricompilazione e senza errori di runtime. Tra le release di Android, le librerie VNDK possono essere modificate e non sono previste garanzie ABI.
Per garantire la compatibilità ABI, Android 9 include un controllo ABI dell'intestazione, come descritto nelle sezioni seguenti.
Informazioni su VNDK e conformità ABI
Il VNDK è un insieme restrittivo di librerie a cui i moduli del fornitore possono collegarsi e che consentono aggiornamenti solo del framework. La conformità ABI si riferisce alla capacità di una versione più recente di una libreria condivisa di funzionare come previsto con un modulo collegato dinamicamente (ovvero funziona come una versione precedente della libreria).
Informazioni sui simboli esportati
Un simbolo esportato (noto anche come simbolo globale) si riferisce a un simbolo che soddisfa tutte le seguenti condizioni:
- Esportati dalle intestazioni pubbliche di una libreria condivisa.
- Viene visualizzato nella tabella
.dynsym
del file.so
corrispondente alla libreria condivisa. - Ha un'associazione WEAK o GLOBAL.
- La visibilità è PREDEFINITA o PROTETTA.
- L'indice della sezione non è UNDEFINED.
- Il tipo è FUNC o OBJECT.
Le intestazioni pubbliche di una libreria condivisa sono definite come le intestazioni
disponibili per altre librerie/altri binari tramite gli attributi
export_include_dirs
, export_header_lib_headers
,
export_static_lib_headers
,
export_shared_lib_headers
e
export_generated_headers
nelle definizioni Android.bp
del modulo corrispondente alla libreria condivisa.
Informazioni sui tipi raggiungibili
Un tipo raggiungibile è qualsiasi tipo integrato o definito dall'utente C/C++ che sia
raggiungibile direttamente o indirettamente tramite un simbolo esportato E esportato
tramite intestazioni pubbliche. Ad esempio, libfoo.so
ha la funzione
Foo
, che è un simbolo esportato trovato nella
tabella .dynsym
. La libreria libfoo.so
include
quanto segue:
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" ], } |
Tabella .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
|
Se esaminiamo Foo
, i tipi raggiungibili diretti/indiretti includono:
Tipo | Descrizione |
---|---|
bool
|
Tipo restituito di Foo .
|
int
|
Tipo del primo parametro Foo .
|
bar_t *
|
Tipo del secondo parametro Foo. Tramite bar_t * ,
bar_t viene esportato tramite foo_exported.h .
bar_t contiene un membro mfoo , di tipo
foo_t , che viene esportato tramite foo_exported.h ,
il che comporta l'esportazione di più tipi:
Tuttavia, foo_private_t NON è raggiungibile perché non è
esportato tramite foo_exported.h . (foo_private_t *
è opaco, pertanto sono consentite modifiche a foo_private_t .)
|
Una spiegazione simile può essere fornita anche per i tipi raggiungibili tramite specificatori di classi base e parametri del modello.
Garantire la conformità dell'ABI
La conformità ABI deve essere garantita per le librerie contrassegnate
vendor_available: true
e vndk.enabled: true
nei
corrispondenti file Android.bp
. Ad esempio:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
Per i tipi di dati raggiungibili direttamente o indirettamente da una funzione esportata, le seguenti modifiche a una libreria sono classificate come ABI-breaking:
Tipo di dati | Descrizione |
---|---|
Strutture e classi |
|
Sindacati |
|
Enumerazioni |
|
Simboli globali |
|
* Le funzioni membro pubbliche e private non devono essere modificate o rimosse perché le funzioni inline pubbliche possono fare riferimento a funzioni membro private. I riferimenti ai simboli alle funzioni membro private possono essere conservati nei binari del chiamante. La modifica o la rimozione di funzioni membro private dalle librerie condivise può comportare la creazione di binari non compatibili con le versioni precedenti.
** Gli offset dei membri di dati pubblici o privati non devono essere modificati perché le funzioni inline possono fare riferimento a questi membri di dati nel corpo della funzione. La modifica degli offset dei membri dei dati può comportare binari non compatibili con le versioni precedenti.
*** Anche se non modificano la struttura della memoria del tipo, esistono differenze semantiche che potrebbero impedire alle librerie di funzionare come previsto.
Utilizzare gli strumenti di conformità ABI
Quando viene creata una libreria VNDK, l'ABI della libreria viene confrontata con il riferimento ABI corrispondente per la versione di VNDK in fase di creazione. I dump dell'ABI di riferimento si trovano in:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Ad esempio, durante la creazione di libfoo
per x86 a livello API 27,
l'ABI dedotta di libfoo
viene confrontata con il riferimento all'indirizzo:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Errore di interruzione dell'ABI
In caso di interruzioni dell'ABI, il log di build mostra avvisi con il tipo di avviso e un
percorso al report abi-diff. Ad esempio, se l'ABI di libbinder
presenta
una modifica incompatibile, il sistema di compilazione genera un errore con un messaggio
simile al seguente:
***************************************************** 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 ----
Crea controlli ABI della libreria VNDK
Quando viene creata una libreria VNDK:
header-abi-dumper
elabora i file di origine compilati per creare la libreria VNDK (i file di origine della libreria e i file di origine ereditati tramite dipendenze transitive statiche) per produrre file.sdump
che corrispondono a ogni origine.
Figura 1. Creazione dei file .sdump
header-abi-linker
elabora quindi i file.sdump
(utilizzando uno script di versione fornito o il file.so
corrispondente alla libreria condivisa) per produrre un file.lsdump
che registra tutte le informazioni ABI corrispondenti alla libreria condivisa.
Figura 2. Creazione del file .lsdump
header-abi-diff
confronta il file.lsdump
con un file.lsdump
di riferimento per generare un report diff che descrive le differenze nelle ABI delle due librerie.
Figura 3. Creazione del report sulle differenze
header-abi-dumper
Lo strumento header-abi-dumper
analizza un file sorgente C/C++ e scarica
l'ABI dedotto da quel file sorgente in un file intermedio. Il sistema di build
esegue header-abi-dumper
su tutti i file sorgente compilati e
crea anche una libreria che include i file sorgente delle dipendenze
transitive.
Input |
|
---|---|
Output | Un file che descrive l'ABI del file di origine (ad esempio,
foo.sdump rappresenta l'ABI di foo.cpp ).
|
Attualmente i file .sdump
sono in formato JSON, la cui stabilità non è garantita nelle versioni future. Pertanto, la formattazione del file .sdump
deve essere considerata un dettaglio di implementazione del sistema di build.
Ad esempio, libfoo.so
ha il seguente file di origine
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; }
Puoi utilizzare header-abi-dumper
per generare un file .sdump
intermedio che rappresenta l'ABI presentato dal file sorgente utilizzando:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
Questo comando indica a header-abi-dumper
di analizzare
foo.cpp
con i flag del compilatore che seguono --
ed
emettere le informazioni ABI esportate dalle intestazioni pubbliche nella
directory exported
. Di seguito è riportato
foo.sdump
generato da
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
contiene le informazioni ABI esportate dal file di origine
foo.cpp
e le intestazioni pubbliche, ad esempio
record_types
. Fai riferimento a struct, unioni o classi definiti nelle intestazioni pubbliche. Ogni tipo di record contiene informazioni sui relativi campi, dimensioni, identificatore di accesso, file di intestazione in cui è definito e altri attributi.pointer_types
. Fai riferimento ai tipi di puntatore direttamente/indirettamente a cui fanno riferimento i record/le funzioni esportati nelle intestazioni pubbliche, insieme al tipo a cui punta il puntatore (tramite il camporeferenced_type
intype_info
). Informazioni simili vengono registrate nel file.sdump
per i tipi qualificati, i tipi C/C++ integrati, i tipi di array e i tipi di riferimento lvalue e rvalue. Queste informazioni consentono il confronto ricorsivo.functions
. Rappresenta le funzioni esportate dalle intestazioni pubbliche. Contengono anche informazioni sul nome modificato della funzione, sul tipo restituito, sui tipi di parametri, sullo specificatore di accesso e su altri attributi.
header-abi-linker
Lo strumento header-abi-linker
prende come input i file intermedi prodotti
da header-abi-dumper
e li collega:
Input |
|
---|---|
Output | Un file che descrive l'ABI di una libreria condivisa (ad esempio,
libfoo.so.lsdump rappresenta l'ABI di libfoo ).
|
Lo strumento unisce i grafici dei tipi in tutti i file intermedi forniti,
tenendo conto delle differenze di una definizione (i tipi definiti dall'utente in diverse
unità di traduzione con lo stesso nome completo potrebbero essere semanticamente
diversi) tra le unità di traduzione. Lo strumento analizza quindi uno script di versione o la tabella .dynsym
della libreria condivisa (file .so
) per creare un elenco dei simboli esportati.
Ad esempio, libfoo
è composto da foo.cpp
e
bar.cpp
. header-abi-linker
può essere richiamato per
creare il dump ABI collegato completo di libfoo
nel seguente modo:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
Output del comando di esempio in 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" : [] }
Lo strumento header-abi-linker
:
- Collega i file
.sdump
forniti (foo.sdump
ebar.sdump
), filtrando le informazioni ABI non presenti nelle intestazioni che si trovano nella directory:exported
. - Analizza
libfoo.so
e raccoglie informazioni sui simboli esportati dalla libreria tramite la tabella.dynsym
. - Aggiunge
_Z3FooiP3bar
e_Z6FooBadiP3foo
.
libfoo.so.lsdump
è il dump ABI finale generato di
libfoo.so
.
header-abi-diff
Lo strumento header-abi-diff
confronta due file .lsdump
che rappresentano l'ABI di due librerie e produce un report diff che indica le
differenze tra le due ABI.
Input |
|
---|---|
Output | Un report sulle differenze che indica le differenze nelle ABI offerte dalle due librerie condivise confrontate. |
Il file diff ABI è in formato di testo protobuf. Il formato è soggetto a modifiche nelle versioni future.
Ad esempio, hai due versioni di
libfoo
: libfoo_old.so
e
libfoo_new.so
. In libfoo_new.so
, in
bar_t
, modifichi il tipo di mfoo
da
foo_t
a foo_t *
. Poiché bar_t
è un
tipo raggiungibile, questo dovrebbe essere contrassegnato come modifica che causa interruzioni dell'ABI da
header-abi-diff
.
Per eseguire header-abi-diff
:
header-abi-diff -old libfoo_old.so.lsdump \ -new libfoo_new.so.lsdump \ -arch arm64 \ -o libfoo.so.abidiff \ -lib libfoo
Output del comando di esempio in 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 } } }
libfoo.so.abidiff
contiene un report di tutte le modifiche che causano interruzioni dell'ABI
in libfoo
. Il messaggio record_type_diffs
indica che un record è stato modificato ed elenca le modifiche incompatibili, che
includono:
- Le dimensioni del record sono cambiate da
24
byte a8
byte. - Il tipo di campo di
mfoo
è stato modificato dafoo
afoo *
(tutte le typedef sono state rimosse).
Il campo type_stack
indica in che modo header-abi-diff
ha raggiunto il tipo modificato (bar
). Questo campo può essere
interpretato come Foo
è una funzione esportata che accetta
bar *
come parametro, che punta a bar
, che è stato
esportato e modificato.
Applica ABI e API
Per applicare l'ABI e l'API delle librerie condivise VNDK, i riferimenti ABI devono
essere archiviati in ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
.
Per creare questi riferimenti, esegui il comando seguente:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
Dopo aver creato i riferimenti, qualsiasi modifica apportata al codice sorgente che comporti una modifica incompatibile di ABI/API in una libreria VNDK ora genera un errore di build.
Per aggiornare i riferimenti ABI per librerie specifiche, esegui questo comando:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
Ad esempio, per aggiornare i riferimenti ABI libbinder
, esegui:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder