《深刻理解计算机系统》 Chapter 7 读书笔记

《深刻理解计算机系统》Chapter 7 读书笔记

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

连接的时机函数

  • 编译时,也就是在源代码被翻译成机器代码时
  • 加载时,也就是在程序被加载器加载到存储器并执行时
  • 运行时,由应用程序执行

连接器使分离编译称为可能。编码

1、编译器驱动程序

大部分编译系统提供编译驱动程序:表明用户在须要时调用语言预处理器、编译器、汇编器和连接器。spa

1.将示例程序从ASCⅡ码源文件翻译成可执行目标文件的步骤

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

   (2)运行C编译器:main.i->ASCII码汇编语言文件main.s

   (3)运行汇编器:main.s->可重定位目标文件

2、静态连接

静态连接器以一组可重定位目标文件和命令行参数做为输入,生成一个彻底连接的能够加载和运行的可执行目标文件做为输出。输入的可重定位目标文件由各类不一样的代码和数据节(section)组成。指令在一个节中,初始化的全局变量在另外一个节中,而未初始化的变量又在另一个节中。操作系统

为了构造可执行文件,连接器必须完成两个任务:符号解析,重定位

3、目标文件

1.三种形式

  • 可重定位目标文件。包含二进制代码和数据,其形式能够在编译时与其余可重定位目标文件合并起来,建立一个可执行目标文件。
  • 可执行目标文件。包含二进制代码和数据,其形式能够被直接拷贝到存储器并执行。
  • 共享目标文件。一种特殊类型的可重定位目标文件,能够在加载或者运行地被动态地加载到存储器并连接。
编译器和汇编器生成可重定位目标文件(包括共享目标文件)。连接器生成可执行目标文件。从技术上来讲,一个目标模块就是一个字节序列,而一个目标文件就是一个存放在磁盘文件中的目标模块。

4、可重定位目标文件

1.一个典型的ELF可重定位目标文件的格式:命令行

  • .text:已编译程序的机器代码。
  • .rodata:只读数据,好比printf语句中的格式串和开关语句的跳转表。
  • .data:已初始化的全局C变量。
  • .bss:未初始化的全局C变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。
  • .symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。
  • .rel.text:一个.text节中位置的列表,当连接器把这个目标文件和其余文件结合时,须要修改这些位置。
  • .rel.data:被模块引用或定义的任何全局变量的重定位信息。
  • .debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项调用编译驱动程序时才会获得这张表。
  • .line:原始C源程序中的行号和.text节中机器指令之间的映射。
  • .strtab:一个字符串表,其内容包括:.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串序列。

5、符号和符号表

每一个可重定位目标模块m都有一个符号表,包含m所定义和引用的符号的信息。
在连接器的上下文中,三种不一样的符号:
1.由m定义并能被其余模块引用的全局符号。全局连接器对应于非静态的C函数以及被定义为Cstatic 属性的全局变量。
2.由其余模块定义并被模块m以引用的全局符号——外部符号,对应于定义在其余模块中的C函数和变量
3.只被模块m定义和引用的本地符号。

6、符号解析

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

在编译时,编译器向汇编器输出每一个全局符号,或者是强或者是弱,而汇编器把这个信息隐含地编码在可重定位目标文件的符号表里。翻译

函数和已初始化的全局变量时强符号;debug

未初始化的全局变量是弱符号。  unix

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

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

2.与静态库连接

全部的编译系统都提供一种机制,将全部相关的目标模块打包成为一个单独的文件,称为静态库

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

  • 对于命令行上的每一个输入文件f,连接器会判断f是一个目标文件仍是一个存档文件。若是f是一个目标文件,那么连接器吧f添加到E, 修改U和D来反映f中的符号定义和引用,并继续下一个输入文件。
  • 若是f是一个存档文件,那么连接器就尝试匹配U中未解析的符号和由存档文件成员定义的符号。若是某个存档文件成员m,定义了一个符号来解析U中的一个引用,那么就将m加到E中,而且连接器修改U和D来反映m中的符号定义和引用。对存档文件中全部的成员目标文件都反复进行这个过程,直到U和D都再也不发生变化。在此时,任何不包含在E中的目标文件都简单地被丢弃,而连接器将继续处理下一个输入文件。
  • 若是当连接器完成对命令行上输入文件的扫描后,U是非空的,那么连接器就好输出一个错误并终止。不然,它会合并和重定位E中的目标文件,从而构建输出的可执行文件。

7、重定位

一旦连接器完成了符号解析这一步,它就是把代码中的每一个符号引用和肯定的一个符号定义(即它的一个输入目标模块中的一个符号表条目)联系起来。

重定位由两步组成:

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

1.重定位条目

当汇编器生成一个目标模块时,它并不知道数据和代码最终存放在存储器中的什么位置。它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。因此,不管什么时候汇编器遇到对最终位置位置的目标引用,它就会生成一个重定位条目,告诉连接器在将目标文件合并成可执行文件时如何修改这个引用。代码的重定位条目放在.rel.text中 已初始化的数据的重定位条目放在.rel.data中。

8、可执行目标文件

可执行目标文件的格式相似于可重定位目标文件的格式。ELF头部描述文件的整体格式。它还包括程序的入口点,也就是当程序运行时要执行的第一条指令的地址。.text 、.rodata和.data 节和可重定位目标文件中的节是类似的,除了这些节已经被重定位到它们最终的运行时存储器地址之外。.init节定义了一个小函数,叫作_init,程序的初始化代码会调用它。由于可执行文件是彻底连接的(已被重定位了),因此它再也不须要.rel节。

9、加载可执行目标文件

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

要运行可执行目标文件p,能够在Unix外壳的命令行中输入它的名字:

unix> ./p

10、动态连接共享库

共享库是一个目标模块,在运行时,能够加载到任意的存储器地址,并和一个在存储器中的程序连接起来。这个过程称为动态连接,是由一个叫作动态连接器的程序来执行的。

共享库也称为共享目标,在Unix系统中一般用.so后缀来表示。微软的操做系统大量地利用了共享库,它们称为DLL(动态连接库)。

相关文章
相关标签/搜索