重新编译程序是否会产生逐位相同的二进制文件?


25

如果我要将程序编译为单个二进制文件,请进行校验和,然后在具有相同编译器和编译器设置的同一台计算机上将其重新编译,并对重新编译的程序进行校验和,校验和会失败吗?

如果是这样,为什么呢?如果不是,使用不同的CPU是否会导致二进制文件不一致?


8
这取决于编译器。其中一些嵌入了时间戳,因此答案是“否”。
ta.speot.is

实际上,它取决于可执行格式,而不取决于编译器。某些可执行文件格式(例如Windows的PE格式)包含一个时间戳,该时间戳与编译时间和日期相关,而其他格式(例如Linux的ELF格式)则没有。无论哪种方式,这个问题都取决于“相同二进制”的定义。如果使用相同的编译器,库和开关以及所有内容来编译相同的源文件,则映像本身将/应该按位相同,但是标头和其他元数据可能会有所不同。
Synetech 2013年

Answers:


19
  1. 在同一台计算机上以相同设置编译相同程序:

    尽管确定的答案是“取决于”,但可以合理地预期大多数编译器在大多数时间都是确定性的,并且所生成的二进制文件应该相同。实际上,某些版本控制系统依赖于此。尽管如此,总有例外。这很可能是一些编译器的地方将决定插入一个时间戳或一些这样的(IIRC,德尔福做,例如)。否则构建过程本身可能会这样做;我见过C程序的makefile文件,这些文件将预处理程序宏设置为当前时间戳。(不过,我认为这将算作是不同的编译器设置。)

    另外,请注意,如果您静态链接二进制文件,则可以有效地合并计算机上所有相关库的状态,并且其中任何一个的任何更改都将影响二进制文件。因此,不仅相关的编译器设置。

  2. 在具有不同CPU的不同计算机上编译相同程序。

    在这里,所有赌注都关闭了。大多数现代的编译器都能够进行针对特定目标的优化。如果启用此选项,则二进制文件可能会有所不同,除非CPU相似(即使这样,也可能)。另外,请参见上面有关静态链接的说明:配置环境远远超出了编译器设置。除非您有非常严格的配置控制,否则两台计算机之间很可能会有所不同。


1
假设我使用的是GCC,而我没有使用march选项(针对特定CPU系列优化二进制文件的选项),而我将使用一个CPU编译一个二进制文件,然后使用另一个CPU编译一个二进制文件。区别?
大卫

1
@David:仍然取决于。首先,您要链接的库可能具有特定于体系结构的构建。因此,的输出gcc -c可能完全相同,但是链接的版本不同。另外,这不只是-march;还有-mtune/-mcpu-mfpmatch(可能还有其他)。其中一些可能在不同的安装中具有不同的默认值,因此您可能需要为计算机强制使用最坏的情况。这样做可能会大大降低性能,尤其是如果您不使用sse还原到i386时。而且,当然,如果您的一个cpus是ARM,另一个是i686 ...
rici

1
此外,GCC是否是有问题的向二进制文件添加时间戳的编译器之一?
大卫

@david:afaik,不。
rici

8

您要问的是“输出是确定性的”。如果您一次编译了程序,则立即再次对其进行编译,您可能最终会得到相同的输出文件。但是,如果发生了任何变化(甚至很小的变化),尤其是在已编译程序使用的组件中,则编译器的输出也可能会发生变化。


2
确实很好。本文有一些非常有趣的观察。特别是,编译与海湾合作委员会可能具有确定性,在它是如何在匿名的命名空间,为此,它使用一个随机数发生器内部轧液的功能在某些情况下,关于输入,例如。要在这种特定情况下获得确定性,请通过指定option提供初始随机种子-frandom-seed=string
2014年

7

重新编译程序是否会产生逐位相同的二进制文件?

对于所有编译器?否。至少不允许使用C#编译器。

埃里克·利珀特(Eric Lippert)对为什么编译器的输出不确定的原因进行了详尽的分析

根据设计,C#编译器永远不会两次生成相同的二进制文件。C#编译器在每次运行该程序集时都会在每个程序集中嵌入一个新生成的GUID,从而确保没有两个程序集是逐位相同的。引用CLI规范:

