c ++ 17,lto,-static-libstdc ++问题:警告:重定位是指带有ld.gold的丢弃部分,然后是__run_exit_handlers中的segfault

时间:2018-04-03 01:20:13

标签: c++ linker-errors c++17 gold-linker

我正在考虑如何调试一个我无法简化的重大问题的建议。

问题:我编译了链接到许多不同库的应用程序。标志包括: -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。

如果我使用static-libstdc ++删除,链接器警告和运行时段错误就会消失。

如果我从c ++ 1z更改为c ++ 14,链接器警告和运行时段错误就会消失。

如果我删除-flto,链接器警告和运行时段错误就会消失。

如果我在编译标志中添加“-g”,链接器警告和运行时段错误就会消失。

我已经尝试通过指定-Wl​​, - debug = all来要求黄金进行额外调试,但它告诉我看似没什么相关性。

如果我尝试使用看似相关的代码的一小部分,将其单独编译并链接到相同的boost库(即尝试生成最少示例),则没有链接器警告,并且程序运行到完成没有问题。

帮助!我该怎么做才能缩小问题范围?

1 个答案:

答案 0 :(得分:6)

此警告通常表示两个编译单元之间COMDAT组内容不一致。如果编译器发出一个COMDAT组G,其中符号A在一个编译单元中定义,但是发出与第二个编译单元中定义的符号A和B相同的组G,则链接器将保留第一个编译单元中的G组并丢弃组G从第二个。在第二个编译单元中从组外部引用符号B将产生此错误。

原因通常是编译器中的错误,使用-flto会使诊断更加困难。在这种情况下,您的第二个编译单元是链接时优化的结果(* .ltrans.o文件名)。有了LTO,你提到的很多改变都会让问题消失,这是相当可信的。

binutils git repo主分支上的最新版本的gold有一个新的[-Wl,]--debug=plugin选项,它将保存日志和所有临时的.ltrans.o文件。拥有日志和这些文件以及所有原始输入文件(您可以通过添加[-Wl,]-t选项获得列表)应该有助于更好地隔离问题。

最新版本的gold还将打印重定位引用的符号。对于本地符号,它将显示符号索引;使用readelf -s获取有关该符号的更多信息。对于全球符号,它将显示名称;您可以为确切名称添加--no-demangle选项。

如果它是一个本地符号,问题几乎肯定是编译器。严禁将comdat组外部引用到组中的本地符号。

如果它是全局符号,则可能是源代码中的编译器问题或单定义规则(ODR)违规。您需要在命名对象文件中标识comdat组,找到其关键符号,然后找到提供链接器保留定义的对象文件(-y选项将有帮助),并比较在这两组由两个对象组成。这些步骤应该有所帮助:

(1)从错误消息开始:

b.o(.data+0x0): warning: relocation refers to symbol "two" defined in discarded section

(2)寻找符号&#34;两个&#34;在b.o:

$ readelf -sW b.o | grep two
     7: 0000000000000008     0 NOTYPE  WEAK   DEFAULT    6 two

倒数第二个字段(&#34; 6&#34;)是部分编号,其中&#34; 2&#34;是定义。

(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

&#34; G&#34;在sh_flags字段中(&#34; Flg&#34;)表示该部分属于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字段(&#34; Inf&#34;)告诉我们键符号是符号#8,它是&#34;一个&#34;。 (这应与步骤4中括号中显示的名称相匹配。)

$ readelf -sW b.o
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     8: 0000000000000000     0 NOTYPE  WEAK   DEFAULT    6 one

(6)现在,您可以在链接中添加-y one选项,以查找哪些对象提供了&#34;一个&#34;的定义:

$ gcc -Wl,-y,one ...
a.o: definition of one
b.o: definition of one

列出的第一个(a.o)是黄金保存的那个;它将丢弃具有相同键符号的所有后续comdat组。

如果您使用相同的技术来检查定义&#34; one&#34;的comdat组。在a.o中,将属于该组的符号与b.o中属于该组的符号进行比较,这样可以为您提供更多线索。