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

Posted

技术标签:

【中文标题】为啥只有注释更改的两个程序二进制文件在 gcc 中不完全匹配?【英文标题】:Why don't two binaries of programs with only comments changed exactly match in gcc?为什么只有注释更改的两个程序二进制文件在 gcc 中不完全匹配? 【发布时间】:2015-11-30 18:45:56 【问题描述】:

我创建了两个 C 程序

    程序 1

    int main()
    
    
    

    程序 2

    int main()
    
    //Some Harmless comments
    
    

AFAIK,编译时,编译器(gcc)应该忽略 cmets 和冗余空白,因此输出必须相似。

但是当我检查输出二进制文件的 md5sum 时,它们不匹配。我还尝试使用优化-O3-Ofast 进行编译,但它们仍然不匹配。

这里发生了什么?

编辑: 确切的命令和 md5sum 是(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

【问题讨论】:

请包含您的确切命令行标志。例如,二进制文件中是否包含调试信息?如果是这样,更改的行号显然会影响它... 同一代码的多个版本的 MD5 总和是否一致? 我无法重现这个。我猜这是因为 GCC 在编译二进制文件时将一大堆元数据嵌入到二进制文件中(包括时间戳)。如果您可以添加您使用的 precise 命令行标志,那将很有用。 不只是检查 MD5sum 并卡住,而是使用 hexdump 和 diff 来准确查看哪些字节不同 虽然回答了“两个编译器输出之间有什么不同?”这个问题。很有趣,我注意到这个问题有一个毫无根据的假设:两个输出应该是相同的,我们需要一些解释说明它们为什么不同。编译器向你保证的是,当你给它一个合法的 C 程序时,输出是一个实现该程序的合法可执行文件。编译器的任何两次执行都生成相同的二进制文件并不能保证 C 标准。 【参考方案1】:

这是因为文件名不同(尽管字符串输出相同)。如果您尝试修改文件本身(而不是拥有两个文件),您会注意到输出二进制文件不再不同。正如 Jens 和我所说,这是因为 GCC 将大量元数据转储到它构建的二进制文件中,including the exact source filename(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

这解释了为什么您的 md5sum 在构建之间不会改变,但它们在不同文件之间是不同的。如果您愿意,您可以按照 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

【讨论】:

编辑:更新说您可以剥离二进制文件以“修复”问题。 这就是为什么你应该比较汇编输出,而不是 MD5 校验和。 我已经问了一个后续问题here。 根据目标文件格式,编译时间也存储在目标文件中。因此,使用 COFF 文件,例如文件 a 和 a2 将不相同。【参考方案2】:

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

尝试运行

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

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

【讨论】:

根据gcc.gnu.org/ml/gcc-help/2007-05/msg00138.html(我知道已过时)他们不保存时间戳,这可能是链接器问题。不过,我确实记得最近读过一篇关于安全公司如何使用二进制文件中的 GCC 时间戳信息来描述黑客团队的工作习惯的故事。 更不用说 OP 声明“md5sums 在具有相同标志的多个编译中匹配”,这表明它可能不是导致问题的时间戳。这可能是因为它们是不同的文件名。 @cyphar 字符串/差异方法也应该捕获不同的文件名。【参考方案3】:

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

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

对二进制文件运行strip 以去除一些脂肪。如果剥离的二进制文件相同,那么它是一些对程序操作不是必需的元数据。 生成程序集中间输出以验证差异不在实际 CPU 指令中(或者,为了更好地确定差异实际在哪里) 使用strings,或将两个程序转储到十六进制并在两个十六进制转储上运行差异。一旦找到差异,您可能会尝试查看它们是否有一些押韵或原因(PID、时间戳、源文件时间戳......)。例如,您可能有一个例程 storing the timestamp at compile time 用于诊断目的。

【讨论】:

我的系统是gcc (GCC) 5.2.0Linux 4.2.0-1-MANJARO #1 SMP PREEMPT x86_64 GNU/Linux 您应该尝试实际上制作两个单独的文件。我也无法通过修改单个文件来重现它。 是的,文件名是罪魁祸首。如果我编译具有相同名称的程序,我可以获得相同的 md5sum。

以上是关于为啥只有注释更改的两个程序二进制文件在 gcc 中不完全匹配?的主要内容,如果未能解决你的问题,请参考以下文章

删除 GCC ABI 更改的注释

gcc编译器

为啥 GCC 编译的 C 程序需要 .eh_frame 部分?

为啥 gcc 在非调试版本中添加符号?

Git pull 很慢……为啥?

gcc和arm-linux-gcc是啥关系?区别是啥?