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

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

本文关键字:获取 文件 对象 单个      更新时间:2023-10-16

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

你将无法找到你想要的答案,因为你 想要让链接器做就是它默认做的事情。这是一个演示。 (它是用 C 语言而不是 C++ 只是为了避免混淆C++名称)。

三个源文件:

爱丽丝·

#include <stdio.h>
void alice(void)
{
puts("alice");
}

鲍勃·

#include <stdio.h>
void bob(void)
{
puts("bob");
}

玛丽·

#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

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

$ 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 库中定义,默认情况下将链接该库。

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

赛亚丽丝

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

这是另一个调用alice并在外部bob的程序,因此 依赖于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

链接器选项-trace指示链接器报告链接的对象文件和链接的 DSO。我们将使用 现在使用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 库和运行时都是链接的。和的我们创建的对象文件,仅链接了两个:

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

我们的计划依赖libabm.a的两个成员:

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

链接。

运行程序:

$ ./sayalice
alice

上面写着"爱丽丝"。

然后为了进行比较,我们将链接程序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

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

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

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

(./libabm.a)mary.o

未链接。

该程序的运行方式如下:

$ ./sayalice_n_bob
alice
bob

以下是该程序的全局符号表:

$ 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.

如您所见,链接器的默认行为是您询问的行为 才能得到。阻止链接器仅提取以下存档成员 在链接中引用,而不是链接所有存档成员,您必须 明确告诉它这样做,将存档置于--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

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

(./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.

退后一步

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

另一个源文件:

爱丽丝2.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

然后尝试像以前一样链接程序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)

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

(./libabm.a)alice2.o

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

尽管从未调用过dave,但它的定义是链接的,因为它位于 对象文件,alice2.o,链接以提供函数alice的定义 - 这。随着dave的定义在链接中,呼吁david成为未解析的参照,默认情况下,链接必须找到 定义,或失败。所以它失败了。

然后你会看到,通过未定义的引用来链接的失败 程序从不使用的符号与链接器的事实一致链接存档中未引用的对象文件。

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

如果您遇到这种联动故障,您可以通过指导 允许未定义引用的链接器。您可以简单地指示它忽略所有未定义的引用,例如:

$ 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

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