Стабильность двоичного интерфейса приложения (ABI) является обязательным условием обновлений только платформы, поскольку модули поставщика могут зависеть от общих библиотек Vendor Native Development Kit (VNDK), которые находятся в системном разделе. В выпуске Android новые общие библиотеки VNDK должны быть ABI-совместимы с ранее выпущенными общими библиотеками VNDK, чтобы модули поставщиков могли работать с этими библиотеками без перекомпиляции и без ошибок во время выполнения. Между выпусками Android библиотеки VNDK могут быть изменены, и никаких гарантий ABI нет.
Чтобы обеспечить совместимость ABI, Android 9 включает средство проверки ABI заголовка, как описано в следующих разделах.
О соответствии VNDK и ABI
VNDK — это ограниченный набор библиотек, к которым могут подключаться модули поставщиков и которые позволяют обновлять только платформу. Соответствие ABI означает способность более новой версии общей библиотеки работать должным образом с модулем, который динамически связан с ней (т. е. работает так же, как работала бы старая версия библиотеки).
Об экспортированных символах
Экспортированный символ (также известный как глобальный символ ) — это символ, который удовлетворяет всем следующим требованиям:
- Экспортируется общедоступными заголовками общей библиотеки.
- Появляется в таблице
.dynsym
файла.so
соответствующего общей библиотеке. - Имеет СЛАБУЮ или ГЛОБАЛЬНУЮ привязку.
- Видимость — ПО УМОЛЧАНИЮ или ЗАЩИТА.
- Индекс раздела не НЕОПРЕДЕЛЕН.
- Тип — FUNC или OBJECT.
Публичные заголовки общей библиотеки определяются как заголовки, доступные другим библиотекам/двоичным файлам через атрибуты export_include_dirs
, export_header_lib_headers
, export_static_lib_headers
, export_shared_lib_headers
и export_generated_headers
в определениях Android.bp
модуля, соответствующего общей библиотеке.
О доступных типах
Достижимый тип — это любой встроенный или определяемый пользователем тип C/C++, который доступен прямо или косвенно через экспортированный символ И экспортируется через общедоступные заголовки. Например, libfoo.so
есть функция Foo
, которая представляет собой экспортированный символ, найденный в таблице .dynsym
. Библиотека libfoo.so
включает в себя следующее:
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" ], } |
Таблица .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 |
Глядя на Foo
, прямые/косвенные доступные типы включают в себя:
Тип | Описание |
---|---|
bool | Возвращаемый тип Foo . |
int | Тип первого параметра Foo . |
bar_t * | Тип второго параметра Foo. Через bar_t * bar_t экспортируется через foo_exported.h .bar_t содержит член mfoo типа foo_t , который экспортируется через foo_exported.h , в результате чего экспортируется больше типов:
Однако foo_private_t НЕ ДОСТУПЕН, поскольку он не экспортируется через foo_exported.h . ( foo_private_t * непрозрачен, поэтому изменения, внесенные в foo_private_t разрешены.) |
Аналогичное объяснение можно дать и для типов, доступных через спецификаторы базового класса и параметры шаблона.
Обеспечить соответствие ABI
Соответствие ABI должно быть обеспечено для библиотек с vendor_available: true
и vndk.enabled: true
в соответствующих файлах Android.bp
. Например:
cc_library { name: "libvndk_example", vendor_available: true, vndk: { enabled: true, } }
Для типов данных, доступных прямо или косвенно с помощью экспортированной функции, следующие изменения в библиотеке классифицируются как нарушение ABI:
Тип данных | Описание |
---|---|
Структуры и классы |
|
Союзы |
|
Перечисления |
|
Глобальные символы |
|
* Как общедоступные, так и закрытые функции-члены нельзя изменять или удалять, поскольку общедоступные встроенные функции могут ссылаться на закрытые функции-члены. Ссылки на символы на частные функции-члены могут храниться в двоичных файлах вызывающего объекта. Изменение или удаление закрытых функций-членов из общих библиотек может привести к обратно несовместимым двоичным файлам.
** Смещения общедоступных или частных элементов данных изменять нельзя, поскольку встроенные функции могут ссылаться на эти элементы данных в теле своей функции. Изменение смещений элементов данных может привести к обратно несовместимым двоичным файлам.
*** Хотя это не меняет структуру памяти типа, существуют семантические различия, которые могут привести к тому, что библиотеки не будут работать должным образом.
Используйте инструменты обеспечения соответствия ABI
При сборке библиотеки VNDK ее ABI сравнивается с соответствующей ссылкой ABI для создаваемой версии VNDK. Эталонные дампы ABI находятся в:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/<PLATFORM_VNDK_VERSION>/<BINDER_BITNESS>/<ARCH>/source-based
Например, при сборке libfoo
для x86 на уровне API 27 предполагаемый ABI libfoo
сравнивается с его ссылкой по адресу:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump
Ошибка поломки ABI
При сбоях ABI в журнале сборки отображаются предупреждения с указанием типа предупреждения и пути к отчету abi-diff. Например, если в ABI libbinder
есть несовместимое изменение, система сборки выдает ошибку с сообщением, подобным следующему:
***************************************************** 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 ----
Сборка проверок ABI библиотеки VNDK
При сборке библиотеки VNDK:
-
header-abi-dumper
обрабатывает исходные файлы, скомпилированные для сборки библиотеки VNDK (собственные исходные файлы библиотеки, а также исходные файлы, унаследованные через статические транзитивные зависимости), для создания файлов.sdump
, соответствующих каждому источнику.Рисунок 1. Создание файлов .sdump
- Затем
header-abi-linker
обрабатывает файлы.sdump
(используя предоставленный ему сценарий версии или файл.so
соответствующий общей библиотеке) для создания файла.lsdump
, в котором регистрируется вся информация ABI, соответствующая общей библиотеке.Рисунок 2. Создание файла .lsdump
-
header-abi-diff
сравнивает файл.lsdump
с эталонным файлом.lsdump
для создания отчета о различиях, в котором описываются различия в ABI двух библиотек.Рисунок 3. Создание отчета о различиях
заголовок-аби-дампер
Инструмент header-abi-dumper
анализирует исходный файл C/C++ и выгружает ABI, полученный из этого исходного файла, в промежуточный файл. Система сборки запускает header-abi-dumper
для всех скомпилированных исходных файлов, а также создает библиотеку, включающую исходные файлы из транзитивных зависимостей.
Входы |
|
---|---|
Выход | Файл, описывающий ABI исходного файла (например, foo.sdump представляет ABI foo.cpp ). |
В настоящее время файлы .sdump
имеют формат JSON, стабильность которого в будущих выпусках не гарантируется. Таким образом, форматирование файла .sdump
следует рассматривать как деталь реализации системы сборки.
Например, libfoo.so
имеет следующий исходный файл 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; }
Вы можете использовать header-abi-dumper
для создания промежуточного файла .sdump
, который представляет ABI, представленный исходным файлом, используя:
$ header-abi-dumper foo.cpp -I exported -o foo.sdump -- -I exported -x c++
Эта команда сообщает header-abi-dumper
проанализировать foo.cpp
с флагами компилятора, следующими за --
, и выдать информацию ABI, которая экспортируется общедоступными заголовками в exported
каталог. Ниже приведен foo.sdump
созданный с помощью 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
содержит информацию ABI, экспортированную исходным файлом foo.cpp
, и общедоступные заголовки, например:
-
record_types
. Обратитесь к структурам, объединениям или классам, определенным в общедоступных заголовках. Каждый тип записи содержит информацию о своих полях, размере, спецификаторе доступа, файле заголовка, в котором она определена, и других атрибутах. -
pointer_types
. Обратитесь к типам указателей, на которые прямо или косвенно ссылаются экспортированные записи/функции в общедоступных заголовках, а также к типу, на который указывает указатель (через полеreferenced_type
вtype_info
). Аналогичная информация регистрируется в файле.sdump
для квалифицированных типов, встроенных типов C/C++, типов массивов и ссылочных типов lvalue и rvalue. Такая информация позволяет проводить рекурсивное сравнение. -
functions
. Представляют функции, экспортированные общедоступными заголовками. У них также есть информация об искаженном имени функции, типе возвращаемого значения, типах параметров, спецификаторе доступа и других атрибутах.
заголовок-аби-линкер
Инструмент header-abi-linker
принимает в качестве входных данных промежуточные файлы, созданные header-abi-dumper
а затем связывает эти файлы:
Входы |
|
---|---|
Выход | Файл, описывающий ABI общей библиотеки (например, libfoo.so.lsdump представляет ABI libfoo ). |
Инструмент объединяет графы типов во всех переданных ему промежуточных файлах, принимая во внимание различия в одном определении (определяемые пользователем типы в разных единицах перевода с одинаковым полным именем могут быть семантически разными) между единицами перевода. Затем инструмент анализирует либо сценарий версии, либо таблицу .dynsym
общей библиотеки (файл .so
), чтобы составить список экспортированных символов.
Например, libfoo
состоит из foo.cpp
и bar.cpp
. header-abi-linker
можно вызвать для создания полного связанного дампа ABI libfoo
следующим образом:
header-abi-linker -I exported foo.sdump bar.sdump \ -o libfoo.so.lsdump \ -so libfoo.so \ -arch arm64 -api current
Пример вывода команды в 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" : [] }
Инструмент header-abi-linker
:
- Связывает предоставленные ему файлы
.sdump
(foo.sdump
иbar.sdump
), отфильтровывая информацию ABI, отсутствующую в заголовках, находящихся в каталоге:exported
. - Анализирует
libfoo.so
и собирает информацию о символах, экспортируемых библиотекой, через ее таблицу.dynsym
. - Добавляет
_Z3FooiP3bar
и_Z6FooBadiP3foo
.
libfoo.so.lsdump
— это окончательный сгенерированный ABI-дамп libfoo.so
.
заголовок-abi-diff
Инструмент header-abi-diff
сравнивает два файла .lsdump
, представляющие ABI двух библиотек, и создает отчет о различиях, в котором указываются различия между двумя ABI.
Входы |
|
---|---|
Выход | Отчет о различиях, в котором указаны различия в ABI, предлагаемых двумя сравниваемыми общими библиотеками. |
Файл различий ABI имеет текстовый формат protobuf . Формат может быть изменен в будущих выпусках.
Например, у вас есть две версии libfoo
: libfoo_old.so
и libfoo_new.so
. В libfoo_new.so
в bar_t
вы меняете тип mfoo
с foo_t
на foo_t *
. Поскольку bar_t
— это достижимый тип, это должно быть помечено как критическое изменение ABI с помощью header-abi-diff
.
Чтобы запустить header-abi-diff
:
header-abi-diff -old libfoo_old.so.lsdump \ -new libfoo_new.so.lsdump \ -arch arm64 \ -o libfoo.so.abidiff \ -lib libfoo
Пример вывода команды в 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
содержит отчет обо всех критических изменениях ABI в libfoo
. Сообщение record_type_diffs
указывает, что запись изменилась, и перечисляет несовместимые изменения, в том числе:
- Размер записи меняется с
24
байт до8
байт. - Тип поля
mfoo
меняется сfoo
наfoo *
(все определения типов удалены).
Поле type_stack
указывает, как header-abi-diff
достиг измененного типа ( bar
). Это поле можно интерпретировать как Foo
— это экспортированная функция, которая принимает bar *
в качестве параметра, указывающего на bar
, который был экспортирован и изменен.
Принудительное использование ABI и API
Чтобы обеспечить использование ABI и API общих библиотек VNDK, ссылки ABI должны быть проверены в ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/
. Чтобы создать эти ссылки, выполните следующую команду:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
После создания ссылок любое изменение исходного кода, которое приводит к несовместимому изменению ABI/API в библиотеке VNDK, теперь приводит к ошибке сборки.
Чтобы обновить ссылки ABI для определенных библиотек, выполните следующую команду:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2>
Например, чтобы обновить ссылки ABI libbinder
, запустите:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libbinder