Die Stabilität der Application Binary Interface (ABI) ist eine Voraussetzung für reine Framework-Updates, da Herstellermodule möglicherweise von den gemeinsam genutzten Bibliotheken des Vendor Native Development Kit (VNDK) abhängen, die sich in der Systempartition befinden. Innerhalb einer Android-Version müssen neu erstellte gemeinsam genutzte VNDK-Bibliotheken ABI-kompatibel zu zuvor veröffentlichten gemeinsam genutzten VNDK-Bibliotheken sein, damit Anbietermodule mit diesen Bibliotheken ohne Neukompilierung und ohne Laufzeitfehler arbeiten können. Zwischen Android-Releases können VNDK-Bibliotheken geändert werden und es gibt keine ABI-Garantien.
Um die ABI-Kompatibilität sicherzustellen, enthält Android 9 einen Header-ABI-Prüfer, wie in den folgenden Abschnitten beschrieben.
Informationen zur VNDK- und ABI-Konformität
Das VNDK ist ein restriktiver Satz von Bibliotheken, auf die Anbietermodule verlinken können und die nur Framework-Updates ermöglichen. ABI-Konformität bezieht sich auf die Fähigkeit einer neueren Version einer gemeinsam genutzten Bibliothek, wie erwartet mit einem Modul zu funktionieren, das dynamisch mit ihr verknüpft ist (d. h. so zu funktionieren, wie es eine ältere Version der Bibliothek tun würde).
Über exportierte Symbole
Ein exportiertes Symbol (auch als globales Symbol bezeichnet) bezieht sich auf ein Symbol, das alle folgenden Anforderungen erfüllt:
- Wird von den öffentlichen Headern einer gemeinsam genutzten Bibliothek exportiert.
- Erscheint in der
.dynsym
Tabelle der.so
Datei, die der gemeinsam genutzten Bibliothek entspricht. - Hat eine schwache oder globale Bindung.
- Die Sichtbarkeit ist STANDARD oder GESCHÜTZT.
- Der Abschnittsindex ist nicht UNDEFINED.
- Der Typ ist entweder FUNC oder OBJECT.
Die öffentlichen Header einer gemeinsam genutzten Bibliothek werden als Header definiert, die anderen Bibliotheken/Binärdateien über die Attribute export_include_dirs
, export_header_lib_headers
, export_static_lib_headers
, export_shared_lib_headers
und export_generated_headers
in den Android.bp
Definitionen des Moduls zur Verfügung stehen, das der gemeinsam genutzten Bibliothek entspricht.
Über erreichbare Typen
Ein erreichbarer Typ ist jeder in C/C++ integrierte oder benutzerdefinierte Typ, der direkt oder indirekt über ein exportiertes Symbol erreichbar UND über öffentliche Header exportiert ist. Beispielsweise verfügt libfoo.so
über die Funktion Foo
, bei der es sich um ein exportiertes Symbol aus der .dynsym
Tabelle handelt. Die Bibliothek libfoo.so
umfasst Folgendes:
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" ], } |
.dynsym-Tabelle | |||||||
---|---|---|---|---|---|---|---|
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 |
Wenn man sich Foo
ansieht, gehören zu den direkt/indirekt erreichbaren Typen:
Typ | Beschreibung |
---|---|
bool | Rückgabetyp von Foo . |
int | Typ des ersten Foo Parameters. |
bar_t * | Typ des zweiten Foo-Parameters. Über bar_t * wird bar_t über foo_exported.h exportiert.bar_t enthält ein Mitglied mfoo vom Typ foo_t , das über foo_exported.h exportiert wird, was dazu führt, dass mehr Typen exportiert werden:
Allerdings ist foo_private_t NICHT erreichbar, da es nicht über foo_exported.h exportiert wird. ( foo_private_t * ist undurchsichtig, daher sind Änderungen an foo_private_t zulässig.) |
Eine ähnliche Erklärung kann auch für Typen gegeben werden, die über Basisklassenspezifizierer und Vorlagenparameter erreichbar sind.
Sicherstellung der ABI-Konformität
Die ABI-Konformität muss für die Bibliotheken sichergestellt werden, die in den entsprechenden Android.bp
Dateien mit vendor_available: true
und vndk.enabled: true
gekennzeichnet sind. Zum Beispiel:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
Für Datentypen, die direkt oder indirekt über eine exportierte Funktion erreichbar sind, werden die folgenden Änderungen an einer Bibliothek als ABI-verletzend eingestuft:
Datentyp | Beschreibung |
---|---|
Strukturen und Klassen |
|
Gewerkschaften |
|
Aufzählungen |
|
Globale Symbole |
|
* Sowohl öffentliche als auch private Mitgliedsfunktionen dürfen nicht geändert oder entfernt werden, da öffentliche Inline-Funktionen auf private Mitgliedsfunktionen verweisen können. Symbolverweise auf private Memberfunktionen können in Aufrufer-Binärdateien gespeichert werden. Das Ändern oder Entfernen privater Mitgliedsfunktionen aus gemeinsam genutzten Bibliotheken kann zu abwärtsinkompatiblen Binärdateien führen.
** Die Offsets zu öffentlichen oder privaten Datenelementen dürfen nicht geändert werden, da Inline-Funktionen in ihrem Funktionskörper auf diese Datenelemente verweisen können. Das Ändern der Offsets von Datenelementen kann zu abwärtsinkompatiblen Binärdateien führen.
*** Diese ändern zwar nicht das Speicherlayout des Typs, es gibt jedoch semantische Unterschiede, die dazu führen können, dass Bibliotheken nicht wie erwartet funktionieren.
Verwendung von ABI-Compliance-Tools
Wenn eine VNDK-Bibliothek erstellt wird, wird der ABI der Bibliothek mit der entsprechenden ABI-Referenz für die Version des zu erstellenden VNDK verglichen. Referenz-ABI-Dumps befinden sich in:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Beim Erstellen libfoo
für x86 auf API-Ebene 27 wird beispielsweise der abgeleitete ABI von libfoo
mit seiner Referenz verglichen:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
ABI-Bruchfehler
Bei ABI-Fehlern zeigt das Build-Protokoll Warnungen mit dem Warnungstyp und einem Pfad zum Abi-Diff-Bericht an. Wenn beispielsweise die ABI von libbinder
eine inkompatible Änderung aufweist, gibt das Build-System einen Fehler mit einer Meldung ähnlich der folgenden aus:
***************************************************** 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 ----
Erstellen von ABI-Prüfungen für die VNDK-Bibliothek
Wenn eine VNDK-Bibliothek erstellt wird:
-
header-abi-dumper
verarbeitet die zum Erstellen der VNDK-Bibliothek kompilierten Quelldateien (die eigenen Quelldateien der Bibliothek sowie durch statische transitive Abhängigkeiten geerbte Quelldateien), um.sdump
Dateien zu erstellen, die jeder Quelle entsprechen.Abbildung 1. Erstellen der .sdump
Dateien -
header-abi-linker
verarbeitet dann die.sdump
Dateien (entweder mithilfe eines bereitgestellten Versionsskripts oder der.so
Datei, die der gemeinsam genutzten Bibliothek entspricht), um eine.lsdump
Datei zu erstellen, die alle der gemeinsam genutzten Bibliothek entsprechenden ABI-Informationen protokolliert.Abbildung 2. Erstellen der .lsdump
-Datei -
header-abi-diff
vergleicht die.lsdump
Datei mit einer Referenz.lsdump
Datei, um einen Diff-Bericht zu erstellen, der die Unterschiede in den ABIs der beiden Bibliotheken beschreibt.Abbildung 3. Erstellen des Diff-Berichts
Header-Abi-Dumper
Das header-abi-dumper
Tool analysiert eine C/C++-Quelldatei und speichert den aus dieser Quelldatei abgeleiteten ABI in einer Zwischendatei. Das Build-System führt header-abi-dumper
für alle kompilierten Quelldateien aus und erstellt gleichzeitig eine Bibliothek, die die Quelldateien aus transitiven Abhängigkeiten enthält.
Eingaben |
|
---|---|
Ausgabe | Eine Datei, die den ABI der Quelldatei beschreibt ( foo.sdump stellt beispielsweise den ABI von foo.cpp dar). |
Derzeit liegen .sdump
Dateien im JSON-Format vor, dessen Stabilität in zukünftigen Versionen nicht garantiert werden kann. Daher sollte die Formatierung .sdump
Datei als Detail der Build-System-Implementierung betrachtet werden.
Beispielsweise verfügt libfoo.so
über die folgende Quelldatei 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; }
Sie können header-abi-dumper
verwenden, um eine .sdump
Zwischendatei zu generieren, die den von der Quelldatei präsentierten ABI darstellt, indem Sie Folgendes verwenden:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
Dieser Befehl weist header-abi-dumper
an, foo.cpp
mit den Compiler-Flags nach --
zu analysieren und die ABI-Informationen auszugeben, die von den öffentlichen Headern im exported
Verzeichnis exportiert werden. Das Folgende ist foo.sdump
, das von header-abi-dumper
generiert wurde:
{ "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
enthält ABI-Informationen, die von der Quelldatei foo.cpp
exportiert wurden, und die öffentlichen Header, zum Beispiel:
-
record_types
. Verweisen Sie auf Strukturen, Unions oder Klassen, die in den öffentlichen Headern definiert sind. Jeder Datensatztyp verfügt über Informationen zu seinen Feldern, seiner Größe, dem Zugriffsbezeichner, der Header-Datei, in der er definiert ist, und anderen Attributen. -
pointer_types
. Verweisen Sie auf Zeigertypen, die direkt/indirekt von den exportierten Datensätzen/Funktionen in den öffentlichen Headern referenziert werden, zusammen mit dem Typ, auf den der Zeiger zeigt (über das Feldreferenced_type
intype_info
). Ähnliche Informationen werden in der.sdump
Datei für qualifizierte Typen, integrierte C/C++-Typen, Array-Typen sowie lvalue- und rvalue-Referenztypen protokolliert. Solche Informationen ermöglichen rekursives Differenzieren. -
functions
. Stellen Sie Funktionen dar, die von öffentlichen Headern exportiert werden. Sie verfügen außerdem über Informationen zum entstellten Namen der Funktion, zum Rückgabetyp, zu den Parametertypen, zum Zugriffsspezifizierer und zu anderen Attributen.
Header-Abi-Linker
Das header-abi-linker
Tool verwendet die von header-abi-dumper
erstellten Zwischendateien als Eingabe und verknüpft diese Dateien dann:
Eingaben |
|
---|---|
Ausgabe | Eine Datei, die den ABI einer gemeinsam genutzten Bibliothek beschreibt (beispielsweise stellt libfoo.so.lsdump den ABI von libfoo dar). |
Das Tool führt die Typdiagramme in allen ihm übergebenen Zwischendateien zusammen und berücksichtigt dabei die Unterschiede zwischen den einzelnen Definitionen (benutzerdefinierte Typen in verschiedenen Übersetzungseinheiten mit demselben vollständig qualifizierten Namen können semantisch unterschiedlich sein) zwischen den Übersetzungseinheiten. Anschließend analysiert das Tool entweder ein Versionsskript oder die .dynsym
Tabelle der gemeinsam genutzten Bibliothek ( .so
Datei), um eine Liste der exportierten Symbole zu erstellen.
Beispielsweise besteht libfoo
aus foo.cpp
und bar.cpp
. header-abi-linker
könnte aufgerufen werden, um den vollständigen verknüpften ABI-Dump von libfoo
wie folgt zu erstellen:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
Beispielbefehlsausgabe 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" : [] }
Das header-abi-linker
Tool:
- Verknüpft die bereitgestellten
.sdump
Dateien (foo.sdump
undbar.sdump
) und filtert die ABI-Informationen heraus, die nicht in den Headern im Verzeichnis vorhanden sind:exported
. - Analysiert
libfoo.so
und sammelt Informationen über die von der Bibliothek über ihre.dynsym
Tabelle exportierten Symbole. - Fügt
_Z3FooiP3bar
und_Z6FooBadiP3foo
hinzu.
libfoo.so.lsdump
ist der endgültig generierte ABI-Dump von libfoo.so
.
header-abi-diff
Das header-abi-diff
Tool vergleicht zwei .lsdump
Dateien, die die ABI zweier Bibliotheken darstellen, und erstellt einen Diff-Bericht, der die Unterschiede zwischen den beiden ABIs angibt.
Eingaben |
|
---|---|
Ausgabe | Ein Diff-Bericht, der die Unterschiede in den ABIs angibt, die von den beiden verglichenen gemeinsam genutzten Bibliotheken angeboten werden. |
Die ABI-Diff-Datei liegt im Protobuf-Textformat vor. Das Format kann sich in zukünftigen Versionen ändern.
Sie haben beispielsweise zwei Versionen von libfoo
: libfoo_old.so
und libfoo_new.so
. In libfoo_new.so
ändern Sie in bar_t
den Typ von mfoo
von foo_t
in foo_t *
. Da bar_t
ein erreichbarer Typ ist, sollte dies durch header-abi-diff
als ABI-Breaking Change gekennzeichnet werden.
So führen Sie header-abi-diff
aus:
header-abi-diff -old libfoo_old.so.lsdump \ -new libfoo_new.so.lsdump \ -arch arm64 \ -o libfoo.so.abidiff \ -lib libfoo
Beispielbefehlsausgabe 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 } } }
Die libfoo.so.abidiff
enthält einen Bericht aller ABI-Breaking-Änderungen in libfoo
. Die Nachricht record_type_diffs
gibt an, dass sich ein Datensatz geändert hat, und listet die inkompatiblen Änderungen auf, darunter:
- Die Größe des Datensatzes ändert sich von
24
Byte auf8
Byte. - Der Feldtyp von
mfoo
ändert sich vonfoo
zufoo *
(alle Typdefinitionen werden entfernt).
Das Feld type_stack
gibt an, wie header-abi-diff
den Typ erreicht hat, der sich geändert hat ( bar
). Dieses Feld kann so interpretiert werden, dass Foo
eine exportierte Funktion ist, die bar *
als Parameter akzeptiert, der auf bar
zeigt, der exportiert und geändert wurde.
Durchsetzung von ABI/API
Um die ABI/API von gemeinsam genutzten VNDK-Bibliotheken zu erzwingen, müssen ABI-Referenzen in ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
eingecheckt werden. Um diese Referenzen zu erstellen, führen Sie den folgenden Befehl aus:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
Nach dem Erstellen der Referenzen führt jede am Quellcode vorgenommene Änderung, die zu einer inkompatiblen ABI/API-Änderung in einer VNDK-Bibliothek führt, nun zu einem Build-Fehler.
Um ABI-Referenzen für bestimmte Bibliotheken zu aktualisieren, führen Sie den folgenden Befehl aus:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
Um beispielsweise libbinder
ABI-Referenzen zu aktualisieren, führen Sie Folgendes aus:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder