Мониторинг ABI ядра Android

В Android 11 и более поздних версиях доступны инструменты для мониторинга интерфейса ABI, которые позволяют стабилизировать ABI ядра Android. Инструменты собирают и сравнивают представления ABI из существующих двоичных файлов ядра (vmlinux+ модули GKI). Эти представления ABI — это файлы .stg и списки символов. Интерфейс, в котором представление дает изображение, называется интерфейсом модуля ядра (KMI). С помощью инструментов можно отслеживать и устранять изменения в KMI.

Инструменты для мониторинга ABI разработаны в AOSP и используют STG (или libabigail в Android 13 и более ранних версиях) для создания и сравнения представлений.

На этой странице описываются инструменты, процесс сбора и анализа представлений ABI, а также использование таких представлений для обеспечения стабильности ABI внутри ядра. На этой странице также приведена информация о том, как вносить изменения в ядра Android.

Процесс

Анализ ABI ядра состоит из нескольких этапов, большинство из которых можно автоматизировать:

  1. Создайте ядро и его представление ABI.
  2. Проанализируйте различия в ABI между сборкой и эталонной версией.
  3. Обновите представление ABI (если требуется).
  4. Как работать со списками символов

Приведенные ниже инструкции подходят для любого ядра, которое можно создать с помощью поддерживаемой цепочки инструментов (например, предварительно созданной цепочки инструментов Clang). repo manifests доступны для всех распространенных ветвей ядра Android и для нескольких ядер, предназначенных для определенных устройств. Они проверяют, что при создании дистрибутива ядра для анализа используется правильная цепочка инструментов.

Списки символов

