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

Стабильность двоичного интерфейса приложения (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 , в результате чего экспортируется больше типов:
  • 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
    • Добавьте или удалите аргументы.
    • Измените типы аргументов.
    • Измените тип возвращаемого значения.
    • Понизьте спецификатор доступа***.
  • Для глобальных символов типа ОБЪЕКТ
    • Измените соответствующий тип 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-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 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 анализирует исходный файл 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-dumper а затем связывает эти файлы:

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

Входы
  • Файл .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