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

时间:2018-09-05 06:22:34

标签: c linux linker static-libraries

假设有3个c文件,例如a.c包含函数xx()yy(),而b.c包含nn()mm()和{{ 1}}包含c.cqq()

我从rr()stat.aa.o中制作了一个静态库b.o。如果我将c.o链接到一个调用stat.a的测试中,则符号xx()也将被导出:yy()同时具有符号nm testxx

  1. 我想知道为什么没有导出符号yyqq吗?
  2. 除了rr以外,是否有任何方法可以防止其他符号被加载?

2 个答案:

答案 0 :(得分:0)

  1. 我想知道为什么qq和rr符号不导出?

您必须将意图How to force gcc to link an unused static library告知链接器

  

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

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

来自How do I include only used symbols when statically linking with gcc?

  

gcc -ffunction-sections -c a.c

     

gcc -L./ -o test test.c -Wl,-gc-sections stat.a

答案 1 :(得分:0)

这是您的方案的实现:

交流

#include <stdio.h>

void xx(void)
{
    puts(__func__);
}

void yy(void)
{
    puts(__func__);
}

b.c

#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__);
}

test.c

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.ob.oc.o

$ 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

Txx的定义(yy)在成员stat.a(a.o)中。 nnmm的定义 在stat.a(b.o)中。 qqrr的定义在stat.a(c.o)中。

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

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

xx,在程序中称为 。也称为{em> not 的yy 定义。都不存在的nnmmqqrr都不存在。

这就是您所观察到的。

  

我想知道为什么没有导出符号qqrr吗?

什么是静态库,例如stat.a,它在链接中的特殊作用是什么?

这是ar archive,通常(但不一定)不包含任何内容 但是目标文件。您可以将此类归档文件提供给链接器,以便从中选择 目标文件,如果需要,则 进行链接。链接器需要这些对象 档案中的文件,这些文件提供了已定义符号的定义 已在输入文件中引用(但尚未定义)它已经链接。的 链接器从存档中提取所需对象文件,并将其输入到 链接,就像它们分别被命名为输入文件和静态库一样 根本没有被提及。

因此,链接程序对输入静态库所做的操作与其所做的操作不同 输入目标文件。任何输入对象文件都无条件链接到输出文件 (是否需要)。

鉴于此,让我们重做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的目标文件。
  • 找到a.o。它从存档中提取a.o,并将其链接到程序中。
  • 链接中没有其他未解析的符号引用, 链接器可以在stat.a(b.o)stat(c.o)中找到定义。所以这些都不 目标文件被提取并链接。

通过(仅)stat.a(a.o)提取链接,链接器获得了一个定义 需要解决xx中的函数调用的test.o中。但是a.o 包含 yy的定义。因此,该定义也链接到程序中。 程序中未定义nnmmqqrr,因为它们都不存在 在链接到程序的目标文件中定义。

这是您第一个问题的答案。您的第二个是:

  

除了xx以外,是否有任何方法可以防止其他符号被加载?

至少有两种方法。

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

还有另一种方法,不需要您在每个源代码中仅编写一个函数 文件。这种方式取决于以下事实: 编译器由各个组成,而这些节实际上是 链接程序区分并合并到输出文件中的单位。通过 默认情况下,每种符号都有一个标准的ELF部分。的 编译器将所有函数定义放在一个 code 部分中,然后 适当的 data 部分中的所有数据定义。你的原因 程序test的链接包含xxyy的定义是: 编译器已将这两个定义都放在a.o的单个代码部分中, 因此,链接器可以将该代码段合并到程序中,也可以不合并: 仅链接xx yy的定义,或者都不链接它们,因此必须使用 链接两者,即使只需要xx。让我们看一下a.o的代码部分的反汇编。默认情况下 代码段称为.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

xx部分中,您将看到yy.text的定义。

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

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

$ gcc -Wall -ffunction-sections -fdata-sections -c a.c b.c c.c test.c

现在再来看一下a.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

现在,我们在a.o中有两个代码段:.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):

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

链接器能够从中丢弃冗余代码节.text.yy 输入文件stat.a(a.o)。这就是yy的冗余定义是 不再存在于程序中。