为什么只有注释更改的两个程序二进制文件在gcc中不完全匹配?


110

我创建了两个C程序

  1. 程序1

    int main()
    {
    }
  2. 程序2

    int main()
    {
    //Some Harmless comments
    }

AFAIK,在编译时,编译器(gcc)应该忽略注释和多余的白点,因此输出必须相似。

但是,当我检查输出二进制文件的md5sums时,它们不匹配。我也试图与优化的编译-O3-Ofast,但他们仍然不匹配。

这是怎么回事

编辑:确切的命令和md5sums是(t1.c是程序1和t2.c是程序2)

gcc ./t1.c -o aaa
gcc ./t2.c -o bbb
98c1a86e593fd0181383662e68bac22f  aaa
c10293cbe6031b13dc6244d01b4d2793  bbb

gcc ./t2.c -Ofast -o bbb
gcc ./t1.c -Ofast -o aaa
2f65a6d5bc9bf1351bdd6919a766fa10  aaa
c0bee139c47183ce62e10c3dbc13c614  bbb


gcc ./t1.c -O3 -o aaa
gcc ./t2.c -O3 -o bbb
564a39d982710b0070bb9349bfc0e2cd  aaa
ad89b15e73b26e32026fd0f1dc152cd2  bbb

是的,md5sums在具有相同标志的多个编译中匹配。

顺便说一句我的系统是gcc (GCC) 5.2.0Linux 4.2.0-1-MANJARO #1 SMP PREEMPT x86_64 GNU/Linux


17
请包括您的确切命令行标志。例如,调试信息是否完全包含在二进制文件中?如果是这样,行号的更改显然会对其产生影响...
Jon Skeet

4
MD5总和在同一代码的多个版本之间是否一致?
unenthusiasticuser 2015年

3
我无法重现。我猜这是由GCC在编译二进制文件(包括时间戳)时将一堆元数据嵌入二进制文件中引起的。如果您可以添加您使用的精确命令行标志,那将很有用。
cyphar

2
不仅仅是检查MD5sum并被卡住,还可以通过hexdump和diff来确切查看哪些字节有所不同
MM

12
尽管问题的答案是“两个编译器输出之间有什么不同?” 有趣的是,我注意到这个问题有一个毫无根据的假设:两个输出应该相同,并且我们需要它们为何不同的一些解释。编译器仅向您保证,当您给它提供合法的C程序时,输出就是实现该程序的合法可执行文件。编译器的任何两个执行都产生相同的二进制文件并不能保证C标准。
埃里克·利珀特

Answers:


159

这是因为文件名不同(尽管字符串输出是相同的)。如果尝试修改文件本身(而不是拥有两个文件),则会注意到输出二进制文件不再相同。正如Jens和我所说的,这是因为GCC将全部元数据负载转储到其构建的二进制文件中,包括确切的源文件名(而AFAICS也是)。

试试这个:

$ cp code.c code2.c subdir/code.c
$ gcc code.c -o a
$ gcc code2.c -o b
$ gcc subdir/code.c -o a2
$ diff a b
Binary files a and b differ
$ diff a2 b
Binary files a2 and b differ
$ diff -s a a2
Files a and a2 are identical

这就解释了为什么您的md5sums在构建之间不会更改,但是在不同文件之间会有所不同。如果愿意,您可以按照Jens的建议进行操作,并比较strings每个二进制文件的输出,您会注意到文件名已嵌入二进制文件中。如果要“修复”此问题,则可以strip二进制文件和元数据将被删除:

$ strip a a2 b
$ diff -s a b
Files a and b are identical
$ diff -s a2 b
Files a2 and b are identical
$ diff -s a a2
Files a and a2 are identical

编辑:更新说,您可以剥离二进制文件以“解决”问题。
cyphar

30
这就是为什么您应该比较程序集输出而不是MD5校验和的原因。

1
在这里问了一个后续问题。
Federico Poloni 2015年

4
根据目标文件格式,编译时间也存储在目标文件中。因此,使用COFF文件(例如文件a和a2)将不会完全相同。
马丁·罗斯瑙

28

最常见的原因是编译器添加的文件名和时间戳(通常在ELF部分的调试信息部分中)。

尝试跑步

 $ strings -a program > x
 ...recompile program...
 $ strings -a program > y
 $ diff x y

您可能会看到原因。我曾经使用它来查找为什么同一源在不同目录中编译时会导致不同代码。发现是,__FILE__宏扩展为绝对文件名,两棵树都不同。


1
根据gcc.gnu.org/ml/gcc-help/2007-05/msg00138.html(据我所知,它已过时)没有保存时​​间戳,这可能是链接器问题。虽然,我确实记得最近读过一个有关安全公司如何使用二进制文件中的GCC时间戳信息来描述黑客团队工作习惯的故事。
cyphar

3
更不用说OP指出“ md5sums在具有相同标志的多个编译中匹配”,这表明它可能不是导致问题的时间戳。这可能是由于它们是不同的文件名。
cyphar

1
@cyphar字符串/差异方法也应捕获不同的文件名。
詹斯2015年

15

注意:请记住,源文件名使用未拆分的二进制文件,因此来自不同名称的源文件的两个程序将具有不同的哈希值。

在类似情况下,如果以上都不适用,您可以尝试:

  • 跑步 strip针对二进制以除去一些脂肪。如果剥离的二进制文件相同,则说明某些元数据对于程序操作不是必需的。
  • 生成程序集中间输出以验证差异是否在实际的CPU指令中(或者,以便更好地指出差异的实际位置)
  • 使用strings,或转储这两个程序以进行十六进制操作并在两个十六进制转储文件上运行差异。找到差异后,您可以尝试查看它们是否有押韵或原因(PID,时间戳,源文件时间戳...)。例如,您可能有一个例程在编译时存储时间戳以用于诊断。

我的系统是gcc (GCC) 5.2.0Linux 4.2.0-1-MANJARO #1 SMP PREEMPT x86_64 GNU/Linux
注册用户

2
您应该尝试实际制作两个单独的文件。我也无法通过修改单个文件来重现它。
cyphar

是的,文件名是罪魁祸首。如果我用相同的名称编译程序,则可以获得相同的md5sums。
注册用户
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.