Stabilność interfejsu ABI (Application Binary Interface) jest warunkiem wstępnym aktualizacji obejmujących tylko platformę, ponieważ moduły dostawcy mogą zależeć od bibliotek współdzielonych pakietu Vendor Native Development Kit (VNDK), które znajdują się na partycji systemowej. W ramach wersji Androida nowo utworzone biblioteki współdzielone VNDK muszą być zgodne z interfejsem ABI z wcześniej opublikowanymi bibliotekami współdzielonymi VNDK, aby moduły dostawcy mogły z nich korzystać bez ponownej kompilacji i bez błędów w czasie działania. Pomiędzy kolejnymi wersjami Androida biblioteki VNDK mogą ulec zmianie i nie ma gwarancji dotyczących ABI.
Aby zapewnić zgodność interfejsów ABI, Android 9 zawiera narzędzie do sprawdzania interfejsów ABI nagłówka, co opisujemy w kolejnych sekcjach.
Informacje o VNDK i zgodności z ABI
VNDK to ograniczony zestaw bibliotek, z którymi mogą być połączone moduły dostawcy. Umożliwia on aktualizacje tylko w zakresie platformy. Zgodność ABI oznacza, że nowsza wersja biblioteki udostępnionej działa zgodnie z oczekiwaniami w przypadku modułu, który jest z nią dynamicznie połączony (tzn. działa tak, jak starsza wersja biblioteki).
Informacje o eksportowanych symbolach
Eksportowany symbol (nazywany też symbolem globalnym) to symbol, który spełnia wszystkie te warunki:
- Eksportowane przez publiczne pliki nagłówkowe biblioteki udostępnionej.
- Pojawia się w tabeli
.dynsym
w pliku.so
odpowiadającym bibliotece udostępnionej. - ma wiązanie WEAK lub GLOBAL;
- Widoczność jest ustawiona na DOMYŚLNA lub CHRONIONA.
- Indeks sekcji nie jest wartością UNDEFINED.
- Typ to FUNC lub OBJECT.
Publiczne pliki nagłówkowe biblioteki udostępnionej to pliki nagłówkowe dostępne dla innych bibliotek lub plików binarnych za pomocą atrybutów export_include_dirs
, export_header_lib_headers
, export_static_lib_headers
, export_shared_lib_headers
i export_generated_headers
w definicjach Android.bp
modułu odpowiadającego bibliotece udostępnionej.
Informacje o typach, do których można dotrzeć
Typ dostępny to dowolny wbudowany lub zdefiniowany przez użytkownika typ C/C++, który jest dostępny bezpośrednio lub pośrednio przez wyeksportowany symbol ORAZ wyeksportowany przez publiczne pliki nagłówkowe. 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.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" ], } |
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
|
W przypadku Foo
bezpośrednie i pośrednie typy, do których można dotrzeć, to:
Typ | Opis |
---|---|
bool
|
Typ zwracany przez Foo .
|
int
|
Typ pierwszego parametru Foo .
|
bar_t *
|
Typ drugiego parametru Foo. Za pomocą bar_t * eksportowane są bar_t przez foo_exported.h .
bar_t zawiera element mfoo typu foo_t , który jest eksportowany przez foo_exported.h . W rezultacie eksportowanych jest więcej typów:
Jednak do foo_private_t NIE można dotrzeć, ponieważ nie jest on eksportowany przez foo_exported.h . (foo_private_t * jest nieprzezroczysty, więc zmiany w foo_private_t są dozwolone).
|
Podobne wyjaśnienie można podać w przypadku typów osiągalnych za pomocą specyfikatorów klasy bazowej i parametrów szablonu.
Zapewnianie zgodności z ABI
Zgodność z ABI musi być zapewniona w przypadku bibliotek oznaczonych jako 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 uzyskać dostęp bezpośrednio lub pośrednio za pomocą wyeksportowanej funkcji, te zmiany w bibliotece są klasyfikowane jako powodujące niezgodność interfejsu ABI:
Typ danych | Opis |
---|---|
Struktury i klasy |
|
Związki zawodowe |
|
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 do symboli prywatnych funkcji członkowskich mogą być przechowywane w binariach wywołujących. Zmiana lub usunięcie prywatnych funkcji składowych z bibliotek udostępnionych może spowodować brak zgodności wstecznej plików binarnych.
** Przesunięcia do publicznych lub prywatnych elementów danych nie mogą być zmieniane, ponieważ funkcje wstawiane mogą odwoływać się do tych elementów danych w treści funkcji. Zmiana przesunięć elementów danych może spowodować brak zgodności wstecznej plików binarnych.
*** Chociaż nie zmieniają one układu pamięci typu, istnieją różnice semantyczne, które mogą powodować, że biblioteki nie będą działać zgodnie z oczekiwaniami.
Korzystanie z narzędzi do sprawdzania zgodności ABI
Podczas kompilowania biblioteki VNDK jej interfejs ABI jest porównywany z odpowiednim interfejsem ABI referencyjnym dla kompilowanej wersji VNDK. Pliki zrzutów ABI znajdują się w tym folderze:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Na przykład w przypadku kompilacji libfoo
dla x86 na poziomie API 27 wnioskowany interfejs ABI libfoo
jest porównywany z interfejsem referencyjnym w tym miejscu:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Błąd przerwania interfejsu ABI
W przypadku niezgodności interfejsu ABI w dzienniku kompilacji wyświetlają się ostrzeżenia z informacją o typie ostrzeżenia i ścieżką do raportu abi-diff. Jeśli na przykład interfejs ABI biblioteki libbinder
zawiera niezgodną zmianę, system kompilacji zgłosi błąd z komunikatem podobnym do tego:
***************************************************** 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 ----
Tworzenie kontroli ABI biblioteki VNDK
Podczas tworzenia biblioteki VNDK:
header-abi-dumper
przetwarza pliki źródłowe skompilowane w celu utworzenia 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
przetwarza pliki.sdump
(używając skryptu wersji lub pliku.so
odpowiadającego bibliotece współdzielonej), aby utworzyć plik.lsdump
, który rejestruje wszystkie informacje ABI odpowiadające bibliotece współdzielonej.
Rysunek 2. Tworzenie pliku .lsdump
header-abi-diff
porównuje plik.lsdump
z plikiem referencyjnym.lsdump
, aby utworzyć raport różnicowy zawierający różnice w interfejsach ABI obu bibliotek.
Rysunek 3. Tworzenie raportu różnicowego
header-abi-dumper
Narzędzie header-abi-dumper
analizuje plik źródłowy C/C++ i zrzuca interfejs ABI wywnioskowany z tego pliku do pliku pośredniego. System kompilacji uruchamia header-abi-dumper
na wszystkich skompilowanych plikach źródłowych, a także tworzy bibliotekę, która zawiera pliki źródłowe z zależności przechodnich.
Wejścia |
|
---|---|
Urządzenie wyjściowe | Plik opisujący interfejs ABI pliku źródłowego (np. foo.sdump reprezentuje interfejs ABI foo.cpp ).
|
Obecnie pliki .sdump
są w formacie JSON, który nie musi być stabilny w przyszłych wersjach. Dlatego formatowanie pliku .sdump
należy traktować jako szczegół implementacji systemu kompilacji.
Na przykład libfoo.so
ma ten 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; }
Za pomocą header-abi-dumper
możesz wygenerować pośredni plik .sdump
, który reprezentuje interfejs ABI przedstawiony w pliku źródłowym:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
To polecenie nakazuje narzędziu header-abi-dumper
przeanalizować plik foo.cpp
za pomocą flag kompilatora podanych po --
i wygenerować informacje o interfejsie ABI eksportowane przez nagłówki publiczne w katalogu exported
. Poniżej znajduje się
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 z pliku źródłowegofoo.cpp
i nagłówki publiczne, np.
record_types
. Odwołuj się do struktur, unii lub klas zdefiniowanych w publicznych plikach nagłówkowych. Każdy typ rekordu zawiera informacje o polach, rozmiarze, specyfikatorze dostępu, pliku nagłówkowym, w którym jest zdefiniowany, i innych atrybutach.pointer_types
. Odwołuj się bezpośrednio lub pośrednio do typów wskaźników, do których odwołują się wyeksportowane rekordy lub funkcje w publicznych nagłówkach, wraz z typem, do którego wskaźnik wskazuje (za pomocą polareferenced_type
wtype_info
). Podobne informacje są rejestrowane w pliku.sdump
w przypadku kwalifikowanych typów, wbudowanych typów C/C++, typów tablic, typów odwołań do wartości lvalue i rvalue. Takie informacje umożliwiają rekurencyjne porównywanie różnic.functions
Reprezentuj funkcje wyeksportowane przez publiczne pliki nagłówkowe. Zawierają one również informacje o zniekształconej nazwie funkcji, typie zwracanej wartości, typach parametrów, specyfikatorze dostępu i innych atrybutach.
header-abi-linker
Narzędzie header-abi-linker
przyjmuje jako dane wejściowe pliki pośrednie wygenerowane przez narzędzie header-abi-dumper
, a następnie łączy te pliki:
Wejścia |
|
---|---|
Urządzenie wyjściowe | Plik opisujący ABI biblioteki współdzielonej (np. libfoo.so.lsdump reprezentuje libfoo ABI).
|
Narzędzie łączy wykresy typów we wszystkich przekazanych mu plikach pośrednich, uwzględniając różnice w definicjach (zdefiniowane przez użytkownika typy w różnych jednostkach tłumaczenia o tej samej w pełni kwalifikowanej nazwie mogą się różnić semantycznie) w różnych jednostkach tłumaczenia. Narzędzie analizuje następnie skrypt wersji lub .dynsym
tabelę biblioteki udostępnionej.dynsym
(plik .so
), aby utworzyć listę wyeksportowanych symboli.
Na przykład pole libfoo
składa się z pól foo.cpp
i bar.cpp
. header-abi-linker
można wywołać, aby utworzyć kompletny zlinkowany zrzut ABI header-abi-linker
w ten sposób:libfoo
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
Przykładowe dane wyjściowe 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 podane pliki
.sdump
(foo.sdump
ibar.sdump
), odfiltrowując informacje ABI, których nie ma w nagłówkach znajdujących się w kataloguexported
. - Analizuje
libfoo.so
i zbiera informacje o symbolach eksportowanych przez bibliotekę za pomocą tabeli.dynsym
. - Dodaje
_Z3FooiP3bar
i_Z6FooBadiP3foo
.
libfoo.so.lsdump
to ostateczny wygenerowany zrzut interfejsu ABI z libfoo.so
.
header-abi-diff
Narzędzie header-abi-diff
porównuje 2 .lsdump
pliki reprezentujące interfejs ABI 2 bibliotek i generuje raport z różnicami między tymi interfejsami.
Wejścia |
|
---|---|
Urządzenie wyjściowe | Raport różnicowy zawierający różnice w interfejsach ABI oferowanych przez porównywane 2 biblioteki współdzielone. |
Plik różnic ABI jest w formacie tekstowym protokołu protobuf. Format może ulec zmianie w kolejnych wersjach.
Na przykład masz 2 wersje strony libfoo
: libfoo_old.so
i libfoo_new.so
. W sekcji libfoo_new.so
w bar_t
zmieniasz typ mfoo
z foo_t
na foo_t *
. Ponieważ bar_t
jest typem osiągalnym, header-abi-diff
powinien oznaczyć tę zmianę jako zmianę powodującą niezgodność ABI.
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 dane wyjściowe 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 } } }
libfoo.so.abidiff
zawiera raport ze wszystkimi zmianami w libfoo
, które powodują niezgodność z ABI. record_type_diffs
wiadomość
wskazuje, że rekord został zmieniony, i zawiera listę niezgodnych zmian, do których należą:
- Rozmiar rekordu zmienia się z
24
bajtów na8
bajtów. - Typ pola
mfoo
zmienia się zfoo
nafoo *
(wszystkie definicje typów są usuwane).
Pole type_stack
wskazuje, jak header-abi-diff
osiągnął typ, który uległ zmianie (bar
). To pole można
interpretować jako Foo
to wyeksportowana funkcja, która przyjmuje
bar *
jako parametr, który wskazuje bar
, który został
wyeksportowany i zmieniony.
Wymuszanie ABI i API
Aby wymusić ABI i API bibliotek współdzielonych VNDK, odwołania do ABI muszą być zarejestrowane w ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
.
Aby utworzyć te odwołania, uruchom to 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, będzie teraz powodować błąd kompilacji.
Aby zaktualizować odniesienia do interfejsu ABI w przypadku konkretnych bibliotek, uruchom to polecenie:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
Aby na przykład zaktualizować odwołania do libbinder
ABI, uruchom:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder