我正在考虑如何调试一个我无法简化的重大问题的建议。
问题:我编译了链接到许多不同库的应用程序。标志包括:
-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库(即尝试生成最少示例),则没有链接器警告,并且程序运行到完成没有问题。
帮助!我该怎么做才能缩小问题范围?
答案 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中属于该组的符号进行比较,这样可以为您提供更多线索。