本书讲述构建程序的关键工具链接器和加载器,内容包括链接和加载、体系结构、目标文件、存储分配、符号管理、库、重定位、加载和覆盖、共享库、动态链接和加载、动态链接的共享库,以及着眼于成熟的现代链接器所做的一些变化;并介绍一个持续的实践项目,即使用Perl语言开发一个可用的小链接器。本书适合高校计算机相关专业的学生、实习程序员、语言设计者和开发人员阅读参考。
自计算机出现以来,链接器和加载器可以说一直是重要的软件开发工具之一。链接器和加载器使得我们可以按照模块来开发程序,而不必开发一个单独的大文件。
早在1947年,程序员就开始使用加载器技术。这是一种很初级的加载器工作方式,如果程序的若干个例程(routine)存储在多个不同的磁带上,那么就借助加载器将它们依次加载到内存中,并将它们合并、重定位以组合成一个程序。在20世纪60年代早期,这些加载器就已经发展得相当完善了,甚至具备编辑的功能。由于当时内存很贵且容量有限,计算机的速度也很慢(以今天的标准),为了充分利用这样的硬件,这些加载器引入了很多复杂的特性。例如,使用复杂的内存覆盖策略解决内存不足的问题,将大容量的程序加载到有限的内存中;使用链接文件重编辑的机制解决算力不足的问题;使用已链接的模块以节省重新编译程序的时间;等等。
20世纪70到80年代,链接技术几乎没有什么进展。链接器趋向于更加简单。虚拟内存技术将应用程序和覆盖机制中的大多数内存管理工作都转移给了操作系统,同时,计算机的处理速度变得越来越快,硬盘容量越来越大,这使得程序员在更新个别模块时也可以重新链接整个程序,而不必仅仅链接修改的地方。从20世纪90年代起,由于增加了诸如动态链接共享库和C 的诸多现代特性,链接器又开始变得复杂起来。处理器技术的发展也促进了链接器的发展。例如,具有长指令字和编译时访存调度等特性的先进处理器架构(在IA64处理器中开始出现)需要将一些新的特性加入链接器中,以确保在链接器中生成的代码可以满足处理器的一些复杂需求,从而充分发挥硬件的新特性。
读者对象
本书可供下述几类读者阅读。
学生:由于链接过程看起来似乎非常简单,操作的过程也很简捷自然,编译原理和操作系统课程通常对链接和加载的过程缺乏重视。对于使用Fortran、Pascal、C进行简单编程的任务,以及不使用内存映射或共享库的操作系统而言,这么做可能是对的;但是现在情况不一样了。C 、Java和其他的面向对象语言需要更加复杂的链接环境。使用内存映射的可执行程序、共享库和动态链接技术都会影响操作系统的很多部分,操作系统的设计者如果忽略链接问题可能会给系统带来很大的麻烦。
程序员:程序员也需要知道链接器都做了什么,尤其是对现代语言而言。C 语言在链接器中引入了很多新的特性,如果不能正确理解这一过程,在链接大型的C 程序时就容易产生一些难以诊断的bug。例如,常见的情况是静态构造函数没有按照程序员预期的顺序执行。反之,如果能正确合理地使用链接器,就能够发挥共享库和动态链接等特性的强大功能,提高程序的灵活性。
编程语言的设计者和开发者。编程语言的设计者应该在构建语言和编译器时了解链接器应该做什么,以及能做什么。在过去的30年中必须借助手工完成的编程细节,今天在C 中已经可以借助链接器自动处理了。(想象一下,如何能在C语言中实现和C 中的模板(template)相同的功能;或者,对于数百个C语言源文件组成的工程,如何保证这些文件中的初始化例程可以在主函数开始之前被正确地执行。为了做到这些,程序员需要完成大量工作。)有了功能更强大的链接器,未来的语言将更加智能,能够自动完成更多的常规任务。由于链接器是编译过程中将整个程序的代码放在一起处理的阶段,因此链接器可以将程序作为一个整体进行变换处理,也可以引入更多的全局程序优化功能。
(编写链接器的人员当然都需要本书。但是全球所有的链接器设计者大概只能坐满一个房间,而且其中至少有一半被邀请作为本书的审阅人,相信他们已经看过本书了。)
章节内容
第1章,链接和加载。这一章对链接的过程进行了简短的历史回顾,并讨论了链接过程中的各个阶段。后通过一个麻雀虽小,五脏俱全的例子来展示链接器的工作过程:对于一个Hello,world程序,我们分析了以编译好的目标文件为输入,生成一个可执行程序的过程。
第2章,体系结构相关问题。这一章从链接器设计的角度分析了计算机体系结构的技术发展方向。我们分析了典型的精简指令集体系结构SPARC,古老而富有活力的寄存器内存体系结构IBM 360/370,以及自成一派的Intel x86体系结构。对于每种体系结构,我们会讨论内存架构、程序寻址架构和指令中的地址格式等重要因素对链接器的影响。
第3章,目标文件。这一章分析了目标文件和可执行文件的内部结构。本章从分析简单的MS-DOS的.COM文件开始,进而不断扩展到其他复杂的文件,包括DOS的EXE文件格式、Windows的COFF格式和PE格式(EXE和DLL)、UNIX的a.out格式和ELF格式以及Intel/Microsoft的OMF格式等。
第4章,存储空间管理。本章介绍了链接过程的个阶段,即以段为单位为被链接的程序分配存储空间。我们以一个实际使用的链接器为例分析了这一过程。
第5章,符号管理。本章介绍了符号绑定和解析的过程,这是一个将符号解析为机器地址的过程,程序中的符号可能在一个文件中被引用,而它的定义出现在另一个文件中。
第6章,库。本章介绍了关于目标代码库创建和使用的
译者序
前言
第1章 链接和加载1
1.1 链接器和加载器做什么1
1.2 从历史发展的角度分析地址绑定1
1.3 链接与加载3
1.3.1 两遍链接4
1.3.2 目标代码库5
1.3.3 重定位和代码修改6
1.4 编译驱动器7
1.5 链接:一个真实的例子9
1.6 练习12
第2章 体系结构相关问题13
2.1 应用程序二进制接口13
2.2 内存地址13
2.3 地址构成规则15
2.4 指令格式15
2.5 过程调用和可寻址性16
2.6 数据访问和指令引用19
2.6.1 IBM 37019
2.6.2 SPARC21
2.6.3 Intel x8623
2.7 分页和虚拟内存24
2.7.1 程序的地址空间26
2.7.2 文件映射27
2.7.3 共享库和程序28
2.7.4 位置无关代码28
2.8 Intel 386分段29
2.9 嵌入式体系结构31
2.9.1 怪异的地址空间31
2.9.2 非统一内存31
2.9.3 内存对齐31
2.10 练习32
第3章 目标文件35
3.1 目标文件中有什么35
3.2 空目标文件格式:MS-DOS的.COM文件36
3.3 代码分段:UNIX的a.out文件36
3.3.1 a.out文件头37
3.3.2 与虚拟内存的交互38
3.4 重定位:MS-DOS的EXE文件41
3.5 符号和重定位43
3.6 可重定位的a.out格式43
3.6.1 重定位项44
3.6.2 符号和字符串44
3.6.3 a.out格式小结45
3.7 UNIX ELF格式45
3.7.1 可重定位文件47
3.7.2 ELF可执行文件51
3.7.3 ELF格式小结52
3.8 IBM 360目标文件格式52
3.8.1 ESD记录53
3.8.2 TXT记录54
3.8.3 RLD记录54
3.8.4 END记录55
3.8.5 小结55
3.9 微软的可移植可执行文件格式55
3.9.1 PE特有区段59
3.9.2 运行PE可执行文件60
3.9.3 PE和COFF61
3.9.4 PE文件小结61
3.10 Intel/Microsoft的OMF文件格式61
3.10.1 OMF记录62
3.10.2 OMF文件的细节63
3.10.3 OMF格式小结65
3.11 不同目标文件格式的比较65
3.12 练习66
3.13 项目66
第4章 存储空间管理69
4.1 段和地址69
4.2 简单的存储布局69
4.3 多种类型的段70
4.4 段与页面的对齐72
4.5 公共块和其他特殊段72
4.5.1 公共块72
4.5.2 C 重复代码消除73
4.5.3 初始化和终结75
4.5.4 IBM伪寄存器76
4.5.5 专用链接表78
4.5.6 x86的存储分配策略78
4.6 链接器控制脚本79
4.7 嵌入式系统的存储分配81
4.8 实际使用的存储分配策略81
4.8.1 UNIX a.out链接器的存储分配策略81
4.8.2 ELF文件中的存储分配策略82
4.8.3 Windows链接器的存储分配策略83
4.9 练习84
4.10 项目85
第5章 符号管理87
5.1 符号名绑定和解析87
5.2 符号表的格式87
5.2.1 模块表89
5.2.2 全局符号表90
5.2.3 符号解析91
5.2.4 特殊符号91
5.3 名称修改92
5.3.1 简单的C和Fortran名称修改92
5.3.2 C 类型编码:类型和范围93
5.3.3 链接时类型检查95
5.4 弱外部符号和其他类型的符号95
5.5 维护调试信息96
5.5.1 行号信息96
5.5.2 符号和变量信息96
5.5.3 实际的问题97
5.6 练习98
5.7 项目98
第6章 库99
6.1 库的目的99
6.2 库的格式99
6.2.1 使用操作系统99
6.2.2 UNIX和Windows的归档文件100
6.2.3 扩展到64位102
6.2.4 Intel OMF库文件102
6.3 创建库文件103
6.4 搜索库文件104
6.5 性能问题105
6.6 弱外部符号105
6.7 练习106
6.8 项目106
第7章 重定位109
7.1 硬件和软件重定位109
7.2 链接时重定位和加载时重定位110
7.3 符号重定位和段重定位110
7.4 基本的重定位技术111
7.4.1 指令重定位112
7.4.2 ECOFF段重定位114
7.4.3 ELF重定位115
7.4.4 OMF重定位116
7.5 可重链接和可重定位的输出格式116
7.6 重定位项的其他格式117
7.6.1 以链表形式组织的引用117
7.6.2 以位图形式组织的引用117
7.6.3 特殊段117
7.7 特殊情况的重定位118
7.8 练习118
7.9 项目119
第8章 加载和覆盖121
8.1 基本的加载过程121
8.2 带重定位的基本加载过程122
8.3 位置无关代码122
8.3.1 TSS/360的位置无关代码123
8.3.2 为每个例程建立的指针表123
8.3.3 目录表123
8.3.4 ELF的位置无关代码124
8.3.5 位置无关代码的开销和收益126
8.4 自举加载127
8.5 基于树状结构的覆盖技术128
8.5.1 定义覆盖技术129
8.5.2 覆盖技术的实现131
8.5.3 覆盖技术的其他细节132
8.5.4 覆盖技术小结132
8.6 练习13