Android 内核 ABI 监控

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

您可以使用 Android 11 及更高版本中提供的应用二进制接口 (ABI) 监控工具来稳定 Android 内核的内核内 ABI。该工具可从现有内核二进制文件(vmlinux+ 模块)收集 ABI 表示法并进行比较。这些 ABI 表示法是 .xml 文件和符号列表。表示法提供视图所在的接口称为“内核模块接口”(KMI)。您可以使用该工具来跟踪 KMI 的变化并弱化相关影响。

ABI 监控工具在 AOSP 中开发,使用 libabigail 生成和比较表示法。

本页面介绍了该工具、收集和分析 ABI 表示法的流程,以及使用此类表示法为内核内 ABI 提供稳定性的情况。本页面还提供了有关如何为 Android 内核贡献更改的信息。

此目录中包含用于 ABI 分析的特定工具。您可以将其与 build_abi.sh 提供的构建脚本结合使用。

流程

内核的 ABI 分析需要执行多个步骤,其中大多数步骤都可以实现自动化:

  1. 通过 repo 获取工具链、构建脚本和内核源代码
  2. 满足任何前提条件(例如提供 libabigail 库和工具集合)。
  3. 构建内核及其 ABI 表示法
  4. 分析 build 与参考表示法之间的 ABI 差异
  5. 更新 ABI 表示法(如果需要)
  6. 使用符号列表

以下说明适用于您可以使用受支持的工具链(例如预构建的 Clang 工具链)构建的任何内核repo manifests 适用于所有 Android 通用内核分支以及多个设备专用内核,可确保您在构建内核分发版本以用于分析时使用正确的工具链。

使用 ABI 监控工具

1. 通过 Repo 获取工具链、构建脚本和内核源代码

您可以通过 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 表示法

此时,您可以使用合适的工具链构建内核,并从其二进制文件(vmlinux + 模块)中提取 ABI 表示法。

与常见的 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 来计算。

对于 Android 13 分支及更高版本,内核 build 中启用了 Bazel。上述命令的 Bazel 等效项如下:

tools/bazel run //common:kernel_aarch64_abi_dist

如需了解详情,请参阅使用 Bazel 支持 ABI 监控 (GKI) 使用 Bazel 支持 ABI 监控(设备内核)

3. 分析 build 与参考表示法之间的 ABI 差异

当通过环境变量 ABI_DEFINITION 提供参考表示法时,build_abi.sh 会分析并报告任何 ABI 差异。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 会终止并返回非零退出代码。

对于 Android 13 分支及更高版本,内核 build 中启用了 Bazel。上述命令的 Bazel 等效项如下:

tools/bazel run //common:kernel_aarch64_abi_dist

如需了解详情,请参阅使用 Bazel 支持 ABI 监控 (GKI) 使用 Bazel 支持 ABI 监控(设备内核)

4. 更新 ABI 表示法(如果需要)

如需更新 ABI 表示法,请使用 --update 标志调用 build_abi.sh。它会更新由 build.config 定义的相应 abi.xml 文件。如需输出因更新而产生的 ABI 差异,请使用 --print-report 调用脚本。更新 abi.xml 文件时,请务必在提交消息中包含报告。

对于 Android 13 分支及更高版本,内核 build 中启用了 Bazel。用于更新 GKI 的 ABI 表示法的 Bazel 命令如下:

tools/bazel run //common:kernel_aarch64_abi_update_kmi_symbol_list &&
tools/bazel run //common:kernel_aarch64_abi_update

如需了解详情,请参阅使用 Bazel 支持 ABI 监控 (GKI) 使用 Bazel 支持 ABI 监控(设备内核)

5. 使用符号列表

使用 KMI 符号列表将 build_abi.sh 参数化,以便在 ABI 提取期间过滤符号。这些 KMI 符号列表文件是列出相关 ABI 内核符号的纯文本文件。例如,包含以下内容的符号列表文件会将 ABI 分析的范围限定为具有名称 symbol1symbol2 的 ELF 符号:

[abi_symbol_list]
   symbol1
   symbol2

