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 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 ABI wcześniejszych wersji bibliotek współdzielonych VNDK, aby moduły dostawców mogły z nimi współpracować bez konieczności ponownej kompilacji i bez błędów w czasie wykonywania. W międzyczasie między wersjami Androida biblioteki VNDK mogą ulec zmianie, a w przypadku ABI nie ma żadnych gwarancji.
Aby zapewnić zgodność interfejsu ABI, Android 9 zawiera sprawdzanie nagłówka ABI, jak opisano w następnych sekcjach.
Zgodność z VNDK i ABI
VNDK to ograniczony zestaw bibliotek, do których mogą się odwoływać moduły dostawców i które umożliwiają aktualizacje tylko dla frameworka. 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
w pliku.so
odpowiadającym zasobom wspólnym. - Ma słabe lub globalne wiązanie.
- widoczność jest ustawiona na DOmyślna lub Chroniony.
- Indeks sekcji nie jest UNDEFINED.
- Typ to FUNC lub OBJECT.
Publiczne nagłówki współdzielonej 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ółdzielonej 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
zawiera funkcję Foo
, która jest wyeksportowanym symbolem znajdującym się 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 zwracanej wartości Foo .
|
int
|
Typ pierwszego parametru Foo .
|
bar_t *
|
Typ drugiego parametru Foo. Za pomocą usługi bar_t * dane bar_t są eksportowane za pomocą usługi 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:
foo_private_t jest jednak 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 podać w przypadku typów dostępnych za pomocą wskaźników klasy podstawowej i parametrów szablonu.
Zapewnianie zgodności z interfejsem ABI
Należy zapewnić zgodność z ABI 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ę następujące zmiany w bibliotece są klasyfikowane jako naruszające ABI:
Typ danych | Opis |
---|---|
Struktury i klasy |
|
Związki |
|
Wyliczenia |
|
Global Symbols |
|
* Zarówno publiczne, jak i prywatne funkcje członka nie mogą zostać zmienione ani usunięte, ponieważ publiczne funkcje w ciele mogą się odwoływać do prywatnych funkcji członka. Odwołania do symboli funkcji prywatnych członków mogą być przechowywane w binarnych plikach wywołujących. 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ęć elementów danych może spowodować brak zgodności wstecznej binarów.
*** Chociaż nie zmieniają one rozkł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 do zapewnienia zgodności z zasadami ABI
Podczas kompilowania biblioteki VNDK porównuje się 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 ABI libbinder
ulegnie zmianie, system kompilacji wyświetli 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 kompilowane pliki źródłowe 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 podobnym plikiem.lsdump
, aby wygenerować raport różnic, który opisuje różnice 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 (na przykład 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ć foo.cpp
z flagami kompilatora pod --
, oraz emituje informacje ABI, które są eksportowane przez publiczne nagłówki 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 ABI wyeksportowane przez plik źródłowyfoo.cpp
oraz 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 swoich polach, rozmiarze, wskaźniku 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 (przez 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
. Przedstawiają funkcje eksportowane przez nagłówki publiczne. Zawierają one też informacje o zagnieżdżonym imieniu funkcji, typie zwracanym, 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 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 przeanalizuje skrypt wersji lub tabelę .dynsym
w bibliotece współdzielonej (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 ostatni wygenerowany zrzut interfejsu ABI dla 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 2 ABI.
Wejścia |
|
---|---|
Urządzenie wyjściowe | Raport różnic podający różnice w ABI oferowanych przez porównywane biblioteki wspólne. |
Plik ABI diff jest w formacie tekstowym protobuf. Format może się zmienić w kolejnych wersjach.
Na przykład masz 2 wersje strony 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
to typ osiągalny, header-abi-diff
powinien oznaczyć to jako zmiana 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 interfejsem ABI.libfoo
Wiadomość 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.
Wymuszanie ABI/API
Aby wymusić ABI/API w bibliotekach wspólnych VNDK, odwołania do ABI muszą zostać zaimportowane do ${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 kodzie źródłowym, która powoduje niezgodną wstecznie zmianę ABI/API w bibliotece VNDK, powoduje błąd kompilacji.
Aby zaktualizować odwołania 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 libbinder
ABI, uruchom:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder