繁体   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