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

Для стабилизации ABI ядер Android можно использовать инструменты мониторинга ABI (Application Binary Interface), доступные в Android 11 и выше. Эти инструменты собирают и сравнивают представления 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, они сохраняются как стабильные и не должны удаляться из списка символов или изменяться после заморозки ветки.

Каждая ветка ядра Android Common Kernel (ACK) KMI имеет свой собственный набор списков символов. Не предпринимается попыток обеспечить стабильность 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 для устройства, используя инструкции из раздела «Как работать со списками символов» . Многие партнеры отправляют один список символов на каждое подтверждение (ACK), но это не является обязательным требованием. Если это облегчает техническое обслуживание, вы можете отправить несколько списков символов.

Расширить KMI

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

О поломках KMI

Ядро имеет исходный код , и бинарные файлы собираются из этого исходного кода. Ветви ядра, отслеживаемые по ABI, включают представление ABI текущего ABI GKI (в виде файла .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. Например, следующие изменения добавляют новую функцию инкрементальной файловой системы в ветку 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 отключен для производственных версий defconfig и gki_defconfig . Пример добавления конфигурации отладки в структуру без нарушения 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, то это изменение обычно можно выборочно подтвердить как есть. Например:

    • Для работы звука необходим репозиторий 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 структуры thermal, которые требовали большего, чем просто добавление новых полей 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 и вашей сборкой, чтобы проверить наличие различий в контрольных суммах символов, экспортируемых vmlinux . Если в каком-либо символе, экспортируемом vmlinux , обнаружено различие в значениях контрольной суммы , и этот символ используется одним из модулей, загружаемых на ваше устройство, модуль не будет загружен.

Если у вас нет всех артефактов сборки, но есть файлы 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).

Для устранения ошибки несоответствия контрольной суммы (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 { } на полное определение.

Это повлияет на все контрольные суммы (CRC) всех символов в файле .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 Common Kernel с вашим ядром, чтобы использовать последнюю базовую версию ядра GKI.

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

Разрешение для Android 16 и выше

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

#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 для поддержания стабильности симтипов.

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

Контрольные суммы (CRC) вычисляются программой 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 не влияет на существующее смещение элементов или размер структуры. Однако это может повлиять на контрольные суммы символов (CRC) или представление 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 .

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

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

В этих случаях исходное описание 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;
};