zoukankan      html  css  js  c++  java
  • 静态链接与动态链接

    引入:理解链接过程

    由一个.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
    ");
    	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
    

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

  • 相关阅读:
    UU跑腿
    Java基础之接口与抽象类的区别
    经典面试题:Redis为什么这么快?
    isBlank与isEmpty
    阅读联机API文档
    从文本中取出链接地址 并检测链接地址能否打开
    2019-01-19=树莓派学习总结
    单片机知识点
    Linux 编程题
    嵌入式基础知识
  • 原文地址:https://www.cnblogs.com/sssaltyfish/p/10764435.html
Copyright © 2011-2022 走看看