基本信息

编辑推荐
经典著作全新译本,添加大量注释,反思20年间技术的“变”与“不变”
内容简介
目录
前言
第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 370 19
前言
早在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章,库。本章介绍了关于目标代码库创建和使用的相关知识,并分析了库文件的结构和性能问题。
第7章,重定位。本章介绍了地址重定位技术,即调整程序中的目标代码,将指令的目标地址调整为其运行时实际地址的过程。本章还介绍了位置无关代码(Position Independent Code, PIC)的相关技术,使用这种技术构建的代码是无须重定位处理的。本章还分析了这种方法的优势和代价。
第8章,加载和覆盖。本章介绍了加载的过程,即将程序从文件中读取出来并装入计算机内存中以供运行的过程。本章还介绍了覆盖技术,一种基于树状结构实现的内存空间节省技术,是一种古老但是行之有效的技术。
第9章,共享库。本章讨论了在不同程序中共享同一份库代码需要完成的工作。本章主要关注静态链接的共享库。
第10章,动态链接和加载。本章将第9章的讨论延伸至动态链接的共享库,并详细分析了两个具体实例—Win32的动态链接库(DLL)和UNIX/Linux的ELF共享库。
媒体评论
—— Guy Steele,美国计算机科学家
链接器和加载器是连接编程语言、操作系统、编译器和处理器体系结构的咽喉要道,其中包含多种复杂的技术细节,这些技术在本书英文版出版20余年后的今天依然重要。对于后摩尔定律时代的程序员和编程语言设计者,只有深入理解这些知识,才有可能重新定义软件和硬件系统。
本书深入且完整地揭示了编译时和运行时过程。首先通过实例阐述不同的编译器和操作系统中链接和加载过程的差异,在此基础上,提出实用的建议来帮助读者创建更高效的代码。这些建议包括如何规避和Windows DLL相关的陷阱,以及如何充分利用UNIX ELF库模式等。书中将一个完整的开发项目作为贯穿全书的练习,使用Perl语言逐步实现一个简单但完备的链接器,帮助读者掌握相关算法和数据结构。
本书特色
涵盖Windows、UNIX、Linux和BeOS等操作系统的动态链接过程。
解释了Java链接模式,以及它是如何应用在Applet和可扩展Java代码中的。
指导读者编写优雅的代码,构建能够被更加高效地编译、加载和运行的应用程序。
本书网站http://linker.iecc.com提供更多资源,包括免费的代码示例。