Die ABI-Stabilität (Application Binary Interface) ist eine Voraussetzung für reine Framework-Updates, da die Anbietermodule von den gemeinsam genutzten Bibliotheken des Vendor Native Development Kit (VNDK) in der Systempartition abhängen können. 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 gemeinsam genutzten Bibliothek entspricht. - Die Bindung hat SCHWACH oder GLOBALE.
- Die Sichtbarkeit ist STANDARD oder GESCHÜTZT.
- Abschnittsindex ist nicht UNEFFEKT.
- Der Typ ist entweder FUNC oder OBJECT.
Die öffentlichen Header einer freigegebenen 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 freigegebenen Bibliothek zur Verfügung stehen.
Erreichbare Typen
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. libfoo.so
hat beispielsweise 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:
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. Das Ändern von Offsets für Datenmitglieder kann zu nicht abwärtskompatiblen Binärdateien führen.
*** Dies ändert zwar nicht das Speicherlayout des Typs, 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
Beim Erstellen von libfoo
für x86 auf API-Ebene 27 wird beispielsweise das abgeleitete ABI von libfoo
mit der Referenz unter folgendem Link 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 Build-System einen Fehler mit einer Meldung wie 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 ----
VNDK-Bibliothek-ABI-Prüfungen erstellen
Wenn eine VNDK-Bibliothek erstellt wird:
header-abi-dumper
verarbeitet die Quelldateien, die zum Erstellen der VNDK-Bibliothek kompiliert wurden (die eigenen Quelldateien der Bibliothek sowie Quelldateien, die über statische transitive Abhängigkeiten übernommen werden), um.sdump
-Dateien zu erstellen, die der jeweiligen Quelle 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: Bericht „Unterschiede“ erstellen
Header-Abi-Dump
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 die foo.sdump
, die 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
. Verweisen Sie auf Strukturen, Unions oder Klassen, die in den öffentlichen Headern definiert sind. 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
. Verweisen Sie auf Zeigertypen, auf die die exportierten Datensätze/Funktionen in den öffentlichen Headern direkt/indirekt verweisen, sowie den Typ, auf den der Zeiger verweist (über das Feldreferenced_type
intype_info
). Ähnliche Informationen werden in der Datei.sdump
für qualifizierte Typen, integrierte C/C++-Typen, Arraytypen sowie Lvalue- und rvalue-Referenztypen protokolliert. Solche Informationen ermöglichen rekursive Differenzen.functions
. Stellt Funktionen dar, die über öffentliche Header exportiert wurden. Sie enthalten auch Informationen zum verwalteten Namen der Funktion, zum Rückgabetyp, zu den Parametertypen, zum Zugriffsbezeichner 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 die ABI einer gemeinsam genutzten Bibliothek beschreibt (z. B. steht 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 die 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 | Ein Differenzbericht, der die Unterschiede in den ABIs angibt, die von den beiden gemeinsam genutzten Bibliotheken im Vergleich 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
, in bar_t
, ändern Sie 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-Ä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 in8
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
Zum Erzwingen der ABI und API von gemeinsam genutzten VNDK-Bibliotheken 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