GCC .obj 文件输出不确定(.debug_info,PROGBITS 部分)
GCC .obj file output is not deterministic (.debug_info, PROGBITS section)
我的编译命令是
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 库;这可能是原因吗?
更新
我查看了正在生成的程序集文件,它们是不同的。结果 .obj
s 会有所不同是有道理的。
仍然不明白为什么会这样。
更新
上面的 qcc
命令不是实际执行的编译器命令:qcc
是编译器 "redirector",因为它将调用与 -V
参数匹配的编译器。
"real" 编译器调用是这样的:
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[=37=]0"
.LASF10304:
.ascii "cAlpha0[=37=]0"
.LASF10222:
.ascii "usWeek[=37=]0"
.LASF14117:
.ascii "_ZN5boost10shared_ptrI27TnRespTravelEstimationEvent"
.ascii "EaSERKS2_[=37=]0"
...
它持续了数百个字节。
现在我仔细检查我的beyond compare,所有的差异部分都是由于boost::detail::variant::make_initializer_node
。那个提升函数每次生成不同的代码吗?
分辨率
原来这是一个 gcc
错误。我用 -O<X> -ggdb<Y>
的所有排列编译了我的 .cpp
,对于 Y>=2,汇编文件 .s
和对象 .obj
是不确定的。
我发现 a gcc bug 描述了这个问题。
我不得不为 删除另一个 post。 . .原因。
non-determinism
的原因
通常的罪魁祸首是宏 __DATE__
、__TIME__
、__TIMESTAMP__
,编译器将其扩展为根据系统时间计算的值。
一种可能是为二进制文件生成的调试信息是以non-deterministic 方式编写的。例如,当编译器进程中调试信息的 in-memory 布局不确定时,就会发生这种情况。我不知道 GCC 的内部结构。但我想这样的事情会在
时发生
- 在调试信息输出中使用random GUIDs,
- 使用mangling of symbols in anonymous namespaces,或
- 按顺序序列化一个散列图,其中键是指向内存的指针。
non-determinism 的后一个来源通常被认为是编译器中的错误(例如 GCC PR65015)
缓解
为了强制 __DATE__
、__TIME__
和 __TIMESTAMP__
宏的可重复扩展,必须模拟和伪造系统时间(例如通过使用 libfaketime/faketime)以编译器。 GCC 的 -Wdate-time
command-line 选项可用于在使用这些预定义宏时发出警告。
要强制 GUID 和重整可重现 "randomness",您可以尝试使用 -frandom-seed=<somestring>
进行编译,其中 <somestring>
是您构建的唯一字符串(例如内容的哈希值您正在编译的源文件应该这样做)。
或者您可以尝试在没有调试信息的情况下进行编译(例如,没有 -ggdb
等标志)或稍后使用一些剥离工具删除调试信息部分。
另见
- https://wiki.debian.org/ReproducibleBuilds/About
- How to produce deterministic binary output with g++? - Stack Overflow
我的编译命令是
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 库;这可能是原因吗?
更新
我查看了正在生成的程序集文件,它们是不同的。结果 .obj
s 会有所不同是有道理的。
仍然不明白为什么会这样。
更新
上面的 qcc
命令不是实际执行的编译器命令:qcc
是编译器 "redirector",因为它将调用与 -V
参数匹配的编译器。
"real" 编译器调用是这样的:
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[=37=]0"
.LASF10304:
.ascii "cAlpha0[=37=]0"
.LASF10222:
.ascii "usWeek[=37=]0"
.LASF14117:
.ascii "_ZN5boost10shared_ptrI27TnRespTravelEstimationEvent"
.ascii "EaSERKS2_[=37=]0"
...
它持续了数百个字节。
现在我仔细检查我的beyond compare,所有的差异部分都是由于boost::detail::variant::make_initializer_node
。那个提升函数每次生成不同的代码吗?
分辨率
原来这是一个 gcc
错误。我用 -O<X> -ggdb<Y>
的所有排列编译了我的 .cpp
,对于 Y>=2,汇编文件 .s
和对象 .obj
是不确定的。
我发现 a gcc bug 描述了这个问题。
我不得不为 删除另一个 post。 . .原因。
non-determinism
的原因通常的罪魁祸首是宏 __DATE__
、__TIME__
、__TIMESTAMP__
,编译器将其扩展为根据系统时间计算的值。
一种可能是为二进制文件生成的调试信息是以non-deterministic 方式编写的。例如,当编译器进程中调试信息的 in-memory 布局不确定时,就会发生这种情况。我不知道 GCC 的内部结构。但我想这样的事情会在
时发生- 在调试信息输出中使用random GUIDs,
- 使用mangling of symbols in anonymous namespaces,或
- 按顺序序列化一个散列图,其中键是指向内存的指针。
non-determinism 的后一个来源通常被认为是编译器中的错误(例如 GCC PR65015)
缓解
为了强制 __DATE__
、__TIME__
和 __TIMESTAMP__
宏的可重复扩展,必须模拟和伪造系统时间(例如通过使用 libfaketime/faketime)以编译器。 GCC 的 -Wdate-time
command-line 选项可用于在使用这些预定义宏时发出警告。
要强制 GUID 和重整可重现 "randomness",您可以尝试使用 -frandom-seed=<somestring>
进行编译,其中 <somestring>
是您构建的唯一字符串(例如内容的哈希值您正在编译的源文件应该这样做)。
或者您可以尝试在没有调试信息的情况下进行编译(例如,没有 -ggdb
等标志)或稍后使用一些剥离工具删除调试信息部分。
另见
- https://wiki.debian.org/ReproducibleBuilds/About
- How to produce deterministic binary output with g++? - Stack Overflow