【问题标题】:Deterministic builds under WindowsWindows 下的确定性构建
【发布时间】:2010-11-13 22:19:13
【问题描述】:

最终目标是比较在完全相同的环境中从完全相同的源构建的 2 个二进制文件,并能够判断它们在功能上确实是等效的。

为此的一个应用程序是将质量检查时间集中在版本之间实际更改的内容上,以及一般的更改监控。

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 将相对路径转换为绝对路径,并将它们与编译器标志等一起放入对象中。这对整个二进制文件有不成比例的影响。路径中的一个字符更改将导致一个字节在整个 .text 部分中在这里和那里多次更改(但是我怀疑有很多对象被链接)。改变路径的长度会导致明显更多的差异。在 obj 文件和链接的二进制文件中。

感觉像带有编译标志的文件路径被用作某种哈希,这使它成为链接的二进制文件,甚至影响不相关的已编译代码的放置顺序。

所以这是一个由 3 部分组成的问题(总结为“现在做什么?”):

  • 我是否应该放弃整个项目并回家,因为我正在尝试做的事情违反了物理定律和 MS 的公司政策?

  • 假设我处理绝对路径问题(在策略级别或通过找到一个神奇的编译器标志),我还需要注意什么其他事情吗? (像 __TIME__ do 这样的东西意味着改变了代码,所以我不介意那些不被忽略的东西)

  • 有没有办法强制编译器使用相对路径,或者让它认为路径不是它的样子?

最后一个原因是令人讨厌的 Windows 文件系统。你永远不知道什么时候删除几个 gigs 的源和对象以及 svn 元数据会因为恶意文件锁定而失败。至少在有空间的情况下创建新的根总是成功的。一次运行多个构建也是一个问题。运行一堆虚拟机虽然是一种解决方案,但却是一个相当繁重的解决方案。

我想知道是否有一种方法可以为进程及其子进程设置虚拟文件系统,以便多个进程树将看到不同“C:\build”目录,仅对它们私有,同时...一种轻量级的虚拟化...

更新:我们最近在 GitHub 上开源了该工具。请参阅文档中的比较部分。

【问题讨论】:

  • (+1) 感谢 peparser 的 --compare 选项。但是这部分PDB ... file path 似乎并不适用于所有情况。如果我只是在将/PDBALTPATH:%_PDB% 添加到链接器命令行(这会导致实际路径从二进制映像中剥离)之后重建一个 VC++ 2015 项目,那么 peparse 会将其报告为 not equivalent 到原始版本。
  • @dxiv 如果可能的话,你能在 github 上打开一个带有附加二进制文件的错误吗?
  • Done,感谢您对此进行调查。

标签: c++ windows visual-c++ portable-executable


【解决方案1】:

我发现了一个额外的工具来帮助解决这个问题: Ducible on GitHub

“这是一个使可移植可执行文件 (PE) 和 PDB 的构建可重现的工具。”

它会修改提供的 *.exe、*.dll 和 *.pdb 文件,用确定性数据替换非确定性数据。

【讨论】:

    【解决方案2】:

    我在一定程度上解决了这个问题。

    目前,我们有构建系统,可确保所有新构建都在恒定长度的路径上(builds/001、builds/002 等),从而避免 PE 布局发生变化。构建后,工具会比较新旧二进制文件,忽略相关的 PE 字段和其他具有已知表面变化的位置。它还运行一些简单的启发式方法来检测动态可忽略的变化。以下是要忽略的完整列表:

    • PE 时间戳和校验和
    • 数字签名目录条目
    • 导出表时间戳
    • 调试器部分时间戳
    • PDB 签名、年龄和文件路径
    • 资源时间戳
    • VS_VERSION_INFO 资源中的所有文件/产品版本
    • 数字签名部分
    • 嵌入式类型库的 MIDL 虚存根(包含时间戳字符串)
    • __FILE__、__DATE__ 和 __TIME__ 宏用作文字字符串(可以是宽字符或窄字符)

    有时,链接器会使某些 PE 部分变大,而不会使其他任何东西失去对齐。看起来它在填充内移动了部分边界——无论如何它都是零,但正因为如此,我会得到具有 1 个字节差异的二进制文件。

    更新:我们最近在 GitHub 上开源了该工具。请参阅文档中的比较部分。

    【讨论】:

    • 以下是 TLB 时间戳的简单解决方法(仅在 msvs_2015 + MIDL 版本 7.00.0555 上测试):peparser_with_tlb
    【解决方案3】:

    标准化构建路径

    一个简单的解决方案是标准化您的构建路径,因此它们始终采用以下形式,例如:

    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 实用程序

    您可能会发现 Microsoft 的 bindiff.exe 工具很有用,可在此处获得:

    Windows XP Service Pack 2 Support Tools

    它有一个 /v 选项,指示它忽略某些二进制字段,例如时间戳、校验和等:

    "BinDiff 使用特殊的比较例程 用于掩码的 Win32 可执行文件 中的各种构建时间戳字段 执行时两个文件 比较。这允许两个可执行文件 要标记为“几乎相同”的文件 当文件真正相同时, 除了它们建造的时间。”

    不过,听起来您可能已经在做 bindiff.exe 的超集了。

    【讨论】:

    • 不幸的是,源路径没有以纯文本形式保存,我找不到任何关于它实际影响的信息以及我是否可以安全地忽略它。 (毕竟假阴性比阳性更糟糕)。
    【解决方案4】:

    有没有办法强制 编译器使用相对路径,或 愚弄它认为路径不是 它是什么?

    您有两种方法可以做到这一点:

    1. 使用 subst.exe 命令并将驱动器号映射到构建文件夹(这可能不可靠)。
    2. 如果 subst.exe 不起作用,则为每个构建文件夹创建共享并使用“net use”命令。这个几乎肯定可以工作。

    在任何一种情况下,您都将在开始特定构建之前为文件夹映射并重复使用相同的驱动器号,以便路径看起来与编译器相同。

    【讨论】:

    • 我建议相同,但在 C:\BUILD\XXX 等公共目录下使用符号链接
    • Preet,如何在 Windows 上创建符号链接?
    • NTFS 支持连接点。但是您需要下载一个实用程序或在 Vista+ 上。 Windows 在技术上确实以不同的方式处理连接点,因此像 subst.exe 这可能会或可能不会工作。
    • 除了需要相同路径指向不同位置的 2 个同时运行的进程外,连接都可以工作。我猜他们会简化清理工作......
    • 我没有看到你的“同时”要求放在最后。为什么不通过顺序构建来简化问题?
    【解决方案5】:

    您是否尝试过反汇编可执行文件并比较反汇编?这应该会删除您提到的许多令人分心的细节,并使删除其他细节变得容易得多。

    【讨论】:

    • 没试过,没有。即使它有效,它也不能真正可靠地自动化......尽管这可能会带来一些确切的不同之处。我会试试的,谢谢。
    • 我相信你可以自动反汇编软件。从命令行运行...这可能是一个很好的解决方案,具体取决于您在反汇编程序的输出中遇到的障碍;)
    猜你喜欢
    • 1970-01-01
    • 2019-01-23
    • 1970-01-01
    • 2020-03-10
    • 2011-06-02
    • 2019-04-16
    • 1970-01-01
    • 2015-07-15
    相关资源
    最近更新 更多