Стабильность двоичного интерфейса приложений (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.
- Уровень доступа – DEFAULT или PROTECTED.
- Индекс раздела не 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 : [ "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 сравнивается с эталонным ABI по следующему адресу:
${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
Инструмент 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-linker принимает промежуточные файлы, созданные header-abi-dumper, в качестве входных данных, а затем связывает эти файлы:
| Входы |
|
|---|---|
| Результат | Файл, описывающий ABI общей библиотеки (например, libfoo.so.lsdump представляет ABI libfoo).
|
Инструмент объединяет графы типов во всех переданных ему промежуточных файлах, учитывая различия в одном определении (типы, определенные пользователем в разных единицах трансляции с одним и тем же полным именем, могут семантически различаться) в разных единицах трансляции. Затем инструмент анализирует скрипт версии или таблицу .dynsym общей библиотеки (файл .so) и составляет список экспортированных символов.
Например, поле libfoo состоит из полей foo.cpp и bar.cpp. Чтобы создать полный дамп ABI, связанный с libfoo, можно вызвать header-abi-linker следующим образом:
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.
header-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 – доступный тип, header-abi-diff должен пометить это как критическое изменение ABI.
Чтобы запустить 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