c++ - 链接错误:在废弃部分中定义的构造 vtable
问题描述
最初的问题分布在来自不同项目的数十万个 LoC 中。它包含很多成分:内联汇编、虚拟继承、间接级别、不同的编译器和编译器选项。(这就像一部惊悚片。)我很难简化为这个 SSCCE:
// a.hpp
struct A {
int i;
~A() { asm("" : "=r"(i)); }
};
struct B : public virtual A { };
struct C : public B { };
struct D {
D(C);
};
// a.cpp
#include "a.hpp"
void f(C) {
}
D::D(C c) {
f(c);
}
// main.cpp
#include "a.hpp"
int main() {
C c;
D d(c);
}
使用这些命令行构建:
g++ -O3 -fPIC -c a.cpp
clang++ -O3 -fPIC -c main.cpp
clang++ -fuse-ld=gold main.o a.o -o main
链接器输出是:
a.o:a.cpp:function D::D(C) [clone .cold]: error: relocation refers to global symbol "construction vtable for B-in-C", which is defined in a discarded section
section group signature: "_ZTV1C"
prevailing definition is from main.o
clang-10: error: linker command failed with exit code 1 (use -v to see invocation)
我相信 gcc、clang 或 gold 都存在错误。我的问题是它在哪里?(我想这是黄金,但我想在报告错误之前确定。)
FWIW:正如我所说,所有的成分都很重要,例如,如果asm
删除了,问题就会消失。使问题消失的更显着的变化是:
- 对所有 TU 使用相同的编译器,(无论是 g++ 还是 clang++。)
- 与 ld 链接(即删除
-fuse-ld=gold
) - 编译
main.cpp
不带-O3
. - 编译
main.cpp
不带-fPIC
. - 在链接器命令行中交换
a.o
和。main.o
解决方案
这似乎是 GCC 中的一个错误,但在 ABI 之外的内联 asm 可能只会导致 GCC 和 Clang 之间不幸的不兼容。
问题是内联汇编使 GCC 认为它~A::A()
可以引发异常,因此它在 中创建异常处理路径D::D()
,这需要为 B-in-C 构建 vtable,它放置在还包含 vtable 的 COMDAT 组中对于 C ( _ZV1C
)。
由于 Clang 不会在_ZV1C
COMDAT 组中生成构造 vtable,但 GCC 会生成,因此您最终会遇到链接器可能会保留 Clang 生成的 COMDAT 组并丢弃 GCC 生成的具有构造 vtable 的版本的情况。如果您链接期望额外符号定义的 GCC 生成的代码,则会收到此错误。
在你的链接中反转 main.o 和 ao 也可以解决这个问题,因为所有三个链接器都会阻止 ao 的 COMDAT 组,它是第一个看到的。
这是 GCC 从 ao 为 D::D() 生成的代码:
0000000000000002 <_ZN1DC1E1C>:
2: 48 83 ec 18 sub $0x18,%rsp
6: 48 8b 06 mov (%rsi),%rax
9: 48 8b 40 e8 mov -0x18(%rax),%rax
d: 8b 04 06 mov (%rsi,%rax,1),%eax
10: 89 44 24 08 mov %eax,0x8(%rsp)
14: 48 8b 05 00 00 00 00 mov 0x0(%rip),%rax
17: R_X86_64_REX_GOTPCRELX _ZTV1C-0x4
1b: 48 8d 40 18 lea 0x18(%rax),%rax
1f: 48 89 04 24 mov %rax,(%rsp)
23: 48 89 e7 mov %rsp,%rdi
26: e8 00 00 00 00 callq 2b <_ZN1DC1E1C+0x29>
27: R_X86_64_PLT32 _Z1f1C-0x4
2b: eb 17 jmp 44 <_ZN1DC1E1C+0x42>
2d: 48 89 c7 mov %rax,%rdi
30: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax
33: R_X86_64_PC32 _ZTC1C0_1B+0x14
37: 48 89 04 24 mov %rax,(%rsp)
3b: 89 44 24 08 mov %eax,0x8(%rsp)
3f: e8 00 00 00 00 callq 44
40: R_X86_64_PLT32 _Unwind_Resume-0x4
44: 48 83 c4 18 add $0x18,%rsp
48: c3 retq
从偏移量 0x2d 到callq
at 0x3f 的代码是异常处理路径,在调用f(c)
. 0x30 处的lea
指令引用 B-in-C ( _ZTC1C0_1B
) 的构造 vtable 中的条目。
如果没有该内联汇编,GCC 将生成与 clang 相同的代码,没有异常处理路径,也不需要构造 vtable。
编译--no-exceptions
,问题也消失了。
无论在-O0
、-O1
、-O2
或-O3
.
至少 GCC 在编译 a.cpp 和 main.cpp 时是一致的,因此可以说这种情况根本不包括在 C++ ABI 中,并且 GCC 和 Clang 可以自由地区别对待。我做了一些简单的尝试,用内联 asm 以外的东西进行复制,但不能。
至于为什么你从 gold 中得到一个错误,而不是来自 bfd 或 lld,gold 正在报告可能是一个真正的错误,但在这种特殊情况下,由于永远不会抛出异常,异常处理代码会从未执行过。但是,当您使用 bfd ld 或 lld 链接时,lea
0x30 处的指令不会被重新定位,没有任何警告,并且在调用f()
.
推荐阅读
- c - GStreamer 1.0 在 C 中将原始编码为 H.264
- javascript - 如何从 Javascript 中获取 cookie?
- python - 将文本文件中的值分配给可用于 python 下一步的变量
- java - @exceptionhandler 在 Spring REST 中不起作用
- javascript - 更改拖放时可拖动的颜色和悬停时可拖放区域内的颜色
- html - 在 CSS 中动态获取导航栏的高度
- angular - Gitlab-ci:如何实现 dayli 自动化测试和手动部署
- javascript - 尝试要求未安装但不必要的模块时发出警告
- c++ - 使用队列制作可变大小、延迟的帧缓冲区时出现意外行为
- java - 如何在 HashSet 中保留插入顺序?