繁体   English   中英

如何防止静态库中的所有符号加载以及链接静态库时为什么导出来自同一.o文件的其他符号以进行测试

[英]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()

我用aoboco制作了一个静态库stat.a 如果我将stat.a链接到调用xx()的测试中,则符号yy()也将导出: nm test具有符号xxyy

  1. 我想知道为什么qqrr符号没有导出?
  2. 有什么方法可以防止加载除xx以外的其他符号?
  1. 我想知道为什么qq和rr符号没有导出?

您必须将意图告知链接器。 如何强制gcc链接未使用的静态库

gcc -L./ -o test test.c -Wl,-整体存档状态a -Wl,-无整体存档

  1. 有什么方法可以防止加载除xx以外的其他符号?

与gcc静态链接时,我该如何仅包括使用的符号?

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 ,其中包含aoboco

$ ar rcs stat.a a.o b.o c.o

链接程序test ,输入test.ostat.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

xxyy的定义( T )在成员stat.a(ao) nnmm定义在stat.a(bo) qqrr定义在stat.a(co)

让我们看看在程序test的符号表中也定义了哪些符号:

$ nm test | egrep 'T (xx|yy|qq|rr|nn|mm)'
000000000000064a T xx
000000000000065d T yy

定义了在程序中调用的 xx 还定义了yy调用)。 都不存在nnmmqqrr ,都没有被调用。

那就是你所观察到的。

我想知道为什么符号qqrr无法导出?

什么是静态库 ,例如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的定义。 因此,该定义也链接到程序中。 在程序中未定义nnmmqqrr ,因为在链接到程序中的目标文件中均未定义它们。

那就是你第一个问题的答案。 您的第二个是:

有什么方法可以防止加载除xx以外的其他符号?

至少有两种方法。

一个简单的方法是自己定义源文件中xxyynnmmqqrr 然后编译目标文件xx.oyy.onn.omm.oqq.orr.o并将它们全部归档在stat.a 然后,如果链接器需要在stat.a中找到定义xx的目标文件,它将找到xx.o ,提取并链接它,并且xx的定义添加到链接中。

还有另一种方法,不需要您在每个源文件中仅编写一个函数。 这种方式取决于以下事实:由编译器生成的ELF目标文件由各个部分组成,而这些部分实际上是链接程序区分并合并到输出文件中的单元。 默认情况下,每种符号都有一个标准的ELF部分。 编译器将所有函数定义放在一个代码段中,并将所有数据定义放在一个适当的数据段中。 您的程序test链接同时包含xxyy的定义的原因是,编译器已将这两个定义都放在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部分看到xxyy的定义。

但是您可以要求编译器将每个全局符号的定义放在目标文件的其自己的部分中。 然后,链接器可以将任何函数定义的代码部分与其他任何部分分开,并且您可以要求链接器丢弃输出文件中未使用的任何部分。 让我们尝试一下。

再次编译所有源文件,这一次每个符号要求一个单独的部分:

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM