Стабильность двоичного интерфейса приложения (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 ----
Сборка библиотеки VNDK ABI проверки
При создании библиотеки 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
. Представляют функции, экспортируемые публичными заголовками. Они также содержат информацию об искаженном имени функции, типе возвращаемого значения, типах параметров, спецификаторе доступа и других атрибутах.
заголовок-abi-linker
Инструмент 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 diff находится в текстовом формате 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 *
(все typedef удаляются).
Поле 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