Die Stabilität der Application Binary Interface (ABI) ist eine Voraussetzung für reine Framework-Updates, da Anbietermodule 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 mit zuvor veröffentlichten gemeinsam genutzten VNDK-Bibliotheken sein, damit Herstellermodule 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-Checker, wie in den folgenden Abschnitten beschrieben.
Informationen zur VNDK- und ABI-Konformität
Das VNDK ist ein restriktiver Satz von Bibliotheken, mit denen Anbietermodule 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 arbeiten, das dynamisch damit verknüpft ist (dh so funktioniert, wie es eine ältere Version der Bibliothek tun würde).
Informationen zu exportierten Symbolen
Ein exportiertes Symbol (auch bekannt als globales Symbol ) bezieht sich auf ein Symbol, das alle folgenden Bedingungen erfüllt:
- Exportiert von den öffentlichen Headern einer gemeinsam genutzten Bibliothek.
- Erscheint in der
.dynsym
Tabelle der.so
-Datei, die der gemeinsam genutzten Bibliothek entspricht. - Hat WEAK oder GLOBAL Bindung.
- Die Sichtbarkeit ist STANDARD oder GESCHÜTZT.
- Abschnittsindex ist nicht UNDEFINED.
- Typ ist entweder FUNC oder OBJECT.
Die öffentlichen Header einer gemeinsam genutzten Bibliothek sind als die Header definiert, die anderen Bibliotheken/Binärdateien über die 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 ein beliebiger in C/C++ integrierter oder benutzerdefinierter Typ, der direkt oder indirekt über ein exportiertes Symbol UND über öffentliche Header erreichbar ist. Zum Beispiel hat libfoo.so
die Funktion Foo
, die ein exportiertes Symbol ist, das in der .dynsym
-Tabelle zu finden ist. Die Bibliothek libfoo.so
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 : [ "include" ], } |
.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 |
Betrachtet man Foo
, gehören zu den direkt/indirekt erreichbaren Typen:
Art | Beschreibung |
---|---|
bool | Rückgabetyp von Foo . |
int | Typ des ersten Foo -Parameters. |
bar_t * | Typ des zweiten Foo-Parameters. Mittels bar_t * wird bar_t durch 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:
foo_private_t ist jedoch NICHT erreichbar, da es nicht über foo_exported.h exportiert wird. ( foot_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 Basisklassenbezeichner und Vorlagenparameter erreichbar sind.
Gewährleistung der ABI-Konformität
Die ABI-Konformität muss für die Bibliotheken mit der Kennzeichnung vendor_available: true
“ und vndk.enabled: true
in den entsprechenden Android.bp
Dateien sichergestellt werden. Zum Beispiel:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
Für Datentypen, die direkt oder indirekt durch eine exportierte Funktion erreichbar sind, werden die folgenden Änderungen an einer Bibliothek als ABI-brechend eingestuft:
Datentyp | Beschreibung |
---|---|
Strukturen und Klassen |
|
Gewerkschaften |
|
Aufzählungen |
|
Globale Symbole |
|
* Sowohl öffentliche als auch private Elementfunktionen dürfen nicht geändert oder entfernt werden, da öffentliche Inline-Funktionen auf private Elementfunktionen verweisen können. Symbolverweise auf private Elementfunktionen können in Caller-Binärdateien gespeichert werden. Das Ändern oder Entfernen privater Elementfunktionen aus gemeinsam genutzten Bibliotheken kann zu abwärtsinkompatiblen Binärdateien führen.
** Die Offsets zu öffentlichen oder privaten Datenmembern dürfen nicht geändert werden, da Inline-Funktionen in ihrem Funktionsrumpf auf diese Datenmember verweisen können. Das Ändern von Datenelement-Offsets kann zu abwärtsinkompatiblen Binärdateien führen.
*** Während diese das Speicherlayout des Typs nicht ändern, gibt es semantische Unterschiede, die dazu führen können, dass Bibliotheken nicht wie erwartet funktionieren.
Verwenden von ABI-Compliance-Tools
Wenn eine VNDK-Bibliothek erstellt wird, wird die ABI der Bibliothek mit der entsprechenden ABI-Referenz für die Version des erstellten VNDK verglichen. Referenz-ABI-Dumps befinden sich in:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based
Beim Erstellen von libfoo
für API-Ebene 27 des VNDK wird libfoo
die abgeleitete ABI von libfoo mit ihrer Referenz verglichen unter:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/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 der VNDK-Bibliothek
Wenn eine VNDK-Bibliothek erstellt wird:
-
header-abi-dumper
verarbeitet die Quelldateien, die kompiliert wurden, um die VNDK-Bibliothek zu erstellen (die eigenen Quelldateien der Bibliothek sowie Quelldateien, die durch statische transitive Abhängigkeiten geerbt wurden), um.sdump
Dateien zu erzeugen, die jeder Quelle entsprechen.Abbildung 1. Erstellen der .sdump
Dateien -
header-abi-linker
verarbeitet dann die.sdump
-Dateien (unter Verwendung entweder eines bereitgestellten Versionsskripts oder der.so
-Datei, die der gemeinsam genutzten Bibliothek entspricht), um eine.lsdump
-Datei zu erzeugen, die alle ABI-Informationen protokolliert, die der gemeinsam genutzten Bibliothek entsprechen.Abbildung 2. Erstellen der .lsdump
-Datei -
header-abi-diff
vergleicht die.lsdump
-Datei mit einer.lsdump
-Referenzdatei, um einen Vergleichsbericht zu erstellen, der die Unterschiede in den ABIs der beiden Bibliotheken umreißt.Abbildung 3. Erstellen des Diff-Berichts
header-abi-dumper
Das Tool header-abi-dumper
analysiert eine C/C++-Quelldatei und speichert die von 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.
Derzeit sind .sdump
Dateien als Protobuf TextFormatted formatiert, was für zukünftige Versionen nicht garantiert stabil ist. Daher sollte die Formatierung der .sdump
-Datei als Detail der Implementierung des Build-Systems betrachtet werden.
Beispielsweise hat libfoo.so
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
dumper verwenden, um eine .sdump
-Zwischendatei zu generieren, die die von der Quelldatei präsentierte ABI darstellt, indem Sie Folgendes verwenden:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -x c++
Dieser Befehl weist header-abi-dumper
dumper an, foo.cpp
zu parsen und die ABI-Informationen auszugeben, die in den öffentlichen Headern im exported
Verzeichnis verfügbar gemacht werden. Dies ist ein Auszug (keine vollständige Darstellung) aus foo.sdump
, der von header-abi-dumper
dumper generiert wurde:
record_types { type_info { name: "foo" size: 12 alignment: 4 referenced_type: "type-1" source_file: "foo/include/foo_exported.h" linker_set_key: "foo" self_type: "type-1" } fields { referenced_type: "type-2" field_offset: 0 field_name: "m1" access: public_access } fields { referenced_type: "type-3" field_offset: 32 field_name: "m2" access: public_access } fields { referenced_type: "type-5" field_offset: 64 field_name: "mPfoo" access: public_access } access: public_access record_kind: struct_kind tag_info { unique_id: "_ZTS3foo" } } record_types { type_info { name: "bar" size: 12 alignment: 4 referenced_type: "type-6" … pointer_types { type_info { name: "bar *" size: 4 alignment: 4 referenced_type: "type-6" source_file: "foo/include/foo_exported.h" linker_set_key: "bar *" self_type: "type-8" } } builtin_types { type_info { name: "int" size: 4 alignment: 4 referenced_type: "type-2" source_file: "" linker_set_key: "int" self_type: "type-2" } is_unsigned: false is_integral: true } functions { return_type: "type-7" function_name: "Foo" source_file: "foo/include/foo_exported.h" parameters { referenced_type: "type-2" default_arg: false } parameters { referenced_type: "type-8" default_arg: false } linker_set_key: "_Z3FooiP3bar" access: public_access }
foo.sdump
enthält ABI-Informationen, die von der Quelldatei foo.cpp
werden, z.
-
record_types
. Verweisen Sie auf Strukturen, Vereinigungen oder Klassen, die von den öffentlichen Headern verfügbar gemacht werden. Jeder Datensatztyp enthält Informationen zu seinen Feldern, seiner Größe, seiner Zugriffskennung, der Header-Datei, in der er angezeigt wurde, usw. -
pointer_types
. Verweisen Sie auf Zeigertypen, auf die direkt/indirekt von Datensätzen/Funktionen verwiesen wird, die von öffentlichen Headern verfügbar gemacht 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 und lvalue- und rvalue-Referenztypen protokolliert (solche Protokollierungsinformationen zu Typen ermöglichen rekursiven Vergleich). -
functions
. Stellen Sie Funktionen dar, die durch öffentliche Header verfügbar gemacht werden. Sie haben auch Informationen über den beschädigten Namen der Funktion, den Rückgabetyp, die Typen der Parameter, den Zugriffsbezeichner usw.
header-abi-linker
Das Tool header-abi-linker
nimmt die von header-abi-dumper
erzeugten Zwischendateien als Eingabe und verknüpft dann diese Dateien:
Eingänge |
|
---|---|
Ausgabe | Eine Datei, die die ABI einer gemeinsam genutzten Bibliothek protokolliert (z. B. repräsentiert libfoo.so.lsdump die ABI von libfoo ). |
Das Tool führt die Typendiagramme in allen ihm übergebenen Zwischendateien zusammen, wobei es Unterschiede zwischen Übersetzungseinheiten berücksichtigt (benutzerdefinierte Typen in verschiedenen Übersetzungseinheiten mit demselben vollständig qualifizierten Namen können semantisch unterschiedlich sein). Das Tool analysiert dann entweder ein Versionsskript oder die .dynsym
Tabelle der gemeinsam genutzten Bibliothek ( .so
-Datei), um eine Liste der exportierten Symbole zu erstellen.
Wenn beispielsweise libfoo
die Datei bar.cpp
(die eine C-Funktionsleiste bar
macht) zu seiner Kompilierung hinzufügt, könnte header-abi-linker
aufgerufen werden, um den vollständig 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
:
record_types { type_info { name: "foo" size: 24 alignment: 8 referenced_type: "type-1" source_file: "foo/include/foo_exported.h" linker_set_key: "foo" self_type: "type-1" } fields { referenced_type: "type-2" field_offset: 0 field_name: "m1" access: public_access } fields { referenced_type: "type-3" field_offset: 64 field_name: "m2" access: public_access } fields { referenced_type: "type-4" field_offset: 128 field_name: "mPfoo" access: public_access } access: public_access record_kind: struct_kind tag_info { unique_id: "_ZTS3foo" } } record_types { type_info { name: "bar" size: 24 alignment: 8 ... builtin_types { type_info { name: "void" size: 0 alignment: 0 referenced_type: "type-6" source_file: "" linker_set_key: "void" self_type: "type-6" } is_unsigned: false is_integral: false } functions { return_type: "type-19" function_name: "Foo" source_file: "foo/include/foo_exported.h" parameters { referenced_type: "type-2" default_arg: false } parameters { referenced_type: "type-20" default_arg: false } linker_set_key: "_Z3FooiP3bar" access: public_access } functions { return_type: "type-6" function_name: "FooBad" source_file: "foo/include/foo_exported_bad.h" parameters { referenced_type: "type-2" default_arg: false } parameters { referenced_type: "type-7" default_arg: false } linker_set_key: "_Z6FooBadiP3foo" access: public_access } elf_functions { name: "_Z3FooiP3bar" } elf_functions { name: "_Z6FooBadiP3foo" }
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 vorhanden sind, die sich im Verzeichnis befinden:exported
. - Analysiert
libfoo.so
und sammelt Informationen über die Symbole, die von der Bibliothek über ihre.dynsym
-Tabelle exportiert werden. - Fügt
_Z3FooiP3bar
undBar
hinzu.
libfoo.so.lsdump
ist der endgültig generierte ABI-Dump von libfoo.so
.
header-abi-diff
Das Tool header-abi-diff
vergleicht zwei .lsdump
-Dateien, die die ABI zweier Bibliotheken darstellen, und erstellt einen Diff-Bericht, der die Unterschiede zwischen den beiden ABIs angibt.
Eingänge |
|
---|---|
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 soll so ausführlich und lesbar wie möglich sein. Das Format kann sich in zukünftigen Versionen ändern. Beispielsweise haben Sie 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 direkt erreichbarer Typ ist, sollte dies durch header-abi-diff
als ABI Breaking Change gekennzeichnet werden.
So führen 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 Changes in libfoo
. Die Nachricht record_type_diffs
zeigt 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 typedefs 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, die auf bar
zeigt, die exportiert und geändert wurde.
Durchsetzung von ABI/API
Um die ABI/API von gemeinsam genutzten VNDK- und LLNDK-Bibliotheken zu erzwingen, müssen ABI-Referenzen in ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/
. Führen Sie zum Erstellen dieser Referenzen 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 Änderung am Quellcode, die zu einer inkompatiblen ABI/API-Änderung in einer VNDK- oder LLNDK-Bibliothek führt, jetzt zu einem Build-Fehler.
Führen Sie den folgenden Befehl aus, um ABI-Referenzen für bestimmte VNDK-Kernbibliotheken zu aktualisieren:
${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
Führen Sie den folgenden Befehl aus, um ABI-Referenzen für bestimmte LLNDK-Bibliotheken zu aktualisieren:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk
Um beispielsweise libm
ABI-Referenzen zu aktualisieren, führen Sie Folgendes aus:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libm --llndk