簡體   English   中英

Windows 下的確定性構建

[英]Deterministic builds under Windows

最終目標是比較在完全相同的環境中從完全相同的源構建的 2 個二進制文件,並能夠判斷它們在功能上確實是等價的。

一個應用程序是將 QA 時間集中在發布之間實際更改的內容上,以及一般的更改監控上。

MSVC 與 PE 格式一起自然使這很難做到。

到目前為止,我發現並消除了這些東西:

  • PE時間戳和校驗和
  • 數字簽名目錄條目
  • 調試器部分時間戳
  • PDB 簽名、年齡和文件路徑
  • 資源時間戳
  • VS_VERSION_INFO 資源中的所有文件/產品版本
  • 數字簽名部分

我解析 PE,查找所有這些東西的偏移量和大小,並在比較二進制文件時忽略字節范圍。 像魅力一樣工作(好吧,對於我運行的少數測試)。 我可以看出,在 Win Server 2008 上構建的 1.0.2.0 版簽名可執行文件等於在我的 Win XP 開發箱上構建的 10.6.6.6 版未簽名可執行文件,只要編譯器版本以及所有源和標頭都相同。 這似乎適用於 VC 7.1 -- 9.0。 (對於發布版本)

有一個警告。

兩個構建的絕對路徑 必須相同, 必須具有相同的長度。

cl.exe 將相對路徑轉換為絕對路徑,並將它們與編譯器標志等一起放入對象中。 這對整個二進制文件有不成比例的影響。 路徑中的一個字符更改將導致一個字節在整個文本部分在這里和那里發生多次更改(但我懷疑鏈接了很多對象)。 改變路徑的長度會導致明顯更多的差異。 在 obj 文件和鏈接二進制文件中。

感覺就像帶有編譯標志的文件路徑被用作某種哈希,這使得它成為鏈接二進制文件甚至影響不相關的編譯代碼片段的放置順序。

所以這是由 3 部分組成的問題(總結為“現在怎么辦?”):

  • 我是否應該放棄整個項目並回家,因為我正在嘗試做的事情違反了 MS 的物理定律和公司政策?

  • 假設我處理絕對路徑問題(在策略級別或通過找到神奇的編譯器標志),還有其他我應該注意的事情嗎? (像 __TIME__ 這樣的東西確實意味着改變了代碼,所以我不介意那些沒有被忽略的東西)

  • 有沒有辦法強制編譯器使用相對路徑,或者讓它認為路徑不是它本來的樣子?

最后一個原因是令人討厭的 Windows 文件系統。 你永遠不知道什么時候刪除幾千兆的源和對象以及 svn 元數據會因為流氓文件鎖而失敗。 至少在有剩余空間的情況下,創建新根總是成功的。 一次運行多個構建也是一個問題。 運行一堆虛擬機雖然是一種解決方案,但相當繁重。

我想知道是否有一種方法可以為一個進程及其子進程設置一個虛擬文件系統,以便多個進程樹將同時看到不同的“C:\build”目錄,僅對它們私有...一盞燈- 各種重量虛擬化......

更新:我們最近在GitHub 上開源了該工具。 請參閱文檔中的比較部分。

我在一定程度上解決了這個問題。

目前我們的構建系統確保所有新構建都在恆定長度的路徑上(builds/001、builds/002 等),從而避免 PE 布局發生變化。 構建工具后,將忽略相關 PE 字段和其他具有已知表面變化的位置來比較新舊二進制文件。 它還運行一些簡單的啟發式方法來檢測動態可忽略的變化。 以下是要忽略的事項的完整列表:

  • PE時間戳和校驗和
  • 數字簽名目錄條目
  • 導出表時間戳
  • 調試器部分時間戳
  • PDB 簽名、年齡和文件路徑
  • 資源時間戳
  • VS_VERSION_INFO 資源中的所有文件/產品版本
  • 數字簽名部分
  • 嵌入式類型庫的 MIDL 虛榮存根(包含時間戳字符串)
  • __FILE__、__DATE__ 和 __TIME__ 宏用作文字字符串時(可以是寬字符或窄字符)

有時,鏈接器會使某些 PE 部分變大,而不會導致其他任何內容不對齊。 看起來它在填充內移動了節邊界——無論如何它都是零,但正因為如此,我將得到具有 1 個字節差異的二進制文件。

更新:我們最近在GitHub 上開源了該工具。 請參閱文檔中的比較部分。

標准化構建路徑

一個簡單的解決方案是對您的構建路徑進行標准化,因此它們始終采用以下形式,例如:

c:\buildXXXX

然后,當您將build0434build0398進行比較時,只需預處理二進制文件以將所有出現的build0434更改為build0398 選擇一個你知道不太可能出現在你的實際源/數據中的模式,除了那些編譯器/鏈接器嵌入到 PE 中的字符串。

然后你就可以做你正常的差異分析了。 通過使用相同長度的路徑名,您不會移動任何數據並導致誤報。

轉儲實用程序

另一個技巧是使用dumpbin.exe (MSVC 附帶)。 使用dumpbin /all將二進制文件的所有詳細信息轉儲到文本/十六進制轉儲。 這可以更明顯地看到發生了什么/哪里發生了變化。

例如:

dumpbin /all program1.exe > program1.txt
dumpbin /all program2.exe > program2.txt
windiff program1.txt program2.txt

或者使用您最喜歡的文本差異工具,而不是 Windiff。

Bindiff 實用程序

你可能會發現微軟的bindiff.exe工具很有用,可以在這里獲得:

Windows XP Service Pack 2 支持工具

它有一個 /v 選項,指示它忽略某些二進制字段,例如時間戳、校驗和等:

“BinDiff 對 Win32 可執行文件使用特殊的比較例程,在執行比較時屏蔽兩個文件中的各種構建時間戳字段。這允許兩個可執行文件在文件真正相同時被標記為“幾乎相同”,除了他們建造的時間。”

但是,聽起來您可能已經在做bindiff.exe 所做的超集。

您是否嘗試反匯編可執行文件並比較反匯編? 這應該會刪除您提到的許多分散注意力的細節,並使刪除其他細節變得容易得多。

有沒有辦法強制編譯器使用相對路徑,或者讓它認為路徑不是它本來的樣子?

您有兩種方法可以做到這一點:

  1. 使用 subst.exe 命令並將驅動器號映射到構建文件夾(這可能不可靠)。
  2. 如果 subst.exe 不起作用,則為每個構建文件夾創建共享並使用“net use”命令。 這幾乎肯定應該有效。

在任何一種情況下,您都將在開始特定構建之前為文件夾映射和重復使用相同的驅動器盤符,以便路徑看起來與編譯器相同。

我遇到了一個額外的工具來幫助解決這個問題: GitHub 上的 Ducible

“這是一種使可移植可執行文件 (PE) 和 PDB 的構建可重現的工具。”

它修改提供的 *.exe、*.dll 和 *.pdb 文件,用確定性數據替換非確定性數據。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM