《深入理解计算机系统》第七章学习笔记
第七章 链接
姓名:王玮怡 学号:20135116
一、关于链接
1、含义
链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储器并执行。链接是由链接器程序自动执行的。
2、执行时间
- 编译时
- 加载时
- 运行时
二、编译器驱动程序
驱动编译器:代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。
三、静态链接
1、静态链接器
Unix的静态链接器(static linker)ld,以一组可重位目标文件和命令行参数作为输入,生成一个完全链接的可以加载和运行的可执行目标文件作为输出。输入的可重定位目标文件由各种不同的代码和数据节(section)组成。指令在一个节中,初始化的全局变量在另一个节中,而未初始化的变量又在另外一个节中。
2、链接器的两个任务
- 符号解析:将每个符号引用刚好与一个符号定义联系起来
- 重定位:链接器将每一个符号定义与一个存储器位置连接起来,然后修改这些符号引用,使得他们指向这个存储器位置,从而重定位
四、目标文件
目标文件的三种形式
- 可重定位目标文件(编译器和汇编器可生成)
- 可执行目标文件(链接器可生成)
- 共享目标文件(编译器和汇编器可生成)
五、可重定位目标文件
- .text:已编译程序的机器代码。
- .rodata:只读数据,比如printf语句中的格式串和开关语句的跳转表。
- .data:已初始化的全局C变量。
- .bss:未初始化的全局C变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。
- .symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。
- .rel.text:一个.text节中位置的列表,当链接器把这个目标文件和其他文件结合时,需要修改这些位置。
- .rel.data:被模块引用或定义的任何全局变量的重定位信息。
- .debug:一个调试符号表,其条目是程序中定义的局部变量和类型定义,程序中定义和引用的全局变量,以及原始的C源文件。只有以-g选项调用编译驱动程序时才会得到这张表。
- .line:原始C源程序中的行号和.text节中机器指令之间的映射。
-
.strtab:一个字符串表,其内容包括:.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以null结尾的字符串序列。
六、符号和符号表
1、链接器的上下文中,有三种不同的符号:
- 由m定义并能被其他模块引用的全局符号。全局链接器符号对应于非静态的C函数以及被定义为不带C static属性的全局变量。
- 由其他模块定义并被模块m引用的全局符号。这些符号称为外部符号(external),对应于定义在其他模块中的C函数和变量。
- 只被模块m定义和引用的本地符号。有的本地链接器符号对应于带static属性的C函数和全局变量。
2、符号表
七、符号解析
1、链接器如何解析多重定义的全局符号
(1)强符号:函数和已初始化的全局变量
(2)弱符号:未初始化的全局变量
(3)处理规则:
- 不允许有多个强符号。
- 如果有一个强符号和多个弱符号,那么选择强符号。
- 如果有多个弱符号,那么从这些弱符号中任意选择一个。
2、与静态库链接
所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库,可以用做链接器的输入。其中,在Linux下是存档文件,Windows下是lib。
在符号解析的阶段,链接器从左到右按照它们在编译器驱动程序命令行上出现的相同顺序来扫描可重定位目标文件和存档文件。(驱动程序自动将命令行中所有的.c文件翻译成.o文件),在这次扫描中,链接器维持一个可重定位目标文件的集合E(这个集合中的文件会被合并起来形成可执行文件),一个未解析的符号(即引用了但是尚未定义的符号)集合U,以及一个在前面输入文件中已定义的符号集D,初始时,E、U和D都是空的。
七、重定位
1、重定位的两个步骤
(1)重定位节和符号定义
- 链接器将所有相同类型的节合并为同一类型的新的聚合节,将运行时存储器地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。
- 完成这一步后,程序中的每个指令和全局变量都有唯一的运行时存储器地址了。
(2)重定义节中的符号引用
- 链接器修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址。
- 链接器依赖于称为重定位条目的可重定位目标模块中的数据结构。
2、重定位条目
(1)无论何时汇编器遇到对最终位置位置的目标引用,它就会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。
(2)代码的重定位条目放在.rel.text中。
(3)已初始化的数据的重定位条目放在.rel.data中。
(4)ELF定义了11种不同的重定位类型。两种最基本的重定位类型:
- *R_386_PC32 重定位一个使用32位PC相对地址的引用。
- *R_386_32 重定位一个使用32位绝对地址的引用。
- offset:需要被修改的引用的节偏移
- symbol:标识被修改的引用应该指向的符号
- type:告知链接器如何修改新的引用
3、重定位符号引用
(1)相对引用
(2)绝对引用
八、可执行目标文件
九、加载可执行目标文件
加载器将可执行目标文件中的执行代码和数据从磁盘拷贝到存储器中,然后通过跳转到程序的第一条指令或入口点来运行该程序。这个将程序拷贝到存储器并运行的过程叫做加载。
- 在32位Linux系统中,代码段总是从地址0x08048000处开始。
- 数据段是在接下来的下一个4KB对齐的地址处。
- 运行时堆在读/写段之后接下来的第一个4KB对齐的地址处,并通过调用malloc库往上增长。
- 有一个段是为共享库保留的。
- 用户栈总是最大的合法用户地址开始,向下增长的(向低存储器地址方向增长)。从栈的上部开始的段是为操作系统驻留存储器的部分(也就是内核)的代码和数据保留的。
十、动态链接共享库
1、静态库的缺点:
- 静态库在更新时,使用该库的程序需要与更新的库进行重新链接。
- 由于使用静态库的程序在链接时都会拷贝静态库里被应用程序引用的目标模块,像printf和scanf这样的函数的代码在运行时都会被复制到每个运行进程的文本段中,这造成了冗余,浪费了稀缺的存储器资源。
2、共享库
- 共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并和一个在存储器中的程序链接起来。这个过程称为动态链接,是由一个叫做动态链接器的程序来执行的。
- 共享库也称为共享目标,在Unix系统中通常用.so后缀来表示。微软的操作系统大量地利用了共享库,它们称为DLL(动态链接库)。
- 共享库是以两种不同的方式来“共享”的(在Windows中分别称为“隐式链接”和“显示链接”)。 首先,在任何给定的文件系统中,对于一个库只有一个.so文件。所有引用该库的可执行目标文件共享这个.so文件中的代码和数据,而不是像静态库的内容那样被拷贝和嵌入引用它们的可执行的文件中。其次,在存储器中,一个共享库的.text节 一个副本可以被不同的正在运行的进程共享。
十一、从应用程序中加载和链接共享库
- 用户对GCC使用
-fPIC
选项指示GNU生成PIC代码
十三、处理目标文件的工具
十四、总结
链接可以在编译时由静态编译器来完成,也可以在加载时和运行时由动态链接器来完成。链接器处理成为目标文件的二进制文件,它有三种不同的形式:可重定位的、可执行的和共享的。可重定位的目标文件由静态链接器合并成为一个可执行的目标文件,它可以加载到存储器中并执行。共享目标文件(共享库)是在运行时由动态链接器链接和加载的,或者隐含地在调用程序被加载和开始执行时,或者根据需要在程序调用dlopen库的函数时。