英语

可复现构建

通常,期望使用相同工具集构建相同的源代码是可复现的,即输出始终完全相同。这使得可以验证二进制发行版或嵌入式系统的构建基础设施是否被破坏。这也可以更容易地验证源代码或工具更改是否对生成的二进制文件没有任何影响。

可复现构建项目 有关此一般主题的更多信息。本文档介绍了构建内核可能不可复现的各种原因,以及如何避免它们。

时间戳

内核在三个地方嵌入时间戳

  • uname() 公开的版本字符串,并包含在 /proc/version

  • 嵌入式 initramfs 中的文件时间戳

  • 如果通过 CONFIG_IKHEADERS 启用,则内核头文件的时间戳将嵌入到内核或相应的模块中,并通过 /sys/kernel/kheaders.tar.xz 公开

默认情况下,时间戳是当前时间,并且对于 kheaders,是各种文件的修改时间。必须使用 KBUILD_BUILD_TIMESTAMP 变量覆盖此时间戳。如果您是从 git 提交构建的,则可以使用其提交日期。

内核使用 __DATE____TIME__ 宏,并且如果使用它们会启用警告。如果您包含使用这些宏的外部代码,则必须通过设置 SOURCE_DATE_EPOCH 环境变量来覆盖它们对应的时间戳。

用户、主机

内核将构建用户和主机名嵌入到 /proc/version 中。必须使用 KBUILD_BUILD_USER 和 KBUILD_BUILD_HOST 变量覆盖这些名称。如果您是从 git 提交构建的,则可以使用其提交者地址。

绝对文件名

当内核在树外构建时,调试信息可能包含源文件的绝对文件名。必须通过在 KCFLAGS 变量中包含 -fdebug-prefix-map 选项来覆盖此名称。

根据使用的编译器,__FILE__ 宏也可能在树外构建中扩展为绝对文件名。如果支持,Kbuild 会自动使用 -fmacro-prefix-map 选项来防止这种情况。

可复现构建网站有关于这些 prefix-map 选项 的更多信息。

源代码包中的生成文件

tools/ 子目录下某些程序的构建过程不完全支持树外构建。这可能会导致以后使用例如 make rpm-pkg 构建的源代码包包含生成的文件。您应该通过运行 make mrpropergit clean -d -f -x 来确保源代码树是原始的,然后再构建源代码包。

模块签名

如果您启用 CONFIG_MODULE_SIG_ALL,默认行为是为每次构建生成不同的临时密钥,从而导致模块不可复现。但是,在源代码中包含签名密钥可能会破坏签名模块的目的。

解决此问题的一种方法是划分构建过程,以便将不可复现的部分视为源

  1. 生成持久签名密钥。将密钥的证书添加到内核源代码中。

  2. 设置 CONFIG_SYSTEM_TRUSTED_KEYS 符号以包含签名密钥的证书,将 CONFIG_MODULE_SIG_KEY 设置为空字符串,并禁用 CONFIG_MODULE_SIG_ALL。构建内核和模块。

  3. 为模块创建分离的签名,并将其作为源发布。

  4. 执行第二次构建,该构建附加模块签名。它可以重建模块或使用步骤 2 的输出。

结构随机化

如果您启用 CONFIG_RANDSTRUCT,则需要在 scripts/basic/randstruct.seed 中预生成随机种子,以便每次构建都使用相同的值。有关详细信息,请参见 scripts/gen-randstruct-seed.sh

调试信息冲突

这不是不可复现的问题,而是生成的文件过于可复现的问题。

一旦您为可复现构建设置了所有必需的变量,即使对于不同的内核版本,vDSO 的调试信息也可能相同。这可能导致不同内核版本的调试信息包之间发生文件冲突。

为避免这种情况,您可以通过在 vDSO 中包含任意的“盐”字符串来使不同内核版本的 vDSO 不同。这由 Kconfig 符号 CONFIG_BUILD_SALT 指定。

Git

Git 中未提交的更改或不同的提交 ID 也可能导致不同的编译结果。例如,在执行 git reset HEAD^ 之后,即使代码相同,编译过程中生成的 include/config/kernel.release 也会不同,最终导致二进制差异。有关详细信息,请参见 scripts/setlocalversion