系统不会考虑对其他 ELF 符号所做的更改。可以使用 KMI_SYMBOL_LIST= 在相应的 build.config 配置文件中将符号列表文件指定为相对于内核源目录 ($KERNEL_DIR) 的文件。如需提供组织级层,您可以通过在 build.config 文件中使用 ADDITIONAL_KMI_SYMBOL_LISTS= 来指定其他符号列表文件。这会指定相对于 $KERNEL_DIR 的更多符号列表文件;如有多个文件名,请用空格分隔。

如要创建初始符号列表或更新现有符号列表,您必须使用带有 --update-symbol-list 参数的 build_abi.sh 脚本。

当脚本在适当的配置下运行时,它会构建内核并提取从 vmlinux 和 GKI 模块导出的符号,以及树中的任何其他模块所需的符号。

考虑导出以下符号(通常通过 EXPORT_SYMBOL* 宏完成)的 vmlinux

  func1
  func2
  func3

另外,假设有两个供应商模块 modA.komodB.ko,它们需要以下符号(换句话说,它们会在其符号表中列出 undefined 符号条目):

 modA.ko:    func1 func2
 modB.ko:    func2

从 ABI 稳定性的角度来看,func1func2 必须保持稳定,因为它们由外部模块使用。相反,func3 导出后,任何模块都不会主动使用它(换句话说,它不是必需项)。因此,符号列表仅包含 func1func2

如要创建或更新现有符号列表,必须运行 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.shdump_abidiff_abi 所用的两个命令可用于提取和比较 ABI 文件。如需了解它们的用法,请参阅以下部分。

从内核树创建 ABI 表示法

假设有一个具有已构建的 vmlinux 和内核模块的 Linux 内核树,dump_abi 工具会使用所选的 ABI 工具创建 ABI 表示法。调用示例如下所示:

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

文件 abi.xml 包含给定目录中 vmlinux 和内核模块的组合可观测 ABI 的文本 ABI 表示法。此文件可以用于手动检查、进一步分析,或用作确保 ABI 稳定性的参考文件。

比较 ABI 表示法

可以使用 diff_abi 比较由 dump_abi 创建的 ABI 表示法。为 dump_abidiff_abi 使用相同的 abi-tool。调用示例如下所示:

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

生成的报告会列出检测到的影响 KMI 的 ABI 更改。指定为 baselinenew 的文件是使用 dump_abi 收集的 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 通用内核 (ACK) KMI 内核分支都有自己的一组符号列表。系统不会尝试在不同的 KMI 内核分支之间提供 ABI 稳定性。例如,android12-5.10 的 KMI 完全独立于 android13-5.10 的 KMI。

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 符号列表引用后,新添加的符号就会立即被作为稳定版来维护。除非可以确认没有设备附带对相应符号的依赖项,否则不应从内核列表中移除符号。

您可以使用 build/abi/extract_symbols 实用程序为设备生成 KMI 符号列表,该实用程序可以从 *.ko 构建工件中提取符号依赖项。该实用程序以注释的形式向输出中添加注解,这对于识别使用某符号的用户来说非常有用。将符号列表提交到 ACK 时,强烈建议保留这些注释,以简化审核流程。如需省略注释,请在运行 extract_symbols 脚本时传递 --skip-module-grouping 选项。许多合作伙伴都会针对每个 ACK 提交一个符号列表,但这不是硬性要求。如果这样做有助于维护,您可以提交多个符号列表。

如需自定义符号列表以及使用高级和低级 ABI 工具进行调试和详细分析,请参阅适用于 Android 内核的 ABI 监控

扩展 KMI

虽然 KMI 符号和相关结构会被作为稳定版来维护;这意味着,如果更改会破坏包含已冻结的 KMI 的内核中的稳定接口,则系统无法接受该更改。GKI 内核仍会保持对扩展开放,这样,今年晚些时候推出的设备就不需要在 KMI 被冻结之前定义所有依赖项。如需扩展 KMI,即使 KMI 被冻结,您也可以针对新的或现有的已导出内核函数,向 KMI 中添加新符号。如果新的内核补丁不会破坏 KMI,也可能会被接受。

关于 KMI 合规性破坏问题

内核具有源代码,系统会基于这些源代码构建二进制文件。受 ABI 监控的内核分支包括 abi.xml,即当前 GKI ABI 的表示法。构建二进制文件(内核二进制文件、vmlinuxImage 加上内核模块)后,可以从二进制文件中提取 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) 导出,然后更新 ABI XML 表示法和符号。例如,以下更改将新的增量 FS 功能添加到 android-12-5.10 分支中,其中包括更新符号列表和 ABI XML 表示法。

  • 如需查看功能更改示例,请参阅 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

如需解决此问题,请同时更新内核和 ACK 中的 KMI 符号列表(请参阅更新 ABI 表示法)。如需查看示例,了解如何更新 ACK 中的 ABI XML 和符号列表,请参阅 aosp/1367601

解决内核 ABI 合规性破坏问题

您可以重构代码以避免更改 ABI更新 ABI 表示法,以处理内核 ABI 合规性破坏问题。请使用以下图表确定最适合您情况的方法。

解决 ABI 合规性破坏问题流程图

图 1. 解决 ABI 合规性破坏问题

重构代码以避免更改 ABI

应尽可能避免修改现有 ABI。在许多情况下,您可以重构代码以移除会影响 ABI 的更改。

  • 重构结构体字段更改。如果变更会修改调试功能的 ABI,请在字段(在结构体和源代码引用中)周围添加一个 #ifdef;并确保已针对正式版 defconfig 和 gki_defconfig,停用了用于 #ifdefCONFIG。如需查看相关示例,了解如何在不破坏 ABI 的情况下将调试配置添加到结构体中,请参阅此补丁集

  • 重构功能以避免更改核心内核。如果需要向 ACK 中添加新功能以支持合作伙伴模块,请尝试重构更改的 ABI 部分,以避免修改内核 ABI。如需查看相关示例,了解如何使用现有内核 ABI 在不更改内核 ABI 的情况下添加其他功能,请参阅 aosp/1312213

修复 Android Gerrit 上的 ABI 合规性破坏问题

