.reloc 节和 base relocation table 仅存在于 Image 文件中,用于当映像被加载运行的 ImageBase 改变后,对映像内的使用的地址进行重定位。
需要进行 ImageBase 重定位的映像性能会变得很糟糕。32 位的应用程序在 64 位系统上运行就是一个典型的例子。
下面以前面的 32 位 helloworld.exe 映像为例子
编译器会为每个映像建立一个首选的 ImageBase 值,ImageBase 是映像被加载运行时所有地址的基地址,因此 ImageBase 显得非常重要。
典型地 helloworld.exe 映像的 ImageBase 被设为:0x00400000
OPTIONAL HEADER VALUES |
32 位映像的 ImageBase 都被设为 0x00400000,而 64 位映像的 ImageBase 被设为 0x00000001_40000000
下面是从 helloworld.exe 映像中的 .text 节摘下来的数据:
00411490: 6A 64 68 00 72 41 00 6A 67 8B 45 08 50 FF 15 C0 jdh.rA.jg.E.P?.à |
注意上面的蓝色粗体部分,实际上这是一条 call 指令的机器代码,这条指令是:
0041149D: FF 15 C0 83 41 00 call dword ptr [004183C0h] |
实际上,这就是调用 LoadString() 函数的一条指令,LoadString() 的地址就放在 0x004183C0 地址上。
当映像被加载后的 ImageBase 还是 0x00400000,那么这个映像就不需要 ImageBase 重定位,但是很遗憾,特别是当 32 位程序在 64 位系统上运行时,这个情况就发生。
在我的实例中,这一次 helloworld.exe 的运行,ImageBase 被加载到 0x012b0000,没错就需要进行 ImageBase 重定位处理。
call dword ptr [004183C0h] |
这个情况下,这个 0x004183C0 地址就会产生错误,加载器需要将它重新重位在:0x012C83C0 地址上。
这个 0x012C83C0 是等于:0x004183C0 - 0x00400000 + 0x012b0000 = 0x012C83C0
计算方法就是得到基于 ImageBase 的偏移量再加上新的 ImageBase 值,这个 0x012C83C0 就是正确的函数地址:
012C14B9 FF 15 C0 83 2C 01 call dword ptr [__imp__LoadStringW@16 (12C83C0h)] |
上面就是 visual studio 2010 调试下得出的 call 指令
现在转入正题,看看 helloworld.exe 映像的 .reloc 节,如下表:
域
|
.reloc 节
|
VirtualSize |
0x00000564
|
VirtualAddress |
0x00028000
|
SizeOfRawData |
0x00000600
|
PointerToRawData |
0x00015400
|
PointerToRelocations |
0
|
PointerToLinenumbers |
0
|
NumberOfRelocations |
0
|
NumberOfLinenumbers |
0
|
Characteristics |
0x42000040
|
helloworld.exe 的 .reloc 位置在 0x00428000(ImageBase + VritualAddress),它在映像文件的位置是 0x00015400 占用 0x600 bytes 的文件空间
域
|
base relocation bale
|
VirtualAddress
|
0x00028000
|
size
|
0x340
|
base relocation table 存放在 .reloc 节里,大小为 0x340 bytes
base relocation table 由 IMAGE_BASE_RELOCATION 结构和它的 Entry 组成
这个结构在 WinNT.h 的定义为:
// typedef struct _IMAGE_BASE_RELOCATION { |
VirtualAddress 是 base relocation table 的位置,它是一个 RVA 值,SizeOfBlock 是 Base Relocation Table 的大小
Entry 结构只有一个 WORD 值,它紧跟着 IMAGE_BASE_RELOCATION 结构后面,但是这个 WORD 却分为两个部分:
位域
|
位
|
长度
|
描述
|
Type
|
[15:12]
|
4 Bits
|
高 4 位用来表示 Base Relocation Table 的类型
|
Offset
|
[11:0]
|
12 Bits
|
低 12 位是 RVA 值,指出需要重定位的位置
|
这个 12 位的 Offset 值是基于 IMAGE_BASE_RELOCATION 结构的 VirtualAddress, 而 VirtualAddress 是基于 ImageBase 的 RVA 值
因此最终的 Offset 值应该是 ImageBase + VirtualAddress + Offset
在 WinNT.h 中定义了 Base Relocation Table 的类型:
// #define IMAGE_REL_BASED_ABSOLUTE 0 |
大部分的 Base Relocation Table 类型都是 IMAGE_REL_BASED_HIGHLOW 类型,表示被重定位的是 32 位的值。
举个例子,有如下的 Entry 值:
这个 Entry 是 0x3493,那么 Base Relocation Table 类型是 3 也就是 IMAGE_REL_BASED_HIGHLOW 类型,它的 Offset 值是 0x0493,那么需要重定位的位置在 0x00411493
这个值计算方法是:ImageBase + VirtualAddress + Offset = 0x00400000 + 0x11000 + 0x493 = 0x00411493
00428000 00 10 01 00 // VirtualAddress = 0x00011000
00428008 93 34 // type = 3, offset = 493
004280DC 00 20 01 00 // VirtualAddress = 0x00012000 004280E4 0C 30 // type = 3, offset = 00C ... ... |
第 1 个 base relocation table 的 size 是 0xDC bytes,因此,下一个 base relocation table 就在 0x004280DC 处
下面是使用 dumpbin 得出的 base relocation table 结果:
BASE RELOCATIONS #7
12000 RVA, 120 SizeOfBlock |
与 dumpbin 的结果是相符的。