可復現的構建

通常希望使用相同的工具集構建相同的原始碼是可復現的,即輸出始終完全相同。這使得驗證二進位制發行版或嵌入式系統的構建基礎架構是否已被破壞成為可能。這也可以更輕鬆地驗證原始碼或工具更改是否對生成的二進位制檔案沒有任何影響。

可復現構建專案 包含有關此一般主題的更多資訊。本文件涵蓋了構建核心可能無法復現的各種原因,以及如何避免這些原因。

時間戳

核心在三個地方嵌入時間戳

  • 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