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

Стабильность двоичного интерфейса приложения (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 привязку.
  • Видимость ПО УМОЛЧАНИЮ или ЗАЩИЩЕНА.
  • Индекс раздела не 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 : [
    "include"
  ],
}
.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 . ( foot_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/(v)ndk/<${PLATFORM_VNDK_VERSION}>/<BINDER_BITNESS>/<ARCH_ARCH-VARIANT>/source-based

Например, при сборке libfoo для уровня API 27 libfoo предполагаемый ABI libfoo сравнивается с его эталоном по адресу:

${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/27/64/<ARCH_ARCH-VARIANT>/source-based/libfoo.so.lsdump

Ошибка поломки АБИ

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

В настоящее время файлы .sdump отформатированы как Protobuf TextFormatted , стабильность которого в будущих выпусках не гарантируется. Таким образом, форматирование файла .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 -- -x c++

Эта команда указывает header-abi-dumper dumper проанализировать foo.cpp и выдать информацию ABI, которая отображается в общедоступных заголовках в exported каталоге. Это выдержка (не полное представление) из foo.sdump , сгенерированного header-abi-dumper :

record_types {
  type_info {
    name: "foo"
    size: 12
    alignment: 4
    referenced_type: "type-1"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "foo"
    self_type: "type-1"
  }
  fields {
    referenced_type: "type-2"
    field_offset: 0
    field_name: "m1"
    access: public_access
  }
  fields {
    referenced_type: "type-3"
    field_offset: 32
    field_name: "m2"
    access: public_access
  }
  fields {
    referenced_type: "type-5"
    field_offset: 64
    field_name: "mPfoo"
    access: public_access
  }
  access: public_access
  record_kind: struct_kind
  tag_info {
    unique_id: "_ZTS3foo"
  }
}
record_types {
  type_info {
    name: "bar"
    size: 12
    alignment: 4
    referenced_type: "type-6"
…
pointer_types {
  type_info {
    name: "bar *"
    size: 4
    alignment: 4
    referenced_type: "type-6"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "bar *"
    self_type: "type-8"
  }
}
builtin_types {
  type_info {
    name: "int"
    size: 4
    alignment: 4
    referenced_type: "type-2"
    source_file: ""
    linker_set_key: "int"
    self_type: "type-2"
  }
  is_unsigned: false
  is_integral: true
}
functions {
  return_type: "type-7"
  function_name: "Foo"
  source_file: "foo/include/foo_exported.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
  parameters {
    referenced_type: "type-8"
    default_arg: false
  }
  linker_set_key: "_Z3FooiP3bar"
  access: public_access
}

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 добавляет файл bar.cpp (который предоставляет функцию bar C) в свою компиляцию, 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 :

record_types {
  type_info {
    name: "foo"
    size: 24
    alignment: 8
    referenced_type: "type-1"
    source_file: "foo/include/foo_exported.h"
    linker_set_key: "foo"
    self_type: "type-1"
  }
  fields {
    referenced_type: "type-2"
    field_offset: 0
    field_name: "m1"
    access: public_access
  }
  fields {
    referenced_type: "type-3"
    field_offset: 64
    field_name: "m2"
    access: public_access
  }
  fields {
    referenced_type: "type-4"
    field_offset: 128
    field_name: "mPfoo"
    access: public_access
  }
  access: public_access
  record_kind: struct_kind
  tag_info {
    unique_id: "_ZTS3foo"
  }
}
record_types {
  type_info {
    name: "bar"
    size: 24
    alignment: 8
...
builtin_types {
  type_info {
    name: "void"
    size: 0
    alignment: 0
    referenced_type: "type-6"
    source_file: ""
    linker_set_key: "void"
    self_type: "type-6"
  }
  is_unsigned: false
  is_integral: false
}
functions {
  return_type: "type-19"
  function_name: "Foo"
  source_file: "foo/include/foo_exported.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
  parameters {
    referenced_type: "type-20"
    default_arg: false
  }
  linker_set_key: "_Z3FooiP3bar"
  access: public_access
}
functions {
  return_type: "type-6"
  function_name: "FooBad"
  source_file: "foo/include/foo_exported_bad.h"
  parameters {
    referenced_type: "type-2"
    default_arg: false
  }
parameters {
    referenced_type: "type-7"
    default_arg: false
  }
  linker_set_key: "_Z6FooBadiP3foo"
  access: public_access
}
elf_functions {
  name: "_Z3FooiP3bar"
}
elf_functions {
  name: "_Z6FooBadiP3foo"
}

Инструмент header-abi-linker :

  • Связывает предоставленные ему файлы .sdump ( foo.sdump и bar.sdump ), отфильтровывая информацию ABI, отсутствующую в заголовках, находящихся в каталоге: exported .
  • libfoo.so и собирает информацию о символах, экспортируемых библиотекой, через свою таблицу .dynsym .
  • Добавляет _Z3FooiP3bar и Bar .

libfoo.so.lsdump — это окончательный сгенерированный ABI-дамп libfoo.so .

заголовок-аби-diff

Инструмент header-abi-diff сравнивает два файла .lsdump , представляющие ABI двух библиотек, и создает отчет о различиях, в котором указываются различия между двумя ABI.

Входы
  • Файл .lsdump , представляющий ABI старой общей библиотеки.
  • Файл .lsdump , представляющий ABI новой общей библиотеки.
Выход Отчет о различиях, в котором указаны различия в ABI, предлагаемых двумя сравниваемыми общими библиотеками.

Файл различий ABI разработан таким образом, чтобы быть максимально подробным и удобочитаемым. Формат может быть изменен в будущих выпусках. Например, у вас есть две версии 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 и LLNDK, ссылки ABI должны быть проверены в ${ANDROID_BUILD_TOP}/prebuilts/abi-dumps/(v)ndk/ . Чтобы создать эти ссылки, выполните следующую команду:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py

После создания ссылок любое изменение исходного кода, которое приводит к несовместимому изменению ABI/API в библиотеке VNDK или LLNDK, теперь приводит к ошибке сборки.

Чтобы обновить ссылки ABI для конкретных основных библиотек VNDK, выполните следующую команду:

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

Чтобы обновить ссылки ABI для определенных библиотек LLNDK, выполните следующую команду:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l <lib1> -l <lib2> --llndk

Например, чтобы обновить ссылки libm ABI, запустите:

${ANDROID_BUILD_TOP}/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l libm --llndk