стабильность ЛПИ

Стабильность интерфейса ABI (Application Binary Interface) является необходимым условием для обновлений, касающихся только фреймворка, поскольку модули сторонних разработчиков могут зависеть от разделяемых библиотек Vendor Native Development Kit (VNDK), расположенных в системном разделе. В рамках выпуска Android новые разделяемые библиотеки VNDK должны быть ABI-совместимы с ранее выпущенными разделяемыми библиотеками VNDK, чтобы модули сторонних разработчиков могли работать с этими библиотеками без перекомпиляции и без ошибок во время выполнения. Между выпусками Android библиотеки VNDK могут изменяться, и гарантий ABI нет.

Для обеспечения совместимости ABI в Android 9 включен инструмент проверки ABI заголовков, как описано в следующих разделах.

О соответствии стандартам VNDK и ABI

VNDK — это ограниченный набор библиотек, к которым могут подключаться модули сторонних разработчиков и которые позволяют обновлять только фреймворки. Соответствие ABI означает способность более новой версии разделяемой библиотеки работать должным образом с модулем, который динамически с ней связан (т.е. работать так же, как и более старая версия библиотеки).

О экспортируемых символах

Экспортируемый символ (также известный как глобальный символ ) — это символ, удовлетворяющий всем следующим условиям:

  • Экспортировано с помощью общедоступных заголовочных файлов общей библиотеки.
  • Отображается в таблице .dynsym файла .so , соответствующего разделяемой библиотеке.
  • Имеет СЛАБУЮ или ОБЩУЮ переплетную структуру.
  • Режим видимости — DEFAULT или PROTECTED.
  • Указатель разделов не является НЕОПРЕДЕЛЕННЫМ.
  • Тип может быть либо 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.

Необходимо обеспечить соответствие стандарту ABI для библиотек, помеченных vendor_available: true и vndk.enabled: true в соответствующих файлах Android.bp . Например:

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 сравнивается с его эталонным значением по адресу:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/vndk/27/64/x86/source-based/libfoo.so.lsdump

Ошибка разрыва ABI

При обнаружении нарушений ABI в журнале сборки отображаются предупреждения с указанием типа предупреждения и пути к отчету о различиях в ABI. Например, если в 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 creation
    Рисунок 1. Создание файлов .sdump
  2. Затем header-abi-linker обрабатывает файлы .sdump (используя либо предоставленный ему скрипт версии, либо файл .so соответствующий разделяемой библиотеке), чтобы создать файл .lsdump , в котором регистрируется вся информация об ABI, соответствующая разделяемой библиотеке.
    lsdump creation
    Рисунок 2. Создание файла .lsdump
  3. header-abi-diff сравнивает файл .lsdump с эталонным файлом .lsdump , чтобы создать отчет о различиях, в котором описываются различия в ABI двух библиотек.
    abi diff creation
    Рисунок 3. Создание отчета о различиях.

header-abi-dumper

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

Входные данные
  • Исходный файл AC/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 файл разделяемой библиотеки
  • Экспортированные файлы включают каталоги.
Выход Файл, описывающий 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 является достижимым типом, это должно быть отмечено как изменение, нарушающее 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 необходимо добавить ссылки на 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