![](/img/trans.png)
[英]Symbols from a static library are not exported while linking to a shared library
[英]How to prevent all symbols from static library to load and why other symbols from same .o file get exported to test while linking static library
假设有三个c文件,例如ac
包含函数xx()
, yy()
而bc
包含nn()
, mm()
和cc
包含qq()
, rr()
。
我用ao
, bo
和co
制作了一个静态库stat.a
如果我将stat.a
链接到调用xx()
的测试中,则符号yy()
也将导出: nm test
具有符号xx
和yy
。
qq
和rr
符号没有导出? xx
以外的其他符号? 您必须将意图告知链接器。 如何强制gcc链接未使用的静态库
gcc -L./ -o test test.c -Wl,-整体存档状态a -Wl,-无整体存档
gcc -fact-sections -c ac
gcc -L./ -o test test.c -Wl,-gc-sections stat.a
这是您的方案的实现:
交流电
#include <stdio.h>
void xx(void)
{
puts(__func__);
}
void yy(void)
{
puts(__func__);
}
公元前
#include <stdio.h>
void nn(void)
{
puts(__func__);
}
void mm(void)
{
puts(__func__);
}
抄送
#include <stdio.h>
void qq(void)
{
puts(__func__);
}
void rr(void)
{
puts(__func__);
}
测试
extern void xx(void);
int main(void)
{
xx();
return 0;
}
将所有*.c
文件编译为*.o
文件:
$ gcc -Wall -c a.c b.c c.c test.c
创建一个静态库stat.a
,其中包含ao
, bo
, co
:
$ ar rcs stat.a a.o b.o c.o
链接程序test
,输入test.o
和stat.a
:
$ gcc -o test test.o stat.a
跑:
$ ./test
xx
让我们看看stat.a
目标文件的符号表:
$ nm stat.a
a.o:
0000000000000000 r __func__.2250
0000000000000003 r __func__.2254
U _GLOBAL_OFFSET_TABLE_
U puts
0000000000000000 T xx
0000000000000013 T yy
b.o:
0000000000000000 r __func__.2250
0000000000000003 r __func__.2254
U _GLOBAL_OFFSET_TABLE_
0000000000000013 T mm
0000000000000000 T nn
U puts
c.o:
0000000000000000 r __func__.2250
0000000000000003 r __func__.2254
U _GLOBAL_OFFSET_TABLE_
U puts
0000000000000000 T qq
0000000000000013 T rr
xx
, yy
的定义( T
)在成员stat.a(ao)
。 nn
, mm
定义在stat.a(bo)
。 qq
, rr
定义在stat.a(co)
。
让我们看看在程序test
的符号表中也定义了哪些符号:
$ nm test | egrep 'T (xx|yy|qq|rr|nn|mm)'
000000000000064a T xx
000000000000065d T yy
定义了在程序中调用的 xx
。 还定义了yy
( 未调用)。 都不存在nn
, mm
, qq
和rr
,都没有被调用。
那就是你所观察到的。
我想知道为什么符号
rr
无法导出?
什么是静态库 ,例如stat.a
,它在链接中的特殊作用是什么?
它是一个ar
档案 ,按惯例-但不一定-除目标文件外不包含任何内容。 你可以提供这样的存档,以从中选择需要的目标文件,如果有的话,进行联动的连接。 链接器需要档案库中的那些目标文件,这些文件提供了已链接的输入文件中已引用但尚未定义的符号的定义。 链接器从存档中提取所需的目标文件,并将它们输入到链接中,就像它们分别被命名为输入文件一样,并且根本没有提及静态库。
因此,链接器对输入静态库所做的操作与对输入目标文件所做的操作不同。 任何输入目标文件都无条件链接到输出文件(无论是否需要)。
鉴于此,让我们重做test
与某些诊断程序( -trace)
的链接,以显示实际链接的文件:
$ gcc -o test test.o stat.a -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
test.o
(stat.a)a.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
除了gcc
默认添加的C程序链接的所有样板文件之外,链接中我们唯一的文件是两个目标文件:
test.o
(stat.a)a.o
链接:
$ gcc -o test test.o stat.a
与链接完全相同 :
$ gcc -o test test.o a.o
让我们仔细考虑一下。
test.o
是第一个链接器输入。 该目标文件无条件地链接到程序中。 test.o
包含对xx
的引用(特别是函数调用),但没有函数xx
定义。 xx
的定义才能完成链接。 stat.a
stat.a
中搜索包含xx
定义的目标文件。 ao
。 它从存档中提取ao
并将其链接到程序中。 stat.a(bo)
或stat(co)
找到其定义。 因此,这些目标文件都没有被提取和链接。 通过提取一个链接(仅) stat.a(ao)
,链接器获得了xx
的定义,该定义需要解析test.o
的函数调用。 但是ao
也包含yy
的定义。 因此,该定义也链接到程序中。 在程序中未定义nn
, mm
, qq
和rr
,因为在链接到程序中的目标文件中均未定义它们。
那就是你第一个问题的答案。 您的第二个是:
有什么方法可以防止加载除
xx
以外的其他符号?
至少有两种方法。
一个简单的方法是自己定义源文件中的xx
, yy
, nn
, mm
, qq
, rr
。 然后编译目标文件xx.o
, yy.o
, nn.o
, mm.o
, qq.o
, rr.o
并将它们全部归档在stat.a
。 然后,如果链接器需要在stat.a
中找到定义xx
的目标文件,它将找到xx.o
,提取并链接它,并且仅将xx
的定义添加到链接中。
还有另一种方法,不需要您在每个源文件中仅编写一个函数。 这种方式取决于以下事实:由编译器生成的ELF目标文件由各个部分组成,而这些部分实际上是链接程序区分并合并到输出文件中的单元。 默认情况下,每种符号都有一个标准的ELF部分。 编译器将所有函数定义放在一个代码段中,并将所有数据定义放在一个适当的数据段中。 您的程序test
链接同时包含xx
和yy
的定义的原因是,编译器已将这两个定义都放在ao
的单个代码段中,因此链接程序可以将该代码段合并到程序中,也可以不合并:它只能链接xx
和 yy
的定义,也不能两者都链接,因此即使只需要xx
,也必须链接两者。 让我们看看ao
的代码部分的反汇编。 默认情况下,代码段称为.text
:
$ objdump -d a.o
a.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <xx>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b <xx+0xb>
b: e8 00 00 00 00 callq 10 <xx+0x10>
10: 90 nop
11: 5d pop %rbp
12: c3 retq
0000000000000013 <yy>:
13: 55 push %rbp
14: 48 89 e5 mov %rsp,%rbp
17: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # 1e <yy+0xb>
1e: e8 00 00 00 00 callq 23 <yy+0x10>
23: 90 nop
24: 5d pop %rbp
25: c3 retq
在那里,您可以在.text
部分看到xx
和yy
的定义。
但是您可以要求编译器将每个全局符号的定义放在目标文件的其自己的部分中。 然后,链接器可以将任何函数定义的代码部分与其他任何部分分开,并且您可以要求链接器丢弃输出文件中未使用的任何部分。 让我们尝试一下。
再次编译所有源文件,这一次每个符号要求一个单独的部分:
$ gcc -Wall -ffunction-sections -fdata-sections -c a.c b.c c.c test.c
现在再来看一下ao
的反汇编:
$ objdump -d a.o
a.o: file format elf64-x86-64
Disassembly of section .text.xx:
0000000000000000 <xx>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b <xx+0xb>
b: e8 00 00 00 00 callq 10 <xx+0x10>
10: 90 nop
11: 5d pop %rbp
12: c3 retq
Disassembly of section .text.yy:
0000000000000000 <yy>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # b <yy+0xb>
b: e8 00 00 00 00 callq 10 <yy+0x10>
10: 90 nop
11: 5d pop %rbp
12: c3 retq
现在我们在ao
有两个代码段: .text.xx
,仅包含xx
的定义; .text.yy
,仅包含yy
的定义。 链接器可以将这些部分中的任何一个合并到一个程序中,而不能合并另一个。
重建stat.a
$ rm stat.a
$ ar rcs stat.a a.o b.o c.o
重新链接程序,这次要求链接器丢弃未使用的输入节( -gc-sections
)。 我们还将要求它跟踪加载的文件( -trace
)并为我们打印一个-Map=mapfile
文件( -Map=mapfile
):
$ gcc -o test test.o stat.a -Wl,-gc-sections,-trace,-Map=mapfile
/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
test.o
(stat.a)a.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
-trace
输出与以前完全相同。 但是,请再次检查程序中定义了哪些符号:
$ nm test | egrep 'T (xx|yy|qq|rr|nn|mm)'
000000000000064a T xx
只有xx
,这是您想要的。
该程序的输出与以前相同:
$ ./test
xx
最后看一下地图文件。 在顶部附近,您会看到:
映射文件
...
Discarded input sections
...
...
.text.yy 0x0000000000000000 0x13 stat.a(a.o)
...
...
链接器能够从输入文件stat.a(ao)
丢弃冗余代码段.text.yy
。 这就是程序中不再存在yy
的冗余定义的原因。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.