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

时间:2018-09-21 02:16:35

标签: linker ld

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

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

谢谢!

相关链接:

1 个答案:

答案 0 :(得分:1)

在撰写本文时,Stackoverflow tag wiki on static libraries 告诉我们:

  

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

     

所需的目标文件是为链接器提供其找到的符号的定义的文件,这些符号在没有其他定义的情况下用于其他输入文件。   从归档文件中提取所需的目标文件(没有其他文件),并将其输入到链接中,就像它们是文件中的单独输入文件一样。   链接命令和静态库都没有提及。

     

...

     

链接程序通常会支持一个选项(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告诉链接器:忘记您需要的内容。只需链接所有{em> lib0.a ... libN.a中的所有目标文件即可。

然后您会看到,--start-group lib0.a ... libN.a --end-group可以成功实现的任何链接也将--whole-archive lib0.a ... libN.a --no-whole-archive成功地实现, 因为后者会链接所有必需的对象文件所有不必要的对象文件,而不必费心告诉它们之间的区别。

但事实并非如此。这是一个简单的示例:

x.c

#include <stdio.h>

void x(void)
{
    puts(__func__);
}

y.c

#include <stdio.h>

void y(void)
{
    puts(__func__);
}

main.c

extern void x(void);

int main(void)
{
    x();
    return 0;
}

编译所有源文件:

$ gcc -Wall -c x.c y.c main.c

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

ar rcs libxy.a x.o y.o

尝试以错误的顺序将程序与main.olibxy.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

失败是因为仅发现了对xmain.o的引用 链接器来不及在x中找到libxy.a(x.o)的定义。已达到libxy.a first ,没有发现所需的目标文件。当时,它尚未链接 程序中根本没有任何目标文件,因此需要解析0个符号引用。有 考虑了libxy.a并认为没有用,因此不再考虑。

正确的链接当然是:

$ gcc -o prog main.o libxy.a

但是,如果您没有意识到只是简单地将链接顺序从前到后,则可以 通过--whole-archive获得成功的链接:

$ gcc -o prog -Wl,--whole-archive libxy.a -Wl,--no-whole-archive main.o
$ ./prog
x

很显然,您无法获得成功

$ 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

因为这与以下内容没有什么不同:

$ gcc -o prog libxy.a main.o

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

交流

#include <stdio.h>

void a(void)
{
    puts(__func__);
}

b.c

#include <stdio.h>

void b(void)
{
    puts(__func__);
}

ab.c

extern void b(void);

void ab(void)
{
    b();
}

ba.c

extern void a(void);

void ba(void)
{
    a();
}

abba.c

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

void abba(void)
{
    ab();
    ba();
}

main2.c

extern void abba(void);

int main(void)
{
    abba();
    return 0;
}

编译所有源:-

$ gcc -Wall a.c b.c ab.c ba.c abba.c main2.c

然后创建以下静态库:

$ 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

(请注意,那些旧的目标文件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.alibaba.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

或者那样:

$ 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

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

$ gcc -o prog2 main2.o libabba.a -Wl,--start-group libbab.a libaba.a -Wl,--end-group
$ ./prog2
b
a

因此,--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

但这是一个解决方案。让我们追踪一下实际链接了哪些目标文件 在每种情况下都进入程序。

使用--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

它们是:

main2.o
(libabba.a)abba.o
(libbab.a)ba.o
(libaba.a)ab.o
(libaba.a)a.o
(libbab.a)b.o

正是程序中所需的

使用--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

它们是:

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

与以前相同,另外:

(libbab.a)x.o
(libaba.a)y.o

是无效代码(可能会有更多,没有限制)。镜像中定义了冗余功能x()y()

$ nm prog2 | egrep 'T (x|y)'
000000000000067a T x
00000000000006ac T y

外卖

  • 使用默认链接,如果可以的话,将输入置于依从顺序。
  • 使用--start-group ... --end-group克服之间的循环依赖 库,当您无法修复库时。请注意,链接速度会受到影响。
  • 仅当您实际需要进行链接时,才使用--whole-archive ... --no-whole-archive ...中所有静态库中的所有目标文件。否则,请执行以下一项 前两件事。


[1]请注意,调用链接器时,链接器的命令行 实际上,它比您显式传递的链接选项要长得多 GCC命令行,并附加了样板选项。因此 始终用--whole-archive关闭--no-whole-archive,然后关闭--start-group--end-group