链接与共享库

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/puliao4167/article/details/86557237

链接简述

    由下图编译的过程可以看出,源程序经过预处理,编译,汇编等步骤形成可重定位目标文件,再由多个可重定位目标文件形成可执行目标文件。

    链接是将各种代码和数据片段收集并组合成一个单一文件的过程,这个文件可被加载到内存中直接执行。链接可以执行于编译时(源代码被翻译成机器码)、加载时(程序被加载器加载到内存并执行)、运行时(由应用程序来执行)

    链接的好处在于①模块化(可构建共享库)②效率高,时间上可分开编译(只需要重新编译修改过的文件即可),空间上可执行文件运行时内存只需包含所调用函数的代码。

链接操作步骤

  1.  确定符号引用关系
  2. 合并相关.O文件
  3. 确定每个符号的地址
  4. 在指令中填入新地址

    同时也可以把链接操作分成两个步骤,符号解析(步骤1,即把每个符号引用和一个符号定义关联起来)和重定位(步骤2\3\4)。

    符号解析:连接器解析符号引用的方法就是将每个引用与他输入的可重定位目标文件的符号表中的符号定义关联起来。每个可重定位目标模块m都有一个符号表,其包含了m定义和引用的符号信息,符号表中有三种不同的符号:

  1. 由模块m定义并能被其他模块引用的全局符号(非静态函数和非静态全局变量)
  2. 由其他模块定义并被模块m引用的全局符号,也成为外部符号(其他模块中定义的非静态函数和非静态全局变量)
  3. 只能被模块m定义和引用的局部符号(静态函数和静态变量)

    注:函数内部局部变量不在符号表中,在函数栈帧中

    对于多重定义的全局符号,在编译时编译器会向汇编器输出每个全局符号,分为强符号(函数和已初始化的全局变量)和弱符号(未初始化的全局变量),汇编器则会把这个信息放在可重定位文件的符号表中,链接器根据以下规则处理多重定义的符号名:

  1. 不允许有多个同名的强符号
  2. 如果有一个强符号和多个弱符号同名,那么选择强符号
  3. 如果由多个弱符号同名,那么从这些弱符号中任意选择一个。

重定位:合并输入模块并为每个符号分配运行时地址。有两步骤组成:①重定位节和符号定义,将所有相同类型的节合并为同一类型的新的聚合节②重定位节中的符号引用,链接器修改代码节和数据节中对每个符号的引用,使其指向正确的运行地址(依赖于重定位条目)

可重定位目标文件

    图中是一个ELF可重定位目标文件格式。

    ELF头包含系统字大小,字节顺序,ELF头大小、目标文件类型(如可重定位、可执行或共享的)、机器类型、节头部表的文件偏移、以及节头部表中条目的大小和数量。节头部表描述不同节的位置和大小。

  • .text已编译程序的机器代码
  • .rodata只读数据
  • .data已初始化的全局和静态C变量
  • .bss未初始化的全局和静态C变量
  • .symtal符号表,存放程序中引用和定义的函数和全局变量的信息
  • .rel.text存放代码的重定位条目,.rel.data存放已初始化数据重定位条目
  • .debug是一个调试符号表,.line是原始C源程序中行号和.text节中机器指令之间的映射,.strtab一个字符串表

可执行目标文件

    段头部表(程序头部表)是将连续的文件节映射到运行时内存段,即节与段的映射并包含每个段的信息。可执行目标文件的ELF头和可重定位文件的ELF头类似,还包括程序的入口点(即当程序运行时候执行的第一条指令的地址),其他.text节等已重定位到他们最终运行时内存地址。

加载可执行目标文件

    Linux系统中每个程序都运行在一个进程上下文中,有自己的虚拟地址空间,shell运行一个可执行目标文件的过程:

  • 读入命令(可执行文件名称)及参数
  • 构造argv和envp
  • 调用fork系统调用,从父进程生成一个子进程
  • 调用execve系统调用启动加载器,加载器删除子进程现有的虚拟内存段,并创建一组新的代码段、数据段、堆和栈,新的堆和栈初始化为零,通过虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码段和数据段就会被初始化为可执行文件的内容
  • 加载器跳转到_start地址,最终会调用应用程序的main函数

静态库与动态链接共享库

    所有的相关目标模块(.o文件)打包成一个单独的文件即为静态库(.a文件)。静态库的缺点:①静态库函数被包含在每个进程的代码段,造成主存的浪费②静态库函数被合并在可执行目标中,磁盘中空间浪费③更新困难

    动态链接共享库是一个目标模块,在运行或加载时,可以加载到任意内存地址,并和一个在内存中的程序链接起来。Linux下用.so后缀表示,Windows下用.dll表示。

    所有引用共享库的可执行目标文件共享这个.so文件中的代码和数据,而不是像静态库中被复制到引用他们的可执行文件中,除此之外,在内存中一个共享库的.text节的一个副本可以被不同的正在运行的进程共享。

linux > gcc -shared -fpic -o lib.so add1.c add2.c

linux > gcc -o a.out main.c ./lib.so

//main.c中引用add1.c或者add2.c

参考《CSAPP》《TLPI》

猜你喜欢

转载自blog.csdn.net/puliao4167/article/details/86557237