Стабильность двоичного интерфейса приложения (ABI) является необходимым условием для обновлений только платформы, поскольку модули поставщика могут зависеть от общих библиотек Vendor Native Development Kit (VNDK), которые находятся в системном разделе. В выпуске Android недавно созданные общие библиотеки VNDK должны быть ABI-совместимы с ранее выпущенными общими библиотеками VNDK, чтобы модули поставщиков могли работать с этими библиотеками без перекомпиляции и без ошибок во время выполнения. Между выпусками Android библиотеки VNDK могут быть изменены, и нет никаких гарантий ABI.
Чтобы обеспечить совместимость с ABI, Android 9 включает средство проверки ABI заголовков, как описано в следующих разделах.
О соответствии VNDK и ABI
VNDK — это ограниченный набор библиотек, на которые могут ссылаться модули поставщиков и которые позволяют выполнять обновления только для платформы. Соответствие ABI относится к способности более новой версии общей библиотеки работать должным образом с модулем, который динамически связан с ней (т. е. работает так, как работала бы более старая версия библиотеки).
Об экспортированных символах
Экспортированный символ (также известный как глобальный символ ) относится к символу, который удовлетворяет всем следующим требованиям:
- Экспортируется общедоступными заголовками общей библиотеки.
- Появляется в таблице
.dynsym
файла.so
, соответствующего общей библиотеке. - Имеет WEAK или GLOBAL привязку.
- Видимость ПО УМОЛЧАНИЮ или ЗАЩИЩЕНА.
- Индекс раздела не UNDEFINED.
- Тип — 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 : [ "include" ], } |
.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 . ( foot_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/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based
Например, при сборке libfoo
для уровня API 27 libfoo
предполагаемый ABI libfoo сравнивается с его эталоном по адресу:
${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/source-based/libfoo.so.lsdump
Ошибка поломки АБИ
При поломках 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
для всех скомпилированных исходных файлов, а также создает библиотеку, включающую исходные файлы из транзитивных зависимостей.
В настоящее время файлы .sdump
отформатированы как Protobuf TextFormatted , стабильность которого в будущих выпусках не гарантируется. Таким образом, форматирование файла .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 -- -x c++
Эта команда указывает header-abi-dumper
dumper проанализировать foo.cpp
и выдать информацию ABI, которая отображается в общедоступных заголовках в exported
каталоге. Это выдержка (не полное представление) из foo.sdump
, сгенерированного 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
содержит информацию 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
добавляет файл bar.cpp
(который предоставляет функцию bar
C) в свою компиляцию, 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
:
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" }
Инструмент header-abi-linker
:
- Связывает предоставленные ему файлы
.sdump
(foo.sdump
иbar.sdump
), отфильтровывая информацию ABI, отсутствующую в заголовках, находящихся в каталоге:exported
. -
libfoo.so
и собирает информацию о символах, экспортируемых библиотекой, через свою таблицу.dynsym
. - Добавляет
_Z3FooiP3bar
иBar
.
libfoo.so.lsdump
— это окончательный сгенерированный ABI-дамп libfoo.so
.
заголовок-аби-diff
Инструмент header-abi-diff
сравнивает два файла .lsdump
, представляющие ABI двух библиотек, и создает отчет о различиях, в котором указываются различия между двумя ABI.
Входы |
|
---|---|
Выход | Отчет о различиях, в котором указаны различия в ABI, предлагаемых двумя сравниваемыми общими библиотеками. |
Файл различий ABI разработан таким образом, чтобы быть максимально подробным и удобочитаемым. Формат может быть изменен в будущих выпусках. Например, у вас есть две версии 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 и LLNDK, ссылки ABI должны быть проверены в ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/
. Чтобы создать эти ссылки, выполните следующую команду:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py
После создания ссылок любое изменение исходного кода, которое приводит к несовместимому изменению ABI/API в библиотеке VNDK или LLNDK, теперь приводит к ошибке сборки.
Чтобы обновить ссылки ABI для конкретных основных библиотек VNDK, выполните следующую команду:
${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
Чтобы обновить ссылки ABI для определенных библиотек LLNDK, выполните следующую команду:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk
Например, чтобы обновить ссылки libm
ABI, запустите:
${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libm --llndk