PE 中涉及的地址有四类,它们分别是:
虚拟内存地址(VA)相对虚拟内存地址(RVA)文件偏移地址(FOA)特殊地址要想了解这些概念,需要先简单地了解一下 32 位环境下 Windows 对内存的管理,以及分页机制的原理。
扩展阅读:32 位环境下的 Windows 内存管理
32 位 CPU 的寻址能力为 4GB(即 232 个字节),但有些用户的物理内存达不到这个值。于是操作系统和 CPU 的内存管理单元共同作用,为用户提供了虚拟内存的管理机制。即分页机制。该机制可以让用户感觉自己好像在使用 4GB 的内存。
分页机制的基本原理是: 操作系统假设一个进程独立拥有 4GB 内存,按照某个固定的大小(如 4KB)将这 4GB 空间分成 N(1M)个页。在某一时刻,所有这些页只有一部分和物理内存是对应的(所以这种机制允许物理内存比 4GB 小)。没有物理内存对应的页面被标记为脏(dirty)的页面,一般存储在一个名为“交换文件”的磁盘文件中。在 Windows XP 系统中,交换文件为 pagefile.sys,它位于系统盘的根目录,是一个系统隐藏文件。 当系统需要读取未在内存中的数据时,这部分数据会将内存中不经常读写的页交换出内存,而把要读取的、位于交换文件中的页换进内存。
通过这种存取机制可以让一个进程拥有比实际内存大得多的内存。利用这种机制管理的内存称为虚拟内存。
用户的 PE 文件被操作系统加载进内存后,PE 对应的进程支配了自己独立的 4GB 虚拟空间。在这个空间中定位的地址称为虚拟内存地址(Virtual Address,Va),所以虚拟内存地址的范围是 00000000h~0ffffffffh。在 PE 中进程本身的 VA 被解释为:进程的基地址+相对虚拟内存地址。
一个进程被操作系统加载到虚拟内存空间后,其相关的动态链接库也会被加载。这些同时加载到进程地址空间的文件称为模块。每一个模块在加载时都会有一个基地址,也就是预先告诉操作系统:它会占用 4GB 空间的哪个部分(即从哪里开始存储该模块)。不同模块的基地址一般是不同的,如果两个模块的基地址相同,就由操作系统来决定这两个模块在虚拟空间中的具体位置。
相对虚拟内存地址(Reverse Virtual Address, Rva)是相对于基地址的偏移,即 RVA 是虚拟内存中用来定位某个特定位置的地址,该地址的值是这个特定位置距离某个模块基地址的偏移量,所以说 RVA 是针对某个模块而存在的。 关于 VA 和 RVA 的概念,可以用图 3-4 来表示。
如图 3-4 所示,假设模块 2 的基地址为 0x01000000,而模块 2 中的某个位置距离模块 2 的基地址偏移为 400h,那么值 0x00000400 就是模块 2 中某个位置的 RVA,而值 0x01000400 是该位置的 VA。记住,RVA 是相对于模块而言的,VA 是相对于整个地址空间而言的。
注意 RVA 与具体模块相关,它有一个范围,该范围从模块的开始到模块结束,脱离开这个范围的 RVA 是无效的,称为越界。越界的 RVA 地址没有任何意义。
文件偏移地址(File Offset Address,FOA)和内存无关,它是指某个位置距离文件头的偏移。
在 PE 结构中还有一种特殊地址,其计算方法并不是从文件头算起,也不是从内存的某个模块的基地址算起,而是从某个特定的位置算起。这种地址在 PE 结构中很少见,如在资源表里就出现过这样的地址。