ld中的--start-group和--whole-archive有什么区别

xxk*_*kkk 2 linker ld

老实说,我认为这个问题应该很容易通过盯着man ld. 但是,通过阅读联机帮助页并阅读其他人完成的代码,我发现人们可以互换使用它们,或者在他们认为传递给链接器的库的顺序可能存在问题的同时使用它们。

我想知道这两个选项之间有什么区别以及使用它们时的最佳实践是什么。

谢谢!

相关链接:

Mik*_*han 5

在撰写本文时,静态库上Stackoverflow 标签 wiki 告诉我们:

静态库是目标文件的存档。用作链接器输入,链接器提取它需要进行链接的目标文件。

所需的目标文件是那些为链接器提供它发现在其他输入文件中使用但没有定义的符号定义的目标文件。所需的目标文件,而不是其他文件,从存档中提取并输入到链接中,就像它们是链接命令中的单个输入文件一样,并且根本没有提及静态库。

...

链接器通常会支持一个选项(GNU ld:--whole-archive,MS 链接:/WHOLEARCHIVE)来覆盖静态库的默认处理并链接所有包含的目标文件,无论它们是否需要。

除了从中提取的目标文件之外,静态库对链接没有任何贡献,这些文件在不同的链接中可能会有所不同。它与共享库形成对比,共享库是另一种在链接中具有非常不同作用的文件。

这应该说明什么--whole-archive。的范围--whole-archive一直持续到链接器命令行结束或直到--no-whole-archive出现1

默认情况下,链接器只会在链接器输入的命令行序列中出现该库的每个点检查静态库。不会为了解析在以后的输入中发现的符号引用而倒退重新检查静态库。

--start-group ... --end-group对选项改变了默认行为。它指示链接器以该顺序... 反复检查 中提到的静态库,只要这样做会产生新符号引用的任何新解析。--start-group ... --end-group对链接器从.... 它只会提取和链接它需要的目标文件,除非--whole-archive 有效。

总结一下:-

--start-group lib0.a ... libN.a --end-group告诉链接器:继续搜索 lib0.a ... libN.a 您需要的目标文件,直到找不到更多.

--whole-archive lib0.a ... libN.a --no-whole-archive告诉链接器:忘记你需要什么。只需链接所有 lib0.a ... libN.a.

您可以看到,您可以使用的任何链接也--start-group lib0.a ... libN.a --end-group将使用 成功--whole-archive lib0.a ... libN.a --no-whole-archive,因为后者将链接所有必要的目标文件所有不必要的目标文件,而无需费心区分。

但反过来就不对了。这是一个简单的例子:

xc

#include <stdio.h>

void x(void)
{
    puts(__func__);
}
Run Code Online (Sandbox Code Playgroud)

yc

#include <stdio.h>

void y(void)
{
    puts(__func__);
}
Run Code Online (Sandbox Code Playgroud)

主文件

extern void x(void);

int main(void)
{
    x();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译所有源文件:

$ gcc -Wall -c x.c y.c main.c
Run Code Online (Sandbox Code Playgroud)

制作一个静态库,存档x.oy.o

ar rcs libxy.a x.o y.o
Run Code Online (Sandbox Code Playgroud)

尝试把一个程序和链接main.o,并libxy.a 在错误的顺序

$ gcc -o prog libxy.a main.o
main.o: In function `main':
main.c:(.text+0x5): undefined reference to `x'
collect2: error: ld returned 1 exit status
Run Code Online (Sandbox Code Playgroud)

那失败了,因为对xin的引用main.o只是被链接器发现太晚而无法找到xin的定义libxy.a(x.o)。它libxy.a 首先到达并没有找到它需要的目标文件。那时,它还没有将任何目标文件链接到程序中,因此需要解析 0 个符号引用。考虑过libxy.a并发现它没有用,它不再考虑它。

当然,正确的链接是:

$ gcc -o prog main.o libxy.a
Run Code Online (Sandbox Code Playgroud)

但是如果您没有意识到您只是将链接顺序从后到前,您可以通过以下方式获得链接成功--whole-archive

$ gcc -o prog -Wl,--whole-archive libxy.a -Wl,--no-whole-archive main.o
$ ./prog
x
Run Code Online (Sandbox Code Playgroud)

显然,你不能让它成功

$ gcc -o prog -Wl,--start-group libxy.a -Wl,--end-group main.o
main.o: In function `main':
main.c:(.text+0x5): undefined reference to `x'
collect2: error: ld returned 1 exit status
Run Code Online (Sandbox Code Playgroud)

因为这与:

$ gcc -o prog libxy.a main.o
Run Code Online (Sandbox Code Playgroud)

现在这是一个链接的示例,该链接因默认行为而失败,但可以通过--start-group ... --end-group.

交流电

#include <stdio.h>

void a(void)
{
    puts(__func__);
}
Run Code Online (Sandbox Code Playgroud)

公元前

#include <stdio.h>

void b(void)
{
    puts(__func__);
}
Run Code Online (Sandbox Code Playgroud)

ab.c

extern void b(void);

void ab(void)
{
    b();
}
Run Code Online (Sandbox Code Playgroud)

ba.c

extern void a(void);

void ba(void)
{
    a();
}
Run Code Online (Sandbox Code Playgroud)

阿巴

extern void ab(void);
extern void ba(void);

void abba(void)
{
    ab();
    ba();
}
Run Code Online (Sandbox Code Playgroud)

main2.c

extern void abba(void);

int main(void)
{
    abba();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译所有来源:-

$ gcc -Wall a.c b.c ab.c ba.c abba.c main2.c
Run Code Online (Sandbox Code Playgroud)

然后制作以下静态库:

$ ar rcs libbab.a ba.o b.o x.o
$ ar rcs libaba.a ab.o a.o y.o
$ ar rcs libabba.a abba.o
Run Code Online (Sandbox Code Playgroud)

(请注意,那些旧的目标文件x.oy.o再次存档在这里)。

这里,libabba.a依赖于libbab.alibaba.a。具体来说, libabba.a(abba.o)引用ab定义于libaba.a(ab.o); 并且它还引用了ba,它在 中定义libbab.a(ba.o)。所以在链接顺序中,libabba.a必须出现在任一libbab.a和之前libaba.a

并且libbab.a依赖于libaba.a. 具体来说,libbab.a(ba.o)引用a中定义的libaba(a.o)

不过libaba.a依赖libbab.alibaba(ab.o)引用b,在 中定义libbab(b.o)。和之间 存在循环依赖。因此,无论我们将其中的哪一个放在默认链接中,它都会因未定义的引用错误而失败。无论是这样:libbab.alibaba.a

$ gcc -o prog2 main2.o libabba.a libaba.a libbab.a
libbab.a(ba.o): In function `ba':
ba.c:(.text+0x5): undefined reference to `a'
collect2: error: ld returned 1 exit status
Run Code Online (Sandbox Code Playgroud)

或者那样:

$ gcc -o prog2 main2.o libabba.a libbab.a libaba.a
libaba.a(ab.o): In function `ab':
ab.c:(.text+0x5): undefined reference to `b'
collect2: error: ld returned 1 exit status
Run Code Online (Sandbox Code Playgroud)

循环依赖是一个问题,这--start-group ... --end-group是解决方案:

$ gcc -o prog2 main2.o libabba.a -Wl,--start-group libbab.a libaba.a -Wl,--end-group
$ ./prog2
b
a
Run Code Online (Sandbox Code Playgroud)

因此--whole-archive ... --no-whole-archive也是一个解决方案:

$ gcc -o prog2 main2.o libabba.a -Wl,--whole-archive libbab.a libaba.a -Wl,--no-whole-archive
$ ./prog2
b
a
Run Code Online (Sandbox Code Playgroud)

但这是一个糟糕的解决方案。在每种情况下,让我们跟踪哪些目标文件实际链接到程序中。

--start-group ... --end-group

$ gcc -o prog2 main2.o libabba.a -Wl,--start-group libbab.a libaba.a -Wl,--end-group -Wl,--trace
/usr/bin/x86_64-linux-gnu-ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libaba.a)ab.o
(libaba.a)a.o
(libbab.a)b.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
Run Code Online (Sandbox Code Playgroud)

他们是:

main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libaba.a)ab.o
(libaba.a)a.o
(libbab.a)b.o
Run Code Online (Sandbox Code Playgroud)

这正是程序中需要的那些。

--whole-archive ... --no-whole-archive

$ gcc -o prog2 main2.o libabba.a -Wl,--whole-archive libbab.a libaba.a -Wl,--no-whole-archive -Wl,-trace
/usr/bin/x86_64-linux-gnu-ld: mode elf_x86_64
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o
/usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o
main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libbab.a)b.o
(libbab.a)x.o
(libaba.a)ab.o
(libaba.a)a.o
(libaba.a)y.o
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/lib/x86_64-linux-gnu/libc.so.6
(/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
libgcc_s.so.1 (/usr/lib/gcc/x86_64-linux-gnu/7/libgcc_s.so.1)
/usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o
/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
Run Code Online (Sandbox Code Playgroud)

他们是:

main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libbab.a)b.o
(libbab.a)x.o
(libaba.a)ab.o
(libaba.a)a.o
(libaba.a)y.o
Run Code Online (Sandbox Code Playgroud)

和以前一样,加上:

(libbab.a)x.o
(libaba.a)y.o
Run Code Online (Sandbox Code Playgroud)

哪些是死代码(可能还有更多,没有限制)。冗余函数x()y()定义在图像中:

$ nm prog2 | egrep 'T (x|y)'
000000000000067a T x
00000000000006ac T y
Run Code Online (Sandbox Code Playgroud)

带走

  • 如果可以,请使用默认链接,将您的输入按依赖顺序排列。
  • 使用--start-group ... --end-group图书馆之间众志成城循环依赖,当你不能修复库。请注意,联动速度会受到影响。
  • 使用--whole-archive ... --no-whole-archive只有当你真正需要的所有目标文件中的所有静态库链接...。否则,请执行前两件事之一。


[1] 并且请注意,当 GCC 调用链接器时,链接器的命令行实际上比您在 GCC 命令行中显式传递的链接选项长得多,并且会默默地附加样板选项。因此,始终紧贴--whole-archive--no-whole-archive,并密切--start-group--end-group