Mvid列应为标识该模块实例的唯一GUID编制索引。[...]应该为每个模块重新生成Mvid [[runtime]本身不使用Mvid,而其他工具(例如调试器[...])则依赖于以下事实:从一个模块到另一个模块,Mvid几乎总是有所不同。

尽管它特定于C#编译器的版本,但是本文中的许多要点都可以应用于任何编译器。

首先,我们假设每次总是以相同的顺序获得相同的文件列表。但这在某些情况下取决于操作系统。当您说“ csc * .cs”时,操作系统提供匹配文件列表的顺序是操作系统的实现细节;编译器不会将该列表按规范顺序排序。


使构建的可复制性不难(除了一些容易丢弃的字段,例如编译时间和汇编GUID)。例如,将输入文件按规范顺序排序是一种方法。甚至那个GUID也可能是程序集其余部分的哈希,而不是新生成的。
CodesInChaos 2013年

我假设您是指Microsoft C#编译器,还是规范的要求?
大卫

@David CLI规范要求使用它。Mono的C#编译器必须做同样的事情。与任何VB .NET编译器相同。
ta.speot.is

4
ECMA标准不必具有时间戳或MVID差异。没有这些,至少有可能在C#中使用相同的二进制文件。因此,主要原因是可疑的设计决策,而不是真正的技术约束。
Shiv

7
  • -frandom-seed=123控制一些GCC内部随机性。man gcc说:

    此选项提供了一个种子,GCC在生成某些在每个编译文件中都必须不同的符号名称时,将使用它们代替随机数。它还可用于将唯一戳记放置在coverage数据文件和生成它们的对象文件中。您可以使用-frandom-seed选项来产生可重复的相同目标文件。

  • __FILE__:将源放置在固定文件夹中(例如/tmp/build

  • 对于__DATE____TIME____TIMESTAMP__
    • libfaketime: https //github.com/wolfcw/libfaketime
    • 覆盖那些宏 -D
    • -Wdate-time或者-Werror=date-time:警告或任失败,如果__TIME____DATE__或者__TIMESTAMP__是被使用。Linux内核4.4默认使用它。
  • 使用D带有的标记ar,或使用https://github.com/nh2/ar-timestamp-wiper/tree/master擦除邮票
  • -fno-guess-branch-probability较旧的手册版本说这是不确定性的根源,但现在已经不行了。不知道这是否被覆盖-frandom-seed

Debian Reproducible builds项目试图逐字节标准化Debian软件包,并且最近获得了Linux Foundation的资助。这不仅包括编译,还应引起关注。

Buildroot有一个BR2_REPRODUCIBLE选项,可能会在软件包级别上给出一些想法,但目前还远远不够。

相关主题:


3

https://reproducible-builds.org/这个项目就是关于这个的,并且正在努力在尽可能多的地方为您的问题“不,它们不会有所不同”做出答案。NixOS和Debian现在其程序包的可重复性超过90%。

如果您编译一个二进制文件,而我编译一个二进制文件,并且它们一点一点相同,那么我可以放心,源代码和工具是决定输出的因素,并且您不会偷偷摸摸木马代码。

如果我们将可复制性与人类可读源的引导可扩展性结合起来(如http://bootstrappable.org/正在进行的工作),我们将获得一个由人类可读源从头开始确定的系统,只有这样,我们才能我们可以相信我们知道系统在做什么。


1
酷链接。我是
Buildroot的狂热爱好者

我之所以没有提到Guix,是因为我不知道在哪里可以找到它们的编号,但是它们早于NixOS在具有验证工具之类的可重复性培训上的时间,因此,我敢肯定,他们在平等的基础上或处于同等地位。
clacke

2

我会说不,这不是100%确定性的。我以前使用的是GCC版本,该版本可为Hitachi H8处理器生成目标二进制文件。

时间戳不是问题。即使忽略了时间戳记问题,特定的处理器体系结构也可能允许以2种略有不同的方式对同一条指令进行编码,其中某些位可以为1或0。但是,有时gcc会生成大小相同的二进制文件,但是某些字节仅相差1位,例如0XE0变为0XE1。


这是否导致了不同的行为或“严重问题”?
弗洛里安·斯特劳布

1

一般来说,没有。最合理的复杂编译器会将编译时间包括在对象模块中。即使您要重置时钟,您也必须在开始编译时非常准确(然后希望磁盘访问等与以前相同的速度)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.