如果您没有故意破坏内核 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 会查找在 build(一个已扩展的 build,会执行常规构建,然后执行一些 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,您的代码将会有变化,并且 ABI XML 和符号列表需要应用于 ACK。为了使 lint 去掉 -1 并且不会破坏 GKI 兼容性,请执行以下步骤:

  1. 将 ABI 代码更改上传到 ACK

  2. 更新 ACK ABI 文件

  3. 合并代码更改及 ABI 更新更改。

将 ABI 代码更改上传到 ACK

更新 ACK ABI 的方式取决于所做更改的类型。

  • 如果 ABI 更改与影响 CTS 或 VTS 测试的功能相关,那么系统通常可以按原样择优挑选更改并应用于 ACK。例如:

  • 如果 ABI 更改是针对可与 ACK 共享的功能,则可以按原样择优挑选更改并应用于 ACK。例如,CTS 或 VTS 测试不需要进行以下更改,但这些更改可以与 ACK 共享:

  • 如果 ABI 更改引入了一项无需包含在 ACK 中的新功能,您可以使用桩将这些符号引入 ACK,如下一部分中所述。

针对 ACK 使用桩

仅当核心内核更改对 ACK 没有益处时(例如性能和电源更改),才必须要使用桩。以下列表详细介绍了针对 GKI 的 ACK 中的桩和部分择优挑选的示例。

  • 核心隔离功能桩 (aosp/1284493)。 ACK 中的功能不是必需的,但 ACK 中必须包含这些符号,您的模块才能使用这些符号。

  • 供应商模块的占位符符号 (aosp/1288860)。

  • 仅针对 ABI 择优挑选按进程 mm 事件跟踪功能补丁 (aosp/1288454)。系统会针对 ACK 择优挑选原始补丁,然后删减补丁,使其仅包含解决 task_structmm_event_count 的 ABI 差异所需的必要更改。此补丁还会更新 mm_event_type 枚举,以包含最终成员。

  • 部分择优挑选热结构体 ABI 更改(不止需要添加新的 ABI 字段)。

    • 补丁 aosp/1255544 解决了合作伙伴内核和 ACK 之间的 ABI 差异。

    • 补丁 aosp/1291018 修复了在上一个补丁的 GKI 测试期间发现的功能问题。修复包括了初始化传感器参数结构体,以将多个热区注册到单个传感器。

  • CONFIG_NL80211_TESTMODE ABI 更改 (aosp/1344321)。此补丁为 ABI 添加了必要的结构体更改,并确保其他字段未造成功能差异,使合作伙伴能够在正式版内核中包含 CONFIG_NL80211_TESTMODE,同时仍保持 GKI 合规性。

更新 ACK ABI 文件

如需更新 ACK ABI 文件,请执行以下操作:

  1. 上传 ABI 更改,并等待收到补丁集的代码审核 +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
    # Or, with Bazel,
    tools/bazel run //common:kernel_aarch64_abi_update_symbol_list &&
    tools/bazel run //common:kernel_aarch64_abi_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 搭配使用后,系统会自动使用提交文本的报告,然后您可以修改该报告以添加主题行(如果消息过长,还可进行删减)。

具有预定义 ABI 的 Android 内核分支

一些内核分支在其源代码分发版本中随附了适用于 Android 的预定义 ABI 表示法。这些 ABI 表示法应准确无误,并反映 build_abi.sh 的结果,就像是您自行执行它一样。由于各种内核配置选项对 ABI 的影响很大,因此这些 .xml 文件通常属于某个特定配置。例如,common-android12-5.10 分支包含与使用 build.config.gki.aarch64 时的构建结果相对应的 abi_gki_aarch64.xml。具体来说,build.config.gki.aarch64 还通过 ABI_DEFINITION 引用此文件。

此类预定义的 ABI 表示法在与 diff_abi 进行比较时会用作基准定义。例如,如要验证有关 ABI 的任何更改的内核补丁,请创建应用了该补丁的 ABI 表示法,然后使用 diff_abi 将其与特定源代码树或配置的预期 ABI 进行比较。如果已设置 ABI_DEFINITION,则相应地运行 build_abi.sh 即可。

在运行时强制执行 KMI

GKI 内核使用 TRIM_UNUSED_KSYMS=yUNUSED_KSYMS_WHITELIST=<union of all symbol lists> 配置选项,这些选项可将导出的符号(例如使用 EXPORT_SYMBOL_GPL() 导出的符号)限制为符号列表中列出的符号。所有其他符号均不会被导出,如果加载的模块需要未导出的符号,该加载操作会被拒绝。系统会在构建时强制执行此限制,并标记缺失的条目。

出于开发目的,您可以使用未采取符号删减(即,所有通常会导出的符号都可使用)的 GKI 内核 build。如需找到这些 build,请在 ci.android.com 上查找 kernel_debug_aarch64 build。

使用模块版本控制强制执行 KMI

通用内核映像 (GKI) 内核使用模块版本控制 (CONFIG_MODVERSIONS) 作为在运行时强制执行 KMI 合规性的一种附加措施。如果模块的预期 KMI 与 vmlinux KMI 不一致,模块版本控制可能会在模块加载时导致循环冗余检查 (CRC) 不一致而失败。例如,以下是在模块加载时由于符号 module_layout() 的 CRC 不一致导致的典型失败:

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 的数据结构),将来对结构进行更改后,会出现问题。

    struct device 中的 fwnode 字段为例。该字段必须对模块不透明,使模块无法对 device->fw_node 的字段进行更改,也无法假设其大小。

    不过,如果模块包含 <linux/fwnode.h>(直接或间接),那么 struct device 中的 fwnode 字段将对模块变为透明。这样一来,模块就可以对 device->fwnode->devdevice->fwnode->ops 进行更改。由于以下几种原因,这种情况会造成问题:

    • 这会破坏核心内核代码对其内部数据结构的假设。

    • 如果未来的内核更新更改了 struct fwnode_handlefwnode 的数据类型),模块将无法用于新内核。此外,abidiff 将不会显示任何差异,因为模块会通过直接操控内部数据结构来破坏 KMI,而这是仅检查二进制文件表示法所无法捕获的。

  • 如果当前模块日后被不兼容的新内核加载,就会被视为 KMI 不兼容。模块版本控制会添加运行时检查,以免意外加载与内核在 KMI 上不兼容的模块。此检查可防止出现难以调试的运行时问题和内核崩溃,这些问题可由未检测到的 KMI 不兼容问题导致。

  • 对于在某些复杂情况下发现 ABI 差异,abidiff 具有局限性,而 CONFIG_MODVERSIONS 却可以在这类情况下捕获这些差异。

