所有PE节都加载到内存中的相同基地址吗?如果没有,PIC代码如何找到其他部分的数据?
例如,假设我有如下代码:
.text
mov rax, myData
.data
myData: ...
对于 PIC 代码,
mov
必须使用相对寻址。我明白这是如何工作的,如果VA(.data) - FileOffset(.data) == VA(.text) - FileOffset(.text)
。 Windows PE 加载程序可以保证这一点吗?如果没有,像上面这样的 PIC 代码如何找到 myData
的相对地址?
在 Windows(或 UEFI)中加载 DLL 或 EXE 时,操作系统会为程序的内存映射选择一个“基地址”。程序中的所有内存地址(全局变量、函数)都相对于该基地址定位,在 Win32 文档中称为“图像基地址”。 PE 部分也以相对于该图像库的偏移量加载。以下是 Windows notepad.exe 中的示例,使用
dumpbin /headers
:
SECTION HEADER #2
.rdata name
92A8 virtual size
26000 virtual address (0000000140026000 to 000000014002F2A7)
9400 size of raw data
24A00 file pointer to raw data (00024A00 to 0002DDFF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
40000040 flags
Initialized Data
Read Only
加载到内存中后,记事本
.rdata
部分的内容将加载到
image_base + 0x26000
。因为每个部分都相对于相同的图像基地址,所以加载图像中任意两点之间的距离始终是一个常数。
注意:同一图像中任意两点之间的距离恒定仅适用于
loaded图像。在磁盘上,PE文件经常会省略空白区域,并且有对齐的考虑。 现在回到您的汇编代码。您编写的代码与位置无关。当您这样编写时,将会应用运行时修复(“重定位”)来使对
myData
的引用起作用。一个更好的例子是使用 x86-64 的 RIP 相对寻址模式:
.text
lea rax, [myData]
.data
myData: ...
这由汇编器翻译为
lea rax, [rip + ???]
。链接器用 ??? 填充此内容。成为
myData
和 lea
指令之间的距离(实际上是紧随其后的指令的地址)。因为这样的距离是恒定的,所以 DLL 中的这条指令会被硬编码,不需要重新定位。
现代操作系统的程序加载器都是这样工作的,但有特定于操作系统的怪癖。