如果我将程序编译成单个二进制文件,进行校验,然后在同一台机器上使用相同的编译器和编译器设置重新编译它并对重新编译的程序进行校验,那么校验和会失败吗?
如果是,这是为什么?如果不是,那么使用不同的 CPU 是否会导致二进制文件不相同?
答案1
在同一台机器上使用相同的设置编译相同的程序:
虽然明确的答案是“视情况而定”,但可以合理地预期大多数编译器在大多数情况下都是确定性的,并且生成的二进制文件应该是相同的。事实上,一些版本控制系统依赖于此。不过,总会有例外;很有可能一些编译器会决定插入时间戳或类似的东西(如果我没记错的话,例如 Delphi 就是这样做的)。或者构建过程本身可能会这样做;我见过 C 程序的 makefile 将预处理器宏设置为当前时间戳。(不过,我猜这应该算作不同的编译器设置。)
另外,请注意,如果您静态链接二进制文件,那么您实际上是在合并计算机上所有相关库的状态,并且其中任何一个库的任何更改也会影响您的二进制文件。因此,相关的不仅仅是编译器设置。
在具有不同 CPU 的不同机器上编译相同的程序。
在这里,一切都无法预料。大多数现代编译器都能够进行针对特定目标的优化;如果启用此选项,则除非 CPU 相似(即使如此,也有可能),否则二进制文件可能会有所不同。另外,请参阅上面关于静态链接的说明:配置环境远远超出了编译器设置。除非您有非常严格的配置控制,否则两台机器之间极有可能存在差异。
答案2
-frandom-seed=123
控制一些 GCC 内部随机性。man gcc
说:此选项提供种子,GCC 使用它代替随机数来生成某些符号名称,这些符号名称在每个编译文件中都必须不同。它还用于在覆盖率数据文件和生成它们的目标文件中放置唯一标记。您可以使用 -frandom-seed 选项来生成可重复的相同目标文件。
__FILE__
:将源文件放在固定文件夹中(例如/tmp/build
)- 为了
__DATE__
,__TIME__
,__TIMESTAMP__
:- libfaketime:https://github.com/wolfcw/libfaketime
- 使用以下方法覆盖这些宏
-D
-Wdate-time
or-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可重现构建项目尝试逐字节标准化 Debian 软件包,最近得到了Linux 基金会资助。这不仅仅包括编译,但它应该是有趣的。
构建根有一个BR2_REPRODUCIBLE
选项可能会在包级别上提供一些想法,但目前还远远未完成。
相关主题:
答案3
你要问的是“输出确定性的”。如果您编译了一次程序,然后立即再次编译它,您可能会得到相同的输出文件。但是,如果任何东西发生了变化 - 即使是很小的变化 - 特别是在编译程序使用的组件中,那么编译器的输出也可能会改变。
答案4
我会说不,它不是 100% 确定的。我之前使用过 GCC 的一个版本,它为日立 H8 处理器生成目标二进制文件。
这不是时间戳的问题。即使忽略时间戳问题,特定的处理器架构也可能允许以 2 种略有不同的方式对同一指令进行编码,其中某些位可以是 1 或 0。我以前的经验表明,生成的二进制文件大多数情况下是相同的,但偶尔 gcc 会生成大小相同的二进制文件,但某些字节仅相差 1 位,例如 0XE0 变为 0XE1。