Die Stabilität der Application Binary Interface (ABI) ist eine Voraussetzung für Updates, die sich nur auf das Framework beziehen, da Anbietermodule von den freigegebenen VNDK-Bibliotheken (Vendor Native Development Kit) abhängen können, die sich in der Systempartition befinden. Innerhalb eines Android-Release müssen neu erstellte VNDK-Freigabebibliotheken ABI-kompatibel mit zuvor veröffentlichten VNDK-Freigabebibliotheken sein, damit Anbietermodule ohne Neukompilierung und ohne Laufzeitfehler mit diesen Bibliotheken funktionieren können. Zwischen Android-Releases können VNDK-Bibliotheken geändert werden und es gibt keine ABI-Garantien.
Zur Gewährleistung der ABI-Kompatibilität enthält Android 9 einen Header-ABI-Checker, wie in den folgenden Abschnitten beschrieben.
VNDK- und ABI-Compliance
Das VNDK ist eine eingeschränkte Gruppe von Bibliotheken, auf die Anbietermodule verknüpft werden können und die nur Framework-Updates ermöglichen. Die ABI-Compliance bezieht sich darauf, ob eine neuere Version einer freigegebenen Bibliothek wie erwartet mit einem dynamisch verknüpften Modul funktioniert (d.h. wie eine ältere Version der Bibliothek).
Exportierte Symbole
Ein exportiertes Symbol (auch globales Symbol genannt) ist ein Symbol, das alle folgenden Kriterien erfüllt:
- Wird von den öffentlichen Headern einer freigegebenen Bibliothek exportiert.
- Wird in der Tabelle
.dynsym
der Datei.so
angezeigt, die der freigegebenen Bibliothek entspricht. - Hat eine WEAK- oder GLOBAL-Bindung.
- Die Sichtbarkeit ist STANDARD oder GESCHÜTZT.
- Der Abschnittsindex ist nicht UNDEFINED.
- „Typ“ ist entweder „FUNC“ oder „OBJECT“.
Die öffentlichen Header einer freigegebenen Bibliothek sind die Header, 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 freigegebenen Bibliothek zur Verfügung stehen.
Arten von Nutzern, die Sie erreichen können
Ein erreichbarer Typ ist jeder C/C++-interne oder benutzerdefinierte Typ, der direkt oder indirekt über ein exportiertes Symbol und über öffentliche Header erreichbar ist. Beispiel: libfoo.so
hat die Funktion Foo
, ein exportiertes Symbol in der Tabelle .dynsym
. Die libfoo.so
-Bibliothek enthält 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
|
Zu den direkt/indirekt erreichbaren Typen gehören:Foo
Eingeben | 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. Dadurch werden mehr Typen exportiert:
foo_private_t ist jedoch 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 gilt auch für Typen, die über Basisklassenspezifizierer und Vorlagenparameter erreichbar sind.
ABI-Compliance sicherstellen
Die ABI-Compliance muss für die Bibliotheken gewährleistet sein, die in den entsprechenden Android.bp
-Dateien als vendor_available: true
und vndk.enabled: true
gekennzeichnet sind. Beispiel:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
Bei Datentypen, die direkt oder indirekt über eine exportierte Funktion erreichbar sind, werden die folgenden Änderungen an einer Bibliothek als ABI-Bruch klassifiziert:
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. Symbolreferenzen auf private Mitgliedsfunktionen können in Binärdateien des Aufrufers beibehalten werden. Das Ändern oder Entfernen privater Mitgliedsfunktionen aus freigegebenen Bibliotheken kann zu nicht abwärtskompatiblen Binärdateien führen.
** Die Offsetwerte für öffentliche oder private Datenglieder dürfen nicht geändert werden, da Inlinefunktionen in ihrem Funktionskörper auf diese Datenglieder verweisen können. Änderungen an den Datenglied-Offsets können zu nicht abwärtskompatiblen Binärdateien führen.
*** Das Speicherlayout des Typs ändert sich dadurch zwar nicht, es gibt jedoch semantische Unterschiede, die dazu führen können, dass Bibliotheken nicht wie erwartet funktionieren.
ABI-Compliance-Tools verwenden
Beim Erstellen einer VNDK-Bibliothek wird das ABI der Bibliothek mit der entsprechenden ABI-Referenz für die Version des erstellten VNDK verglichen. Referenz ABI-Dumps finden Sie hier:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Wenn Sie beispielsweise libfoo
für x86 auf API-Ebene 27 erstellen, wird das abgeleitete ABI von libfoo
mit der Referenz unter folgendem Pfad verglichen:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
ABI-Bruchfehler
Bei ABI-Unterbrechungen werden im Build-Log Warnungen mit dem Warntyp und einem Pfad zum abi-diff-Bericht angezeigt. Wenn beispielsweise das ABI von libbinder
eine inkompatible Änderung aufweist, gibt das Buildsystem einen Fehler mit einer Meldung aus, die in etwa so aussieht:
***************************************************** 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 ----
ABI-Prüfungen für VNDK-Bibliothek erstellen
Wenn eine VNDK-Bibliothek erstellt wird, geschieht Folgendes:
header-abi-dumper
verarbeitet die Quelldateien, die zum Erstellen der VNDK-Bibliothek kompiliert wurden (die eigenen Quelldateien der Bibliothek sowie Quelldateien, die über statische übertragbare Abhängigkeiten übernommen wurden), um.sdump
-Dateien zu generieren, die den einzelnen Quellen entsprechen.
Abbildung 1 .sdump
-Dateien erstellenheader-abi-linker
verarbeitet dann die.sdump
-Dateien (entweder mit einem bereitgestellten Versionsscript oder der.so
-Datei, die der freigegebenen Bibliothek entspricht), um eine.lsdump
-Datei zu erstellen, in der alle ABI-Informationen für die freigegebene Bibliothek protokolliert werden.
Abbildung 2. .lsdump
-Datei erstellenheader-abi-diff
vergleicht die.lsdump
-Datei mit einer Referenz.lsdump
-Datei, um einen Differenzbericht zu erstellen, der die Unterschiede in den ABIs der beiden Bibliotheken aufzeigt.
Abbildung 3 Differenzbericht erstellen
header-abi-dumper
Das Tool header-abi-dumper
analysiert eine C/C++-Quelldatei und speichert das daraus abgeleitete ABI in einer Zwischendatei. Das Build-System führt header-abi-dumper
auf allen kompilierten Quelldateien aus und erstellt gleichzeitig eine Bibliothek, die die Quelldateien aus transitiven Abhängigkeiten enthält.
Eingänge |
|
---|---|
Ausgabe | Eine Datei, die das ABI der Quelldatei beschreibt (z. B. foo.sdump für das ABI von foo.cpp ).
|
Derzeit sind .sdump
-Dateien im JSON-Format, das in zukünftigen Releases möglicherweise nicht mehr unterstützt wird. Daher sollte die .sdump
-Dateiformatierung als Implementierungsdetail des Build-Systems betrachtet werden.
Angenommen, libfoo.so
enthält 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; }
Mit header-abi-dumper
können Sie eine Zwischendatei vom Typ .sdump
generieren, die das von der Quelldatei dargestellte ABI darstellt:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
Mit diesem Befehl wird header-abi-dumper
angewiesen, foo.cpp
mit den Compiler-Flags nach --
zu parsen und die ABI-Informationen auszugeben, die von den öffentlichen Headern im Verzeichnis exported
exportiert werden. Im Folgenden finden Sie ein Beispiel für einen foo.sdump
, der 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 aus der Quelldatei foo.cpp
exportiert wurden, und die öffentlichen Header, z. B.
record_types
. Sie beziehen sich auf in den öffentlichen Headern definierte Strukturen, Unionen oder Klassen. Jeder Datensatztyp enthält Informationen zu seinen Feldern, seiner Größe, dem Zugriffsspezifier, der Headerdatei, in der er definiert ist, und anderen Attributen.pointer_types
: Hier finden Sie Informationen zu den Zeigertypen, auf die die exportierten Einträge/Funktionen in den öffentlichen Headern direkt oder indirekt verweisen, sowie zum Typ, auf den der Zeiger verweist (über das Feldreferenced_type
intype_info
). Ähnliche Informationen werden in der Datei.sdump
für qualifizierte Typen, vordefinierte C/C++-Typen, Arraytypen sowie L‑ und R‑Wert-Referenztypen protokolliert. Diese Informationen ermöglichen rekursives Diffing.functions
. Stellt Funktionen dar, die über öffentliche Header exportiert wurden. Außerdem enthalten sie Informationen zum mangled-Namen der Funktion, zum Rückgabetyp, zu den Parametertypen, zum Zugriffsspezifizierer und zu anderen Attributen.
header-abi-linker
Das header-abi-linker
-Tool nimmt die von header-abi-dumper
erstellten Zwischendateien als Eingabe und verknüpft diese Dateien:
Eingänge |
|
---|---|
Ausgabe | Eine Datei, die das ABI einer freigegebenen Bibliothek beschreibt (z. B. libfoo.so.lsdump für das ABI von libfoo ).
|
Das Tool führt die Typgrafen in allen angegebenen Zwischendateien zusammen und berücksichtigt dabei Unterschiede zwischen Übersetzungseinheiten, die sich auf eine Definition beziehen (benutzerdefinierte Typen in verschiedenen Übersetzungseinheiten mit demselben vollqualifizierten Namen, die semantisch unterschiedlich sein können). Das Tool analysiert dann entweder ein Versionsscript oder die .dynsym
-Tabelle der freigegebenen Bibliothek (.so
-Datei), um eine Liste der exportierten Symbole zu erstellen.
libfoo
besteht beispielsweise aus foo.cpp
und bar.cpp
. header-abi-linker
kann wie unten beschrieben aufgerufen werden, um den vollständigen verknüpften ABI-Dump von libfoo
zu erstellen:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
Beispiel für eine Befehlsausgabe 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 Verzeichnisexported
vorhanden sind. - Hier wird
libfoo.so
analysiert und Informationen zu den Symbolen erfasst, die über die.dynsym
-Tabelle aus der Bibliothek exportiert wurden. - 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 das ABI von zwei Bibliotheken darstellen, und erstellt einen Differenzbericht mit den Unterschieden zwischen den beiden ABIs.
Eingänge |
|
---|---|
Ausgabe | Einen Diff-Bericht mit den Unterschieden in den ABIs, die von den beiden verglichenen geteilten Bibliotheken angeboten werden. |
Die ABI-Diff-Datei ist im Protobuf-Textformat. Das Format kann sich in zukünftigen Releases ändern.
Angenommen, Sie haben zwei Versionen von libfoo
: libfoo_old.so
und libfoo_new.so
. In libfoo_new.so
ändern Sie unter bar_t
den Typ von mfoo
von foo_t
in foo_t *
. Da bar_t
ein erreichbarer Typ ist, sollte dies von header-abi-diff
als ABI-Bruchänderung 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
Beispiel für eine Befehlsausgabe 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 zu allen ABI-Unterbrechungsänderungen in libfoo
. Die record_type_diffs
-Nachricht weist darauf hin, dass sich ein Eintrag geändert hat, und listet die inkompatiblen Änderungen auf. Dazu gehören:
- Die Größe des Eintrags ändert sich von
24
Byte auf8
Byte. - Der Feldtyp von
mfoo
ändert sich vonfoo
infoo *
(alle Typdefinitionen werden entfernt).
Das Feld type_stack
gibt an, wie header-abi-diff
den geänderten Typ (bar
) erreicht hat. Dieses Feld kann so interpretiert werden, dass Foo
eine exportierte Funktion ist, die bar *
als Parameter annimmt, der auf bar
verweist, die exportiert und geändert wurde.
ABI und API erzwingen
Um die ABI und API der freigegebenen VNDK-Bibliotheken durchzusetzen, müssen ABI-Referenzen in ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
eingecheckt werden.
Führen Sie den folgenden Befehl aus, um diese Verweise zu erstellen:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
Nach dem Erstellen der Verweise führt jede Änderung am Quellcode, die zu einer inkompatiblen ABI-/API-Änderung in einer VNDK-Bibliothek führt, zu einem Buildfehler.
Führen Sie den folgenden Befehl aus, um ABI-Referenzen für bestimmte Bibliotheken zu aktualisieren:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
Wenn Sie beispielsweise die ABI-Referenzen für libbinder
aktualisieren möchten, führen Sie Folgendes aus:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder