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

Стабильность двоичного интерфейса приложения (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

Для библиотек, отмеченных как 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 сравнивается с его ссылкой по адресу:

${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 ----

Сборка библиотеки VNDK для проверки ABI

При создании библиотеки 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. Создание отчета о различиях

заголовок-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 . Представляют функции, экспортируемые публичными заголовками. Они также содержат информацию об изменённом имени функции, типе возвращаемого значения, типах параметров, спецификаторе доступа и других атрибутах.

заголовок-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 . 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 * (все typedef удаляются).

Поле 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