Стабильность ABI

Стабильность двоичного интерфейса приложений (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, что приводит к экспорту дополнительных типов:
  • int : – тип m1.
  • int * : – тип m2.
  • foo_private_t * :  – тип mPfoo.

Однако домен 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:

Тип данных Описание
Структуры и классы
  • Изменить размер типа класса или структуры.
  • Базовые классы
    • Добавлять и удалять базовые классы.
    • Добавьте или удалите виртуально унаследованные базовые классы.
    • Изменить порядок базовых классов.
  • Функции-члены
    • Удаление функций-членов*.
    • Добавлять или удалять аргументы из функций-членов.
    • Изменять типы аргументов или типы возвращаемых значений функций-членов*.
    • Изменить макет виртуальной таблицы.
  • Участники данных
    • Удалите статические члены данных.
    • Добавлять или удалять нестатические члены данных.
    • Изменять типы членов данных.
    • Измените смещения для нестатических элементов данных**.
    • Изменить квалификаторы const, volatile и/или restricted для элементов данных***.
    • Понизить уровень спецификаторов доступа элементов данных***.
  • Измените аргументы шаблона.
Профсоюзы
  • Добавление и удаление элементов данных.
  • Изменить размер типа объединения.
  • Изменять типы членов данных.
Перечисления
  • Изменить тип.
  • Изменять названия перечислителей.
  • Измените значения перечислителей.
Глобальные символы
  • Удалите символы, экспортированные общедоступными заголовками.
  • Для глобальных символов типа FUNC
    • Добавлять или удалять аргументы.
    • Изменить типы аргументов.
    • Изменить тип возвращаемого значения.
    • Понизить спецификатор доступа***.
  • Для глобальных символов типа OBJECT:
    • Измените соответствующий тип C/C++.
    • Понизить спецификатор доступа***.

* Нельзя изменять или удалять как общедоступные, так и закрытые функции-члены, поскольку общедоступные встроенные функции могут ссылаться на закрытые функции-члены. Ссылки на символы частных функций-членов могут храниться в двоичных файлах вызывающего кода. Изменение или удаление частных функций-членов из общих библиотек может привести к созданию бинарных файлов, несовместимых с предыдущими версиями.

** Смещения к общедоступным или частным членам данных не должны быть изменены, поскольку встроенные функции могут ссылаться на этих членов данных в теле функции. Изменение смещений элементов данных может привести к созданию бинарных файлов, несовместимых с предыдущими версиями.

*** Хотя эти изменения не влияют на структуру памяти типа, они могут привести к тому, что библиотеки будут работать не так, как ожидается.

Используйте инструменты для проверки совместимости 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:

  1. header-abi-dumper обрабатывает исходные файлы, скомпилированные для создания библиотеки VNDK (собственные исходные файлы библиотеки, а также исходные файлы, унаследованные через статические транзитивные зависимости), чтобы создать файлы .sdump, соответствующие каждому источнику.
    Создание файла sdump
    Рисунок 1. Создание файлов .sdump
  2. Затем header-abi-linker обрабатывает файлы .sdump (используя предоставленный ему скрипт версии или файл .so, соответствующий общей библиотеке), чтобы создать файл .lsdump, в котором регистрируется вся информация ABI, относящаяся к общей библиотеке.
    создание файла lsdump;
    Рисунок 2. Создание файла .lsdump
  3. header-abi-diff сравнивает файл .lsdump с эталонным файлом .lsdump и создает отчет о различиях, в котором перечислены различия в ABI двух библиотек.
    создание diff-файла ABI
    Рисунок 3. Создание отчета о различиях

header-abi-dumper

Инструмент header-abi-dumper анализирует исходный файл C/C++ и сохраняет ABI, полученный из этого файла, в промежуточный файл. Система сборки запускает header-abi-dumper для всех скомпилированных исходных файлов, а также создает библиотеку, которая включает исходные файлы из транзитивных зависимостей.

Входы
  • Исходный файл C/C++
  • В экспортируемые данные включены каталоги
  • Параметры компилятора
Результат Файл, описывающий 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, в качестве входных данных, а затем связывает эти файлы:

Входы
  • Промежуточные файлы, созданные header-abi-dumper
  • Скрипт версии или файл сопоставления (необязательно)
  • .so файл общей библиотеки
  • Экспортированные каталоги include
Результат Файл, описывающий 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.

Входы
  • Файл .lsdump, представляющий ABI старой общей библиотеки.
  • Файл .lsdump, представляющий 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