启用模块版本控制可防止所有这些问题。

在不启动设备的情况下检查是否有 CRC 不一致问题

abidiff 会比较并报告内核之间的 CRC 不一致问题。利用此工具可以同时捕获 CRC 不一致问题和其他 ABI 差异。

此外,启用了 CONFIG_MODVERSIONS 的完整内核 build 还会在正常的构建流程中生成 Module.symvers 文件。对于内核 (vmlinux) 和模块导出的每一个符号,此文件中都会有一行内容与之对应。每一行都包含以下内容:CRC 值、符号名称、符号命名空间、导出该符号的 vmlinux 或模块的名称以及导出类型(例如 EXPORT_SYMBOLEXPORT_SYMBOL_GPL)。

您可以比较 GKI build 和您的 build 所生成的 Module.symvers 文件,以检查 vmlinux 导出的符号是否存在任何 CRC 不一致问题。如果 vmlinux 导出的任何符号存在 CRC 值不一致的情况,并且您在您的设备上加载的某个模块使用了该符号,则该模块将不会加载。

如果您没有所有构建工件,而只有 GKI 内核和您的内核的 vmlinux 文件,则可以通过对这两个内核运行以下命令并比较输出来比较特定符号的 CRC 值:

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

例如,以下命令会检查 module_layout 符号的 CRC 值:

nm vmlinux | grep __crc_module_layout
0000000008663742 A __crc_module_layout

解决 CRC 不一致问题

加载模块时,请按照以下步骤解决 CRC 不一致问题:

  1. 通过在用于构建内核的命令前加上 KBUILD_SYMTYPES=1 来构建 GKI 内核和设备内核,如下所示:

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

    此命令会为每个 .o 文件生成一个 .symtypes 文件。使用 build_abi.sh, 时,已隐式设置了 KBUILD_SYMTYPES=1 标志。

    对于 Android 13 分支及更高版本,内核 build 中启用了 Bazel。上述命令的 Bazel 等效项如下:

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

    如需了解详情,请参阅 Kleaf 中的 KBUILD_SYMTYPES

  2. 使用以下命令找到具有 CRC 不一致问题的符号导出到的 .c 文件:

    cd common && git grep EXPORT_SYMBOL.*module_layout
    kernel/module.c:EXPORT_SYMBOL(module_layout);
    
  3. .c 文件具有 GKI 中的相应 .symtypes 文件,以及您的设备内核构建工件。使用以下命令找到 .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 值,但由于您的内核发生了更改,因此您的内核会具有该符号的内部详细信息。这是因为您的内核中的某个文件添加了 GKI 内核中不存在的 #include

如要确定导致差异的 #include,请按以下步骤操作:

  1. 打开定义具有此差异的符号或数据类型的头文件。例如,修改 struct fwnode_handleinclude/linux/fwnode.h

  2. 在头文件顶部添加以下代码:

    #ifdef CRC_CATCH
    #error "Included from here"
    #endif
    
  3. 在模块的 .c 文件(具有 CRC 不一致问题的文件)中,在任何 #include 行之前添加以下内容作为第一行。

    #define CRC_CATCH 1
    
  4. 编译模块。所生成的构建时错误会显示导致此 CRC 不一致问题的头文件 #include 的链。例如:

    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);

devm_of_platform_populate() 有一个 CRC 不一致问题。

如果您比较该符号的 .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 并合并