数据结构
PE结构
## 概念 **PE**(Portable Execute)文件是Windows下可执行文件的总称,常见的有 DLL,EXE,OCX,SYS 等。它是微软在 UNIX 平台的 COFF(通用对象文件格式)基础上制作而成。最初设计用来提高程序在不同操作系统上的移植性,但实际上这种文件格式仅用在 Windows 系列操作系统下。PE文件是指 32 位可执行文件,也称为PE32。64位的可执行文件称为 PE+ 或 PE32+,是PE(PE32)的一种扩展形式(不是PE64)。名称 | 描述 |
---|---|
地址 | 是 “虚拟地址” 而不是“物理地址”。不是“物理地址”的原因shi数据在内存的位置经常在变,这样可以节省内存开支、避开错误的内存位置等的优势。同时用户并不需要知道具体的“真实地址”,因为系统自己会为程序准备好内存空间的(只要内存足够大) |
节 | 节 是 PE 文件中 代码 或 数据 的基本单元。原则上讲,节只分为 “代码节” 和 “数据节” 。 |
镜像文件 | 包含以 EXE 文件为代表的 “可执行文件”、以DLL文件为代表的“动态链接库”。因为他们常常被直接“复制”到内存,有“镜像”的某种意思。 |
RVA | Relatively Virtual Address。偏移(又称“相对虚拟地址”)。相对镜像基址的偏移。 |
VA | Virtual Address。基址。 |
PA(物理地址)/VA(虚拟地址)/RVA(相对虚拟地址)
概念
当一个 PE 文件被加载到内存中以后,我们称之为”映象”(image)
每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称作一页或页面(page)。每一页有连续的地址范围。这些页被映射到物理内存,但并不是所有的地址空间必须在内存中才能运行程序。当程序引用一部分在物理内存中的地址空间时,有硬件立刻执行必要的映射。当程序引用的到一部分不在物理内存中的地址空间时,由操作系统负责将缺的部分装入物理内存并重新执行失败的指令。
从某个角度来讲,虚拟内存是对基址寄存器和界限寄存器的一种综合。虚拟存储器的基本思想是程序,数据,堆栈的总的大小可以超过物理存储器的大小,操作系统把当前使用的部分保留在内存中,而把其他未被使用的部分保存在磁盘上。
线性地址
线性地址(Linear Address) 是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386 的线性地址空间容量为 4G(2的32次方即32根地址总线寻址)
逻辑地址
逻辑地址(Logical Address) 是指由程式产生的和段相关的偏移地址部分。例如,你在进行 C 语言指针编程中,能读取指针变量本身值( &操作 ),实际上这个值就是逻辑地址,他是相对于你当前进程数据段的地址,不和绝对物理地址相干。只有在 Intel 实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,cpu不进行自动地址转换);逻辑也就是在Intel保护模式下程式执行代码段限长内的偏移地址(假定代码段、数据段如果完全相同)
分页
大部分虚拟内存中使用一种**分页**(paging)的技术。当程序执行下面的指令时,它把地址为1000的内存单元的内容复制到REG中。地址可以通过索引、基址寄存器、段寄存器或其他方式产生。
1 | MOV REG, 1000 |
由程序产生的这些地址称为虚拟地址(VA),他们构成了一个虚拟地址空间。在没有虚拟内存的计算机上,系统直接将虚拟地址送到内存总线上,读写操作使用具有同样地址的物理内存字;在使用虚拟内存的情况下,虚拟地址不是被直接送到内存的总线上,而是被送到内存管理单元(MMU),MMU把虚拟地址映射为物理内存地址。
MMU
实现VA到PA的映射(因此实现方便的动态内存管理); 实现不同的访问权限。
虚拟地址空间按照固定大小划分称为页面(page)的若干单元。在物理内存中对应的单元称为页框(page frame)。页面和页框的大小通常是一样的。实际系统中的页面大小从512字节到1GB。
转换检测缓冲区
转换检测缓冲区(Translation Lookaside Buffer,TLB):虚拟地址到物理地址的映射必须非常快,所以采用TLB,将虚拟地址直接映射到物理地址。
基地址和偏移地址
cpu和内存之间通过20条地址总线相连接,地址总线就是cpu通过地址找到对应的内存的物理数据的传递工具
例如:有20根地址总线的,总共能拥有 2^20=1048576个不相同的地址,一个地址代表一个存储单元,一个存储单元能够存储1byte数据,总共能存储1M数据。即20根地址总线一共能够处理1M的内存数据
CPU的地址使用16进制表示,最多能够找到2^16=65536个地址(64k内存数据)
这样就要引入段地址的概念,每一个段也就是每一个64K就是一个基地址,段内的数据的地址就是当前基地址的偏移地址。此时通过段地址+偏移地址就能够找到真正的内存数据了
cpu表示的地址为:基地址:偏移地址 (2个16位的地址 2byte)
例如:0BAC:0100
0BAC是基地址,0100是偏移地址,必须要转换成 20位(也就是5位的16进制)才能在20位地址总线中传递,才能达到 1G的数据访问范围:内存的物理地址 =基地址*16+偏移地址,即0BAC*16+0100=0BAC0+0100=0BBC0H
,实际传递二进制就是:0000 1011 1011 1100 0000
32位汇编 32根地址总线总共能够直接找到2的32次方个地址,也就是4294967296 byte数据 也就是 4G的内存,而且不在将内存分成一段一段 所有的内存区域都是连续的
PE文件与内存映像
在执行一个PE文件的时候,windows并不在一开始就将整个文件读入内存的,而是采用与内存映射文件类似的机制。也就是说,windows 装载器在装载的时候仅仅建立好虚拟地址和PE文件之间的映射关系。 当且仅当真正执行到某个内存页中的指令或者访问某一页中的数据时,这个页面才会被从磁盘提交到物理内存,这种机制使文件装入的速度和文件大小没有太大的关系。文件中使用偏移(offset),内存中使用VA来表示位置。VA 与 RVA 满足下面的换算关系: RVA + ImageBase = VA
当 PE 文件被执行时,PE 装载器会为进程分配 4GB 的虚拟地址空间,然后把程序所占用的磁盘空间作为虚拟内存映射到这个4GB的虚拟地址空间中。一般情况下,exe文件默认会映射到虚拟地址空间中的0X400000h的位置,而dll文件默认会定到10000000h。
ImageBase字段
ImageBase字段作用是指出文件的优先装入地址。也就是说当文件被执行时,如果可能的话,Windows优先将文件装入 到由ImageBase字段指定的地址中,只有指定的地址已经被其他模块使用时,文件才被装入到其他地址中。链接器产生可执行文件的时候对应这个地址来生成机器码,所以当文件被装入这个地址时不需 要进行重定位操作,装入的速度最快,如果文件被装载到其他地址的话,将不得不进行重定位操作, 这样就要慢一点。
对于EXE文件来说,由于每个文件总是使用独立的虚拟地址空间,优先装入地址不可能被其他模 块占据,所以EXE总是能够按照这个地址装入,这也意味着EXE文件不再需要重定位信息。对于DLL 文件来说,由于多个DLL文件全部使用宿主EXE文件的地址空间,不能保证优先装入地址没有被其他 的DLL使用,所以DLL文件中必须包含重定位信息以防万一。因此,在IMAGE_FILE_HEADER结构的 Characteristics 字段中,DLL文件对应的IMAGE_FILE_RELOCS_STRIPPED 位总是为0,而EXE文件的这个标志位总是为1。