首页 > 其他 > 详细

静态链接与动态链接

时间:2019-04-24 19:56:26      阅读:175      评论:0      收藏:0      [点我收藏+]

引入:理解链接过程

由一个.c源文件得到一个二进制可执行文件需要经历预处理、编译、汇编和链接:

  • 预处理:包括头文件的包含、宏定义的扩展、条件编译的选择等 gcc -E hello.c

  • 编译:经过词法分析、语法分析、语义分析,将源代码翻译成汇编代码 gcc -S hello.c

  • 汇编:把作为中间结果的汇编代码翻译成了机器代码,即目标代码 gcc -c hello.s

代码在链接之前经历:源码文件(.c)---> 汇编代码(.s)---> 目标文件(.o),此时得到的目标文件还不是可以运行的二进制文件,利用 readelf -e hello.o 可以看到hello.o只有elf头和节头,没有程序头,它不是一个可执行文件,它是一个可重定位文件,需要经过链接将它制作成可执行文件

链接:由于项目很可能有多个文件(模块)相互依赖、相互引用,因此需要通过链接处理好各个模块之间的相互引用关系。链接过程做的事包括:

  • 符号决议
  • 地址空间分配
  • 符号重定位

Q:为什么要链接?gcc -c产生的编译结果的elf文件不是已经包含了能够执行的汇编代码吗?

A:编译过程只针对单个文件进行,将高级程序语言的代码翻译成机器码,而没有考虑具体的运行时,如程序的加载地址、外部的变量符号函数等。举个例子:

1.c

#include <stdio.h>

extern int a;
int main()
{
    a += 1;
    printf("hello\n");
    return 0;
}

2.c

int a = 1;

源文件1.c引用了2.c中的变量a。编译它们得到1.o2.o

gcc -c 1.c 2.c

然后利用objdump -d 1.o查看反汇编代码:

qxy@qxy-XPS-13-9360:~/Desktop/test$ objdump -d 1.o

1.o:     文件格式 elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   8b 05 00 00 00 00       mov    0x0(%rip),%eax        # a <main+0xa>
   a:   83 c0 01                add    $0x1,%eax
   d:   89 05 00 00 00 00       mov    %eax,0x0(%rip)        # 13 <main+0x13>
  13:   bf 00 00 00 00          mov    $0x0,%edi
  18:   e8 00 00 00 00          callq  1d <main+0x1d>
  1d:   b8 00 00 00 00          mov    $0x0,%eax
  22:   5d                      pop    %rbp
  23:   c3                      retq   

此时编译得到的代码只是单纯的对源文件的c代码进行转译,而并没有得到正确的变量a的值。同时也可以查看该目标代码的elf头:

qxy@qxy-XPS-13-9360:~/Desktop/test$ readelf -h 1.o
ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              REL (可重定位文件)
  系统架构:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口点地址:               0x0
  程序头起点:          0 (bytes into file)
  Start of section headers:          736 (bytes into file)
  标志:             0x0
  本头的大小:       64 (字节)
  程序头大小:       0 (字节)
  Number of program headers:         0
  节头大小:         64 (字节)
  节头数量:         13
  字符串表索引节头: 12

它的入口点地址是0x0,也不是程序最终的入口虚拟地址

然后再使用gcc -o hello 1.o 2.o将两个目标文件进行链接,得到可执行文件hello,此时再看main函数的反汇编代码和文件的elf头:

qxy@qxy-XPS-13-9360:~/Desktop/test$ objdump -d hello
...
00000000004005fd <main>:
  4005fd:   55                      push   %rbp
  4005fe:   48 89 e5                mov    %rsp,%rbp
  400601:   8b 05 29 0a 20 00       mov    0x200a29(%rip),%eax        # 601030 <a>
  400607:   83 c0 01                add    $0x1,%eax
  40060a:   89 05 20 0a 20 00       mov    %eax,0x200a20(%rip)        # 601030 <a>
  400610:   bf b4 06 40 00          mov    $0x4006b4,%edi
  400615:   e8 d6 fe ff ff          callq  4004f0 <puts@plt>
  40061a:   b8 00 00 00 00          mov    $0x0,%eax
  40061f:   5d                      pop    %rbp
  400620:   c3                      retq   
  400621:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  400628:   00 00 00 
  40062b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
  ...
  
  qxy@qxy-XPS-13-9360:~/Desktop/test$ readelf -h hello
ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  版本:                              1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              EXEC (可执行文件)
  系统架构:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口点地址:               0x400500
  程序头起点:          64 (bytes into file)
  Start of section headers:          6488 (bytes into file)
  标志:             0x0
  本头的大小:       64 (字节)
  程序头大小:       56 (字节)
  Number of program headers:         9
  节头大小:         64 (字节)
  节头数量:         30
  字符串表索引节头: 29

显而易见有几处发生了变化:文件从重定位文件变成了可执行文件,入口地址变更为一个合适的虚拟地址,add语句中找到了正确的变量a。这就是前文所说的“符号决议,地址空间和分配符号重定位”。链接器在重定位的过程中对目标文件中未定义的部分发生修改

静态链接与动态链接

静态链接: 所有目标文件和外部库静态地绑定在一起。在最终的可执行文件中,所有符号都被解析出来,运行时不依赖任何外部库

动态链接: 外部内容没有被完整地拷贝进最终的可执行文件,而是在运行时动态地加载。程序运行时必须能够找到这些库,解析动态链接进来的符号引用,然后才能真正开始执行程序

继续以上述代码举例。首先是静态库:

$ gcc -c 1.c 2.c
$ ar rv lib2static.a 2.o
$ gcc -o hello_static 1.o -L. -l2static

2.o打包成静态库,然后链接静态库得到可执行文件hello_static由于静态库的内容已经完整的整合到hello_static中,因此可以在任何目录下执行hello_static,无论当前目录中有没有lib2static.a

静态库libxx.a相当于是若干个x.o的整合包,对他们进行归档形成一个静态库文件。链接一个静态库与链接库中包含的所有.o文件效果等同

然后是动态库:

$ gcc -c 1.c 2.c
$ gcc --shared 2.o -o lib2dynamic.so
$ gcc -o hello_dynamic 1.o -L. -l2dynamic

由于动态库目录指定为当前目录,链接得到的可执行文件hello_dynamic只能在与动态库lib2dynamic.so同目录下运行。此时如果重命名、删除lib2dynamic.so,或将hello_dynamic拷贝到没有lib2dynamic.so的目录下运行,会得到错误:

qxy@qxy-XPS-13-9360:~/Desktop/test$ ./hello_dynamic
./hello_dynamic: error while loading shared libraries: lib2dynamic.so: cannot open shared object file: No such file or directory

因为程序在执行的过程中动态加载动态库的内容,但发现找不到这个文件

静态链接与动态链接

原文:https://www.cnblogs.com/sssaltyfish/p/10764435.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!