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

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

Инструмент мониторинга ABI разработан в AOSP и использует libabigail для создания и сравнения представлений.

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

Этот каталог содержит специальные инструменты для анализа ABI. Используйте его со сценариями сборки, предоставленными build_abi.sh .)

Процесс

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

  1. Получите цепочку инструментов, скрипты сборки и исходники ядра через repo .
  2. Предоставьте все предварительные условия (например, библиотеку libabigail и набор инструментов).
  3. Соберите ядро ​​и его ABI-представление .
  4. Проанализируйте различия ABI между сборкой и эталоном .
  5. Обновите представление ABI (при необходимости) .
  6. Работа со списками символов .

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

Используйте инструменты мониторинга ABI

1. Приобретите набор инструментов, скрипты сборки и исходники ядра через репозиторий.

Вы можете приобрести набор инструментов, скрипты сборки (эти скрипты) и исходники ядра с repo . Подробную документацию см. в соответствующей информации по сборке ядер Android .

Чтобы проиллюстрировать процесс, в следующих шагах используется common-android12-5.10 , ветвь ядра Android, которая на момент написания этой статьи является последним выпущенным ядром GKI. Чтобы получить эту ветку через repo , выполните следующее:

repo init -u https://android.googlesource.com/kernel/manifest -b common-android12-5.10
repo sync

2. Предоставьте предварительные условия

Инструментарий ABI использует libabigail , библиотеку и набор инструментов для анализа двоичных файлов. Подходящий набор предварительно собранных двоичных файлов поставляется с инструментами сборки ядра и автоматически используется с build_abi.sh .

Чтобы использовать инструменты более низкого уровня (например, dump_abi ), добавьте инструменты сборки ядра в PATH .

3. Соберите ядро ​​и его ABI-представление.

На данный момент вы готовы собрать ядро ​​с правильным набором инструментов и извлечь представление ABI из его двоичных файлов ( vmlinux + модули).

Подобно обычному процессу сборки ядра Android (с использованием build.sh ), этот шаг требует запуска build_abi.sh .

BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh

Это создает ядро ​​и извлекает представление ABI в подкаталог out_abi . В этом случае out/android12-5.10/dist/abi.xml является символической ссылкой на out_abi/android12-5.10/dist/abi-<id>.xml . < id> вычисляется путем выполнения git describe дерево исходного кода ядра.

4. Проанализируйте различия ABI между сборкой и эталонным представлением.

build_abi.sh анализирует и сообщает о любых различиях ABI, когда ссылка предоставляется через переменную среды ABI_DEFINITION . ABI_DEFINITION должен указывать на справочный файл относительно дерева исходных текстов ядра и может быть указан в командной строке или, чаще, как значение в build.config . Ниже приводится пример:

BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh

В приведенной выше команде build.config.gki.aarch64 определяет эталонный файл (как ABI_DEFINITION=android/abi_gki_aarch64.xml ), а diff_abi вызывает abidiff для сравнения свежесгенерированного представления ABI с эталонным файлом. build_abi.sh печатает местоположение отчета и выдает краткий отчет о любой поломке ABI. Если обнаружены поломки, build_abi.sh завершает работу и возвращает ненулевой код выхода.

5. Обновите представление ABI (при необходимости)

Чтобы обновить представление ABI, вызовите build_abi.sh с флагом --update . Он обновляет соответствующий файл abi.xml , определенный в build.config . Чтобы распечатать различия ABI из-за обновления, вызовите скрипт с --print-report . Не забудьте включить отчет в сообщение фиксации при обновлении файла abi.xml .

6. Работа со списками символов

build_abi.sh со списками символов KMI для фильтрации символов во время извлечения ABI. Это простые текстовые файлы, в которых перечислены соответствующие символы ядра ABI. Например, файл списка символов со следующим содержимым ограничивает анализ ABI символами ELF с именами symbol1 и symbol2 :

[abi_symbol_list]
   symbol1
   symbol2

Изменения других символов ELF не учитываются. Файл списка символов можно указать в соответствующем файле конфигурации build.config с параметром KMI_SYMBOL_LIST= как файл, относящийся к исходному каталогу ядра ( $KERNEL_DIR ). Чтобы обеспечить уровень организации, вы можете указать дополнительные файлы списка символов, используя ADDITIONAL_KMI_SYMBOL_LISTS= в файле build.config . Это определяет дополнительные файлы списка символов относительно $KERNEL_DIR ; разделяйте несколько имен файлов пробелами.

