Stabilność interfejsu binarnego aplikacji (ABI) jest warunkiem wstępnym aktualizacji tylko frameworku, ponieważ moduły dostawcy mogą zależeć od udostępnionych bibliotek pakietu programistycznego dostawcy (VNDK), które znajdują się na partycji systemowej. W ramach wersji Androida nowo utworzone biblioteki udostępnione VNDK muszą być zgodne z ABI bibliotek udostępnionych VNDK, które zostały opublikowane wcześniej, aby moduły dostawców mogły współpracować z tymi bibliotekami bez konieczności ponownego kompilowania i bez błędów w czasie wykonywania. Między wersjami Androida można zmieniać biblioteki VNDK bez gwarancji ABI.
Aby zapewnić zgodność z interfejsem ABI, Android 9 zawiera narzędzie do sprawdzania interfejsu ABI nagłówka. Więcej informacji znajdziesz w kolejnych sekcjach.
Zgodność z VNDK i ABI
VNDK to ograniczony zbiór bibliotek, do których moduły dostawców mogą zamieszczać linki, a które umożliwiają aktualizacje tylko na poziomie platformy. Zgodność z interfejsem ABI to zdolność nowszej wersji biblioteki udostępnionej do prawidłowego działania z modułem, który jest z nią dynamicznie powiązany (czyli działa tak samo jak starsza wersja biblioteki).
Wyeksportowane symbole
Symbol wyeksportowany (zwany też symbolem globalnym) to symbol, który spełnia wszystkie te warunki:
- Eksportowane za pomocą publicznych nagłówków w zasobach wspólnych.
- Pojawia się w tabeli
.dynsym
pliku.so
odpowiadającego zasobom wspólnym. - Ma wiązanie słabe lub GLOBALNE.
- widoczność jest ustawiona na DOMYŚLNIE lub CHRONIONY.
- Indeks sekcji nie jest UNDEFINED.
- Typ może być FUNC lub OBJECT.
Publiczne nagłówki wspólnej biblioteki są definiowane jako nagłówki 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 wspólnej bibliotece.
Typy dostępnych miejsc docelowych
Dostępny typ to dowolny typ wbudowany lub zdefiniowany przez użytkownika w C/C++, który jest dostępny bezpośrednio lub pośrednio za pomocą wyeksportowanego symbolu ORAZ wyeksportowanego za pomocą nagłówków publicznych. Na przykład libfoo.so
ma funkcję Foo
, która jest eksportowanym symbolem znalezionym w tabeli .dynsym
. Biblioteka libfoo.so
zawiera:
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
typy bezpośrednie/pośrednie to:
Typ | Opis |
---|---|
bool
|
Typ zwrotu: Foo .
|
int
|
Typ pierwszego parametru Foo .
|
bar_t *
|
Typ drugiego parametru Foo. Za pomocą funkcji bar_t * dane bar_t są eksportowane za pomocą funkcji foo_exported.h .
bar_t zawiera element mfoo typu foo_t , który jest eksportowany przez foo_exported.h , co powoduje eksport większej liczby typów:
Jednak foo_private_t jest niedostępna, ponieważ nie została wyeksportowana przez foo_exported.h . (foo_private_t *
jest nieprzezroczysty, dlatego zmiany wprowadzone w foo_private_t są dozwolone).
|
Podobne wyjaśnienie można też wyjaśnić w przypadku typów osiągalnych za pomocą specyfikatorów klas podstawowych i parametrów szablonu.
Zadbaj o zgodność 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 dostępnych bezpośrednio lub pośrednio przez wyeksportowaną funkcję te zmiany w bibliotece są klasyfikowane jako naruszające ABI:
Typ danych | Opis |
---|---|
Struktury i zajęcia |
|
Związki |
|
Wyliczenia |
|
Global Symbols |
|
* Nie można zmieniać ani usuwać ani publicznych, jak i prywatnych funkcji składowych, ponieważ publiczne funkcje wbudowane mogą odnosić się do prywatnych funkcji członków. Odwołania do symboli funkcji prywatnych członków mogą być przechowywane w plikach binarnych wywołującego. Zmiana lub usunięcie prywatnych funkcji członkowskich z bibliotek udostępnionych może spowodować brak zgodności wstecznej plików binarnych.
** Odsunięcia do publicznych lub prywatnych elementów danych nie można zmieniać, ponieważ funkcje wbudowane mogą się do nich odwoływać w swoim kodzie. Zmiana przesunięcia elementów danych może spowodować brak zgodności wstecznej plików binarnych.
*** Chociaż te zmiany nie wpływają na układ pamięci danego typu, istnieją różnice semantyczne, które mogą spowodować, że biblioteki nie będą działać zgodnie z oczekiwaniami.
Korzystanie z narzędzi do zapewnienia zgodności z przepisami ABI
Podczas kompilowania biblioteki VNDK porównujemy ABI biblioteki z odpowiednim odwołaniem ABI dla wersji VNDK, która jest kompilowana. Informacje Dumpy ABI znajdują się w tych miejscach:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Na przykład podczas kompilowania libfoo
na platformie x86 na poziomie API 27 wywnioskowany ABI libfoo
jest porównywany z odniesieniem w tych miejscach:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Błąd naruszenia interfejsu ABI
W przypadku naruszenia ABI w logu kompilacji wyświetlane są ostrzeżenia z typem ostrzeżenia i ścieżką do raportu abi-diff. Jeśli na przykład w interfejsie ABI libbinder
występuje niekompatybilna zmiana, 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 kompilowania biblioteki VNDK:
header-abi-dumper
przetwarza pliki źródłowe skompilowane w celu utworzenia biblioteki VNDK (własne pliki źródłowe biblioteki oraz pliki źródłowe dziedziczone przez statyczną zależność transitive), aby wygenerować pliki.sdump
odpowiadające poszczególnym źródłom.
Rysunek 1. Tworzenie plików .sdump
- Następnie
header-abi-linker
przetwarza pliki.sdump
(korzystając z dostarczonego skryptu wersji lub pliku.so
odpowiadającego współdzielonej bibliotece), aby wygenerować plik.lsdump
, który zawiera wszystkie informacje ABI odpowiadające współdzielonej bibliotece.
Rysunek 2. Tworzenie pliku .lsdump
header-abi-diff
porównuje plik.lsdump
z pobranym pliku.lsdump
, aby wygenerować raport różnic, który zawiera informacje o różnicach w ABI tych dwóch bibliotek.
Rysunek 3. Tworzenie raportu różnic
header-abi-dumper
Narzędzie header-abi-dumper
analizuje plik źródłowy C/C++ i zapisują wywnioskowany z niego ABI do pliku pośredniego. System kompilacji wykonuje 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ściami biernymi.
Wejścia |
|
---|---|
Urządzenie wyjściowe | Plik, który opisuje ABI pliku źródłowego (np.
foo.sdump reprezentuje ABI pliku foo.cpp ).
|
Obecnie pliki .sdump
są w formacie JSON, który nie jest gwarantowany jako stabilny w przyszłych wersjach. Dlatego formatowanie pliku .sdump
należy uznać za 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ą narzędzia header-abi-dumper
możesz wygenerować pośredni plik .sdump
, który reprezentuje interfejs ABI przedstawiony przez plik źródłowy. Aby to zrobić, użyj:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
To polecenie informuje header-abi-dumper
, aby przeanalizował plik foo.cpp
z flagami kompilatora następujący po --
i wysłał informacje ABI eksportowane przez nagłówki publiczne w katalogu exported
. Poniżej przedstawiamy 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 o interfejsie ABI wyeksportowane przez plik źródłowy foo.cpp
i nagłówki publiczne, na przykład
record_types
. Zapoznaj się ze strukturami, uniami i klasami zdefiniowanymi w publicznych nagłówkach. Każdy typ rekordu zawiera informacje o polach, rozmiarze, specyfikatorze dostępu, pliku nagłówka, w którym jest zdefiniowany, oraz inne atrybuty.pointer_types
. Odwołuje się do typów wskaźników, do których bezpośrednio lub pośrednio odwołują się wyeksportowane rekordy/funkcje w nagłówkach publicznych, oraz do typu, do którego odwołuje się wskaźnik (poprzez polereferenced_type
w plikutype_info
). Podobne informacje są rejestrowane w pliku.sdump
w przypadku typów kwalifikowanych, wbudowanych typów C/C++, typów tablic oraz typów referencji lvalue i rvalue. Te informacje umożliwiają rekurencyjne porównywanie.functions
. Reprezentuj funkcje wyeksportowane za pomocą nagłówków publicznych. Zawierają one też informacje o zagnieżdżonym nazwie funkcji, typie zwracanym, typach parametrów, specyfikatorze dostępu i innych atrybutach.
nagłówek-abi-linker,
Narzędzie header-abi-linker
pobiera jako dane wejściowe pliki pośrednie utworzone przez header-abi-dumper
, a następnie łączy te pliki:
Wejścia |
|
---|---|
Urządzenie wyjściowe | Plik opisujący ABI biblioteki współdzielonej (na przykład libfoo.so.lsdump reprezentuje ABI biblioteki libfoo ).
|
Narzędzie scala wykresy typów we wszystkich podanych mu plikach pośrednich, biorąc pod uwagę różnice w definicji (typy zdefiniowane przez użytkownika w różnych jednostkach tłumaczenia o tej samej pełnej nazwie mogą się różnić pod względem semantycznym) w jednostkach tłumaczenia. Następnie narzędzie analizuje skrypt wersji lub tabelę .dynsym
w zasobach wspólnych (plik .so
), aby utworzyć listę wyeksportowanych symboli.
Na przykład pole libfoo
składa się z polów foo.cpp
i bar.cpp
. Aby utworzyć pełny powiązany zrzut ABI dla libfoo
, można wywołać header-abi-linker
w ten sposób:
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 formacie 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 przekazane mu pliki
.sdump
(foo.sdump
ibar.sdump
), odfiltrowując informacje ABI, których nie ma w nagłówkach znajdujących się w kataloguexported
. - Przetwarza
libfoo.so
i zbiera informacje o symbolach wyeksportowanych przez bibliotekę za pomocą tabeli.dynsym
. - Dodaje
_Z3FooiP3bar
i_Z6FooBadiP3foo
.
libfoo.so.lsdump
to końcowy zrzut interfejsu ABI aplikacji libfoo.so
.
header-abi-diff
Narzędzie header-abi-diff
porównuje 2 pliki .lsdump
reprezentujące ABI 2 bibliotek i generuje raport różnic, w którym podano różnice między tymi 2 ABI.
Wejścia |
|
---|---|
Urządzenie wyjściowe | Raport różnic podający różnice w ABI oferowanych przez 2 porównywane biblioteki wspólne. |
Plik diff ABI jest w formacie tekstowym protobuf. Format może się zmienić w kolejnych wersjach.
Załóżmy, że masz 2 wersje tagu libfoo
: libfoo_old.so
i libfoo_new.so
. W libfoo_new.so
w zadaniu bar_t
zmieniasz typ wartości mfoo
z foo_t
na foo_t *
. Ponieważ bar_t
to typ osiągalny, header-abi-diff
powinien oznaczyć to jako zmianę 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 formacie 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 zmianach w plikach ABI, które powodują przerwanie zgodności z wersją ABI. Komunikat record_type_diffs
wskazuje, że rekord się zmienił, i wypisuje niezgodne zmiany, które obejmują:
- Rozmiar rekordu zmienił się z
24
na8
bajtów. - Typ pola
mfoo
zmienił się zfoo
nafoo *
(wszystkie typy definicji zostały usunięte).
Pole type_stack
wskazuje, jak header-abi-diff
dotarło do typu, który się zmienił (bar
). To pole może być interpretowane jako Foo
, czyli wyeksportowana funkcja, która przyjmuje jako parametr bar *
, który wskazuje na bar
, który został wyeksportowany i zmieniony.
Wymuszaj ABI i API
Aby egzekwować interfejsy ABI i API w bibliotekach udostępnionych VNDK, odwołania ABI muszą być sprawdzane 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 wprowadzona w źródle, która powoduje niezgodność ABI/API w bibliotece VNDK, powoduje błąd kompilacji.
Aby zaktualizować odwołania do ABI dla określonych 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 interfejsu ABI libbinder
, uruchom polecenie:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder