我正在寻找一些关于如何调试我无法简化为最小示例的重大问题的建议。
问题:我编译链接到许多不同库的应用程序。这些标志包括:
-static-libstdc++ -static-libgcc -pipe -std=c++1z -fno-PIC -flto=10 -m64 -O3 -flto=10 -fuse-linker-plugin -fuse-ld=gold -UNDEBUG -lrt -ldl
编译器是 gcc-7.3.0,针对 binutils-2.30 编译。 Boost 使用与程序其余部分相同的标志进行编译,并进行静态链接。
链接程序时,我收到各种关于重定位引用丢弃部分的警告,无论是在我自己的代码中还是在 boost 中。 例如:
/tmp/ccq2Ddku.ltrans13.ltrans.o:<artificial>:function boost::system::(anonymous namespace)::generic_error_category::message(int) const: warning: relocation refers to discarded section
然后当我运行程序时,它在销毁时出现段错误,回溯:
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x00007ffff7345a49 in __run_exit_handlers () from /lib64/libc.so.6
#2 0x00007ffff7345a95 in exit () from /lib64/libc.so.6
#3 0x00007ffff732eb3c in __libc_start_main () from /lib64/libc.so.6
#4 0x000000000049b3e3 in _start ()
试图调用的函数指针是0x0。
如果我删除 using static-libstdc++,链接器警告和运行时段错误就会消失。
如果我从 c++1z 更改为 c++14,链接器警告和运行时段错误就会消失。
如果我删除 -flto,链接器警告和运行时段错误就会消失。
如果我将“-g”添加到编译标志,链接器警告和运行时段错误就会消失。
我曾尝试通过指定 -Wl,--debug=all 要求 gold 进行额外的调试,但它告诉我似乎没有任何相关信息。
如果我尝试使用一小部分看起来相关的代码,单独编译并链接到相同的 boost 库(即尝试生成最小示例),则没有链接器警告,并且程序运行到顺利完成。
帮助!我该怎么做才能缩小问题范围?
最佳答案
此警告通常表示两个编译单元之间 COMDAT 组的内容不一致。如果编译器在一个编译单元中发出带有符号 A 的 COMDAT 组 G,但在第二个编译单元中发出带有符号 A 和 B 的相同组 G,则链接器将保留第一个编译单元中的组 G 并丢弃组 G从第二个。从第二个编译单元中的组外部对符号 B 的任何引用都会产生此错误。
原因通常是编译器中的错误,使用 -flto 会使诊断变得更加困难。在这种情况下,您的第二个编译单元是链接时优化的结果(*.ltrans.o 文件名)。使用 LTO,可以相信您提到的许多更改将使问题消失。
binutils git repo 的 master 分支上的 gold 的最新版本有一个新的 [-Wl,]--debug=plugin
选项,它将保存日志和所有临时 . ltrans.o 文件。拥有日志和那些文件,以及所有原始输入文件(您可以通过添加 [-Wl,]-t
选项获得列表)应该有助于更好地隔离问题。
最新版的gold也会打印搬迁引用的symbol。对于本地符号,它会显示符号索引;使用 readelf -s
获取有关符号的更多信息。对于全局符号,它将显示名称;您可以为确切名称添加 --no-demangle
选项。
如果是局部符号,几乎可以肯定是编译器的问题。严格禁止从 comdat 组外部引用组中的本地符号。
如果它是全局符号,则可能是编译器问题或源代码中的单一定义规则 (ODR) 违规。您需要在命名目标文件中识别 comdat 组,找到它的关键符号,然后找到提供链接器保存的定义的目标文件(-y 选项会有所帮助),并比较这些组中定义的符号由两个对象。这些步骤应该有所帮助:
(1) 从报错信息说起:
b.o(.data+0x0): warning: relocation refers to symbol "two" defined in discarded section
(2) 在 b.o 中寻找符号“二”:
$ readelf -sW b.o | grep two
7: 0000000000000008 0 NOTYPE WEAK DEFAULT 6 two
倒数第二个字段(“6”)是定义“二”的节号。
(3) 验证第 6 节实际上是一个 comdat 组:
$ readelf -SW b.o
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 6] .one PROGBITS 0000000000000000 000058 000018 00 WAG 0 0 1
sh_flags 字段(“Flg”)中的“G”表示该部分属于一个 comdat 组。
(4) 查找包含该部分的 comdat 组:
$ readelf -g b.o
COMDAT group section [ 1] `.group' [one] contains 1 sections:
[Index] Name
[ 6] .one
这向我们表明第 6 节是组第 1 节的成员。
(5) 找到该组的关键符号:
$ readelf -SW b.o
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 1] .group GROUP 0000000000000000 000040 000008 04 7 8 4
sh_info 字段(“Inf”)告诉我们关键符号是符号 #8,即“一”。 (这应该与步骤 4 中括号中显示的名称匹配。)
$ readelf -sW b.o
Num: Value Size Type Bind Vis Ndx Name
8: 0000000000000000 0 NOTYPE WEAK DEFAULT 6 one
(6) 现在您可以将 -y one
选项添加到您的链接以查找哪些对象提供了“one”的定义:
$ gcc -Wl,-y,one ...
a.o: definition of one
b.o: definition of one
列出的第一个 (a.o) 是 gold 保留的;它将丢弃所有具有相同 key 符号的后续 comdat 组。
如果您使用相同的技术检查在 a.o 中定义“one”的 comdat 组,并将属于该组的符号与属于 b.o 中的组的符号进行比较,这应该会给您更多线索。
关于c++17,lto,-static-libstdc++ 问题 : Warning: relocation refers to discarded section with ld. gold,然后在 __run_exit_handlers 中出现段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49620868/