Stabilność interfejsu binarnego aplikacji (ABI) jest warunkiem wstępnym aktualizacji tylko dla platformy, ponieważ moduły dostawców mogą zależeć od bibliotek współdzielonych Vendor Native Development Kit (VNDK), które znajdują się na partycji systemowej. W wydaniu systemu Android nowo utworzone biblioteki współdzielone VNDK muszą być zgodne 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 wydaniami Androida biblioteki VNDK można zmieniać i nie ma gwarancji ABI.
Aby zapewnić zgodność z ABI, system Android 9 zawiera nagłówek sprawdzania ABI, zgodnie z opisem w poniższych sekcjach.
O zgodności z VNDK i ABI
VNDK to restrykcyjny zestaw bibliotek, z którymi mogą się łączyć moduły dostawcy i które umożliwiają aktualizacje tylko w ramach struktury. Zgodność z ABI odnosi się do zdolności nowszej wersji biblioteki współdzielonej do pracy zgodnie z oczekiwaniami z modułem, który jest z nią dynamicznie połączony (tj. działa tak, jak starsza wersja biblioteki).
O eksportowanych symbolach
Wyeksportowany symbol (znany również jako symbol globalny ) odnosi się do symbolu, który spełnia wszystkie poniższe kryteria:
- Wyeksportowane przez publiczne nagłówki biblioteki współdzielonej.
- Pojawia się w tabeli
.dynsym
pliku.so
odpowiadającego bibliotece współdzielonej. - Ma wiązanie SŁABE lub GLOBALNE.
- Widoczność jest DOMYŚLNA lub ZABEZPIECZONA.
- Indeks sekcji nie jest NIEZDEFINIOWANY.
- Typ to FUNC lub OBJECT.
Publiczne nagłówki biblioteki współdzielonej są zdefiniowane jako nagłówki dostępne dla innych bibliotek/plików binarnych za pośrednictwem 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ącego bibliotece współdzielonej.
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 przez eksportowany symbol ORAZ eksportowany 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:
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 : [ "include" ], } |
.dynsym tabela | |||||||
---|---|---|---|---|---|---|---|
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 osiągalne bezpośrednio/pośrednio obejmują:
Rodzaj | Opis |
---|---|
bool | Typ zwrotu 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 . ( foot_private_t * jest nieprzezroczysty, dlatego zmiany wprowadzone w foo_private_t są dozwolone.) |
Podobne wyjaśnienie można podać również dla typów osiągalnych przez specyfikatory klasy bazowej i parametry szablonu.
Zapewnienie zgodności z ABI
Zgodność z ABI musi być zapewniona 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 wyeksportowaną funkcję następujące zmiany w bibliotece są klasyfikowane jako łamiące ABI:
Typ danych | Opis |
---|---|
Struktury i klasy |
|
Związki |
|
Wyliczenia |
|
Symbole globalne |
|
* Zarówno publiczne, jak i prywatne funkcje członkowskie nie mogą być zmieniane ani usuwane, ponieważ publiczne funkcje wbudowane mogą odwoływać się do prywatnych funkcji członkowskich. Odwołania symboli do prywatnych funkcji składowych mogą być przechowywane w plikach binarnych wywołujących. Zmiana lub usunięcie prywatnych funkcji składowych z bibliotek współdzielonych może spowodować powstanie plików binarnych niezgodnych z poprzednimi wersjami.
** Przesunięcia do publicznych lub prywatnych członków danych nie mogą być zmieniane, ponieważ funkcje wbudowane mogą odwoływać się do tych członków danych w ich treści funkcji. Zmiana przesunięć elementów 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ą prowadzić do tego, że biblioteki nie będą działać zgodnie z oczekiwaniami.
Korzystanie z narzędzi zgodności ABI
Po skompilowaniu biblioteki VNDK, ABI biblioteki jest porównywane z odpowiednim odniesieniem ABI dla kompilowanej wersji VNDK. Referencyjne zrzuty ABI znajdują się w:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based
Na przykład podczas budowania libfoo
dla poziomu API 27 VNDK, wywnioskowany ABI libfoo
jest porównywany z jego referencją pod adresem:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/source-based/libfoo.so.lsdump
Błąd zerwania 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 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
Po zbudowaniu biblioteki 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 przez statyczne zależności przechodnie), w celu utworzenia plików.sdump
, które odpowiadają każdemu źródłu.Rysunek 1. Tworzenie plików .sdump
-
header-abi-linker
następnie przetwarza pliki.sdump
(przy użyciu dostarczonego skryptu wersji lub pliku.so
odpowiadającego bibliotece współdzielonej) w celu utworzenia pliku.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
w celu wygenerowania raportu różnicy, który przedstawia różnice w ABI dwóch 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.
Obecnie pliki .sdump
są sformatowane jako Protobuf TextFormatted , co nie gwarantuje stabilności w przyszłych wydaniach. W związku z tym formatowanie pliku .sdump
należy traktować jako szczegół implementacji systemu kompilacji.
Na przykład libfoo.so
zawiera 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
, który reprezentuje ABI prezentowany przez plik źródłowy, używając:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -x c++
To polecenie mówi header-abi-dumper
, aby przeanalizował foo.cpp
i wyemitował informacje ABI, które są ujawnione w publicznych nagłówkach w exported
katalogu. To jest fragment (nie pełna reprezentacja) z foo.sdump
wygenerowanego przez header-abi-dumper
:
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
zawiera informacje ABI ujawnione przez plik źródłowy foo.cpp
, np.:
-
record_types
. Odwołaj się do struktur, związków lub klas uwidocznionych przez publiczne nagłówki. Każdy typ rekordu zawiera informacje o swoich polach, jego rozmiarze, specyfikatorze dostępu, pliku nagłówkowym, w którym został ujawniony itp. -
pointer_types
. Odwołaj się do typów wskaźników bezpośrednio/pośrednio, do których odwołują się rekordy/funkcje uwidocznione przez publiczne nagłówki, wraz z typem, na który wskazuje wskaźnik (za pośrednictwem polareferenced_type
wtype_info
). Podobne informacje są rejestrowane w pliku.sdump
dla kwalifikowanych typów, wbudowanych typów C/C++, typów tablic oraz typów odwołań lvalue i rvalue (takie rejestrowanie informacji o typach umożliwia porównywanie rekurencyjne). -
functions
. Reprezentuj funkcje udostępniane przez publiczne nagłówki. Zawierają również informacje o zniekształconej nazwie funkcji, typie zwracanym, typach parametrów, specyfikatorze dostępu itp.
header-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, który rejestruje ABI biblioteki współdzielonej (np libfoo.so.lsdump reprezentuje ABI libfoo ). |
Narzędzie łączy grafy typów we wszystkich przekazanych mu 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ą 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ę eksportowanych symboli.
Na przykład, gdy libfoo
doda plik bar.cpp
(który uwidacznia bar
funkcji C) do swojej kompilacji, można wywołać header-abi-linker
w celu utworzenia pełnego połączonego zrzutu 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 dane wyjściowe polecenia w 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" }
Narzędzie header-abi-linker
:
- Łączy dostarczone do niego pliki
.sdump
(foo.sdump
ibar.sdump
), odfiltrowując informacje ABI nieobecne w nagłówkach znajdujących się w katalogu:exported
. - Analizuje
libfoo.so
i zbiera informacje o symbolach wyeksportowanych przez bibliotekę poprzez jej tabelę.dynsym
. - Dodaje
_Z3FooiP3bar
iBar
.
libfoo.so.lsdump
to ostateczny 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óżnicy określający różnice między dwoma ABI.
Wejścia |
|
---|---|
Wyjście | Raport diff określający różnice w ABI oferowanych przez dwie porównywane biblioteki współdzielone. |
Plik diff ABI został zaprojektowany tak, aby był jak najbardziej szczegółowy i czytelny. 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 bezpośrednio osiągalnym, powinno być oznaczone jako zmiana przerywają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 } } }
libfoo.so.abidiff
zawiera raport o wszystkich zmianach ABI w libfoo
. Komunikat record_type_diffs
wskazuje, że rekord uległ zmianie i zawiera listę niezgodnych zmian, które obejmują:
- Rozmiar rekordu zmieniający się z
24
bajtów na8
bajtów. - Typ pola
mfoo
zmieniający 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że być interpretowane jako Foo
jest funkcją eksportowaną, która przyjmuje bar *
jako parametr wskazujący na bar
, który został wyeksportowany i zmieniony.
Wymuszanie ABI/API
Aby wymusić stosowanie ABI/API bibliotek współdzielonych VNDK i LLNDK, odwołania ABI muszą zostać zaewidencjonowane w ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/
. 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 dokonana w kodzie źródłowym, która powoduje niezgodną zmianę ABI/API w bibliotece VNDK lub LLNDK, powoduje teraz błąd kompilacji.
Aby zaktualizować odwołania ABI dla określonych podstawowych bibliotek VNDK, uruchom następujące polecenie:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
Na przykład, aby zaktualizować libbinder
ABI, uruchom:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder
Aby zaktualizować odwołania ABI dla określonych bibliotek LLNDK, uruchom następujące polecenie:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk
Na przykład, aby zaktualizować odniesienia libm
ABI, uruchom:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libm --llndk