PE 与COFF OBJ 文件格式

PE 与COFF OBJ 文件格式

一个操作系统的可执行文件格式,从许多角度来看,都是操作系统内建行为的一面镜子。
虽然可执行档格式通常并不是一个程序员认为迫切需要学习的东西,但操作系统的许多
有用的知识却可以在这个过程中获得。动态联结、加载器行为、以及内存管理,是特
别容易在这个学习过程中推理而得的三个主题。

在这一章,我要带你走一趟真正的旅游 -- 游览 Portable Executable 文件格式。PE 档是
微软设计用于其所有 Win32 位操作系统(Windows NT 、Windows 95 、Win32s )的可
执行档格式。

你或许会惊讶为什么我要在这本书中涵盖这一章,毕竟在Microsoft Developer Network
CD-ROM 之中已有数篇相关文章。主要的原因是,PE 文件中的结构,同时也是 Windows
95 的关键性数据结构。例如,Windows 95 把 PE 档的表头段(header section )映像到
内存中,并以之表示一个被加载的模块。为了了解 Windows 95 的核心如何工作,你
需要了解PE 格式。就是这么简单!

我探讨 PE 文件的另一个理由是,就像所有出自于微软的文件一样,微软的 PE 文件彷
佛假设你已经和 PE 文件融为一体,因此它非常地不够充份,不够详细。我这一章的目
标是要补充那些文件,并且把它和你每天的经验关联在一起。沿着这样的方向,我会展
示 PE 文件影响操作系统的各种方式。

在可见的未来,PE 格式将在微软的操作系统(包括 Cairo )中扮演一个吃重的角色。甚
至即使你使用 Visual C++ 在 Windows 3.1 中开发程序,你还是要使用PE 文件(因为
Visual C++ 的 32 位 DOS 扩充组件也是使用这种格式)。如果你打算在 Windows 95
内设计任何层面的系统程序,PE 格式的知识更是不可或缺。

在讨论 PE 格式的过程中,我不打算费劲地显示无穷无尽的hex dump (16 位倾印码),
然后解释个别位的重要性(译注)。取而代之的是,我要解释内嵌在其中的观念,并
把它和你每天的 Win32 程序经验关联起来。例如,线程区域变量(thread local variable )
的概念就几乎把我逼疯,直到我了解它是如何精致优雅地在可执行档中实作出来。由于
许多Win32 程序员拥有 Win16 背景,我会想办法让 PE 格式与其 16 位兄弟(NE 格
式)尽量产生一些关联。
译注:事实上,适当追踪具代表性的16 位倾印码,不但不枯躁(我已经在我所主持 的多次研讨会中验证了这个论点),而且经由「铁证如山」的追踪,我们才能从模模糊 糊的视野中解脱出来。我将在我自己的新书中详细解剖 PE 文件格式。

在微软引入这个可执行档格式的同时,它也引入了一个新的目标模块(object module )格
式和函数库(library )格式。这些格式由新的编译器和组译器产生。新的 LIB 格式基本
上只不过是一群 OBJ 档绑在一起,再加上一些索引。所以当我说 OBJ 档时,我说的是
COFF OBJ 和 LIB 档。这些新的 OBJ 和 LIB 格式分享了 PE 格式的许多观念。直到
最近,都还没有公开可用的信息,介绍微软的 OBJ 和 LIB 档。甚至在我下笔的时刻,
资料都还很缺乏。所以,把 OBJ 和 LIB 文件格式也涵盖进来是值得的。

大家都知道,Windows NT (第一个 Win32 操作系统)有 VAX VMS 和 UNIX 的血统。
NT 开发小组中的许多关键人物在进入微软之前就是在那些平台上设计并写码。当他们
开始设计 NT ,很自然地他们会想到使用过去写过并且测试过的工具。那些工具所产生
的可执行档以及目标模块(object module )的格式称为COFF (Common Object File
Format )。

