目录 Table of Contents
4 PE 文件到内存的映射与区块表
PE 文件到内存的映射
在执行一个 PE 文件的时候, windows 并不在一开始就将整个文件读入内存, 而是采用与内存映射文件类似的机制.
也就是说, windows 装载器在装载的时候仅仅建立好虚拟地址和 PE 文件之间的映射关系.
当且仅当真正执行到某个内存页中的指令或者访问某一页中的数据时, 这个页面才会被从磁盘提交到物理内存, 这种机制让文件装入的速度和文件的大小没有太大关系.
需要的时候才装进内存.
内存分页技术, 操作系统原理, 这里不细讲 : https://www.zhihu.com/question/50796850
但是要注意的是, 系统装载可执行文件的方法又不完全等同于内存映射文件.
内存映射文件就是直接当镜子一样复制过去
当使用内存映射文件的时候, 系统对 "原著" 相当忠实, 如果将磁盘文件和内存映像进行比较, 可以发现不管是数据本身还是数据之间的相对位置, 它们都是完全相同的.
而我们知道, 在装载可执行文件的时候, 有些数据在装入前会被预处理, 比如重定位等等, 正因为如此, 装入以后, 数据之间的相对位置可能发生微妙的变化.正如 PE 结构图所示, 它们有些是折线映射过去的.
windows 装载器在装载 DOS 部分, PE 文件头部分和节表 (Section 表) 部分是不进行任何特殊处理的, 而在装载节 (Section) 的时候则会自动按节 (Section) 的属性做不同的处理.
一般情况下, 它会处理以下方面的内容 :
内存页的属性
对于磁盘映射文件来说, 所有的页都是按照磁盘映射文件函数指定的属性设置的.
但是在装载可执行文件时, 与节对应的内存页属性要按照节的属性来设置.
所以, 在同属于一个模块的内存页中, 从不同节映射过来的内存页属性是不同的.
节的偏移地址
节的起始地址在磁盘文件中是按照 IMAGE_OPTIONAL_HEADER32 结构的 FileAlignment 字段的值进行对齐的, 而当被加载到内存中时是按照同一结构中的 SectionAlignment 字段的值对齐的, 二者的值可能不同, 所以一个节被装入内存后相对于文件头的偏移和在磁盘文件中的偏移可能是不同的.
注意, 节事实上就是相同属性数据的组合 !当节被装入内存时, 相同一个节所对应的内存页都将被赋予相同的页属性, 事实上, windows 对内存属性的设置是以页为单位进行的, 所以节在内存中的对齐单位必须至少是一个页的大小.
对于 32 位操作系统来说, 这个值一般是 4KB == 1000H; 对于 64 位操作系统, 这个值一般是 8KB == 2000H
在磁盘中就没有这个限制, 因为磁盘中的摆放是以空间为主导. 在磁盘只是存放, 不是使用, 所以不用设置那么详细的属性. 试想想看, 如果在磁盘中都是以 4KB 为大小对齐的话, 不够就用 00 来填充, 那么一个只有 20byte 的数据就要消耗 4KB 的空间来存放, 是不是很浪费 ?
节的尺寸
对节的尺寸的处理主要分为两个方面 :
第一个方面, 正如刚刚我们所讲的, 由于磁盘映像和内存映像中对齐存储单位的不同而导致了长度扩展不同 (填充 00 的数量不同);
第二个方面, 是对于未初始化数据的节处理问题. 既然是未初始化, 那么没有必要为其在磁盘中浪费空间资源, 但在内存中不同, 因为程序一运行, 之前未初始化的数据便有可能要被赋值初始化, 那么就必须为他们留下空间.
不需要进行映射的节
有些节并不需要被映射到内存中, 例如 .reloc
节, 重定位数据对于文件的执行代码来说是透明的, 没用的, 它只是提供 windows 装载器使用, 执行代码根本不会去访问它们, 所以没有必要将它们映射到物理内存中.
关于重定位, 比如你要拿一个 dll 里面的函数过来用, 不同程序把这个函数拿过来放着之后, 这个函数的地址是不同的, 我们需要重新定位这个函数的地址才能继续用.
节表 (区块表)
节表, 也称之为区块表
类似于一张地图, 索引
PE 文件中所有节的属性都被定义在节表中
节表由一系列的 IMAGE_SECTION_HEADER 结构排列而成
每个结构用来描述一个节
结构的排列顺序和它们描述的节在文件中的排列顺序是一致的.
全部有效结构的最后一个空的 IMAGE_SECTION_HEADER 结构作为结束, 所以节表中总的 IMAGE_SECTION_HEADER 结构数量等于节的数量加一. 就是一串结构体, 这个串串的尾巴是个空的结构体作为结束标志.
节表总是被存放在紧接着 PE 文件头的地方.
另外, 节表中 IMAGE_SECTION_HEADER 结构的总数总是由 PE 文件头 IMAGE_NT_HEADERS 结构中的 FileHeader.NumberOfSections 字段来指定的.
typedef struct IMAGE_SECTION_HEADER
{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //节表名称, 如 `.text`
// IMAGE_SIZEOF_SHORT_NAME = 8
union{
DWORD PhysicalAddress; // 物理地址
DWORD VitualSize; // 真实长度, 这两个值是一个联合结构, 可以使用其中的任何一个, 一般是取后面一个
}Misc;
DWORD VitualAddress; // 节区的 RVA 地址
DWORD SizeOfRawData; // 在文件中对齐后的尺寸
DWORD PointerToRawData; // 在文件中的偏移量
DWORD PointerToRelocations; // 在 OBJ 文件中使用, 重定位的偏移
DWORD PointerToLinenumbers; // 行号表的偏移 (供调试使用地)
WORD NumberOfRelocations; // 在 OBJ 文件中使用, 重定位项数目
WORD NumberOdLinenumbers; // 行号表中行号的数目
DWORD Characteristics; // 节属性, 如可读, 可写, 可执行等
}IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;