Stabilność interfejsu binarnego aplikacji (ABI) jest warunkiem wstępnym aktualizacji wyłącznie platformy, ponieważ moduły dostawcy mogą zależeć od bibliotek współdzielonych zestawu Vendor Native Development Kit (VNDK), które znajdują się na partycji systemowej. W wersji na Androida nowo utworzone 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 rekompilacji i błędów w czasie wykonywania. Pomiędzy wersjami Androida biblioteki VNDK można zmieniać i nie ma gwarancji ABI.
Aby zapewnić zgodność z interfejsem ABI, system Android 9 zawiera moduł sprawdzania nagłówka ABI, zgodnie z opisem w poniższych sekcjach.
Informacje o zgodności z VNDK i ABI
VNDK to restrykcyjny zestaw bibliotek, z którymi mogą się łączyć moduły dostawców i które umożliwiają aktualizacje wyłącznie 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 połączony (tzn. działa tak, jak działałaby starsza wersja biblioteki).
Informacje o eksportowanych symbolach
Eksportowany symbol (znany również jako symbol globalny ) odnosi się do symbolu, który spełnia wszystkie poniższe kryteria:
- Eksportowane przez publiczne nagłówki biblioteki współdzielonej.
- Pojawia się w tabeli
.dynsym
pliku.so
odpowiadającej bibliotece współdzielonej. - Ma słabe lub GLOBALNE powiązanie.
- Widoczność jest DOMYŚLNA lub CHRONIONA.
- Indeks sekcji nie jest NIEZDEFINIOWANY.
- Typ to FUNC lub OBJECT.
Nagłówki publiczne biblioteki współdzielonej są zdefiniowane 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 Android.bp
modułu odpowiadającego bibliotece współdzielonej.
Informacje o typach osiągalnych
Typ osiągalny to dowolny typ wbudowany lub zdefiniowany przez użytkownika w języku C/C++, który jest osiągalny bezpośrednio lub pośrednio za pośrednictwem wyeksportowanego symbolu ORAZ wyeksportowany za pośrednictwem publicznych nagłówków. Na przykład libfoo.so
ma funkcję Foo
, która jest wyeksportowanym symbolem znalezionym 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 |
Patrząc na Foo
, typy osiągalne bezpośrednio/pośrednio obejmują:
Typ | Opis |
---|---|
bool | Zwracany typ Foo . |
int | Typ pierwszego parametru Foo . |
bar_t * | Typ drugiego parametru Foo. Za pomocą bar_t * , bar_t jest eksportowany poprzez foo_exported.h .bar_t zawiera element mfoo typu foo_t , który jest eksportowany poprzez 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 nieprzezroczysty, dlatego dozwolone są zmiany wprowadzone w foo_private_t .) |
Podobne wyjaśnienie można podać dla typów osiągalnych poprzez specyfikatory klasy bazowej i parametry 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 osiągalnych bezpośrednio lub pośrednio przez eksportowaną funkcję następujące zmiany w bibliotece są klasyfikowane jako naruszające ABI:
Typ danych | Opis |
---|---|
Struktury i klasy |
|
Związki |
|
Wyliczenia |
|
Symbole globalne |
|
* Nie wolno zmieniać ani usuwać zarówno publicznych, jak i prywatnych funkcji członkowskich, ponieważ publiczne funkcje wbudowane mogą odnosić się do prywatnych funkcji członkowskich. Odniesienia symboliczne do prywatnych funkcji członkowskich mogą być przechowywane w plikach binarnych wywołującego. Zmiana lub usunięcie prywatnych funkcji członkowskich z bibliotek współdzielonych może spowodować powstanie niekompatybilnych wstecz plików binarnych.
** Nie można zmieniać przesunięć w stosunku do publicznych lub prywatnych elementów danych, ponieważ funkcje wbudowane mogą odwoływać się do tych elementów danych w treści funkcji. Zmiana przesunięć elementów danych może spowodować, że pliki binarne będą niezgodne wstecz.
*** Chociaż nie zmieniają one układu pamięci typu, istnieją różnice semantyczne, które mogą prowadzić do tego, że biblioteki nie będą działać zgodnie z oczekiwaniami.
Korzystanie z narzędzi zgodności ABI
Kiedy budowana jest biblioteka VNDK, jej ABI jest porównywane z odpowiednią referencją ABI dla budowanej wersji 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, podczas budowania 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
zawiera niezgodną zmianę, system kompilacji zgłasza błąd i komunikat podobny 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
Kiedy 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 dziedziczone poprzez 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
(używając dostarczonego mu 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 referencyjnym plikiem.lsdump
w celu utworzenia raportu różnicowego przedstawiającego różnice w ABI obu bibliotek.Rysunek 3. Tworzenie raportu różnicowego
nagłówek-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, który nie gwarantuje stabilności w przyszłych wersjach. W związku z tym formatowanie pliku .sdump
należy traktować 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
aby wygenerować pośredni plik .sdump
, który reprezentuje ABI prezentowany przez plik źródłowy, używając:
$ 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ącymi po --
i wyemitował informacje ABI eksportowane przez publiczne nagłówki w exported
katalogu. 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 przez plik źródłowy foo.cpp
oraz publiczne nagłówki, na przykład:
-
record_types
. Odwołaj się do struktur, związków lub klas zdefiniowanych w publicznych nagłówkach. Każdy typ rekordu zawiera informacje o swoich polach, jego rozmiarze, specyfikatorze dostępu, pliku nagłówkowym, w którym jest zdefiniowany, i innych atrybutach. -
pointer_types
. Odwołaj się do typów wskaźników, do których bezpośrednio/pośrednio odwołują się wyeksportowane rekordy/funkcje w publicznych nagłówkach, 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 tablicowych oraz typów referencyjnych lvalue i rvalue. Takie informacje umożliwiają różnicowanie rekurencyjne. -
functions
. Reprezentuj funkcje eksportowane przez nagłówki publiczne. Zawierają także informacje o zniekształconej nazwie funkcji, typie zwracanym, 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 libfoo ). |
Narzędzie łączy wykresy typów we wszystkich otrzymanych plikach pośrednich, biorąc pod uwagę jedną definicję (typy zdefiniowane przez użytkownika w różnych jednostkach tłumaczeniowych o tej samej w pełni kwalifikowanej nazwie, mogą różnić się semantycznie) różnice pomię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
. można wywołać header-abi-linker
aby utworzyć kompletny 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 z nim pliki
.sdump
(foo.sdump
ibar.sdump
), odfiltrowując informacje ABI, których nie ma w nagłówkach znajdujących się w katalogu:exported
. - Analizuje bibliotekę
libfoo.so
i zbiera informacje o symbolach wyeksportowanych przez bibliotekę za pośrednictwem tabeli.dynsym
. - Dodaje
_Z3FooiP3bar
i_Z6FooBadiP3foo
.
libfoo.so.lsdump
to końcowy wygenerowany zrzut ABI libfoo.so
.
nagłówek-abi-diff
Narzędzie header-abi-diff
porównuje dwa pliki .lsdump
reprezentujące ABI dwóch bibliotek i tworzy raport różnicowy stwierdzający różnice między dwoma ABI.
Wejścia |
|
---|---|
Wyjście | Raport różnicowy stwierdzający różnice w ABI oferowanych przez dwie porównywane biblioteki współdzielone. |
Plik różnicowy ABI ma format tekstowy 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, powinno to zostać oznaczone jako zmiana zakłócająca 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 wszystkich istotnych zmian ABI w libfoo
. Komunikat record_type_diffs
wskazuje, że rekord został zmieniony i zawiera listę niezgodnych zmian, które obejmują:
- Zmiana rozmiaru rekordu z
24
bajtów na8
bajtów. - Typ pola
mfoo
zmienia się zfoo
nafoo *
(wszystkie typy typedef są usuwane).
Pole type_stack
wskazuje, w jaki sposób header-abi-diff
osiągnął zmieniony typ ( bar
). To pole można interpretować, ponieważ Foo
jest wyeksportowaną funkcją, która przyjmuje bar *
jako parametr wskazujący na bar
, który został wyeksportowany i zmieniony.
Egzekwowanie ABI/API
Aby wymusić ABI/API bibliotek współdzielonych VNDK, odniesienia 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 odniesień jakakolwiek 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ć odniesienia ABI libbinder
, uruchom:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder