小言_互联网的博客

C++程序动态链接

376人阅读  评论(0)

C++程序动态链接

关于动态链接的原理,可以先看下这篇

先看代码:

//simpleApp.cpp
int foo(int a, int b);

int main(){
   
    int a = 0;
    int b = 1;
    int c;
    c = foo(a, b);
    return c;
}
[root@xxxx] g++ -c simpleApp.cpp                #生成.o文件
[root@xxxx] g++ -o simpleApp simpleApp.cpp      #生成可执行文件
/tmp/cc8NKLpJ.o: In function `main':
simpleApp.cpp:(.text+0x21): undefined reference to `foo(int, int)'
collect2: error: ld returned 1 exit status

可以看到,生成.o文件成功。生成可执行文件失败,错误提示为foo(int, int)未定义的引用。

使用objdump工具查看一下生成的.o文件的汇编代码:

[root@xxxx]# objdump -d simpleApp.o

simpleApp.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
   f:   c7 45 f8 01 00 00 00    movl   $0x1,-0x8(%rbp)
  16:   8b 55 f8                mov    -0x8(%rbp),%edx
  19:   8b 45 fc                mov    -0x4(%rbp),%eax
  1c:   89 d6                   mov    %edx,%esi
  1e:   89 c7                   mov    %eax,%edi
  20:   e8 00 00 00 00          callq  25 <main+0x25>           #这是调用foo函数的地方,随便填了一个数
  25:   89 45 f4                mov    %eax,-0xc(%rbp)
  28:   8b 45 f4                mov    -0xc(%rbp),%eax
  2b:   c9                      leaveq 
  2c:   c3                      retq 

从汇编代码中可以看到,在main函数中调用foo函数的地方,编译器随意填了一个数。

再查看一下simpleApp.o的符号表:

[root@xxxx]# readelf -s simpleApp.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND                      #未使用
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS simpleApp.cpp        #源文件名
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1                      #某个段
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3                      #某个段
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4                      #某个段
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6                      #某个段
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    7                      #某个段
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    5                      #某个段
     8: 0000000000000000    45 FUNC    GLOBAL DEFAULT    1 main                 #main函数
     9: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _Z3fooii             #未定义的foo函数

从上述符号表中我们可以看到foo函数的符号被标记为UND,即未定义的符号。

//foo.c

int foo(int a, int b){
   
    return a + b;
}

在simpleApp.cpp中引用了foo.c中的foo(int, int)函数。

上一次讲了静态链接,这次讲一下动态链接。首先将foo.c编译生成.so动态链接库。

[root@xxxx] g++ -shared -fpic -o foo.so foo.c

以动态链接的方式将simpleApp.o和foo.so链接起来生成可执行文件:

[root@xxxx] g++ -o simpleApp simpleApp.o foo.so

查看其汇编代码:

[root@xxxx]# objdump -d simpleApp

simpleApp:     file format elf64-x86-64

Disassembly of section .plt:

......
0000000000400530 <.plt>:
  400530:       ff 35 d2 0a 20 00       pushq  0x200ad2(%rip)        # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
  400536:       ff 25 d4 0a 20 00       jmpq   *0x200ad4(%rip)        # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
  40053c:       0f 1f 40 00             nopl   0x0(%rax)

0000000000400540 <_Z3fooii@plt>:
  400540:       ff 25 d2 0a 20 00       jmpq   *0x200ad2(%rip)        # 601018 <_Z3fooii>
  400546:       68 00 00 00 00          pushq  $0x0
  40054b:       e9 e0 ff ff ff          jmpq   400530 <.plt>          #跳转.plt    

Disassembly of section .text:
......
0000000000400636 <main>:
  400636:       55                      push   %rbp
  400637:       48 89 e5                mov    %rsp,%rbp
  40063a:       48 83 ec 10             sub    $0x10,%rsp
  40063e:       c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
  400645:       c7 45 f8 01 00 00 00    movl   $0x1,-0x8(%rbp)
  40064c:       8b 55 f8                mov    -0x8(%rbp),%edx
  40064f:       8b 45 fc                mov    -0x4(%rbp),%eax
  400652:       89 d6                   mov    %edx,%esi
  400654:       89 c7                   mov    %eax,%edi
  400656:       e8 e5 fe ff ff          callq  400540 <_Z3fooii@plt>
  40065b:       89 45 f4                mov    %eax,-0xc(%rbp)
  40065e:       8b 45 f4                mov    -0xc(%rbp),%eax
  400661:       c9                      leaveq 
  400662:       c3                      retq   
  400663:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  40066a:       00 00 00 
  40066d:       0f 1f 00                nopl   (%rax)
......

从汇编代码中可以看出,main函数调用了_Z3fooii@plt函数,而_Z3fooii@plt实际上并不是真正的foo函数,只是一个foo函数的入口,因为真正的foo函数在动态链接库中。

这里采用了一种延迟绑定的机制,main第一次调用_Z3fooii@plt函数时,会先跳转到*0x200ad2(%rip)位置,这个位置是.got.plt段中保存真正foo函数地址的位置,然而第一次调用时,它并没有指向真正的foo函数,而是指向其之后的指令,跳转到.plt中的代码,之后跳转到动态链接器。于是会先跳转进入动态链接器执行,动态链接器找到真正的foo函数的地址,将其回填到.got.plt段中保存真正foo函数地址的位置。这样之后再次调用_Z3fooii@plt函数时,就可以直接跳转到真正的foo函数去执行了。

为了验证我们的想法,可以看一下.got.plt段中的内容:

[root@xxxx]# objdump -s simpleApp

simpleApp:     file format elf64-x86-64

......
Contents of section .got.plt:
 601000 d00d6000 00000000 00000000 00000000  ..`.............
 601010 00000000 00000000 46054000 00000000  ........F.@.....
......

此时看到的.got.plt段中的内容是初始的内容,而0x601018的内容0x400546即指向跳转真正foo函数的指令的后一条指令,第一次调用结束后,这个位置存放的内容将会变成真正的foo函数地址。

0x00601000  GOT[0]  addr of .dynamic
0x00601008  GOT[1]  addr of reloc extry
0x00601010  GOT[2]  addr of dynamic linker
0x00601018  GOT[3]  foo()

为什么需要动态绑定?

使用延迟绑定的动机是对于一个像libc.so这样的动态链接库中包含很多函数,而一个应用程序往往只会使用到其中的小部分。把函数地址的解析推迟到它实际被调用的地方,能避免动态链接库在加载时对没有使用到的函数进行不必要的重定位。第一次调用过程运行时开销较大,但是其后的每次调用只会花费一条指令和一个间接的内存引用。


转载:https://blog.csdn.net/qq_38600065/article/details/117387437
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场