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

c++17, lto, -static-libstdc++ issue: Warning: relocation refers to discarded section with ld.gold, then segfault in __run_exit_handlers

我正在寻求一些关于如何调试我无法简化为最小示例的重大问题的建议。

问题:我将 link 的应用程序编译到许多不同的库中。这些标志包括: -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 使用与程序其余部分相同的标志进行编译,并且 link 是静态编译的。

当程序被 linked 时,我在我自己的代码和 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++,linker 警告和 运行time segfault 就会消失。

如果我从 c++1z 更改为 c++14,linker 警告和 运行time segfault 就会消失。

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

如果我将“-g”添加到编译标志,link错误警告和运行时间段错误就会消失。

我曾尝试通过指定 -Wl,--debug=all 要求 gold 进行额外调试,但它告诉我似乎没有任何相关信息。

如果我尝试使用一小部分看起来相关的代码,编译并 link 它单独但对相同的 boost 库(即尝试生成最小示例),没有 linker 警告,程序 运行 顺利完成。

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

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

原因通常是编译器中的错误,使用 -flto 会使诊断变得更加困难。在这种情况下,您的第二个编译单元是 link-time 优化的结果(*.ltrans.o 文件名)。使用 LTO,相信您提到的许多更改将使问题消失。

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

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

如果是局部符号,几乎可以肯定是编译器的问题。严格禁止从 comdat 组外部引用组中的本地符号。

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

(1) 从报错信息开始:​​

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

(2) 在b.o中寻找符号"two":

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

next-to-last 字段(“6”)是定义 "two" 的节号。

(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,即 "one"。 (这应该与步骤 4 中括号中显示的名称匹配。)

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

(6) 现在您可以将 -y one 选项添加到 link 以查找哪些对象提供了 "one":

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

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

如果您使用相同的技术检查在 a.o 中定义 "one" 的 comdat 组,并将属于该组的符号与属于 [=71 中的组的符号进行比较=], 那应该会给你更多线索。