同名函数及链接过程

1. 背景:

        当模块与模块之间复用一些函数,但是函数实现又略有不同,不修改函数名时候的链接方式。

简单例子:

        liba由a.c和ap.c组成,主程序由main.c和mp.c组成,其中ap.c和mp.c中有相同接口的print函数,但是其内部实现不同。期望结果为foo调用liba的print,main的print调用mp.c中的print。

a.c
#include "ap.h"

void foo ()
{
    print ();
}

ap.c
#include <stdio.h>

void print ()
{
    print ("Liba.\n");
}

main.c
extern void foo (void);
extern void print (void);

int main ()
{
    foo ();
    print ();
}

mp.c
#include <stdio.h>

void print ()
{
    printf ("Main.\n");
}

2. 静态链接:

        静态链接使用ar进行打包,将由汇编器生成的.o文件打包,生成的静态库其实就是一堆.o的集合,所有符号都尚未进行链接,只有在将静态库进行链接的时候,才会进行所有的.o和主程序进行链接。

        同时注意,在上述例子中,静态库中的a.c中的foo将无法找到静态库中的print,因为生成静态库仅仅是对不同的.o的一个打包的过程,并没有将静态库中的每个.o进行关联分析,个人简单理解为对静态库的链接仅仅是将所有的.o进行链接,但是有一点区别:链接是与顺序有关的,当出现重复的符号时,主程序的符号弱出现在静态库之前,则将会覆盖静态库的符号,并没有报错,但是当库出现在主程序的符号之前时,会产生重定义的错误。编译器(GCC-4.8.3)应该是保证了主程序符号的有效性。因此看起来静态链接并不能产生期望的结果。

[@iZ25yrrem9lZ lib]$ hcc main.c  -la mp.c -L./liba 
/tmp/cclbjTuU.o: In function `print':
mp.c:(.text+0x0): multiple definition of `print'
./liba/liba.a(ap.o):ap.c:(.text+0x0): first defined here
collect2: 错误:ld 返回 1
[@iZ25yrrem9lZ lib]$ hcc main.c mp.c -la -L./liba 
[@iZ25yrrem9lZ lib]$ ./a.out 
Main
Main
[@iZ25yrrem9lZ lib]$ 

3. 动态链接:

3.1 动态链接、静态链接的区别:

        动态链接库生成的方式不同于静态链接库,动态链接库并不是简单打包的过程,中间有一个链接的动作,但是和静态链接不同,静态链接会根据链接脚本进行重定位,最后生成的可执行文件中每个符号都有对应的有效地址。但是动态链接库是在程序运行过程中才进行加载(包括延迟加载),因此此时并没有相应的绝对地址,仍然是汇编器生成的相对地址。但是对于动态链接库中的符号,在动态链接库生成的时候,会对内部的符号进行处理,根据不同的选项生成延迟绑定的plt函数或者是内部函数(Bsymbolic选项)。

3.2 fPIC、GOT表:

        GOT表为Global offset table(全局偏移表),当加载动态库的函数时遇到不确定的符号,将通过该表进行查找。

        PIC为position independent code(位置无关代码)。

        PLT为procedure linkage table(过程链接表),主要处理动态库函数在第一次使用时才进行绑定,其重定位本质仍是查找GOT,本文不展开叙述。

        当编译过程中加上 -fPIC选项时,编译器会修改外部函数的属性(a.o为fPIC编译的代码,liba/a.o为没有fPIC代码,下图皆同),加了fPIC选项的未定义符号print为一个R_X86_64_PLT32的重定位类型。同时查看符号表,多出了一个GOT的段。

[@iZ25yrrem9lZ lib]$ readelf -r a.o liba/a.o

File: a.o

Relocation section '.rela.text' at offset 0x548 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000005  000a00000004 R_X86_64_PLT32    0000000000000000 print - 4

Relocation section '.rela.eh_frame' at offset 0x560 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0

File: liba/a.o

Relocation section '.rela.text' at offset 0x518 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000005  000900000002 R_X86_64_PC32     0000000000000000 print - 4

Relocation section '.rela.eh_frame' at offset 0x530 contains 1 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0
[@iZ25yrrem9lZ lib]$ nm a.o liba/a.o

a.o:
                 U _GLOBAL_OFFSET_TABLE_
0000000000000000 T foo
                 U print

liba/a.o:
0000000000000000 T foo
                 U print

3.3 动态加载时候的几种符号查询:

        动态链接对符号的查找主要有两种场景,即模块内部和模块外部。

        在模块内部使用时,将采用相对跳转和调用。如下(使用了Bsymbolic),这里的print使用的是底下地址为598的print,是相对调用。

000000000000058c <foo>:
 58c:   55                      push   %rbp
 58d:   48 89 e5                mov    %rsp,%rbp
 590:   e8 03 00 00 00          callq  598 <print>
 595:   c9                      leaveq 
 596:   c3                      retq   
 597:   90                      nop 

0000000000000598 <print>:
 598:   55                      push   %rbp
 599:   48 89 e5                mov    %rsp,%rbp
 59c:   48 8d 3d 53 00 00 00    lea    0x53(%rip),%rdi        # 5f6 <_fini+0xe>
 5a3:   e8 f8 fe ff ff          callq  4a0 <puts@plt>
 5a8:   c9                      leaveq 
 5a9:   c3                      retq   
 5aa:   90                      nop 
 5ab:   90                      nop 
 5ac:   90                      nop 
 5ad:   90                      nop 
 5ae:   90                      nop 
 5af:   90                      nop 
        在模块外部,使用的是间接访问,需要对GOT进行查询(这里因为延迟绑定的缘故使用的是plt),这里调用的不是下面5c8内部的print。

00000000000005bc <foo>:
 5bc:   55                      push   %rbp
 5bd:   48 89 e5                mov    %rsp,%rbp
 5c0:   e8 13 ff ff ff          callq  4d8 <print@plt>
 5c5:   c9                      leaveq
 5c6:   c3                      retq
 5c7:   90                      nop

00000000000005c8 <print>:
 5c8:   55                      push   %rbp
 5c9:   48 89 e5                mov    %rsp,%rbp
 5cc:   48 8d 3d 53 00 00 00    lea    0x53(%rip),%rdi        # 626 <_fini+0xe>
 5d3:   e8 e0 fe ff ff          callq  4b8 <puts@plt>
 5d8:   c9                      leaveq
 5d9:   c3                      retq
 5da:   90                      nop
 5db:   90                      nop
 5dc:   90                      nop
 5dd:   90                      nop
 5de:   90                      nop
 5df:   90                      nop
        4d8位于.plt段,主要进行的还是对GOT的查询。

00000000000004d8 <print@plt>:
 4d8:   ff 25 ba 03 20 00       jmpq   *0x2003ba(%rip)        # 200898 <_GLOBAL_OFFSET_TABLE_+0x28>
 4de:   68 02 00 00 00          pushq  $0x2
 4e3:   e9 c0 ff ff ff          jmpq   4a8 <_init+0x18>
        当创建共享库的时候,默认都会当成全局符号进行处理。

3.4 Bsymbolic选项:

        打开这个选项以后,构建动态库的时候,动态库都会先选择库内的符号进行使用。

[@iZ25yrrem9lZ lib]$ gcc liba/a.c -fPIC -c -o a.o
[@iZ25yrrem9lZ lib]$ gcc liba/ap.c -fPIC -c -o ap.o
[@iZ25yrrem9lZ lib]$ gcc --shared -o liba.so a.o ap.o -Wl,-Bsymbolic
[@iZ25yrrem9lZ lib]$ gcc main.c mp.c -la -L. -Wl,-rpath=./
[@iZ25yrrem9lZ lib]$ ./a.out 
Liba
Main
[@iZ25yrrem9lZ lib]$ 

参考资料:

1. 《程序员的自我修养-连接、装在与库》第七章、动态链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值