Stabilność interfejsu binarnego aplikacji (ABI) jest warunkiem wstępnym aktualizacji tylko struktury, ponieważ moduły dostawców mogą zależeć od bibliotek współdzielonych VNDK (Vendor Native Development Kit), które znajdują się na partycji systemowej. W ramach jednej wersji systemu Android nowo zbudowane biblioteki współdzielone VNDK muszą być kompatybilne z ABI z wcześniej wydanymi bibliotekami współdzielonymi VNDK, aby moduły dostawców mogły współpracować z tymi bibliotekami bez ponownej kompilacji i bez błędów w czasie wykonywania. Pomiędzy wersjami Androida biblioteki VNDK można zmieniać i nie ma gwarancji ABI.
Aby zapewnić zgodność z ABI, system Android 9 zawiera moduł sprawdzania nagłówka ABI, zgodnie z opisem w poniższych sekcjach.
O zgodności VNDK i ABI
VNDK to restrykcyjny zestaw bibliotek, z którymi mogą łączyć się moduły dostawców i które umożliwiają aktualizacje tylko dla platformy. Zgodność z ABI odnosi się do zdolności nowszej wersji biblioteki współdzielonej do działania zgodnie z oczekiwaniami z modułem, który jest z nią dynamicznie powiązany (tj. działa tak, jak działałaby starsza wersja biblioteki).
Informacje o wyeksportowanych symbolach
Wyeksportowany symbol (znany również jako symbol globalny ) odnosi się do symbolu, który spełnia wszystkie poniższe warunki:
- Eksportowane przez publiczne nagłówki udostępnionej biblioteki.
- Pojawia się w tabeli
.dynsym
pliku.so
odpowiadającego udostępnionej bibliotece. - Ma wiązanie SŁABE lub GLOBALNE.
- Widoczność jest DOMYŚLNA lub CHRONIONA.
- Indeks sekcji nie jest NIEZDEFINIOWANY.
- Typ to FUNC lub OBJECT.
Publiczne nagłówki biblioteki współużytkowanej są definiowane jako nagłówki dostępne dla innych bibliotek/plików binarnych za pośrednictwem atrybutów export_include_dirs
, export_header_lib_headers
, export_static_lib_headers
, export_shared_lib_headers
i export_generated_headers
w definicjach modułu Android.bp
odpowiadającej udostępnionej bibliotece.
O osiągalnych typach
Typ osiągalny to dowolny wbudowany lub zdefiniowany przez użytkownika typ C/C++, który jest osiągalny bezpośrednio lub pośrednio poprzez wyeksportowany symbol ORAZ wyeksportowany przez publiczne nagłówki. Na przykład libfoo.so
ma funkcję Foo
, która jest wyeksportowanym symbolem znajdującym się w tabeli .dynsym
. Biblioteka libfoo.so
zawiera następujące elementy:
foo_exported.h | foo.prywatny.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" ], } |
tabela .dynsym | |||||||
---|---|---|---|---|---|---|---|
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 |
Patrząc na Foo
, typy osiągalne bezpośrednio/pośrednio obejmują:
Typ | Opis |
---|---|
bool | Zwróć typ Foo . |
int | Typ pierwszego parametru Foo . |
bar_t * | Typ drugiego parametru Foo. Za pomocą bar_t * bar_t jest eksportowany przez foo_exported.h .bar_t zawiera element mfoo , typu foo_t , który jest eksportowany przez foo_exported.h , co skutkuje eksportowaniem większej liczby typów:
Jednak foo_private_t NIE jest osiągalny, ponieważ nie jest eksportowany przez foo_exported.h . ( foo_private_t * jest nieprzezroczyste, dlatego dozwolone są zmiany wprowadzone w foo_private_t .) |
Podobne wyjaśnienie można podać dla typów osiągalnych za pomocą specyfikatorów klasy bazowej i parametrów szablonu.
Zapewnienie zgodności z ABI
Należy zapewnić zgodność z ABI dla bibliotek oznaczonych vendor_available: true
i vndk.enabled: true
w odpowiednich plikach Android.bp
. Na przykład:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
W przypadku typów danych, do których można dotrzeć bezpośrednio lub pośrednio przez wyeksportowaną funkcję, następujące zmiany w bibliotece są klasyfikowane jako naruszające ABI:
Typ danych | Opis |
---|---|
Struktury i klasy |
|
Związki |
|
wyliczenia |
|
Symbole globalne |
|
* Zarówno publiczne, jak i prywatne funkcje składowe nie mogą być zmieniane ani usuwane, ponieważ publiczne funkcje wbudowane mogą odwoływać się do prywatnych funkcji składowych. Odwołania symboli do prywatnych funkcji składowych mogą być przechowywane w plikach binarnych wywołującego. Zmiana lub usunięcie prywatnych funkcji składowych z bibliotek współużytkowanych może spowodować powstanie niekompatybilnych wstecznie plików binarnych.
** Przesunięcia do publicznych lub prywatnych elementów danych nie mogą być zmieniane, ponieważ funkcje wbudowane mogą odwoływać się do tych elementów danych w treści ich funkcji. Zmiana przesunięć elementu członkowskiego danych może spowodować, że pliki binarne będą niezgodne z poprzednimi wersjami.
*** Chociaż nie zmieniają one układu pamięci typu, istnieją różnice semantyczne, które mogą spowodować, że biblioteki nie będą działać zgodnie z oczekiwaniami.
Korzystanie z narzędzi zgodności ABI
Kiedy budowana jest biblioteka VNDK, ABI biblioteki jest porównywane z odpowiednim odwołaniem ABI dla wersji tworzonej biblioteki VNDK. Referencyjne zrzuty ABI znajdują się w:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Na przykład, budując libfoo
dla x86 na poziomie API 27, wywnioskowany ABI libfoo
jest porównywany z jego referencją pod adresem:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Błąd uszkodzenia ABI
W przypadku awarii ABI dziennik kompilacji wyświetla ostrzeżenia z typem ostrzeżenia i ścieżką do raportu abi-diff. Na przykład, jeśli ABI libbinder
ma niekompatybilną zmianę, system kompilacji zgłasza błąd z komunikatem podobnym do następującego:
***************************************************** 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 ----
Budowanie kontroli ABI biblioteki VNDK
Gdy budowana jest biblioteka VNDK:
-
header-abi-dumper
przetwarza pliki źródłowe skompilowane w celu zbudowania biblioteki VNDK (własne pliki źródłowe biblioteki, a także pliki źródłowe odziedziczone przez statyczne zależności przechodnie), aby utworzyć pliki.sdump
odpowiadające każdemu źródłu.Rysunek 1. Tworzenie plików .sdump
-
header-abi-linker
następnie przetwarza pliki.sdump
(przy użyciu dostarczonego mu skryptu wersji lub pliku.so
odpowiadającego udostępnionej bibliotece) w celu utworzenia pliku.lsdump
, który rejestruje wszystkie informacje ABI odpowiadające udostępnionej bibliotece.Rysunek 2. Tworzenie pliku .lsdump
-
header-abi-diff
porównuje plik.lsdump
z referencyjnym plikiem.lsdump
w celu utworzenia raportu różnic, który przedstawia różnice w ABI obu bibliotek.Rysunek 3. Tworzenie raportu różnic
header-abi-dumper
Narzędzie header-abi-dumper
analizuje plik źródłowy C/C++ i zrzuca ABI wywnioskowane z tego pliku źródłowego do pliku pośredniego. System kompilacji uruchamia header-abi-dumper
na wszystkich skompilowanych plikach źródłowych, jednocześnie budując bibliotekę zawierającą pliki źródłowe z zależności przechodnich.
Wejścia |
|
---|---|
Wyjście | Plik opisujący ABI pliku źródłowego (na przykład foo.sdump reprezentuje ABI foo.cpp ). |
Obecnie pliki .sdump
są w formacie JSON, co nie gwarantuje stabilności w przyszłych wersjach. W związku z tym formatowanie pliku .sdump
powinno być traktowane jako szczegół implementacji systemu kompilacji.
Na przykład libfoo.so
ma następujący plik źródłowy 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; }
Możesz użyć header-abi-dumper
do wygenerowania pośredniego pliku .sdump
reprezentującego ABI prezentowanego przez plik źródłowy przy użyciu:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
To polecenie mówi header-abi-dumper
, aby przeanalizował foo.cpp
z flagami kompilatora następującymi po --
i wyemitował informacje ABI, które są eksportowane przez publiczne nagłówki w exported
katalogu. Poniżej plik foo.sdump
wygenerowany przez header-abi-dumper
:
{ "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
zawiera informacje ABI wyeksportowane przez plik źródłowy foo.cpp
i publiczne nagłówki, na przykład
-
record_types
. Odwołaj się do struktur, unii lub klas zdefiniowanych w publicznych nagłówkach. Każdy typ rekordu zawiera informacje o swoich polach, rozmiarze, specyfikatorze dostępu, pliku nagłówkowym, w którym jest zdefiniowany, oraz innych atrybutach. -
pointer_types
. Odwołaj się do typów wskaźników, do których odwołują się bezpośrednio/pośrednio wyeksportowane rekordy/funkcje w nagłówkach publicznych, wraz z typem, na który wskazuje wskaźnik (poprzez polereferenced_type
wtype_info
). Podobne informacje są rejestrowane w pliku.sdump
dla typów kwalifikowanych, wbudowanych typów C/C++, typów tablic oraz typów referencyjnych lvalue i rvalue. Takie informacje umożliwiają różnicowanie rekurencyjne. -
functions
. Reprezentuj funkcje eksportowane przez nagłówki publiczne. Zawierają również informacje o zniekształconej nazwie funkcji, zwracanym typie, typach parametrów, specyfikatorze dostępu i innych atrybutach.
nagłówek-abi-linker
Narzędzie header-abi-linker
pobiera pliki pośrednie utworzone przez header-abi-dumper
jako dane wejściowe, a następnie łączy te pliki:
Wejścia |
|
---|---|
Wyjście | Plik opisujący ABI biblioteki współdzielonej (na przykład libfoo.so.lsdump reprezentuje ABI biblioteki libfoo ). |
Narzędzie łączy wykresy typów we wszystkich przekazanych mu plikach pośrednich, biorąc pod uwagę różnice w jednej definicji (typy zdefiniowane przez użytkownika w różnych jednostkach tłumaczeniowych o tej samej pełnej nazwie, mogą być semantycznie różne) różnice między jednostkami tłumaczeniowymi. Następnie narzędzie analizuje skrypt wersji lub tabelę .dynsym
biblioteki współdzielonej (plik .so
), aby utworzyć listę wyeksportowanych symboli.
Na przykład libfoo
składa się z foo.cpp
i bar.cpp
. header-abi-linker
można wywołać, aby utworzyć kompletny połączony zrzut ABI libfoo
w następujący sposób:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
Przykładowe wyjście polecenia w 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" : [] }
Narzędzie header-abi-linker
:
- Łączy dostarczone pliki
.sdump
(foo.sdump
ibar.sdump
), odfiltrowując informacje ABI nieobecne w nagłówkach znajdujących się w katalogu:exported
. - Analizuje plik
libfoo.so
i zbiera informacje o symbolach wyeksportowanych przez bibliotekę za pośrednictwem tabeli.dynsym
. - Dodaje
_Z3FooiP3bar
i_Z6FooBadiP3foo
.
libfoo.so.lsdump
to ostateczny wygenerowany zrzut ABI libfoo.so
.
różnica-nagłówka
Narzędzie header-abi-diff
porównuje dwa pliki .lsdump
reprezentujące ABI dwóch bibliotek i tworzy raport różnicowy określający różnice między tymi dwoma ABI.
Wejścia |
|
---|---|
Wyjście | Raport różnic określający różnice w ABI oferowanych przez dwie porównywane biblioteki współdzielone. |
Plik diff ABI jest w formacie tekstowym protobuf . Format może ulec zmianie w przyszłych wydaniach.
Na przykład masz dwie wersje libfoo
: libfoo_old.so
i libfoo_new.so
. W libfoo_new.so
, w bar_t
zmieniasz typ mfoo
z foo_t
na foo_t *
. Ponieważ bar_t
jest typem osiągalnym, należy go oznaczyć jako zmianę przerywającą ABI przez header-abi-diff
.
Aby uruchomić header-abi-diff
:
header-abi-diff -old libfoo_old.so.lsdump \ -new libfoo_new.so.lsdump \ -arch arm64 \ -o libfoo.so.abidiff \ -lib libfoo
Przykładowe wyjście polecenia w 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 } } }
Plik libfoo.so.abidiff
zawiera raport o wszystkich przełomowych zmianach ABI w libfoo
. Komunikat record_type_diffs
wskazuje, że rekord został zmieniony i zawiera listę niezgodnych zmian, które obejmują:
- Rozmiar rekordu zmienia się z
24
bajtów na8
bajtów. - Typ pola
mfoo
zmienia się zfoo
nafoo *
(wszystkie definicje typów są usunięte).
Pole type_stack
wskazuje, w jaki sposób header-abi-diff
osiągnął typ, który się zmienił ( bar
). To pole może być interpretowane jako Foo
jest wyeksportowaną funkcją, która przyjmuje bar *
jako parametr, który wskazuje na bar
, który został wyeksportowany i zmieniony.
Wymuszanie ABI/API
Aby wymusić ABI/API bibliotek współdzielonych VNDK, odwołania ABI muszą zostać sprawdzone w ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
. Aby utworzyć te odniesienia, uruchom następujące polecenie:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
Po utworzeniu odwołań każda zmiana w kodzie źródłowym, która powoduje niezgodną zmianę ABI/API w bibliotece VNDK, powoduje teraz błąd kompilacji.
Aby zaktualizować odniesienia ABI dla określonych bibliotek, uruchom następującą komendę:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
Na przykład, aby zaktualizować referencje ABI libbinder
, uruchom:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder