Стабильность двоичного интерфейса приложения (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
Для библиотек, отмеченных как vendor_available: true
и vndk.enabled: true
в соответствующих файлах Android.bp
, необходимо обеспечить соответствие ABI. Например:
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. Создание отчета о различиях
заголовок-abi-dumper
Инструмент 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 представлен в текстовом формате 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