弱链接与使用 COMDAT 部分有什么区别?

What's the difference between weak linkage vs using a COMDAT section?

本文关键字:什么 区别 COMDAT 链接      更新时间:2023-10-16

假设我在不同的对象文件中定义了对象的多个定义,我想将它们链接在一起。将这些对象文件链接在一起时,似乎有两种方法可以选择使用哪种定义:将符号标记为弱符号,或将其放在其自己的 COMDAT 部分中(在每个目标文件中(。

我的理解方式(这可能是错误的(,弱链接允许通过一个符号的定义覆盖另一个符号的定义,这取决于链接顺序。COMDAT 允许多个定义,方法是将符号放置在唯一的 COMDAT 部分中,并选择链接时使用哪个部分。

有人告诉我,这些是彼此正交的概念,但不确定为什么人们会同时使用两者而不是其中之一。举一个更具体的例子,我相信 c++ 中没有键函数的虚拟表在它们使用的所有模块中都定义了,并且这些模块可以链接在一起,因为 vtable 本身被声明为弱表,并且在它自己的 COMDA 中。

但不确定为什么会同时使用两者而不是其中之一。

当函数存在不同的(即不等效的(可能实现时,最常使用弱链接。

例如,libc.a可以提供pthread_mutex_lock定义,该定义不执行任何操作,而libpthread.a将提供实际锁定互斥锁的同一函数的定义。

用户代码(main.o(可以盲目调用pthread_mutex_lock,正确的事情会发生,这取决于libpthread.a是否被链接。

如果链接器可以自由选择任一pthread_mutex_lock,这显然不起作用(如果它们都是 COMDAT 符号,就会发生这种情况(。

comdat 和弱符号之间的区别,来自 GCC 模糊链接文档:

这些构造的重复副本将在链接时被丢弃。这称为 COMDAT 支持。

在不支持 COMDAT 但支持弱符号的目标上,GCC 使用它们。这样,一个副本将覆盖所有其他副本,但未使用的副本将覆盖 副本仍占用可执行文件中的空间。

所以它们的行为是一样的,但 comdat 方法不会使用不必要的空间。上面的文档还描述了编译器何时发出这些(对于内联函数、VTable、模板实例化等(。

编译器(g++ 和 clang(将同时发出一个 comdat 部分和一个弱符号(comdat 部分就足够了,但我想它会同时发出两者,以防链接器不支持 comdat 部分(:

$ cat tt1.cpp 
struct s1 {
   virtual void f1() {}
};    
s1 s1;
$ g++ -S tt1.cpp
$ cat tt1.s | c++filt 
.section .text. ... ,s1::f1(),comdat
.weak s1::f1()
...
.section .data. ... ,vtable for s1,comdat
.weak vtable for s1
...

请注意,在生成的程序集中,comdat 被定义为 .section ... comdat 而不是 .comdat 。这显然导致objdumpreadelf不承认它,他们只将它们显示为弱符号:

$ g++ -c tt1.cpp && readelf -s tt1.o | c++filt 
...
    17: 0000000000000000    11 FUNC    WEAK   DEFAULT    8 s1::f1()
    19: 0000000000000000    24 OBJECT  WEAK   DEFAULT   11 vtable for s1
...

我们可以通过这个例子来验证弱符号和通信部分的行为:

$ cat aa.s
    .data
    .globl  var1
    .type   var1, @object
var1:
    .byte   0xb
$ cat aa2.s
    .data
    .globl  var1
    .type   var1, @object
var1:
    .byte   0xc
$ cat aa3.c
#include <stdio.h>
extern char var1;
int main() {
    printf("var1 = %0x%xn", var1);
}
$ as aa.s -o aa.o && as aa2.s -o aa2.o && gcc aa3.c aa2.o aa.o
    aa.o:(.data+0x0): multiple definition of `var1'
    aa2.o:(.data+0x0): first defined here
# symbols are neither weak nor in comdat,
# as expected we got multiple definition error
$ cat aa.s
    .data
    .globl  var1
    .weak   var1
    .type   var1, @object
var1:
    .byte   0xb
$ cat aa2.s
    .data
    .globl  var1
    .weak   var1
    .type   var1, @object
var1:
    .byte   0xc
$ as aa.s -o aa.o && as aa2.s -o aa2.o && gcc aa3.c aa2.o aa.o && ./a.out
var1 = 0xc
# change order of aa.o and aa2.o
$ gcc aa3.c aa.o aa2.o && ./a.out
var1 = 0xb
# only weak symbols, it worked
$ objdump -D ./a.out
...
0000000000201010 <var1>:
  201010:   0b                      .byte 0xb
  201011:   0c                      .byte 0xc
# both values are emitted, only the fist is used
$ cat aa.s
    .data
    .section    .data.var1,"aG",@progbits,var1,comdat
    .globl  var1
    .type   var1, @object
var1:
    .byte   0xb
$ cat aa2.s
    .data
    .section    .data.var1,"aG",@progbits,var1,comdat
    .globl  var1
    .type   var1, @object
var1:
    .byte   0xc
$ as aa.s -o aa.o && as aa2.s -o aa2.o && gcc aa3.c aa2.o aa.o && ./a.out
var1 = 0xc
# change order
$ as aa.s -o aa.o && as aa2.s -o aa2.o && gcc aa3.c aa.o aa2.o && ./a.out
var1 = 0xb
# only comdat, it worked
$ objdump -D ./a.out
0000000000201010 <var1>:
  201010:   0b                      .byte 0xb
# only a single value is emitted