如何在单个 (.a) 存档中只获取所需的目标文件

How to take only required object files inside a single (.a) archive

只是一个简单的问题,但我在任何地方都找不到答案。将所有目标文件放入存档时,如何指示clang++只取所需的目标文件进行链接,以避免由于存档中不需要的符号而导致未定义的符号错误?

您将无法找到您正在寻找的答案,因为您 想让 linker 做的是它默认做的事情。这是一个演示。 (它在 C 而不是 C++ 中只是为了让我们免于 C++ 名称修改的混淆)。

三个源文件:

alice.c

#include <stdio.h>

void alice(void)
{
    puts("alice");
}

bob.c

#include <stdio.h>

void bob(void)
{
    puts("bob");
}

mary.c

#include <stdio.h>

void mary(void)
{
    puts("mary");
}

编译它们并将目标文件放入存档中:

$ clang -Wall -c alice.c
$ clang -Wall -c bob.c
$ clang -Wall -c mary.c
$ ar rc libabm.a alice.o bob.o mary.o

这里是存档的成员列表:

$ ar -t libabm.a
alice.o
bob.o
mary.o

这里是这些成员的符号table:

$ nm libabm.a

alice.o:
0000000000000000 T alice
                 U puts

bob.o:
0000000000000000 T bob
                 U puts

mary.o:
0000000000000000 T mary
                 U puts

其中 T 表示定义的函数,U 表示未定义的函数。 puts 是 在标准 C 库中定义,默认情况下将 linked。

下面是一个从外部调用alice的程序,因此依赖于 alice.o:

说alice.c

extern void alice(void);

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

还有另一个程序在外部调用 alicebob,因此 依赖于 alice.obob.o.

sayalice_n_bob.c

extern void alice(void);
extern void bob(void);

int main(void)
{
    alice();
    bob();
    return 0;
}

同时编译这两个源代码:

$ clang -Wall -c sayalice.c
$ clang -Wall -c sayalice_n_bob.c

linker 选项 -trace 指示 linker 报告 linked 的目标文件和 DSO。我们将使用 现在 link 使用 sayalice.olibabm.a:

编程 sayalice
$ clang -o sayalice sayalice.o -L. -labm -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crt1.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crti.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/crtbegin.o
sayalice.o
(./libabm.a)alice.o
libgcc_s.so.1 (/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/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/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/libgcc_s.so.1)
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/crtend.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crtn.o

我们看到所有样板 C 库和运行时都是 linked。和的 我们 创建的目标文件,只有两个是 linked:

sayalice.o
(./libabm.a)alice.o

我们程序libabm.a的两个成员依赖于:

(./libabm.a)bob.o
(./libabm.a)mary.o

不是 linked.

运行程序:

$ ./sayalice
alice

它说 "alice"。

然后为了比较,我们将 link 编程 sayalice_n_bob,再次与 -trace:

$ clang -o sayalice_n_bob sayalice_n_bob.o -L. -labm -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crt1.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crti.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/crtbegin.o
sayalice_n_bob.o
(./libabm.a)alice.o
(./libabm.a)bob.o
libgcc_s.so.1 (/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/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/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/libgcc_s.so.1)
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/crtend.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crtn.o

这一次,我们的三个目标文件被 link编辑:

sayalice_n_bob.o
(./libabm.a)alice.o
(./libabm.a)bob.o

并且 libabm.a 中唯一一个程序 依赖的成员:

(./libabm.a)mary.o

未 link 编辑。

这个程序运行如下:

$ ./sayalice_n_bob
alice
bob

这里是程序的全局符号table:

$ nm -g sayalice_n_bob
0000000000400520 T alice
0000000000400540 T bob
0000000000601030 B __bss_start
0000000000601020 D __data_start
0000000000601020 W data_start
0000000000601028 D __dso_handle
0000000000601030 D _edata
0000000000601038 B _end
00000000004005d4 T _fini
                 w __gmon_start__
00000000004003d0 T _init
00000000004005e0 R _IO_stdin_used
00000000004005d0 T __libc_csu_fini
0000000000400560 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
00000000004004f0 T main
                 U puts@@GLIBC_2.2.5
0000000000400410 T _start
0000000000601030 D __TMC_END__

alicebob,但没有 mary

因此,如您所见,linker 的默认行为就是您要问的行为 要得到。 停止 linker 仅提取存档成员 在 linkage 中引用而不是 link all 存档成员,你必须 通过将存档放在 --whole-archive 的范围内,明确告诉它这样做 linkage 命令行中的选项:

$ clang -o sayalice_n_bob sayalice_n_bob.o -L. -Wl,--whole-archive -labm -Wl,--no-whole-archive -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crt1.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crti.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/crtbegin.o
sayalice_n_bob.o
(./libabm.a)alice.o
(./libabm.a)bob.o
(./libabm.a)mary.o
libgcc_s.so.1 (/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/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/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/libgcc_s.so.1)
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/crtend.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crtn.o

您会看到所有存档成员都是 linked:

(./libabm.a)alice.o
(./libabm.a)bob.o
(./libabm.a)mary.o

程序现在定义了所有 alicebobmary:

$ nm -g sayalice_n_bob
0000000000400520 T alice
0000000000400540 T bob
0000000000601030 B __bss_start
0000000000601020 D __data_start
0000000000601020 W data_start
0000000000601028 D __dso_handle
0000000000601030 D _edata
0000000000601038 B _end
00000000004005f4 T _fini
                 w __gmon_start__
00000000004003d0 T _init
0000000000400600 R _IO_stdin_used
00000000004005f0 T __libc_csu_fini
0000000000400580 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
00000000004004f0 T main
0000000000400560 T mary
                 U puts@@GLIBC_2.2.5
0000000000400410 T _start
0000000000601030 D __TMC_END__

尽管它从不调用 mary

退一步

你问这个问题是因为你相信如果你可以 link 从档案 仅那些定义已在 linkage 中引用的符号的目标文件 那么 linkage 不会因未定义的符号引用而失败 从不使用。但那不是真的,这里有一个证明它不是。

另一个源文件:

alice2.c

#include <stdio.h>

extern void david(void);

void alice(void)
{
    puts("alice");
}

void dave(void)
{
    david();
}

编译:

$ clang -Wall -c alice2.c

alice.o替换为libabm.a中的alice2.o:

$ ar d libabm.a alice.o
$ ar r libabm.a alice2.o

然后像以前一样尝试link编程sayalice

$ clang -o sayalice sayalice.o -L. -labm -Wl,-trace
/usr/bin/ld: mode elf_x86_64
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crt1.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crti.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/crtbegin.o
sayalice.o
(./libabm.a)alice2.o
libgcc_s.so.1 (/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/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/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/libgcc_s.so.1)
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/crtend.o
/usr/bin/../lib/gcc/x86_64-linux-gnu/7.2.0/../../../x86_64-linux-gnu/crtn.o
./libabm.a(alice2.o): In function `dave':
alice2.c:(.text+0x25): undefined reference to `david'
/usr/bin/ld: link errors found, deleting executable `sayalice'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

这一次,唯一获得 linked 的存档成员是:

(./libabm.a)alice2.o

因为在sayalice.o中只调用了alice。然而 linkage 失败了 对函数 david 的未定义引用,程序从不调用 david 仅在函数 dave 的定义中被调用,而 dave 从未被调用。

虽然 dave 从未被调用,但它的定义是 linked 因为它位于一个 目标文件 alice2.o,即 linked 以提供函数定义 alice - 调用。并且根据 linkage 中 dave 的定义,对 david 的调用 成为未解析的引用,默认情况下 linkage 必须找到一个 定义,否则失败。所以它失败了。

然后你会看到 linkage 的失败是由于对 a 的未定义引用 程序从不使用的符号与 linker 的事实一致 link 未引用存档中的目标文件。

如何在不使用的符号的未定义引用中幸存下来

如果您遇到这种link年龄故障,您可以通过将 linker 容忍未定义的引用。您可以直接将其指向 ignore 所有未定义的引用,例如:

$ clang -o sayalice sayalice.o -L. -labm -Wl,--unresolved-symbols=ignore-all
$ ./sayalice
alice

或者更谨慎地,您可以将其指向只是发出警告,而不是失败,因为 未定义的引用,例如:

$ clang -o sayalice sayalice.o -L. -labm -Wl,--warn-unresolved-symbols
./libabm.a(alice2.o): In function `dave':
alice2.c:(.text+0x25): warning: undefined reference to `david'
$ ./sayalice
alice

这样,您可以在诊断中检查唯一未定义的符号是 你期待的那些。