Die ABI-Stabilität (Application Binary Interface) ist eine Voraussetzung für reine Framework-Updates, da Vendormodule möglicherweise von den gemeinsam genutzten VNDK-Bibliotheken (Vendor Native Development Kit) abhängen, die sich in der Systempartition befinden. In einem Android-Release müssen neu erstellte gemeinsam genutzte VNDK-Bibliotheken ABI-kompatibel mit zuvor veröffentlichten gemeinsam genutzten VNDK-Bibliotheken sein, damit Vendormodule ohne Neukompilierung und ohne Laufzeitfehler mit diesen Bibliotheken funktionieren. Zwischen Android-Releases können VNDK-Bibliotheken geändert werden. Es gibt keine ABI-Garantien.
Um die ABI-Kompatibilität zu gewährleisten, enthält Android 9 eine Header-ABI-Prüfung, wie in den folgenden Abschnitten beschrieben.
VNDK- und ABI-Compliance
Das VNDK ist eine restriktive Gruppe von Bibliotheken, mit denen Vendormodule verknüpft werden können und die reine 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. wie eine ältere Version der Bibliothek).
Exportierte Symbole
Ein exportiertes Symbol (auch als globales Symbol bezeichnet) ist ein Symbol, das alle folgenden Bedingungen erfüllt:
- Von den öffentlichen Headern einer gemeinsam genutzten Bibliothek exportiert.
- Wird in der Tabelle
.dynsym
der Datei.so
angezeigt, die der gemeinsam genutzten Bibliothek entspricht. - Hat eine WEAK- oder GLOBAL-Bindung.
- Die Sichtbarkeit ist STANDARD oder GESCHÜTZT.
- Der Abschnittsindex ist nicht UNDEFINED.
- Der Typ ist entweder FUNC oder OBJECT.
Die öffentlichen Headern einer gemeinsam genutzten Bibliothek werden als die 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 Android.bp
-Definitionen des Moduls zur Verfügung stehen, das der gemeinsam genutzten Bibliothek entspricht.
Erreichbare Typen
Ein erreichbarer Typ ist ein integrierter oder benutzerdefinierter C/C++-Typ, der direkt oder indirekt über ein exportiertes Symbol UND über öffentliche Header exportiert wird. libfoo.so
hat beispielsweise die Funktion Foo
, die ein exportiertes Symbol in der Tabelle .dynsym
ist. 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
|
In Foo
sind die folgenden direkt/indirekt erreichbaren Typen enthalten:
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 das Element 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 kann auch für Typen gegeben werden, die über Basisklassenspezifizierer und Vorlagenparameter erreichbar sind.
ABI-Compliance sicherstellen
Die ABI-Konformität muss für die Bibliotheken, die in den entsprechenden Android.bp
-Dateien mit vendor_available: true
und vndk.enabled: true
gekennzeichnet sind, gewährleistet werden. 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-inkompatibel eingestuft:
Datentyp | Beschreibung |
---|---|
Strukturen und Klassen |
|
Gewerkschaften |
|
Aufzählungen |
|
Globale Symbole |
|
* Sowohl öffentliche als auch private Member-Funktionen dürfen nicht geändert oder entfernt werden, da öffentliche Inline-Funktionen auf private Member-Funktionen verweisen können. Symbolverweise auf private Member-Funktionen können in Aufrufer-Binärdateien beibehalten werden. Das Ändern oder Entfernen privater Member-Funktionen aus gemeinsam genutzten Bibliotheken kann zu nicht abwärtskompatiblen Binärdateien führen.
** Die Offsets für öffentliche oder private Datenmember dürfen nicht geändert werden, da Inlinefunktionen in ihrem Funktionskörper auf diese Datenmember verweisen können. Wenn Sie die Offsets von Datenmembern ändern, kann dies zu nicht abwärtskompatiblen Binärdateien führen.
*** Diese Änderungen wirken sich zwar nicht auf das Speicherlayout des Typs aus, es gibt jedoch semantische Unterschiede, die dazu führen können, dass Bibliotheken nicht wie erwartet funktionieren.
ABI-Compliance-Tools verwenden
Wenn eine VNDK-Bibliothek erstellt wird, wird die ABI der Bibliothek mit der entsprechenden ABI-Referenz für die Version des VNDK verglichen, die erstellt wird. Referenz ABI-Dumps befinden sich unter:
${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 die 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-Fehler
Bei ABI-Änderungen werden im Build-Log Warnungen mit dem Warnungstyp und einem Pfad zum abi-diff-Bericht angezeigt. Wenn beispielsweise das ABI von libbinder
eine inkompatible Änderung enthält, 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 ----
ABI-Prüfungen für VNDK-Bibliotheken 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 durch statische transitive Abhängigkeiten übernommen wurden), um.sdump
-Dateien zu erstellen, die jeder Quelle entsprechen.
Abbildung 1: .sdump
-Dateien erstellenheader-abi-linker
verarbeitet dann die.sdump
-Dateien (entweder mit einem bereitgestellten Versionsskript oder mit der.so
-Datei, die der gemeinsam genutzten Bibliothek entspricht), um eine.lsdump
-Datei zu erstellen, in der alle ABI-Informationen für die gemeinsam genutzte Bibliothek protokolliert werden.
Abbildung 2. .lsdump
-Datei erstellen- Mit
header-abi-diff
wird die Datei.lsdump
mit einer Referenzdatei.lsdump
verglichen, um einen Diff-Bericht zu erstellen, in dem die Unterschiede in den ABIs der beiden Bibliotheken aufgeführt sind.
Abbildung 3. Differenzbericht erstellen
header-abi-dumper
Das Tool header-abi-dumper
parst eine C/C++-Quelldatei und speichert das aus dieser Quelldatei abgeleitete 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.
Eingänge |
|
---|---|
Ausgabe | Eine Datei, die die ABI der Quelldatei beschreibt (z. B. foo.sdump für die ABI von foo.cpp ).
|
Derzeit haben .sdump
-Dateien das JSON-Format, das in zukünftigen Versionen möglicherweise nicht stabil ist. Daher sollte die Formatierung von .sdump
-Dateien als Implementierungsdetail des Build-Systems betrachtet werden.
libfoo.so
hat beispielsweise 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 temporäre .sdump
-Datei generieren, die die ABI der Quelldatei darstellt:
$ 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 parsen und die ABI-Informationen auszugeben, die von den öffentlichen Headern im Verzeichnis exported
exportiert werden. Im Folgenden finden Sie 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
und den öffentlichen Headern exportiert wurden, 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, seinem Zugriffsspezifizierer, der Headerdatei, in der er definiert ist, und anderen Attributen.pointer_types
: Verweist direkt oder indirekt auf Zeigertypen, auf die in den exportierten Datensätzen/Funktionen in den öffentlichen Headern verwiesen wird, zusammen mit dem 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, Array-Typen sowie Lvalue- und Rvalue-Referenztypen protokolliert. Diese Informationen ermöglichen rekursives Differenzieren.functions
. Stellt Funktionen dar, die von öffentlichen Headern exportiert werden. Sie enthalten auch Informationen zum mangled Name der Funktion, zum Rückgabetyp, zu den Typen der Parameter, 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 sie dann:
Eingänge |
|
---|---|
Ausgabe | Eine Datei, die die ABI einer gemeinsam genutzten Bibliothek beschreibt (z. B. libfoo.so.lsdump für die ABI von libfoo ).
|
Das Tool führt die Typdiagramme in allen ihm übergebenen Zwischendateien zusammen und berücksichtigt dabei Unterschiede zwischen Übersetzungseinheiten, die durch die Einmaldefinition entstehen (nutzerdefinierte Typen in verschiedenen Übersetzungseinheiten mit demselben vollständig qualifizierten Namen können semantisch unterschiedlich sein). Das Tool parst dann entweder ein Versionsskript oder die .dynsym
-Tabelle der gemeinsam genutzten Bibliothek (.so
-Datei), um eine Liste der exportierten Symbole zu erstellen.
libfoo
besteht beispielsweise aus foo.cpp
und bar.cpp
. header-abi-linker
kann 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. - Analysiert
libfoo.so
und erfasst Informationen zu den Symbolen, die von der Bibliothek über die Tabelle.dynsym
exportiert werden. - Fügt
_Z3FooiP3bar
und_Z6FooBadiP3foo
hinzu.
libfoo.so.lsdump
ist die endgültige generierte ABI-Dumpdatei 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 Diff-Bericht, in dem die Unterschiede zwischen den beiden ABIs angegeben werden.
Eingänge |
|
---|---|
Ausgabe | Ein Diff-Bericht mit den Unterschieden in den ABIs der beiden verglichenen gemeinsam genutzten Bibliotheken. |
Die ABI-Differenzdatei hat das 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 in 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-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
Beispiel für die 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 Datei libfoo.so.abidiff
enthält einen Bericht zu allen ABI-inkompatiblen Änderungen in libfoo
. Die Meldung record_type_diffs
weist darauf hin, dass sich ein Datensatz geändert hat. Außerdem werden die inkompatiblen Änderungen aufgeführt, darunter:
- Die Größe des Datensatzes hat sich von
24
Byte auf8
Byte geändert. - Der Feldtyp von
mfoo
wird vonfoo
infoo *
geändert (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 akzeptiert, der auf bar
verweist, der exportiert und geändert wurde.
ABI und API erzwingen
Um die ABI und API von gemeinsam genutzten VNDK-Bibliotheken zu erzwingen, müssen ABI-Referenzen in ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
eingecheckt werden.
Führen Sie den folgenden Befehl aus, um diese Referenzen zu erstellen:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
Nachdem Sie die Referenzen erstellt haben, führt jede Änderung am Quellcode, die eine inkompatible ABI-/API-Änderung in einer VNDK-Bibliothek zur Folge hat, zu einem Build-Fehler.
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 von libbinder
aktualisieren möchten, führen Sie Folgendes aus:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder