WIN32汇编语言教程:第17章 PE文件 · 17.5 重定位表(1)

什么是重定位,代码又是在什么情况下才需要重定位呢?这个问题早在13.4.2节中就回答过了,那就是在32位代码中,涉及到直接寻址的指令都是需要重定位的(而在DOS的16位代码中,只有涉及到段操作的指令才是需要重定位的,对此有兴趣的读者可以参考相关的资料),虽然13.4.2节的例子中给出了一段不需要重定位的代码,但这段代码的精髓在于将所有的直接寻址指令用寄存器寻址方式代替,如果这种方法成为操作系统处理重定位问题的标准办法,那就相当于不存在直接寻址指令了,这在编程中带来的麻烦是不可想像的,所以那种能自身完成重定位的代码只能在小范围内使用。

对于操作系统来说,其任务就是在对可执行程序透明的情况下完成重定位操作,在现实中,重定位信息是在编译的时候由编译器生成并被保留在可执行文件中的,在程序被执行前由操作系统根据重定位信息修正代码,这样在开发程序的时候就不用考虑重定位问题了。

重定位信息在PE文件中被存放在重定位表中,本节要讨论的就是重定位表的结构和使用方法。

17.5.1 重定位表的结构

1. 重定位所需的数据

在开始分析重定位表的结构之前需要了解两个问题:第一,对一条指令进行重定位需要哪些信息;第二,这些信息中哪些应该被保存在重定位表中。下面举例来说明这两个问题。

作为例子,现将13.4.2节中的那段代码搬回来:

:00400FFC 0000                                         ;dwVar变量
:00401000 55            push ebp
:00401001 8BEC         mov ebp, esp
:00401003 83C4FC       add esp, FFFFFFFC
:00401006 A1FC0F4000   mov eax, dword ptr [00400FFC]  ;mov eax,dwVar
:0040100B 8B45FC       mov eax, dword ptr [ebp-04]    ;mov eax,@dwLocal
:0040100E 8B4508       mov eax, dword ptr [ebp+08]    ;mov eax,_dwParam
:00401011 C9            leave
:00401012 C20400       ret 0004
:00401015 68D2040000   push 000004D2
:0040101A E8E1FFFFFF   call 00401000                  ;invoke Proc1,1234

其中地址为00401006h处的mov eax,dword ptr [00400ffc]就是一句需要重定位的指令,当整个程序的起始地址位于00400000h处的时候,这句代码是正确的,假如将它移到00500000h处的时候,这句指令必须变成mov eax,dword ptr [00500ffc]才是正确的。这就意味着它需要重定位。

让我们看看需要改变的是什么,重定位前的指令机器码是A1 FC 0F 40 00,而重定位后将是A1 FC 0F 50 00,也就是说00401007h开始的双字00400ffch变成了00500ffch,改变的正是起始地址的差值(00500000h-00400000h)=00100000h。

所以,重定位的算法可以描述为:将直接寻址指令中的双字地址加上模块实际装入地址与模块建议装入地址之差。为了进行这个运算,需要有3个数据,首先是需要修正的机器码地址;其次是模块的建议装入地址;最后是模块的实际装入地址。这就是第一个问题的答案。

在这3个数据中,模块的建议装入地址已经在PE文件头中定义了,而模块的实际装入地址是Windows装载器确定的,到装载文件的时候自然会知道,所以第二个问题的答案很简单,那就是应该被保存在重定位表中的仅仅是需要修正的代码的地址。

事实上正是如此,PE文件的重定位表中保存的就是一大堆需要修正的代码的地址。

2. 重定位表的位置

重定位表一般会被单独存放在一个可丢弃的以“.reloc”命名的节中,但是和资源一样,这并不是必然的,因为重定位表放在其他节中也是合法的,惟一可以肯定的是,如果重定位表存在的话,它的地址肯定可以在PE文件头中的数据目录中找到。如表17.4所示,重定位表的位置和大小可以从数据目录中的第6个IMAGE_DATA_DIRECTORY结构中获取。

3. 重定位表的结构

虽然重定位表中的有用数据是那些需要重定位机器码的地址指针,但为了节省空间,PE文件对存放的方式做了一些优化。

在正常的情况下,每个32位的指针占用4个字节,如果有n个重定位项,那么重定位表的总大小是4×n字节大小。

