C 如何防止加载静态库中的所有符号,以及为什么在链接静态库时将同一.o文件中的其他符号导出到测试中
假设有三个c文件,例如C 如何防止加载静态库中的所有符号,以及为什么在链接静态库时将同一.o文件中的其他符号导出到测试中,c,linux,linker,static-libraries,C,Linux,Linker,Static Libraries,假设有三个c文件,例如a.c包含函数xx(),yy()和b.c包含nn(),mm()和c.c包含qq(),rr() 我用a.o、b.o和c.o制作了一个静态库stat.a。如果我将stat.a链接到调用xx()的测试中,那么符号yy()也会被导出:nm test既有符号xx也有符号yy 我想知道为什么没有导出符号qq和rr 是否有任何方法防止加载xx以外的任何其他符号 我想知道为什么qq和rr的符号不能导出 你必须将你的意图告知链接者 gcc-L./-o test test.c-Wl,--整个归
a.c
包含函数xx()
,yy()
和b.c
包含nn()
,mm()
和c.c
包含qq()
,rr()
我用a.o
、b.o
和c.o
制作了一个静态库stat.a
。如果我将stat.a
链接到调用xx()
的测试中,那么符号yy()
也会被导出:nm test
既有符号xx
也有符号yy
qq
和rr
xx
以外的任何其他符号下面是您的场景的一个实现: a.c
#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
,包含a.o
,b.o
,c.o
:
$ ar rcs stat.a a.o b.o c.o
$ 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
链接程序test
,输入test.o
和stat.a
:
$ gcc -o test test.o 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
运行:
让我们在stat.a
中查看对象文件的符号表:
$ gcc -o test test.o 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(a.o)
中。nn
,mm
都在统计a(b.o)中。qq
,rr
的定义见stat.a(c.o)
让我们看看哪些符号也在程序的符号表中定义测试:
$ nm test | egrep 'T (xx|yy|qq|rr|nn|mm)'
000000000000064a T xx
000000000000065d T yy
已定义程序中调用的xx
yy
,它不被调用,也是
定义nn
、mm
、qq
和rr
都没有被调用
这就是你观察到的
我想知道为什么没有导出符号qq
和rr
什么是静态库,例如stat.a
,它在链接中的特殊作用是什么
这是一种传统上(但不一定)不包含任何内容的方法
但是对象文件。您可以向链接器提供这样的存档,从中选择
对象文件,如果有,它需要进行链接。链接器需要这些对象
归档文件中的文件,这些文件为已删除的符号提供定义
在已链接的输入文件中引用但尚未定义。这个
链接器从存档中提取所需的对象文件并将其输入到
链接,就像它们分别命名为输入文件和静态库一样
根本没有提到
因此,链接器对输入静态库所做的与它所做的不同
使用输入对象文件。任何输入对象文件都无条件链接到输出文件
(无论是否需要)
有鉴于此,让我们重做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
除了所有用于C程序链接的锅炉板文件之外,gcc
通过
默认情况下,链接中我们的唯一文件是两个对象文件:
test.o
(stat.a)a.o
联系:
$ gcc -o test test.o stat.a
$ gcc -o test test.o 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
的对象文件
- 它查找
a.o
。它从存档中提取a.o
,并将其链接到程序中
链接中没有其他未解析的符号引用
链接器可以在stat.a(b.o)
或stat(c.o)
中找到定义。所以这两个都不是
提取并链接对象文件
通过提取一个链接(just)stat.a(a.o)
,链接器得到了一个定义
它需要在test.o
中解析函数调用。但是a.o
也包含
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
中。然后,如果链接器需要找到
定义xx
的stat.a
中的对象文件,它将查找xx.o
,提取并链接它,
以及xx的定义
$ 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
$ nm test | egrep 'T (xx|yy|qq|rr|nn|mm)'
000000000000064a T xx
$ ./test
xx
...
Discarded input sections
...
...
.text.yy 0x0000000000000000 0x13 stat.a(a.o)
...
...