La stabilità dell'Application Binary Interface (ABI) è un prerequisito degli aggiornamenti solo del framework poiché i moduli del fornitore possono dipendere dalle librerie condivise del Vendor Native Development Kit (VNDK) che risiedono nella partizione di sistema. All'interno di una versione Android, le librerie condivise VNDK di nuova creazione devono essere compatibili con ABI con le librerie condivise VNDK rilasciate in precedenza in modo che i moduli del fornitore possano funzionare con tali librerie senza ricompilazione e senza errori di runtime. Tra una release e l'altra di Android, le librerie VNDK possono essere modificate e non ci sono garanzie ABI.
Per garantire la compatibilità ABI, Android 9 include un controllo ABI dell'intestazione, come descritto nelle sezioni seguenti.
Informazioni sulla conformità VNDK e ABI
Il VNDK è un insieme restrittivo di librerie a cui possono collegarsi i moduli del fornitore e che abilitano 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 ad essa collegato dinamicamente (ovvero funziona come farebbe una versione precedente della libreria).
Informazioni sui simboli esportati
Un simbolo esportato (noto anche come simbolo globale ) si riferisce a un simbolo che soddisfa tutti i seguenti requisiti:
- Esportato dalle intestazioni pubbliche di una libreria condivisa.
- Appare nella tabella
.dynsym
del file.so
corrispondente alla libreria condivisa. - Ha una rilegatura DEBOLE o GLOBALE.
- 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 intestazioni disponibili per altre librerie/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 incorporato C/C++ o definito dall'utente che è raggiungibile direttamente o indirettamente tramite un simbolo esportato ED 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.privato.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 |
Osservando 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 , che si traduce nell'esportazione di più tipi:
Tuttavia, foo_private_t NON è raggiungibile perché non viene esportato tramite foo_exported.h . ( foo_private_t * è opaco, pertanto sono consentite le modifiche apportate a foo_private_t .) |
Una spiegazione simile può essere fornita anche per i tipi raggiungibili tramite specificatori di classe base e parametri di modello.
Garantire la conformità ABI
È necessario garantire la conformità ABI per le librerie contrassegnate come vendor_available: true
e vndk.enabled: true
nei file Android.bp
corrispondenti. Per 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 di rottura ABI:
Tipo di dati | Descrizione |
---|---|
Strutture e classi |
|
Sindacati |
|
Enumerazioni |
|
Simboli globali |
|
* Sia le funzioni membro pubbliche che quelle private non devono essere modificate o rimosse poiché le funzioni inline pubbliche possono fare riferimento a funzioni membro private. I riferimenti ai simboli alle funzioni membro private possono essere mantenuti nei file binari del chiamante. La modifica o la rimozione di funzioni membro private dalle librerie condivise può comportare file binari incompatibili con le versioni precedenti.
** Gli offset per i membri dati pubblici o privati non devono essere modificati poiché le funzioni inline possono fare riferimento a questi membri dati nel corpo della funzione. La modifica degli offset dei membri dati può comportare file binari incompatibili con le versioni precedenti.
*** Sebbene questi non modifichino il layout della memoria del tipo, esistono differenze semantiche che potrebbero portare le librerie a non funzionare come previsto.
Utilizzo degli strumenti di compliance ABI
Quando viene creata una libreria VNDK, l'ABI della libreria viene confrontato con il riferimento ABI corrispondente per la versione del VNDK in fase di creazione. Le discariche ABI di riferimento sono ubicate in:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Ad esempio, durante la creazione libfoo
per x86 al livello API 27, l'ABI dedotta da libfoo
viene confrontata con il suo riferimento in:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Errore di rottura ABI
In caso di interruzioni ABI, il log di build visualizza 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 ----
Creazione di controlli ABI della libreria VNDK
Quando viene creata una libreria VNDK:
-
header-abi-dumper
elabora i file sorgente compilati per creare la libreria VNDK (i file sorgente della libreria così come i file sorgente ereditati tramite dipendenze transitive statiche), per produrre file.sdump
che corrispondono a ciascuna sorgente.Figura 1. Creazione dei file .sdump
-
header-abi-linker
elabora quindi i file.sdump
(utilizzando uno script di versione fornitogli 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 produrre un report diff che delinea le differenze negli ABI delle due librerie.Figura 3. Creazione del rapporto sulle differenze
intestazione-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 compilazione esegue header-abi-dumper
su tutti i file sorgente compilati creando allo stesso tempo una libreria che include i file sorgente da dipendenze transitive.
Ingressi |
|
---|---|
Produzione | Un file che descrive l'ABI del file sorgente (ad esempio, foo.sdump rappresenta l'ABI di foo.cpp ). |
Attualmente i file .sdump
sono in formato JSON, che non è garantito che sia stabile nelle versioni future. Pertanto, la formattazione del file .sdump
dovrebbe essere considerata un dettaglio di implementazione del sistema di compilazione.
Ad esempio, libfoo.so
ha il seguente file sorgente 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 dice a header-abi-dumper
di analizzare foo.cpp
con i flag del compilatore che seguono --
ed emettere le informazioni ABI che vengono esportate dalle intestazioni pubbliche nella directory exported
. Quello che segue è 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 informazioni ABI esportate dal file sorgente foo.cpp
e dalle intestazioni pubbliche, ad esempio,
-
record_types
. Fare riferimento a strutture, unioni o classi definite nelle intestazioni pubbliche. Ogni tipo di record contiene informazioni sui campi, la dimensione, lo specificatore di accesso, il file di intestazione in cui è definito e altri attributi. -
pointer_types
. Fare riferimento ai tipi di puntatore a cui fanno riferimento direttamente/indirettamente i record/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 tipi qualificati, tipi C/C++ incorporati, tipi di matrice e tipi di riferimento lvalue e rvalue. Tali informazioni consentono differenze ricorsive. -
functions
. Rappresentano funzioni esportate da intestazioni pubbliche. Contengono anche informazioni sul nome alterato della funzione, sul tipo restituito, sui tipi dei 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
quindi collega tali file:
Ingressi |
|
---|---|
Produzione | 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 a una definizione (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
è costituito da foo.cpp
e bar.cpp
. header-abi-linker
potrebbe essere invocato per creare il dump ABI completo collegato di libfoo
come segue:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
Esempio di output del comando 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 risiedono nella directory:exported
. - Analizza
libfoo.so
e raccoglie informazioni sui simboli esportati dalla libreria attraverso la sua tabella.dynsym
. - Aggiunge
_Z3FooiP3bar
e_Z6FooBadiP3foo
.
libfoo.so.lsdump
è il dump ABI finale generato di libfoo.so
.
intestazione-abi-diff
Lo strumento header-abi-diff
confronta due file .lsdump
che rappresentano l'ABI di due librerie e produce un rapporto diff che indica le differenze tra i due ABI.
Ingressi |
|
---|---|
Produzione | Un rapporto sulle differenze che indica le differenze negli ABI offerti dalle due librerie condivise a confronto. |
Il file diff ABI è in formato 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
, cambi il tipo di mfoo
da foo_t
a foo_t *
. Poiché bar_t
è un tipo raggiungibile, questo dovrebbe essere contrassegnato come modifica sostanziale 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
Esempio di output del comando 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 rapporto di tutte le modifiche di rilievo ABI in libfoo
. Il messaggio record_type_diffs
indica che un record è stato modificato ed elenca le modifiche incompatibili, che includono:
- La dimensione del record cambia da
24
byte a8
byte. - Il tipo di campo di
mfoo
cambia dafoo
afoo *
(tutte le typedef vengono rimosse).
Il campo type_stack
indica come header-abi-diff
ha raggiunto il tipo che è cambiato ( 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.
Applicazione dell'ABI/API
Per applicare l'ABI/API delle librerie condivise VNDK, i riferimenti ABI devono essere controllati 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 risulta in una modifica ABI/API incompatibile in una libreria VNDK ora risulta in un errore di compilazione.
Per aggiornare i riferimenti ABI per librerie specifiche, eseguire il comando seguente:
${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
, eseguire:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder