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

use*_*570 3 c c++ linker unix-ar llvm-clang

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

\n

Mik*_*han 9

您将无法找到您正在寻找的答案,因为您想让链接器执行的操作就是它默认执行的操作。这是一个演示。(它是用 C 而不是 C++ 编写的,只是为了让我们免受 C++ 名称修改的混淆)。

三个源文件:

爱丽丝.c

#include <stdio.h>

void alice(void)
{
    puts("alice");
}
Run Code Online (Sandbox Code Playgroud)

鲍勃.c

#include <stdio.h>

void bob(void)
{
    puts("bob");
}
Run Code Online (Sandbox Code Playgroud)

玛丽.c

#include <stdio.h>

void mary(void)
{
    puts("mary");
}
Run Code Online (Sandbox Code Playgroud)

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

$ 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
Run Code Online (Sandbox Code Playgroud)

以下是档案馆的成员名单:

$ ar -t libabm.a
alice.o
bob.o
mary.o
Run Code Online (Sandbox Code Playgroud)

以下是这些成员的符号表:

$ nm libabm.a

alice.o:
0000000000000000 T alice
                 U puts

bob.o:
0000000000000000 T bob
                 U puts

mary.o:
0000000000000000 T mary
                 U puts
Run Code Online (Sandbox Code Playgroud)

其中T表示已定义的函数和U未定义的函数。puts在标准C库中定义,默认会链接。

现在这是一个外部调用的程序alice,因此依赖于 alice.o

萨亚利斯.c

extern void alice(void);

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

这是另一个在外部调用aliceand的程序bob,因此依赖于alice.oand bob.o

sayalice_n_bob.c

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

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

也编译这两个源:

$ clang -Wall -c sayalice.c
$ clang -Wall -c sayalice_n_bob.c
Run Code Online (Sandbox Code Playgroud)

链接器选项-trace 指示链接器报告链接的目标文件和 DSO。我们现在将使用它来sayalice使用sayalice.o和来链接程序libabm.a

$ 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
Run Code Online (Sandbox Code Playgroud)

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

sayalice.o
(./libabm.a)alice.o
Run Code Online (Sandbox Code Playgroud)

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

(./libabm.a)bob.o
(./libabm.a)mary.o
Run Code Online (Sandbox Code Playgroud)

没有链接

运行程序:

$ ./sayalice
alice
Run Code Online (Sandbox Code Playgroud)

它说“爱丽丝”。

然后为了进行比较,我们将sayalice_n_bob再次链接 program -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
Run Code Online (Sandbox Code Playgroud)

这次,我们链接了三个目标文件:

sayalice_n_bob.o
(./libabm.a)alice.o
(./libabm.a)bob.o
Run Code Online (Sandbox Code Playgroud)

libabm.a该程序唯一依赖的成员是:

(./libabm.a)mary.o
Run Code Online (Sandbox Code Playgroud)

没有链接。

该程序运行如下:

$ ./sayalice_n_bob
alice
bob
Run Code Online (Sandbox Code Playgroud)

这是程序的全局符号表:

$ 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__
Run Code Online (Sandbox Code Playgroud)

alicebob,但不是mary

正如您所看到的,链接器的默认行为就是您询问如何获取的行为。要阻止链接器仅提取链接中引用的存档成员并链接所有--whole-archive存档成员,您必须通过将存档放置在 链接命令行中的选项范围内来明确告诉它这样做:

$ 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
Run Code Online (Sandbox Code Playgroud)

在那里您可以看到所有存档成员都已链接:

(./libabm.a)alice.o
(./libabm.a)bob.o
(./libabm.a)mary.o
Run Code Online (Sandbox Code Playgroud)

该程序现在定义了所有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__
Run Code Online (Sandbox Code Playgroud)

尽管它从不调用 mary

并退一步

您提出这个问题是因为您相信,如果您可以从存档中仅链接那些定义已在链接中引用的符号的对象文件,则链接不会因对程序从未使用的符号的未定义引用而失败。但事实并非如此,这里有一个证明事实并非如此。

另一个源文件:

爱丽丝2.c

#include <stdio.h>

extern void david(void);

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

void dave(void)
{
    david();
}
Run Code Online (Sandbox Code Playgroud)

编译一下:

$ clang -Wall -c alice2.c
Run Code Online (Sandbox Code Playgroud)

替换alice.o为:alice2.olibabm.a

$ ar d libabm.a alice.o
$ ar r libabm.a alice2.o
Run Code Online (Sandbox Code Playgroud)

然后尝试像以前一样链接程序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)
Run Code Online (Sandbox Code Playgroud)

这次,唯一链接的存档成员是:

(./libabm.a)alice2.o
Run Code Online (Sandbox Code Playgroud)

因为只有alice被调用进来sayalice.o。然而,链接失败,并出现对函数的未定义引用david程序从不调用该函数。 仅在 function 的定义中david dave调用,并且dave从不被调用。

尽管dave从未被调用,但它的定义是链接的,因为它位于目标文件 中,该文件被链接以提供被调用alice2.o函数的定义。通过链接中的定义,对 的调用将 成为未解析的引用,默认情况下链接必须为其找到定义,否则就会失败。所以它失败了。alicedavedavid

然后您会看到,通过对程序从未使用的符号的未定义引用而导致的链接失败与链接器不链接存档中未引用的目标文件这一事实是一致的。

如何避免对不使用的符号的未定义引用

如果遇到这种链接失败,可以通过指示链接器容忍未定义的引用来避免它。您可以简单地指示它忽略 所有未定义的引用,例如:

$ clang -o sayalice sayalice.o -L. -labm -Wl,--unresolved-symbols=ignore-all
$ ./sayalice
alice
Run Code Online (Sandbox Code Playgroud)

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

$ 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
Run Code Online (Sandbox Code Playgroud)

这样,您可以在诊断中检查唯一未定义的符号是否是您期望的符号。