WIN32汇编语言教程:第17章 PE文件 · 17.1 PE文件的结构(3)
这个结构中的大部分字段都不重要,读者可以从注释中理解它们的含义,下面说明的这些字段是比较重要的。
● AddressOfEntryPoint字段
指出文件被执行时的入口地址,这是一个RVA地址(RVA的含义在下一节中详细介绍)。如果在一个可执行文件上附加了一段代码并想让这段代码首先被执行,那么只需要将这个入口地址指向附加的代码就可以了。
● ImageBase字段
指出文件的优先装入地址。也就是说当文件被执行时,如果可能的话,Windows优先将文件装入到由ImageBase字段指定的地址中,只有指定的地址已经被其他模块使用时,文件才被装入到其他地址中。链接器产生可执行文件的时候对应这个地址来生成机器码,所以当文件被装入这个地址时不需要进行重定位操作,装入的速度最快,如果文件被装载到其他地址的话,将不得不进行重定位操作,这样就要慢一点。
对于EXE文件来说,由于每个文件总是使用独立的虚拟地址空间,优先装入地址不可能被其他模块占据,所以EXE总是能够按照这个地址装入,这也意味着EXE文件不再需要重定位信息。对于DLL文件来说,由于多个DLL文件全部使用宿主EXE文件的地址空间,不能保证优先装入地址没有被其他的DLL使用,所以DLL文件中必须包含重定位信息以防万一。因此,在前面介绍的IMAGE_FILE_HEADER 结构的Characteristics字段中,DLL文件对应的IMAGE_FILE_RELOCS_STRIPPED位总是为0,而EXE文件的这个标志位总是为1。
在链接的时候,可以通过对link.exe指定/base:address选项来自定义优先装入地址,如果不指定这个选项的话,一般EXE文件的默认优先装入地址被定为00400000h,而DLL文件的默认优先装入地址被定为10000000h。
● SectionAlignment字段和FileAlignment字段
SectionAlignment字段指定了节被装入内存后的对齐单位。也就是说,每个节被装入的地址必定是本字段指定数值的整数倍。而FileAlignment字段指定了节存储在磁盘文件中时的对齐单位。
● Subsystem字段
指定使用界面的子系统,它的取值如表17.3所示。这个字段决定了系统如何为程序建立初始的界面,链接时的/subsystem:xxx选项指定的就是这个字段的值,在前面章节的编程中我们早已知道:如果将子系统指定为Windows CUI,那么系统会自动为程序建立一个控制台窗口,而指定为Windows GUI的话,窗口必须由程序自己建立。
表17.3 界面子系统的取值和含义
取值 | Windows.inc中的预定义值 | 含义 |
0 | IMAGE_SUBSYSTEM_UNKNOWN | 未知的子系统 |
1 | IMAGE_SUBSYSTEM_NATIVE | 不需要子系统(如驱动程序) |
2 | IMAGE_SUBSYSTEM_WINDOWS_GUI | Windows图形界面 |
3 | IMAGE_SUBSYSTEM_WINDOWS_CUI | Windows控制台界面 |
5 | IMAGE_SUBSYSTEM_OS2_CUI | OS2控制台界面 |
7 | IMAGE_SUBSYSTEM_POSIX_CUI | POSIX控制台界面 |
8 | IMAGE_SUBSYSTEM_NATIVE_WINDOWS | 不需要子系统 |
9 | IMAGE_SUBSYSTEM_WINDOWS_CE_GUI | Windows CE图形界面 |
● DataDirectory字段
这个字段可以说是最重要的字段之一,它由16个相同的IMAGE_DATA_DIRECTORY结构组成,虽然PE文件中的数据是按照装入内存后的页属性归类而被放在不同的节中的,但是这些处于各个节中的数据按照用途可以被分为导出表、导入表、资源、重定位表等数据块,这16个IMAGE_DATA_DIRECTORY结构就是用来定义多种不同用途的数据块的(如表17.4所示)。IMAGE_DATA_DIRECTORY结构的定义很简单,它仅仅指出了某种数据块的位置和长度。
IMAGE_DATA_DIRECTORY STRUCT VirtualAddress DWORD ? ;数据的起始RVA isize DWORD ? ;数据块的长度 IMAGE_DATA_DIRECTORY ENDS
表17.4 数据目录列表的含义
索引 | 索引值在Windows.inc中的预定义值 | 对应的数据块 |
0 | IMAGE_DIRECTORY_ENTRY_EXPORT | 导出表 |
1 | IMAGE_DIRECTORY_ENTRY_IMPORT | 导入表 |
2 | IMAGE_DIRECTORY_ENTRY_RESOURCE | 资源 |
3 | IMAGE_DIRECTORY_ENTRY_EXCEPTION | 异常(具体资料不详) |
4 | IMAGE_DIRECTORY_ENTRY_SECURITY | 安全(具体资料不详) |
5 | IMAGE_DIRECTORY_ENTRY_BASERELOC | 重定位表 |
6 | IMAGE_DIRECTORY_ENTRY_DEBUG | 调试信息 |
7 | IMAGE_DIRECTORY_ENTRY_ARCHITECTURE | 版权信息 |
8 | IMAGE_DIRECTORY_ENTRY_GLOBALPTR | 具体资料不详 |
9 | IMAGE_DIRECTORY_ENTRY_TLS | Thread Local Storage |
10 | IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG | 具体资料不详 |
11 | IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT | 具体资料不详 |
12 | IMAGE_DIRECTORY_ENTRY_IAT | 导入函数地址表 |
13 | IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT | 具体资料不详 |
14 | IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR | 具体资料不详 |
15 | 未使用 |
在PE文件中寻找特定的数据时就是从这些IMAGE_DATA_DIRECTORY结构开始的,比如要存取资源,那么必须从第3个IMAGE_DATA_DIRECTORY结构(索引为2)中得到资源数据块的大小和位置;同理,如果要查看PE文件导入了哪些DLL文件的哪些API函数,那就必须首先从第2个IMAGE_DATA_DIRECTORY结构得到导入表的位置和大小。
从排列位置来看,PE文件在DOS部分和PE文件头部分以后就是节表和多个不同的节(如图17.1中的③和④所示)。要理解什么是节表,什么是节以及它们之间的关系,那就首先要了解Windows是如何将PE文件映射到内存的。
1. PE文件到内存的映射
在执行一个PE文件的时候,Windows并不在一开始就将整个文件读入内存,而是采用与内存映射文件类似的机制,也就是说,Windows装载器在装载的时候仅仅建立好虚拟地址和PE文件之间的映射关系,只有真正执行到某个内存页中的指令或者访问某一页中的数据时,这个页面才会被从磁盘提交到物理内存,这种机制使文件装入的速度和文件大小没有太大的关系。
图17.2 PE文件到内存的映射
但是系统装载可执行文件的方法又不完全等同于内存映射文件。当使用内存映射文件时,系统对“原著”非常忠实,如果将磁盘文件和内存映像对比一下,可以发现不管是数据本身还是数据之间的相对位置都是完全相同的。而装载可执行文件的时候,有些数据在装入前会被预先处理(如需要重定位的代码),而装入以后,数据之间的相对位置也可能改变,如图17.2所示,一个节的偏移和大小在装入内存前后可能是完全不同的。
Windows装载器在装载DOS部分、PE文件头部分和节表部分时不进行任何处理,而装载节的时候将根据节的属性做不同的处理,一般需要处理以下几个方面的内容。
上页:第17章 PE文件 · 17.1 PE文件的结构(2) 下页:第17章 PE文件 · 17.1 PE文件的结构(4)