直接寻址指令在程序中还是比较多的,在比较靠近的重定位表项中,32位指针的高位地址总是相同的,如果把这些相近表项的高位地址统一表示,那么就可以省略一部分的空间,当按照一个内存页来分割时,在一个页面中寻址需要的指针位数是12位(一页等于4096字节,等于2的12次方),假如将这12位凑齐16位放入一个字类型的数据中,并用一个附加的双字来表示页的起始指针,另一个双字来表示本页中重定位项数的话,那么占用的总空间会是4+4+2×n字节大小,计算一下就可以发现,当某个内存页中的重定位项多于4项的时候,后一种方法的占用空间就会比前面的方法要小。

PE文件中重定位表的组织方法就是采用类似的按页分割的方法,从PE文件头的数据目录中得到重定位表的地址后,这个地址指向的就是顺序排列在一起的很多重定位块,每一块用来描述一个内存页中的所有重定位项。

每个重定位块以一个IMAGE_BASE_RELOCATION结构开头,后面跟着在本页面中使用的所有重定位项,每个重定位项占用16位的地址(也就是一个word),结构的定义是这样的:

IMAGE_BASE_RELOCATION STRUCT
VirtualAddress dd  ?      ;重定位内存页的起始RVA
SizeOfBlock dd     ?      ;重定位块的长度
IMAGE_BASE_RELOCATION ENDS

VirtualAddress字段是当前页面起始地址的RVA值,本块中所有重定位项中的12位地址加上这个起始地址后就得到了真正的RVA值。SizeOfBlock字段定义的是当前重定位块的大小,从这个字段的值可以算出块中重定位项的数量,由于SizeOfBlock=4+4+2×n,也就是sizeof IMAGE_BASE_RELOCATION+2×n,所以重定位项的数量n就等于(SizeOfBlock-sizeof IMAGE_BASE_RELOCATION)÷2。

IMAGE_BASE_RELOCATION结构后面跟着的n个字就是重定位项,每个重定位项的16位数据位中的低12位就是需要重定位的数据在页面中的地址,剩下的高4位也没有被浪费,它们被用来描述当前重定位项的种类,其定义如表17.7所示。

表17.7 重定位项高4位的含义

高4位值在Windows.inc中的预定义值含   义
0IMAGE_REL_BASED_ABSOLUTE这个重定位项无意义,仅仅用来作为对齐用
1IMAGE_REL_BASED_HIGH重定位地址指向的双字中,仅仅高16位需要被修正
2IMAGE_REL_BASED_LOW重定位地址指向的双字中,仅仅低16位需要被修正
3IMAGE_REL_BASED_HIGHLOW重定位地址指向的双字的32位都需要被修正,这是修正算法中举例的情况
4IMAGE_REL_BASED_HIGHADJ不详
5IMAGE_REL_BASED_MIPS_JMPADDR不详
6IMAGE_REL_BASED_SECTION不详
7IMAGE_REL_BASED_REL32不详

虽然高4位定义了多种重定位项的属性,但实际上在PE文件中只能看到0和3这两种情况。

所有的重定位块最终以一个VirtualAddress字段为0的IMAGE_BASE_RELOCATION结构作为结束,读者现在一定明白了为什么可执行文件的代码总是从装入地址的1000h处开始定义的了(比如装入00400000h处的.exe文件的代码总是从00401000h开始,而装入10000000h处的.dll文件的代码总是从10001000h处开始),要是代码从装入地址处开始定义,那么第一页代码的重定位块的VirtualAddress字段就会是0,这就和重定位块的结束方式冲突了。

下面的例子举出了一个重定位表的实际情况,假设模块被装入00400000h处:

重定位表偏移 数据            说明
0000h      00001000h      第一个块:页面起始地址是00401000h
0004h      00000010h      重定位块长度是10h
0008h      3012h          16位重定位项,重定位位置:00401012h
000ah      3040h          16位重定位项,重定位位置:00401040h
000ch      306fh          16位重定位项,重定位位置:0040106fh
000eh      0000h          用于对齐的空白数据
0010h      00002000h      第二个块:页面起始地址是00402000h
0014h      0000000ch      重定位块长度是0ch
0018h      3080h          16位重定位项,重定位位置:00402080h
001ah      30f0h          16位重定位项,重定位位置:004020f0h
001ch      00000000h      重定位数据块结束

上页:第17章 PE文件 · 17.4 资源(4) 下页:第17章 PE文件 · 17.5 重定位表(2)

第17章 PE文件

版权所有 © 中山市飞娥软件工作室 证书:粤ICP备09170368号