COFF 格式本身已有良好的基础,但还需要扩充以满足现代操作系统如 Windows NT 的
需要。更新的结果就是 PE (Portable Executable )格式。称为可移植性(portable )是因
为任何机器(Intel 386 、MIPS 、Alpha 、Power PC 等等)上的 NT 都可以使用相同的可
执行档格式。当然啦,CPU 指令的二进制编码是完全不同的,你不可能把一个在 MIPS 机
器编译好的 PE 文件拿到 Intel 系统来跑。重要的是,程序加载器以及程序开发工具不需
要针对每一个新的操作系统重写。

微软对于 Windows NT 的高度企图心可以从一件事获得证明:它放弃了自己原有的32 位
元工具和文件格式。Windows 3.x 的虚拟装置驱动程序(VxDs )就是以不同的 32 位
文件布局(LE 格式)呈现,而它早在 NT 跳上舞台之前就出现了。由于 Windows 的
性格是 "if it ain't broke, don't fix it",所以Windows 95 同时使用 PE 和 LE 两种格式。
这使得微软能够继续大规模地使用既有的 Windows 3.x 码。

虽然,预期一个全新的操作系统(如 Windows NT )有一个全新的可执行档格式是合理
的,但面对 object module (.OBJ 和 .LIB )又不尽然是这么回事。在 Visual C++ 32 位
元版 1.0 之前,所有的微软编译器都使用 Intel 的OMF (Object Module Format )规格,
但 Win32 编译器改采 COFF 格式的 OBJ 档。微软的一些竞争者(如 Borland 公司)
则坚持使用 Intel 的 OMF 格式。

一些微软阴谋论者可能会认为改变 OBJ 格式正是微软企图打击对手的证据。因为如果
要宣称能够「与微软兼容」于 OBJ 层次,其它厂商就必须把它们的所有 32 位工具
都转换为 COFF OBJ 和 LIB 格式。简单地说,OBJ 和 LIB 文件格式可视为是微软为
求某种利益而放弃既有标准的又一个例子。

WINNT.H 之中有一些 PE 格式的说明(虽然是极其模糊暧眛),它定义了一些COFF OBJ
用的结构。我将在稍后细部解释时使用这些 WINNT.H 中定义的字段名称。在 WINNT.H
之中有一段名为 "Image Format",从这里开始就是旧的 MZ-DOS 格式和 NE 格式的表
头,然后才是 PE 格式资料。WINNT.H 提供了 PE 文件所需要的数据结构的明确定义,
但只以少量文字说明那些结构、字段、旗标的意义。PE 格式的表头设计者 Michael J.
O'Leary 想必是一位「长名称,具自身说明意义」的忠实拥护者,以及一位「巢状结构和
宏」的忠实拥护者。当面对 WINNT.H 写码时,你得面对像这样的句子:

pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;

除了读取 PE 格式的成份,你还需要把一些 PE 档倾印出来,看看呈现其中的观念。如
果你使用微软的 Win32 开发工具,Visual C++ 和 Win32 SDK 提供的 DUMPBIN 程序
可以解析 PE 档和 COFF OBJ/LIB 档,并以人类可读的形式输出。DUMPBIN 甚至有一
项极佳能力,可以反汇编文件中的 code section 。有鉴于微软曾经宣称你不可以反汇编其
产品,这就有趣了。它竟然供应一个工具,如此轻易地反汇编它的程序和 DLLs 。如果
反汇编 EXEs 和 OBJs 的能力没有用,为什么微软要把这项功能加到 DUMPBIN 之中
呢?似乎这又是一个 "Do as we say ,not as we do"(照着我们所说的做,不要照着我们所
做的做)的例子。

Borland 的使用者可以使用 TDUMP 观察 PE 档,但 TDUMP 并不了解 COFF OBJ
档,这不是个大问题,因为 Borland 根本就不产生 COFF OBJ 档。我自己也写了一个PE
档和 COFF OBJ/LIB 档的倾印工具,名为 PEDUMP ,并且我认为我提供了比DUMPBIN
更容易了解的输出格式。它没有反汇编能力,但其它功能都类似DUMPBIN ,并且还有
一些新功能使它身价高一些。PEDUMP 的原始码放在书附磁盘中,所以我不打算在书中
列出太多。取而代之的是,我要列出 PEDUMP 的一些输出结果,用来解释我所要说的
观念。

你可能感兴趣的:(windows,汇编,object,Module,微软,Borland)