《深刻理解计算机系统》读书笔记第七章——连接

《深刻理解计算机系统》第七章

连接是将各类代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或拷贝)到存储器并执行。服务器

连接的时机数据结构

  • 编译时,也就是在源代码被翻译成机器代码时函数

  • 加载时,也就是在程序被加载器加载到存储器并执行时。
  • 运行时,由应用程序执行。
  • 在现代系统中,连接是由连接器自动执行的。

7.1 编译器驱动程序

       编译系统提供编译驱动程序——调用语言预处理器、编译器、汇编器和连接器。工具

  (1)运行C预处理器:源程序main.c->ASCII码中间文件main.i 性能

  (2)运行C编译器:main.i->ASCII码汇编语言文件main.s
    (3)运行汇编器:main.s->可重定位目标文件
编码

7.2 静态连接

连接器必须完成两个主要任务:spa

  • 符号解析:目标文件定义和引用符号。符号解析的目的是将每一个符号引用恰好和一个符号定义联系起来。
  • 重定位:编译器和汇编器生成从地址0开始的饿代码和数据节。连接器经过把每一个符号定义与一个存储器位置联系起来,而后修改全部对这些符号的引用,使得它们指向这个存储器位置,从而重定位这些节。

7.3 目标文件

连接器必须完成两个主要任务:

  • 符号解析——目标文件定义和引用符号。符号解析的目的是将每一个符号引用恰好和一个符号定义联系起来。
  • 重定位——编译器和汇编器生成从地址0开始的饿代码和数据节。连接器经过把每一个符号定义与一个存储器位置联系起来,而后修改全部对这些符号的引用,使得它们指向这个存储器位置,从而重定位这些节。

7.4 可重定位目标文件

一个典型的ELF可重定位目标文件包含下面几个节:操作系统

 7.5 符号和符号表

在连接器的上下文中,有三种不一样的符号:命令行

  • 由m定义并能被其余模块引用的全局符号
  • 由其余模块定义并被模块m引用的全局符号
  • 只被模块m引用的本地符号

7.6 符号解析

7.6.1 连接器如何解析多重定义的全局符号 

1.在编译是,编译器向汇编器输出每一个全局符号,或者是强或者是弱,而汇编器把这个信息隐含地编码在可重定位目标文件的符号表里。函数和已初始化的全局变量时强符号,未初始化的全局变量是弱符号。翻译

2.根据强弱符号的定义,Unix连接器使用下面的规则来处理多重定义的符号:

  • 强符号:函数和已经初始化的全局变量
  • 弱符号:未初始化的全局变量

  • 规则:

    规则1:不容许有多个强符号。
    规则2:若是有一个强符号和多个弱符号,那么选择强符号。
    规则3:若是有多个弱符号,那么从这些弱符号中任意选择一个。

7.6.2 与静态库连接

全部的编译系统都提供一种机制,将全部相关的目标模块打包成为一个单独的文件,称为静态库(Linux下是存档文件,Windows下是lib),能够用作连接器的输入。

  • 当连接器构造一个输出的可执行文件时,它只拷贝静态库里被应用程序引用的目标模块。
  • 存档文件:一组链接起来的可重定位目标文件的集合,有一个头部用来描述每一个成员目标文件的大小和位置。存档文件名由后缀.a标识。
  • 连接时加上-static参数:告诉编译器驱动程序,连接器应该构建一个彻底连接的可执行目标文件,它能够加载到存储器并执行,在加载时无需更进一步的连接。

7.6.3 连接器如何使用静态库来解析引用 

图2

通常准则:

  • 将库放在命令行的结尾。
  • 若是各个库的成员是相互独立(也就是说没有成员引用另外一个成员定义的符号),那么这些库就能够按照任何顺序放置在命令行的结尾处。
  • 若是库不是相互独立的,那么它们必须排序,使得对于每一个被存档文件的成员外部引用的符号s,在命令行中至少有一个s的定义是在对s的引用以后的。

7.7 重定位

重定位两步:

  • 重定位节和符号定义:
    • 连接器将全部相同类型的节合并为同一类型的新的聚合节,将运行时存储器地址赋给新的聚合节,赋给输入模块定义的每一个节,以及赋给输入模块定义的每一个符号。
    • 此时,程序中的每一个指令和全局变量都有惟一的运行时存储器地址了。
  • 重定位节中的符号引用:
    • 连接器修改代码节和数据节中对每一个符号的引用,使得它们指向正确的运行时地址。
    • 连接器依赖于称为重定位条目的可重定位目标模块中的数据结构。

7.7.1 重定位条目

ELF定义两种最基本的重定位类型:

  • R_386_PC32    重定位一个使用32位PC相对地址的引用。
  • R_386_32       重定位一个使用32位绝对地址的引用。

7.7.2 重定位符号引用

引用类型:

  • 重定位符号相对引用
  • 重定位符号绝对引用

7.8 可执行目标文件

ELF可执行文件被设计得很容易加载到存储器,可执行文件的连续的片被映射到连续的存储器段。段头部表描述了这种映射关系。

可执行文件是彻底连接的(已被重定位了),因此它再也不须要.rel节。

7.9 加载可执行目标文件

加载器将可执行目标文件中的执行代码和数据从磁盘拷贝到存储器中,而后经过跳转到程序的第一条指令或入口点来运行该程序。这个将程序拷贝到存储器并运行的过程叫作加载。

  • 在32位Linux系统中,代码段老是从地址0x08048000处开始。
  • 数据段是在接下来的下一个4KB对齐的地址处。
  • 运行时堆在读/写段以后接下来的第一个4KB对齐的地址处,并经过调用malloc库往上增加。
  • 有一个段是为共享库保留的。
  • 用户栈老是最大的合法用户地址开始,向下增加的(向低存储器地址方向增加)。从栈的上部开始的段是为操做系统驻留存储器的部分(也就是内核)的代码和数据保留的。

7.10 动态连接共享库

共享库

  • 共享库是一个目标模块,在运行时,能够加载到任意的存储器地址,并和一个在存储器中的程序连接起来。这个过程称为动态连接,是由一个叫作动态连接器的程序来执行的。
  • 共享库也称为共享目标,在Unix系统中一般用.so后缀来表示。微软的操做系统大量地利用了共享库,它们称为DLL(动态连接库)。
  • 共享库是以两种不一样的方式来“共享”的。

7.11 从应用程序中加载和连接共享库

动态连接在现实中的例子:

  • 分发软件
  • 构建高性能Web服务器

7.12 与位置无关的代码(PIC)

编译库代码,使得不须要连接器修改库代码就能够在任何地址加载和执行这些代码。

  • 用户对GCC使用-fPIC选项指示GNU生成PIC代码

常见PIC类型:

  • PIC数据引用
  • PIC函数调用

7.13 处理目标文件的工具

  • AR:建立静态库,插入、删除、列出和提取成员。
  • STRINGS:列出一个目标文件中全部可打印的字符串。
  • STRIP:从目标文件中删除符号表信息。
  • NM:列出一个目标文件中符号表定义的符号。
  • SIZE:列出目标文件中节的名字和大小。
  • READELF:可以显示一个目标文件的全部信息。
  • OBJDUMP:反汇编
  • LDD:列出一个可执行文件运行时须要的共享库。

7.14 小结

  • 连接能够在编译时由静态编译器来完成,也能够在加载时和运行时由动态连接器来完成。
  • 连接器处理称为目标文件的二进制文件,它有三种不一样的形式:可重定位的、可撕的和共享的:
  • 可重定位的目标文件由静态连接器合并成一个可执行的目标文件,它能够加载到存储器中并执行。
  • 共享目标文件(共享库)是在运行时由动态连接器连接和加载的,或者隐含地在调用程序被加载和开始执行时或者根据须要在程序调用dopen库的函数时。
  • 连接器的两个主要任务是符号解析和重定位,符号解析将目标文件中的每一个全局符号都绑定到一个惟一的定义,而重定位肯定每一个符号的最终存储器地址,并修改对那些目标的引用。
  • 静态连接器是由像G∝这样的编译驱动器调用的。
  • 多个目标文件能够定义相同的符号,而连接器用来悄悄地解析这些多重定义的规则可能在用户程序中引入的微妙错误。 
相关文章
相关标签/搜索