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

Стабильность двоичного интерфейса приложения (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
    • Добавить или удалить аргументы.
    • Измените типы аргументов.
    • Измените тип возврата.
    • Понизьте спецификатор доступа***.
  • Для глобальных символов типа 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. Создание отчета о различиях

заголовок-аби-дампер

Инструмент 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 diff находится в текстовом формате 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