Чтобы создать начальный список символов или обновить существующий , необходимо использовать скрипт build_abi.sh с параметром --update-symbol-list .

Когда сценарий запускается с соответствующей конфигурацией, он собирает ядро ​​и извлекает символы, экспортированные из vmlinux и GKI и требуемые любым другим модулем в дереве.

Рассмотрите возможность экспорта vmlinux следующих символов (обычно это делается с помощью макросов EXPORT_SYMBOL* ):

  func1
  func2
  func3

Кроме того, представьте, что есть два модуля поставщика, modA.ko и modB.ko , которым требуются следующие символы (другими словами, они перечисляют undefined записи символов в своей таблице символов):

 modA.ko:    func1 func2
 modB.ko:    func2

С точки зрения стабильности ABI функции func1 и func2 должны оставаться стабильными, поскольку они используются внешним модулем. Наоборот, пока func3 экспортируется, он не используется активно (другими словами, не требуется) ни одним модулем. Таким образом, список символов содержит только func1 и func2 .

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

BUILD_CONFIG=path/to/build.config.device build/build_abi.sh --update-symbol-list

В этом примере build.config.device должен включать несколько параметров конфигурации:

  • vmlinux должен быть в списке FILES .
  • KMI_SYMBOL_LIST должен быть установлен и указывать на список символов KMI для обновления.
  • GKI_MODULES_LIST должен быть установлен и указывать на список модулей GKI. Обычно это путь android/gki_aarch64_modules .

Работа с инструментами ABI более низкого уровня

Большинству пользователей нужно будет использовать только build_abi.sh . В некоторых случаях может потребоваться работа напрямую с инструментами ABI более низкого уровня. Две команды, используемые build_abi.sh , dump_abi и diff_abi , доступны для извлечения и сравнения файлов ABI. См. следующие разделы для их использования.

Создание представлений ABI из деревьев ядра

При наличии дерева ядра Linux со встроенными vmlinux и модулями ядра инструмент dump_abi создает представление ABI с помощью выбранного инструмента ABI. Пример вызова выглядит следующим образом:

dump_abi --linux-tree path/to/out --out-file /path/to/abi.xml

Файл abi.xml содержит текстовое представление ABI комбинированного наблюдаемого ABI vmlinux и модулей ядра в данном каталоге. Этот файл можно использовать для ручной проверки, дальнейшего анализа или в качестве справочного файла для обеспечения стабильности ABI.

Сравните представления ABI

Представления ABI, созданные с помощью dump_abi , можно сравнить с diff_abi . Используйте один и тот же инструмент abi как для dump_abi , так и для diff_abi . Пример вызова выглядит следующим образом:

diff_abi --baseline abi1.xml --new abi2.xml --report report.out

В созданном отчете перечислены обнаруженные изменения ABI, влияющие на KMI. Файлы, указанные как baseline и new , являются представлениями ABI, которые были собраны с помощью dump_abi . diff_abi распространяет код выхода базового инструмента и поэтому возвращает ненулевое значение, когда сравниваемые ABI несовместимы.

Фильтрация представлений и символов KMI

Чтобы отфильтровать представления, созданные с помощью dump_abi или отфильтровать символы по сравнению с diff_abi , используйте параметр --kmi-symbol-list , который указывает путь к файлу списка символов KMI:

dump_abi --linux-tree path/to/out --out-file /path/to/abi.xml --kmi-symbol-list /path/to/symbol_list_file

Работа со списками символов

KMI не включает все символы в ядре или даже все из более чем 30 000 экспортированных символов. Вместо этого символы, которые могут использоваться модулями, явно перечислены в наборе файлов списков символов, поддерживаемых общедоступно в корне дерева ядра. Объединение всех символов во всех файлах списка символов определяет набор символов KMI, поддерживаемых как стабильный. Примером файла списка символов является abi_gki_aarch64_db845c , в котором объявляются символы, необходимые для DragonBoard 845c .

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

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

Инструменты ABI используют списки символов KMI, чтобы ограничить, какие интерфейсы должны отслеживаться для обеспечения стабильности. Основной список символов содержит символы, которые требуются модулям ядра GKI. Ожидается, что поставщики представят и обновят дополнительные списки символов, чтобы гарантировать, что интерфейсы, на которые они полагаются, поддерживают совместимость с ABI. Например, чтобы увидеть список списков символов для android13-5.15 , обратитесь к https://android.googlesource.com/kernel/common/+/refs/heads/android13-5.15/android .

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

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

