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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何防止静态库中的所有符号加载以及为什么在链接静态库时导出相同.o文件中的其他符号进行测试相关的知识,希望对你有一定的参考价值。

假设有三个c文件,比如a.c包含函数xx()yy()b.c包含nn()mm()c.c包含qq()rr()

我用stat.aa.ob.o制作了一个静态库c.o。如果我将stat.a链接到一个调用xx()的测试,那么符号yy()也会被导出:nm test有符号xxyy

  1. 我想知道为什么符号qqrr不会出口?
  2. 是否有任何方法来阻止任何其他符号加载xx
答案

以下是您的方案的实现:

交流转换器

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

C.C

#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

定义了在程序中调用的xxyy,未被称为,也被定义。 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
  • 链接器在qazxsw poi中搜索包含qazxsw poi定义的目标文件。
  • 它找到了stat.a。它从存档中提取xx并将其链接到程序中。
  • 链接中没有其他未解析的符号引用,链接器可以在a.oa.o中找到定义。因此,这些目标文件都不会被提取和链接。

通过提取链接(只)stat.a(b.o),链接器有一个stat(c.o)的定义,它需要解决stat.a(a.o)中的函数调用。但xx也包含test.o的定义。所以这个定义也与程序有关。 a.oyynnmm未在程序中定义,因为它们都没有在链接到程序的目标文件中定义。

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

是否有任何方法来阻止任何其他符号加载qq

至少有两种方法。

一个是简单地在源文件中单独定义rrxxxxyynnmm中的每一个。然后编译对象文件qqrrxx.oyy.onn.omm.o并将它们存档在qq.o中。然后,如果链接器需要在rr.o中找到定义stat.a的目标文件,它将找到stat.a,提取并链接它,并且仅将xx的定义添加到链接中。

还有另一种方法,不需要在每个源文件中只编写一个函数。这种方式取决于由编译器生成的ELF目标文件由各个部分组成的事实,这些部分实际上是链接器区分并合并到输出文件中的单元。默认情况下,每种符号都有一个标准的ELF部分。编译器将所有函数定义放在一个代码段中,将所有数据定义放在适当的数据段中。程序xx.o的链接包含xxtest的定义的原因是编译器已将这两个定义放在xx的单个代码部分中,因此链接器可以将该代码段合并到程序中,或者不:它只能链接yya.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 )并为我们打印mapfile(-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-linu

以上是关于如何防止静态库中的所有符号加载以及为什么在链接静态库时导出相同.o文件中的其他符号进行测试的主要内容,如果未能解决你的问题,请参考以下文章

如何使静态库中的 gcc 链接强符号覆盖弱符号?

静态库中的符号有时会链接到可执行文件,有时不会

静态链接到 Qt 时加载 Qt 插件?

与静态库中的 std::string 相关的 C++ 未定义符号

单个静态对象库中的多个重复符号

导出符号意味着什么?