GCC .obj 文件输出不是确定性的(.debug_info,PROGBITS 部分)

Posted

技术标签:

【中文标题】GCC .obj 文件输出不是确定性的(.debug_info,PROGBITS 部分)【英文标题】:GCC .obj file output is not deterministic (.debug_info, PROGBITS section) 【发布时间】:2017-01-27 19:42:17 【问题描述】:

我的编译命令是C:\work\PROJ-test\QNX_SDK\host\win32\x86/usr/bin/qcc -c -Wc,-frandom-seed="sadfsasafssadsa" -Wc,-MP,-MT,C:/work/PROJ-test/N_Manag/src/bld/N_Manag//armle-v7/release/nav_event_rcv.cpp.o,-MMD,C:/work/PROJ-test/N_Manag/src/bld/N_Manag//armle-v7/release/nav_event_rcv.cpp.d -Vgcc_ntoarmv7le -w9 -shared -O3 -ggdb3 -DBUILD_VERSION= -DPASLOGOPTIONS=0x02 -DPASLOGAPPZONES=31,23,30,9,8,3 -DNS1_5PORT -DBOARD_TYPE=PRODUCTION C:/work/PROJ-test/N_Manag/src/nav_event_rcv.cpp -o C:/work/PROJ-test/N_Manag/src/bld/N_Manag//armle-v7/release/nav_event_rcv.cpp.o

当我连续两次运行此命令时,两个 .obj 文件不同,而不仅仅是时间戳的几个字节。

我们正在切换构建系统,因此我们希望我们的构建是二进制兼容的。我的绝大多数目标文件都是二进制相同的。一些使用 __DATE____TIME__ 宏的部分有几个字节的不同,但这个完全不同!

我使用了一个 elf-dump 实用程序,发现两个编译之间完全不同的部分是这个

  [544] .debug_info
       PROGBITS        00000000 047d70 1021ed 00   0   0  1
       [00000000]: 

但我不知道PROGBITS 包含什么以及为什么它包含用于连续编译的不同项目。 This site 只是声明 PROGBITS 是一个属性,但不是它所指示的(以及为什么连续编译会有所不同)。

问题

如何使.obj 二进制确定性的生成?

想法

不知何故,正在编译的代码实际上是在修改.obj.debug_info 部分。这个.cpp 使用了一堆boost 库;有没有可能是这个原因?

更新

我查看了正在生成的程序集文件,它们是不同的。结果.objs 会有所不同是有道理的。 仍然不明白为什么会发生这种情况。

更新 上面的qcc 命令并不是实际执行的编译器命令:qcc 是一个编译器“重定向器”,它会调用与-V 参数匹配的那个。 “真正的”编译器调用是这样的:

C:/work/Proj/QNX_SDK/host/win32/x86/usr/lib/gcc/arm-unknown-nto-qnx6.5.0eabi/4.4.2/cc1plus -Wall -O3 -ggdb3 -DBUILD_VERSION= -DPASLOGOPTIONS=0x02 -DPASLOGAPPZONES=31,23,30,9,8,3 -DNS1_5PORT -DBOARD_TYPE=PRODUCTION -quiet -fno-builtin -fpic -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mlittle-endian -nostdinc -nostdinc++ -D__cplusplus -D__QNX__ -D__QNXNTO__ -D__GNUC__=4 -D__GNUC_MINOR__=4 -D__GNUC_PATCHLEVEL__=2 -D__NO_INLINE__ -D__DEPRECATED -D__EXCEPTIONS -D__unix__ -D__unix -D__ELF__ -fpic -DPIC=1 -D__ARM__ -D__arm__ -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=softfp -D__LITTLEENDIAN__ -D__ARMEL__ -U__ARMEB__ -frandom-seed=sadfsasafssadsa -MP -MT C:/work/Proj/N_Manag/src/bld/N_Manag//armle-v7/release/nav_event_rcv.cpp.o -MMD C:/work/Proj/N_Manag/src/bld/N_Manag//armle-v7/release/nav_event_rcv.cpp.d -isystem C:/work/Proj/QNX_SDK/target/qnx6/usr/include -isystem C:/work/Proj/QNX_SDK/host/win32/x86/usr/lib/gcc/arm-unknown-nto-qnx6.5.0eabi/4.4.2/include -isystem C:/work/Proj/QNX_SDK/target/qnx6/usr/include/cpp/c -isystem C:/work/Proj/QNX_SDK/target/qnx6/usr/include/cpp C:/work/Proj/N_Manag/src/nav_event_rcv.cpp -dumpbase C:/work/Proj/N_Manag/src/nav_event_rcv.cpp -o C:\work\Proj\nav_event_rcv.s

更新

我认为看看.s 程序集输出是值得的,因为那里存在重大差异。

记住,我使用的是-frandom-seed

.s 文件有 105 万行,差异开始于 ~900k 行。

左:

.LASF17345: .ascii "_ZN5boost6detail7variant21make_initializer_node5app" .ascii "lyINS_3mpl4pairINS3_INS5_INS3_INS5_INS3_INS5_INS3_I" .ascii "NS5_INS3_INS5_INS3_INS5_INS3_INS5_INS3_INS5_INS3_IN" .ascii "S5_INS3_INS5_INS3_INS5_INS3_INS5_INS3_INS5_INS3_INS" .ascii "5_INS3_INS5_INS3_INS5_INS3_INS5_INS3_INS5_INS1_16in" .ascii "itializer_rootEN4mpl_4int_ILi0EEEEENS4_6l_iterINS4_" ...

右:

.LASF17764: .ascii "_ZNKSt8numpunctIcE13decimal_pointEv\000" .LASF10304: .ascii "cAlpha0\000" .LASF10222: .ascii "usWeek\000" .LASF14117: .ascii "_ZN5boost10shared_ptrI27TnRespTravelEstimationEvent" .ascii "EaSERKS2_\000" ...

它持续了几百个字节。

现在我仔细检查了我无法比较的结果,所有差异部分都归因于boost::detail::variant::make_initializer_node。那个 boost 函数每次生成不同的代码吗?

分辨率

原来这是一个gcc 错误。我用-O<X> -ggdb<Y> 的所有排列编译了我的.cpp,对于Y>=2,程序集文件.s 和对象.obj 是不确定的。

我找到了描述此问题的 a gcc bug。


我不得不删除另一个帖子。 . .原因。

【问题讨论】:

PROGBITS是属性,段名是.debug_info @kennytm 为什么.debug_info的生成是不确定的? 您可以使用readelf--debug-dump=info 以文本形式转储.debuginfo 的内容,希望能更好地理解差异。或--dwarf=infoobjdump @yugr 我有汇编文件。那不是比 elf-dump 更好吗? @yugr 好的。区别在于第二个十六进制数,如: DW_AT_name : (indirect string, offset: 0x373c94): operator= 【参考方案1】:

非确定性的原因

通常的罪魁祸首是宏 __DATE____TIME____TIMESTAMP__,编译器会将其扩展为从系统时间计算的值。

一种可能性是为二进制文件生成的调试信息是以不确定的方式编写的。例如,当编译器进程中调试信息的内存布局不确定时,可能会发生这种情况。我不知道 GCC 的内部结构。但我想这样的事情可能会发生在

在调试信息输出中使用random GUIDs, 使用mangling of symbols in anonymous namespaces,或 按顺序序列化哈希图,其中键是指向内存的指针。

后一种不确定性来源通常被认为是编译器中的错误(例如GCC PR65015)

缓解

要强制__DATE____TIME____TIMESTAMP__ 宏的可重现扩展,必须模拟和伪造系统时间(例如,通过使用libfaketime/faketime)到编译器。 GCC 的-Wdate-time 命令行选项可用于在使用这些预定义宏时发出警告。

要强制 GUID 和重整的可重现“随机性”,您可以尝试使用 -frandom-seed=<somestring> 进行编译,其中 <somestring> 是您构建的唯一字符串(例如,您正在编译的源文件内容的哈希值应该去做)。

或者,您可以尝试在没有调试信息的情况下进行编译(例如,没有-ggdb 等标志)或稍后使用一些剥离工具删除调试信息部分。

另见

https://wiki.debian.org/ReproducibleBuilds/About How to produce deterministic binary output with g++? - Stack Overflow

【讨论】:

这只是猜测,并没有真正回答任何问题。 删除调试信息不​​是一种选择,因为正如@yugr 所说,关键是要弄清楚为什么会发生这种情况。我有数百个.obj 文件,只有少数有这个问题。我会尝试使用-frandom-seed 的建议并反馈。 @yugr 我添加了-frandom-seed="obj",但仍然有不同的.s 文件 @Adrian 请说清楚。你得到了不同的生成程序集文件,但你也得到了不同的目标文件吗?您最初的问题仅与目标文件有关。顺便说一句,GCC 4.4 已经很老了,所以它很可能包含一些导致差异的错误。 @jotik 是的。请参阅下面的答案。

以上是关于GCC .obj 文件输出不是确定性的(.debug_info,PROGBITS 部分)的主要内容,如果未能解决你的问题,请参考以下文章

linux gcc编译参数有啥用?

如何在没有crt的情况下链接obj文件?

如何阻止GCC从obj文件中的字符串文字中删除尾随换行符?

确定哪些目标文件导致 .dll 大小增加 [C++]

make 删除依赖文件

GCC编译选项