KMI не включает все символы в ядре и даже не все из более чем 30 000 экспортированных символов. Вместо этого символы, которые могут использоваться модулями поставщиков, явно перечислены в наборе файлов со списками символов, которые общедоступны в дереве ядра (gki/{ARCH}/symbols/* или android/abi_gki_{ARCH}_* в Android 15 и более ранних версиях). Объединение всех символов во всех файлах списка символов определяет набор символов KMI, которые поддерживаются как стабильные. Пример файла со списком символов: gki/aarch64/symbols/db845c, в котором объявлены символы, необходимые для DragonBoard 845c.

В KMI входят только символы, перечисленные в списке символов, а также связанные с ними структуры и определения. Если нужных символов нет, вы можете добавить их в список. После того как новые интерфейсы будут добавлены в список символов и описание KMI, они будут считаться стабильными и не должны удаляться из списка символов или изменяться после заморозки ветки.

У каждой ветви ядра KMI Android Common Kernel (ACK) есть свой набор списков символов. Стабильность ABI между разными ветвями ядра KMI не обеспечивается. Например, KMI для android12-5.10 полностью независим от KMI для android13-5.10.

Инструменты ABI используют списки символов KMI, чтобы ограничить количество интерфейсов, которые необходимо отслеживать на предмет стабильности. Поставщики должны отправлять и обновлять собственные списки символов, чтобы подтверждать, что интерфейсы, на которые они полагаются, поддерживают совместимость с ABI. Например, чтобы посмотреть список списков символов для ядра android16-6.12, перейдите на страницу https://android.googlesource.com/kernel/common/+/refs/heads/android16-6.12/gki/aarch64/symbols.

Список символов содержит символы, которые, как сообщается, необходимы для конкретного поставщика или устройства. Полный список, используемый инструментами, представляет собой объединение всех файлов со списком символов KMI. Инструменты ABI определяют детали каждого символа, включая сигнатуру функции и вложенные структуры данных.

Когда KMI заморожен, в существующие интерфейсы KMI нельзя вносить изменения. Однако поставщики могут в любое время добавлять символы в KMI, если это не влияет на стабильность существующего ABI. Новые символы становятся стабильными, как только они упоминаются в списке символов KMI. Символы не должны удаляться из списка для ядра, если нет подтверждения, что ни одно устройство не поставлялось с зависимостью от этого символа.

Чтобы создать список символов KMI для устройства, следуйте инструкциям из статьи Как работать со списками символов. Многие партнеры отправляют один список символов на каждое подтверждение, но это не обязательное требование. При необходимости вы можете отправить несколько списков символов.

Как продлить срок действия ключа управления медиаконтентом

Символы KMI и связанные с ними структуры остаются стабильными (то есть изменения, нарушающие стабильность интерфейсов в ядре с замороженным KMI, не могут быть приняты), но ядро GKI открыто для расширений, чтобы устройства, выпущенные позже в этом году, не должны были определять все свои зависимости до заморозки KMI. Чтобы расширить KMI, вы можете добавить в него новые символы для новых или существующих экспортированных функций ядра, даже если KMI заморожен. Также могут быть приняты новые исправления ядра, если они не нарушают KMI.

О нарушениях совместимости ядра

У ядра есть источники, на основе которых создаются двоичные файлы. В ветвях ядра, отслеживаемых ABI, есть представление ABI текущего GKI ABI (в виде файла .stg). После сборки двоичных файлов (vmlinux, Image и любых модулей GKI) из них можно извлечь представление ABI. Любое изменение исходного файла ядра может повлиять на двоичные файлы, а значит, и на извлеченные .stg. Анализ совместимости ABI сравнивает зафиксированный файл .stg с файлом, извлеченным из артефактов сборки, и устанавливает ярлык Lint-1 для изменения в Gerrit, если обнаруживает семантическое различие.

Как обрабатывать изменения ABI

Например, следующий патч приводит к очевидному нарушению совместимости ABI:

diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
index 42786e6364ef..e15f1d0f137b 100644
--- a/include/linux/mm_types.h
+++ b/include/linux/mm_types.h
@@ -657,6 +657,7 @@ struct mm_struct {
                ANDROID_KABI_RESERVE(1);
        } __randomize_layout;

+       int tickle_count;
        /*
         * The mm_cpumask needs to be at the end of mm_struct, because it
         * is dynamically sized based on nr_cpu_ids.

Если вы запустите сборку ABI с примененным патчем, инструмент завершит работу с ненулевым кодом ошибки и сообщит о различии ABI, как показано ниже.

function symbol 'struct block_device* I_BDEV(struct inode*)' changed
  CRC changed from 0x8d400dbd to 0xabfc92ad

function symbol 'void* PDE_DATA(const struct inode*)' changed
  CRC changed from 0xc3c38b5c to 0x7ad96c0d

function symbol 'void __ClearPageMovable(struct page*)' changed
  CRC changed from 0xf489e5e8 to 0x92bd005e

... 4492 omitted; 4495 symbols have only CRC changes

type 'struct mm_struct' changed
  byte size changed from 992 to 1000
  member 'int tickle_count' was added
  member 'unsigned long cpu_bitmap[0]' changed
    offset changed by 64

Различия в ABI, обнаруженные во время сборки

Чаще всего ошибки возникают, когда драйвер использует новый символ из ядра, которого нет ни в одном из списков символов.

Если символа нет в списке, сначала убедитесь, что он экспортирован с помощью EXPORT_SYMBOL_GPL(symbol_name), а затем обновите список символов и представление ABI. Например, следующие изменения добавляют новую функцию Incremental FS в ветку android-12-5.10, включая обновление списка символов и представления ABI.

  • Пример изменения функции можно найти в aosp/1345659.
  • Пример списка символов можно найти в aosp/1346742.
  • Пример изменения представления ABI можно найти в aosp/1349377.

Если символ экспортирован (вами или ранее), но ни один другой драйвер его не использует, может возникнуть ошибка сборки, похожая на приведенную ниже.

Comparing the KMI and the symbol lists:
+ build/abi/compare_to_symbol_list out/$BRANCH/common/Module.symvers out/$BRANCH/common/abi_symbollist.raw
ERROR: Differences between ksymtab and symbol list detected!
Symbols missing from ksymtab:
Symbols missing from symbol list:
 - simple_strtoull

Чтобы устранить проблему, обновите список символов KMI в ядре и ACK (см. раздел Обновление представления ABI). Пример обновления списка символов и представления ABI в ACK можно найти в aosp/1367601.

Как устранять проблемы с ABI ядра

Вы можете устранить нарушения ABI ядра, проведя рефакторинг кода, чтобы не менять ABI, или обновив представление ABI. Чтобы определить, какой подход лучше всего подходит для вашей ситуации, воспользуйтесь следующей таблицей.

Блок-схема нарушения ABI

Рисунок 1. Устранение несовместимости ABI

Как провести рефакторинг кода, чтобы избежать изменений ABI

Постарайтесь не изменять существующий ABI. Во многих случаях можно провести рефакторинг кода, чтобы удалить изменения, влияющие на ABI.

  • Рефакторинг изменений полей структуры. Если изменение влияет на ABI для функции отладки, добавьте #ifdef вокруг полей (в структурах и ссылках на источник) и убедитесь, что CONFIG, используемый для #ifdef, отключен для производственной конфигурации по умолчанию и gki_defconfig. Пример того, как добавить debug config в структуру, не нарушая ABI, можно найти в этом наборе исправлений.

  • Рефакторинг функций без изменения основного ядра. Если для поддержки партнерских модулей в ACK необходимо добавить новые функции, попробуйте провести рефакторинг части ABI, чтобы избежать изменения ABI ядра. Пример использования существующего ABI ядра для добавления дополнительных возможностей без изменения ABI ядра приведен в aosp/1312213.

Как исправить неработающий ABI в Android Gerrit

Если вы не планировали нарушать ABI ядра, вам нужно провести расследование, используя рекомендации, предоставленные инструментами мониторинга ABI. Чаще всего проблемы возникают из-за изменений в структуре данных и связанных с ними изменений в контрольной сумме CRC символов или из-за изменений в параметрах конфигурации, которые приводят к одному из вышеперечисленных факторов. Сначала устраните проблемы, обнаруженные инструментом.

Вы можете воспроизвести результаты ABI локально. Подробнее о том, как собрать ядро и его представление ABI…

О ярлыках Lint-1

Если вы загружаете изменения в ветку с замороженным или завершенным KMI, они должны пройти анализ на соответствие и совместимость ABI, чтобы убедиться, что изменения в представлении ABI отражают фактический ABI и не содержат несовместимостей (удаления символов или изменений типов).

Каждый из этих анализов ABI может установить ярлык Lint-1 и заблокировать отправку изменений, пока все проблемы не будут решены или ярлык не будет переопределен.

Обновление ABI ядра

Если изменить ABI необходимо, примените изменения кода, представление ABI и список символов к ACK. Чтобы Lint удалил значение -1 и не нарушил совместимость с GKI, выполните следующие действия:

  1. Загрузите изменения кода в ACK.

  2. Дождитесь, пока для набора исправлений не будет выставлена оценка Code-Review +2.

  3. Обновите эталонное представление ABI.

  4. Объедините изменения в коде и обновление ABI.

Загрузка изменений кода ABI в ACK

Обновление ACK ABI зависит от типа вносимых изменений.

  • Если изменение ABI связано с функцией, которая влияет на тесты CTS или VTS, его обычно можно выборочно скопировать в ACK как есть. Например:

    • aosp/1289677 необходимо для работы аудио.
    • Для работы USB-порта требуется aosp/1295945.
  • Если изменение ABI относится к функции, которую можно передать в ACK, то это изменение можно перенести в ACK как есть. Например, следующие изменения не нужны для тестирования CTS или VTS, но их можно передать в ACK:

    • aosp/1250412 – изменение функции терморегуляции.
    • aosp/1288857 – это изменение EXPORT_SYMBOL_GPL.
  • Если изменение ABI вводит новую функцию, которую не нужно включать в ACK, вы можете ввести символы в ACK, используя заглушку, как описано в следующем разделе.

Используйте заглушки для ACK

Заглушки должны быть необходимы только для основных изменений ядра, которые не приносят пользы ACK, например изменений производительности и энергопотребления. Ниже приведены примеры заглушек и частичных переносов в ACK для GKI.

  • Заглушка функции изоляции ядра (aosp/1284493). Возможности ACK не обязательны, но символы должны присутствовать в ACK, чтобы ваши модули могли их использовать.

  • Символ-плейсхолдер для модуля поставщика (aosp/1288860).

  • Выборка только для ABI функции отслеживания событий на уровне процесса mm (aosp/1288454). Исходный патч был выбран для ACK, а затем усечен, чтобы включать только необходимые изменения для устранения различий ABI для task_struct и mm_event_count. Этот патч также обновляет перечисление mm_event_type, чтобы оно содержало окончательные элементы.

  • Частичный перенос изменений ABI структуры термодатчика, которые требовали не только добавления новых полей ABI.

    • Исправление aosp/1255544 устранило различия в ABI между ядром партнера и ACK.

    • Исправление aosp/1291018 устраняет функциональные проблемы, обнаруженные во время тестирования GKI предыдущего исправления. Для этого мы инициализировали структуру параметров датчика, чтобы зарегистрировать несколько температурных зон для одного датчика.

  • Изменения ABI CONFIG_NL80211_TESTMODE (aosp/1344321). Этот патч добавил необходимые изменения структуры для ABI и убедился, что дополнительные поля не вызывают функциональных различий, что позволяет партнерам включать CONFIG_NL80211_TESTMODE в свои производственные ядра и при этом поддерживать соответствие GKI.

Как принудительно использовать KMI во время выполнения

В ядрах GKI используются параметры конфигурации TRIM_UNUSED_KSYMS=y и UNUSED_KSYMS_WHITELIST=<union of all symbol lists>, которые ограничивают экспортируемые символы (например, символы, экспортируемые с помощью EXPORT_SYMBOL_GPL()) теми, которые указаны в списке символов. Все остальные символы не экспортируются, и загрузка модуля, требующего неэкспортированного символа, запрещена. Это ограничение применяется во время сборки, и отсутствующие записи помечаются.

Для разработки можно использовать сборку ядра GKI, которая не включает обрезку символов (то есть можно использовать все обычно экспортируемые символы). Чтобы найти эти сборки, найдите сборки kernel_debug_aarch64 на сайте ci.android.com.

Как обеспечить соблюдение KMI с помощью версий модулей

В ядрах Generic Kernel Image (GKI) используется версионирование модулей (CONFIG_MODVERSIONS) в качестве дополнительной меры для обеспечения соответствия KMI во время выполнения. Управление версиями модулей может вызывать ошибки несоответствия циклического избыточного кода (CRC) при загрузке модуля, если ожидаемый KMI модуля не соответствует KMI vmlinux. Например, ниже приведен типичный сбой, который происходит во время загрузки модуля из-за несоответствия CRC для символа module_layout():

init: Loading module /lib/modules/kernel/.../XXX.ko with args ""
XXX: disagrees about version of symbol module_layout
init: Failed to insmod '/lib/modules/kernel/.../XXX.ko' with args ''

Как использовать версии модулей

Управление версиями модулей полезно по следующим причинам:

  • При изменении видимости структуры данных используется управление версиями модулей. Если модули изменяют непрозрачные структуры данных, то есть структуры данных, которые не являются частью KMI, они перестают работать после последующих изменений структуры.

    Например, рассмотрим поле fwnode в struct device. Это поле ДОЛЖНО быть непрозрачным для модулей, чтобы они не могли вносить изменения в поля device->fw_node или делать предположения о его размере.

    Однако если модуль включает <linux/fwnode.h> (прямо или косвенно), поле fwnode в struct device становится для него прозрачным. После этого модуль может вносить изменения в device->fwnode->dev или device->fwnode->ops. Этот сценарий проблематичен по нескольким причинам, изложенным ниже:

    • Это может нарушить предположения, которые основной код ядра делает о своих внутренних структурах данных.

    • Если в будущем обновлении ядра изменится struct fwnode_handle (тип данных fwnode), модуль перестанет работать с новым ядром. Кроме того, stgdiff не покажет никаких различий, поскольку модуль нарушает целостность KMI, напрямую манипулируя внутренними структурами данных способами, которые нельзя обнаружить, просто проверив двоичное представление.

  • Текущий модуль считается несовместимым с KMI, если он загружается позже новым несовместимым ядром. Версионирование модулей добавляет проверку во время выполнения, чтобы избежать случайной загрузки модуля, несовместимого с KMI ядра. Эта проверка позволяет предотвратить труднодиагностируемые проблемы во время выполнения и сбои ядра, которые могут возникнуть из-за незамеченной несовместимости в KMI.

Включение версий модулей позволяет избежать всех этих проблем.

Проверка на несоответствие CRC без загрузки устройства

stgdiff сравнивает и сообщает о несоответствиях CRC между ядрами, а также о других различиях ABI.

Кроме того, при полной сборке ядра с включенным параметром CONFIG_MODVERSIONS в рамках обычного процесса сборки создается файл Module.symvers. В этом файле содержится по одной строке для каждого символа, экспортированного ядром (vmlinux) и модулями. Каждая строка состоит из значения CRC, названия символа, пространства имен символа, названия модуля или vmlinux, экспортирующего символ, и типа экспорта (например, EXPORT_SYMBOL или EXPORT_SYMBOL_GPL).

Вы можете сравнить файлы Module.symvers из сборки GKI и вашей сборки, чтобы проверить, есть ли различия в контрольных суммах CRC символов, экспортированных vmlinux. Если значение CRC какого-либо символа, экспортированного vmlinux и, отличается от значения CRC того же символа, используемого в одном из загруженных на устройство модулей, модуль не загружается.

Если у вас нет всех артефактов сборки, но есть файлы vmlinux ядра GKI и вашего ядра, вы можете сравнить значения CRC для определенного символа, выполнив следующую команду для обоих ядер и сравнив выходные данные:

nm <path to vmlinux>/vmlinux | grep __crc_<symbol name>

Например, следующая команда проверяет значение CRC для символа module_layout:

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

Как устранить несоответствие контрольных сумм

Чтобы устранить несоответствие CRC при загрузке модуля, выполните следующие действия:

  1. Соберите ядро GKI и ядро устройства, используя параметр --kbuild_symtypes, как показано в следующей команде:

    tools/bazel run --kbuild_symtypes //common:kernel_aarch64_dist

    Эта команда создает файл .symtypes для каждого файла .o. Подробную информацию можно найти в KBUILD_SYMTYPES в Kleaf.

    В Android 13 и более ранних версиях создайте ядро GKI и ядро устройства, добавив KBUILD_SYMTYPES=1 в начало команды, которую вы используете для создания ядра, как показано в следующей команде:

    KBUILD_SYMTYPES=1 BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh

    При использовании параметра build_abi.sh, флаг KBUILD_SYMTYPES=1 устанавливается автоматически.

  2. Найдите файл .c, в который экспортируется символ с несовпадающей контрольной суммой CRC, с помощью следующей команды:

    git -C common grep EXPORT_SYMBOL.*module_layout
    kernel/module/version.c:EXPORT_SYMBOL(module_layout);
  3. Файл .c имеет соответствующий файл .symtypes в GKI и артефакты сборки ядра устройства. Найдите файл .symtypes, используя следующие команды:

    cd bazel-bin/common/kernel_aarch64/symtypes
    ls -1 kernel/module/version.symtypes

    В Android 13 и более ранних версиях, где используются устаревшие скрипты сборки, это, скорее всего, out/$BRANCH/common или out_abi/$BRANCH/common.

    Каждый файл .symtypes представляет собой файл обычного текста, состоящий из описаний типа и символа:

    • Каждая строка имеет вид key description, где описание может ссылаться на другие ключи в том же файле.

    • Клавиши, например [s|u|e|t]#foo, относятся к [struct|union|enum|typedef] foo. Пример:

      t#bool typedef _Bool bool
      
    • Ключи без префикса x# – это просто названия символов. Пример:

      find_module s#module * find_module ( const char * )
      
  4. Сравните два файла и устраните все различия.

Лучше всего создать symtypes с помощью сборки непосредственно перед проблемным изменением и после него. Сохранив все файлы, вы сможете сравнить их одновременно.

Например,

diff -r -N -U0 good bad

В противном случае просто сравните нужные файлы.

Случай 1. Различия из-за видимости типов данных

Новый #include может добавить в исходный файл новое определение типа (например, struct foo). В таких случаях его описание в соответствующем файле .symtypes изменится с пустого structure_type foo { } на полное определение.

Это повлияет на все контрольные суммы всех символов в файле .symtypes, описания которых прямо или косвенно зависят от определения типа.

Например, если добавить в файл include/linux/device.h ядра следующую строку, возникнут несоответствия CRC, одно из которых будет связано с файлом module_layout():

 #include <linux/fwnode.h>

Сравнение module/version.symtypes для этого символа выявляет следующие различия:

 $ diff -u <GKI>/kernel/module/version.symtypes <your kernel>/kernel/module/version.symtypes
  --- <GKI>/kernel/module/version.symtypes
  +++ <your kernel>/kernel/module/version.symtypes
  @@ -334,12 +334,15 @@
  ...
  -s#fwnode_handle structure_type fwnode_handle { }
  +s#fwnode_reference_args structure_type fwnode_reference_args { s#fwnode_handle * fwnode ; unsigned int nargs ; t#u64 args [ 8 ] ; }
  ...

Если в ядре GKI есть полное определение типа, а в вашем ядре его нет (что маловероятно), объедините последнее общее ядро Android со своим ядром, чтобы использовать последнюю базу ядра GKI.

В большинстве случаев в ядре GKI отсутствует полное определение типа в файле .symtypes, но в вашем ядре оно есть благодаря дополнительным директивам #include.

Разрешение для Android 16 и более поздних версий

Убедитесь, что в исходном файле есть заголовок стабилизации KABI Android:

#include <linux/android_kabi.h>

Для каждого затронутого типа добавьте ANDROID_KABI_DECLONLY(name); в глобальной области действия в затронутый исходный файл.

Например, если разница symtypes была такой:

--- good/drivers/android/vendor_hooks.symtypes
+++ bad/drivers/android/vendor_hooks.symtypes
@@ -1051 +1051,2 @@
-s#ubuf_info structure_type ubuf_info { }
+s#ubuf_info structure_type ubuf_info { member pointer_type { const_type { s#ubuf_info_ops } } ops data_member_location(0) , member t#refcount_t refcnt data_member_location(8) , member t#u8 flags data_member_location(12) } byte_size(16)
+s#ubuf_info_ops structure_type ubuf_info_ops { member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } , formal_parameter t#bool ) -> base_type void } complete data_member_location(0) , member pointer_type { subroutine_type ( formal_parameter pointer_type { s#sk_buff } , formal_parameter pointer_type { s#ubuf_info } ) -> base_type int byte_size(4) encoding(5) } link_skb data_member_location(8) } byte_size(16)

Проблема в том, что у типа struct ubuf_info теперь есть полное определение в symtypes. Чтобы решить эту проблему, добавьте в файл drivers/android/vendor_hooks.c следующую строку:

ANDROID_KABI_DECLONLY(ubuf_info);

Это указывает gendwarfksyms, что указанный тип не определен в файле.

Более сложный вариант: новый файл #include сам находится в файле заголовка. В этом случае вам может потребоваться распределить разные наборы вызовов макроса ANDROID_KABI_DECLONLY по исходным файлам, которые косвенно извлекают дополнительные определения типов, поскольку некоторые из них уже могут иметь некоторые определения типов.

Для удобства чтения размещайте такие вызовы макросов в начале исходного файла.

Разрешение для Android 15 и более ранних версий

Часто для решения проблемы достаточно скрыть новый #include от genksyms.

#ifndef __GENKSYMS__
#include <linux/fwnode.h>
#endif

В противном случае выполните следующие действия, чтобы определить, какой #include вызывает расхождение:

  1. Откройте заголовочный файл, в котором определен символ или тип данных, имеющий это отличие. Например, измените значение в столбце "include/linux/fwnode.h" строки "struct fwnode_handle".

  2. Добавьте следующий код в начало заголовочного файла:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. В файле .c модуля, в котором не совпадает контрольная сумма CRC, добавьте следующую строку перед всеми строками #include:

    #define CRC_CATCH 1
    
  4. Скомпилируйте модуль. В результате ошибки сборки будет показана цепочка заголовочных файлов #include, которые привели к несоответствию CRC. Пример:

    In file included from .../drivers/clk/XXX.c:16:`
    In file included from .../include/linux/of_device.h:5:
    In file included from .../include/linux/cpu.h:17:
    In file included from .../include/linux/node.h:18:
    .../include/linux/device.h:16:2: error: "Included from here"
    #error "Included from here"
    

    Одна из ссылок в этой цепочке #include связана с изменением, внесенным в ваше ядро, которое отсутствует в ядре GKI.

Случай 2. Различия из-за изменений типа данных

Если несовпадение CRC для символа или типа данных не связано с различием в видимости, то оно вызвано фактическими изменениями (добавлением, удалением или изменением) в самом типе данных.

Например, если внести в ядро следующее изменение, возникнет несколько ошибок CRC, поскольку многие символы косвенно зависят от этого типа изменений:

diff --git a/include/linux/iommu.h b/include/linux/iommu.h
  --- a/include/linux/iommu.h
  +++ b/include/linux/iommu.h
  @@ -259,7 +259,7 @@ struct iommu_ops {
     void (*iotlb_sync)(struct iommu_domain *domain);
     phys_addr_t (*iova_to_phys)(struct iommu_domain *domain, dma_addr_t iova);
     phys_addr_t (*iova_to_phys_hard)(struct iommu_domain *domain,
  -        dma_addr_t iova);
  +        dma_addr_t iova, unsigned long trans_flag);
     int (*add_device)(struct device *dev);
     void (*remove_device)(struct device *dev);
     struct iommu_group *(*device_group)(struct device *dev);

Одно несоответствие CRC относится к файлу devm_of_platform_populate().

Если сравнить файлы .symtypes для этого символа, они могут выглядеть следующим образом:

 $ diff -u <GKI>/drivers/of/platform.symtypes <your kernel>/drivers/of/platform.symtypes
  --- <GKI>/drivers/of/platform.symtypes
  +++ <your kernel>/drivers/of/platform.symtypes
  @@ -399,7 +399,7 @@
  ...
  -s#iommu_ops structure_type iommu_ops { ... ; t#phy
  s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t ) ; int
    ( * add_device ) ( s#device * ) ; ...
  +s#iommu_ops structure_type iommu_ops { ... ; t#phy
  s_addr_t ( * iova_to_phys_hard ) ( s#iommu_domain * , t#dma_addr_t , unsigned long ) ; int ( * add_device ) ( s#device * ) ; ...

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

  1. Найдите определение символа в исходном коде (обычно в файлах .h).

    • Чтобы найти коммит, в котором были изменены символы ядра GKI, выполните следующую команду:
    git blame
    • Для удаленных символов (если символ удален в одном дереве и вы хотите удалить его в другом) нужно найти изменение, которое удалило строку. Используйте следующую команду в дереве, где была удалена строка:
    git log -S "copy paste of deleted line/word" -- <file where it was deleted>
  2. Проверьте список коммитов, чтобы найти изменение или удаление. Скорее всего, вам нужен первый коммит. Если нет, прокрутите список вниз, пока не найдете нужную запись.

  3. После того как вы найдете коммит, отмените его в ядре или обновите его, чтобы подавить изменение CRC, а затем загрузите его в ACK и объедините. Каждый остаточный разрыв ABI необходимо проверить на безопасность и при необходимости зарегистрировать разрешенный разрыв.

Предпочитать существующие отступы

Некоторые структуры в GKI дополняются, чтобы их можно было расширять без нарушения работы существующих модулей поставщика. Если в вышестоящем коммите (например) в такую структуру добавляется элемент, то можно изменить ее так, чтобы использовать часть отступов. После этого изменение не учитывается при расчете CRC.

Стандартизированный макрос ANDROID_KABI_RESERVE с самодокументированием резервирует u64 пространства (выровненного по краю). Он используется вместо декларации участника.

Пример:

struct data {
        u64 handle;
        ANDROID_KABI_RESERVE(1);
        ANDROID_KABI_RESERVE(2);
};

Дополнительные данные можно использовать без влияния на контрольные суммы символов с помощью ANDROID_KABI_USE (или ANDROID_KABI_USE2 или других вариантов, которые могут быть определены).

Член sekret доступен так, как если бы он был объявлен напрямую, но макрос на самом деле разворачивается в анонимного члена объединения, содержащего sekret, а также элементы, используемые gendwarfksyms для поддержания стабильности symtype.

struct data {
        u64 handle;
        ANDROID_KABI_USE(1, void *sekret);
        ANDROID_KABI_RESERVE(2);
};
Разрешение для Android 16 и более поздних версий

Контрольные суммы рассчитываются с помощью gendwarfksyms, который использует сведения для отладки DWARF, поэтому поддерживает типы C и Rust. Решение зависит от типа изменения. Вот несколько примеров.

Новые или измененные перечислители

Иногда добавляются новые перечислители, а иногда изменяется значение перечислителя MAX или похожего. Эти изменения безопасны, если они не "выходят" за пределы GKI или если мы можем быть уверены, что модули поставщика не будут использовать их значения.

Пример:

 enum outcome {
       SUCCESS,
       FAILURE,
       RETRY,
+      TRY_HARDER,
       OUTCOME_LIMIT
 };

Добавление TRY_HARDER и изменение OUTCOME_LIMIT можно скрыть от вычисления CRC с помощью вызовов макросов в глобальной области видимости:

ANDROID_KABI_ENUMERATOR_IGNORE(outcome, TRY_HARDER);
ANDROID_KABI_ENUMERATOR_VALUE(outcome, OUTCOME_LIMIT, 3);

Для удобства чтения разместите их сразу после определения enum.

Новый элемент структуры, занимающий существующее отверстие

Из-за выравнивания между urgent и scratch будут неиспользуемые байты.

        void *data;
        bool urgent;
+       bool retry;
        void *scratch;

Добавление retry не влияет на существующее смещение элемента или размер структуры. Однако это может повлиять на контрольные суммы символов, представление ABI или и то и другое.

После этого файл не будет учитываться при расчете контрольной суммы CRC:

        void *data;
        bool urgent;
+       ANDROID_KABI_IGNORE(1, bool retry);
        void *scratch_space;

Член retry доступен так, как если бы он был объявлен напрямую, но макрос на самом деле разворачивается в анонимный член объединения, содержащий retry, а также элементы, используемые gendwarfksyms для поддержания стабильности symtype.

Расширение структуры за счет новых участников

Иногда элементы добавляются в конец структуры. Это не повлияет на смещения существующих элементов и пользователей структуры, которые обращаются к ней только по указателю. Размер структуры влияет на ее CRC, и изменения в ней можно подавить с помощью дополнительного вызова макроса в глобальной области видимости, как показано ниже:

struct data {
        u64 handle;
        u64 counter;
        ANDROID_KABI_IGNORE(1, void *sekret);
};

ANDROID_KABI_BYTE_SIZE(data, 16);

Для удобства чтения разместите этот код сразу после определения struct.

Все остальные изменения типа или типа символа.

В редких случаях могут происходить изменения, которые не относятся ни к одной из перечисленных выше категорий. В результате этого контрольная сумма меняется, и ее нельзя подавить с помощью описанных выше макросов.

В таких случаях исходное описание типа или символа symtypes можно дополнить вызовом ANDROID_KABI_TYPE_STRING в глобальной области действия.

struct data {
        /* extensive changes */
};

ANDROID_KABI_TYPE_STRING("s#data", "original s#data symtypes definition");

Для удобства чтения разместите его сразу после определения типа или символа.

Разрешение для Android 15 и более ранних версий

Изменения типа и типа символа должны быть скрыты от genksyms. Это можно сделать, управляя предварительной обработкой с помощью __GENKSYMS__.

Таким образом можно выразить произвольные преобразования кода.

Например, чтобы скрыть новый элемент, занимающий отверстие в существующей конструкции:

struct parcel {
        void *data;
        bool urgent;
#ifndef __GENKSYMS__
        bool retry;
#endif
        void *scratch_space;
};