Вы можете создать список символов KMI для устройства с помощью утилиты build/abi/extract_symbols , которая извлекает зависимости символов из артефактов сборки *.ko . Эта утилита добавляет к выходным данным аннотации в виде комментариев, которые полезны для идентификации пользователей символа. При отправке списка символов в ACK настоятельно рекомендуется сохранять эти комментарии, чтобы облегчить процесс проверки. Чтобы опустить комментарии, передайте параметр --skip-module-grouping при запуске скрипта extract_symbols . Многие партнеры отправляют один список символов на каждый ACK, но это не является жестким требованием. Если это поможет в обслуживании, вы можете отправить несколько списков символов.

Справку по настройке списков символов и использованию инструментов ABI высокого и низкого уровня для отладки и подробного анализа см. в ABI Monitoring for Android Kernels .

Расширить KMI

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

О поломках КМИ

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

Устранение поломок ABI

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

 diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
  index 5ed8f6292a53..f2ecb34c7645 100644
  --- a/include/linux/mm_types.h
  +++ b/include/linux/mm_types.h
  @@ -339,6 +339,7 @@ struct core_state {
   struct kioctx_table;
   struct mm_struct {
      struct {
  +       int dummy;
          struct vm_area_struct *mmap;            /* list of VMAs */
          struct rb_root mm_rb;
          u64 vmacache_seqnum;                   /* per-thread vmacache */

Когда вы снова запускаете build_abi.sh с этим исправлением, инструмент завершает работу с ненулевым кодом ошибки и сообщает о разнице в ABI, подобной этой:

 Leaf changes summary: 1 artifact changed
  Changed leaf types summary: 1 leaf type changed
  Removed/Changed/Added functions summary: 0 Removed, 0 Changed, 0 Added function
  Removed/Changed/Added variables summary: 0 Removed, 0 Changed, 0 Added variable

  'struct mm_struct at mm_types.h:372:1' changed:
    type size changed from 6848 to 6912 (in bits)
    there are data member changes:
  [...]

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

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

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

  • Пример изменения функции находится в aosp/1345659 .
  • Пример списка символов находится в aosp/1346742 .
  • Пример изменения ABI XML находится в 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 ). Пример обновления XML-файла 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. Наиболее распространенными причинами сбоев являются добавленные или удаленные функции, измененные структуры данных или изменения в ABI, вызванные добавлением параметров конфигурации, которые приводят к любому из вышеупомянутых. Начните с решения проблем, обнаруженных инструментом.

Вы можете воспроизвести тест ABI локально, выполнив следующую команду с теми же аргументами, которые вы использовали бы для запуска build/build.sh :

Это пример команды для ядер GKI:

BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh

О этикетках Lint-1

Если вы загружаете изменения в ветку, содержащую замороженный или завершенный KMI, изменения должны пройти через ABIAnalyzer , чтобы гарантировать, что изменения не повлияют на стабильный ABI несовместимым образом. Во время этого процесса ABIAnalyzer ищет отчет ABI, созданный во время сборки (расширенная сборка, которая выполняет обычную сборку, а затем некоторые шаги извлечения и сравнения ABI. Если ABIAnalyzer находит непустой отчет, он устанавливает метку Lint-1. и изменение блокируется от отправки до разрешения, пока набор исправлений не получит метку Lint+1.

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

Если вам нужно обновить представление ABI ядра, вы должны обновить соответствующий файл abi.xml в дереве исходного кода ядра. Самый удобный способ сделать это — использовать build/build_abi.sh следующим образом:

build/build_abi.sh --update --print-report

Используйте те же аргументы, что и для запуска build/build.sh . Это обновит правильный abi.xml в исходном дереве и распечатает обнаруженные различия. На практике включайте распечатанный (краткий) отчет в сообщение коммита (хотя бы частично).

Обновить представление ABI

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

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

  2. Обновите файлы ACK ABI .

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

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

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

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

    • aosp/1289677 необходим для работы звука.
    • aosp/1295945 необходим для работы USB.
  • Если изменение 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 ).

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

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

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

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

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

Обновите файлы ACK ABI

Чтобы обновить файлы ACK ABI:

  1. Загрузите изменения ABI и дождитесь получения Code-Review +2 для набора исправлений.

  2. Обновите файлы ACK ABI.

    cp partner kernel/android/abi_gki_aarch64_partner ACK kernel/abi_gki_aarch64_partner
    BUILD_CONFIG=common/build.config.gki.aarch64 build/build_abi.sh --update
    
  3. Зафиксируйте обновление ABI:

    cd common
    git add android/abi*
    git commit -s -F $DIST_DIR/abi.report.short
    <push to gerrit>
    

    $DIST_DIR/abi.report.short содержит краткий отчет об изменениях. Использование флага -F с git commit автоматически использует отчет для текста фиксации, который затем можно отредактировать, добавив строку темы (или обрезать, если сообщение слишком длинное).

Ветки ядра Android с предопределенным ABI

Некоторые ветки ядра поставляются с предопределенными представлениями ABI для Android как часть исходного дистрибутива. Эти представления ABI должны быть точными и отражать результат build_abi.sh , как если бы вы выполняли его самостоятельно. Поскольку на ABI сильно влияют различные параметры конфигурации ядра, эти файлы .xml обычно относятся к определенной конфигурации. Например, ветка common-android12-5.10 содержит abi_gki_aarch64.xml , который соответствует результату сборки при использовании build.config.gki.aarch64 . В частности, build.config.gki.aarch64 также ссылается на этот файл через ABI_DEFINITION .

Такие предопределенные представления ABI используются в качестве базового определения при сравнении с diff_abi . Например, чтобы проверить исправление ядра относительно любых изменений в ABI, создайте представление ABI с примененным исправлением и используйте diff_abi , чтобы сравнить его с ожидаемым ABI для этого конкретного исходного дерева или конфигурации. Если установлен ABI_DEFINITION , соответственно будет запущен build_abi.sh .

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

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

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

Включение управления версиями модуля предотвращает все эти проблемы.

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

abidiff сравнивает и сообщает о несоответствиях CRC между ядрами. Этот инструмент позволяет выявлять несоответствие CRC одновременно с другими различиями ABI.

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

Вы можете сравнить файлы Module.symvers между сборкой GKI и вашей сборкой, чтобы проверить наличие различий CRC в символах, экспортируемых vmlinux . Если в любом символе, экспортируемом 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

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

  1. Соберите ядро ​​GKI и ядро ​​вашего устройства, KBUILD_SYMTYPES=1 к команде, которую вы используете для сборки ядра, как показано в следующей команде:

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

    Эта команда создает файл .symtypes для каждого файла .o . При использовании build_abi.sh, флаг KBUILD_SYMTYPES=1 уже неявно установлен.

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

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

    cd out/$BRANCH/common && ls -1 kernel/module.*
    kernel/module.o
    kernel/module.o.symversions
    kernel/module.symtypes
    

    Ниже приведены характеристики файла .c :

    • Формат файла .c — одна (потенциально очень длинная) строка на символ.

    • [s|u|e|etc]# в начале строки означает, что символ имеет тип данных [struct|union|enum|etc] . Например:

      t#bool typedef _Bool bool
      
    • Отсутствие префикса # в начале строки указывает на то, что символ является функцией. Например:

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

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

Если одно ядро ​​сохраняет символ или тип данных непрозрачным для модулей, а другое ядро ​​— нет, эта разница появляется между файлами .symtypes двух ядер. В файле .symtypes одного из ядер символ UNKNOWN , а в файле .symtypes другого ядра имеется расширенное представление символа или типа данных.

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

 #include <linux/fwnode.h>

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

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

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

В большинстве случаев ядро ​​GKI имеет значение UNKNOWN , но ваше ядро ​​имеет внутренние детали этого символа из-за изменений, внесенных в ваше ядро. Это связано с тем, что в один из файлов вашего ядра добавлен #include , которого нет в ядре GKI.

Чтобы определить #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.

  5. Определите изменение, отмените его в своем ядре или загрузите его в ACK и объедините .

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

Если несоответствие CRC для символа или типа данных не связано с разницей в видимости, то оно связано с фактическими изменениями (добавлениями, удалениями или изменениями) в самом типе данных. Как правило, abidiff улавливает это, но если он пропускает какие-либо из-за известных пробелов в обнаружении, механизм MODVERSIONS может их уловить.

Например, внесение следующего изменения в ваше ядро ​​вызывает несколько несоответствий 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 struct 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 struct 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. После того, как вы обнаружите изменение, либо отмените его в своем ядре, либо загрузите его в ACK и выполните слияние .