EXE文件的重定位

这份文档基于EXE的文件格式 https://www.cnblogs.com/zxyLeaf/articles/14132526.html

前面在解释文件头的第[6-7] ,[18h-19h]字节含义时并没有说明什么是重定位表,什么是重定位项。因为这涉及到一个重要的概念叫做重定位。 这一小节就来说明什么是重定位。

背景知识及定义

一个.asm汇编文件,经过编译器编译后生成.obj文件,再由链接器(linker)链接生成.exe文件,也就是可执行程序,这个可执行程序被保存在硬盘(disk)中。当我们运行这个程序时,加载器(loader)会把在硬盘中的可执行文件加载到内存(memory)上的空余位置,这时运行中的程序会就是进程。

当编译器,链接器在生成可执行文件时,并不会知道程序在真正运行时加载到哪一个物理地址上,所以编译器对于段,变量,函数的编译均采用相对地址的形式进行。但是程序真正在内存中运行时,CPU是需要知道段,变量等真正的物理地址的。

因此,在加载程序到内存时,谁来将编译器产生的相对地址转换成绝对物理地址呢?这个操作又是如何完成的呢?

上面的第一个问题就是重定位要解决的事情,这里我们给出重定位的定义:

操作系统根据EXE文件头中的重定位表将程序中引用的段地址进行修正的过程称为重定位

接下里的内容由两大部分组成:第一部分在验证在编译完成后,EXE文件中的变量,段址记录的都是相对值;第二部分用来说明重定位的具体过程。

储存在硬盘中的EXE文件

hello2.asm的具体代码如下:

data segment; 1000:0000
abc db "Hello, abc!$"; 12字节=0Ch字节 
    db 10h dup(0); 16字节
data ends

code segment; 
assume cs:code, ds:data, ss:stk
begin:
   mov ax, data
   mov ds, ax
   mov ah, 9
   mov dx, offset abc
   int 21h
   mov ax, code; 编译时code被设成以下值
               ; (code段-首个段的距离)/10h
   mov ds, ax
   mov ah, 9
   mov dx, offset xyz
   int 21h
   mov ah, 4Ch
   int 21h
xyz db "Hello, xyz!$"
main:
   jmp begin
code ends

stk segment stack
db 100h dup('$'); 为了方便查看,将堆栈中的初始值改为'$'
stk ends
end main

通过Qview打开hello2.exe文件,跳过200h字节的文件头,来到程序开始的地方

可以看到编译后源文件中的段,变量被编译成了相对地址

  • mov ax, data--------------------mov ax, 0000-----------------data与第一个段地址的差(data)=0
  • mov dx, offset abc---------mov dx, 0000-----------------abc位置与该段段首的差=(0200Ch-0200h)=0h
  • mov ax, code-------------------mov ax, 0002-----------------code与data段地址的差=(0220h-0200h)/10h=2h
  • mov dx, offset xyz---------mov dx, 001C-----------------zxy位置与该段段首的差=(023Ch-0220h)=1Ch

根据上述结果,我们可以知道,在程序加载到内存中开始运行时,我们只要得到每个段的物理地址,我们就可以计算出每个变量的物理地址。那么要如何根据程序的代码段来算出每个段的物理地址呢?这里涉及到两个问题:

  1. 如何判断编译后的exe文件中指令中的立即数是一个地址?例如,mov ax,0000这条指令中0000是一个相对地址还是一个立即数呢?

    这就是文件头中重定位表项的作用了。重定位项一定指向需要重定位的段地址所在的地方

  2. 根据重定位项查到一个相对地址后,重定位的具体过程是怎样的?

    操作系统把需要重定位的段地址取出来,和首段的段地址相加后再写回去

    等重定位完成后,设置ds=es=psp设置ss:sp,再jmp cs:ip

重定位表

重定位表是由重定位项组成的,每一个重定位项指向需要重定位的段地址所在的地方。为了确定重定位项指向的具体地址,EXE文件头采用Δ段地址:偏移量的方式。

所以,每一个重定位项含有四个字节:其中前两个字节是需要重定位的数据的偏移地址,后两个字节是段地址的Δ值,即需要(重定位的段地址-首段段地)址。

读懂文件头中的重定位表

现在,我们通过具体的例子来说明重定位的过程。

  • [6-7]字节:重定位表项的个数
  • [18h-19h]字节:重定位表的相对文件头的偏移位置
  • 根据[18h-19h]字节找到重定位表,以及[6-7]字节确定重定位表的长度= 重定位表项的个数*4 个字节

继续看hello2.exe的文件头

00000000: 4d5a 5001 0200 0200 2000 0000 ffff 0500 MZP..... .......
00000010: 0001 e80e 2800 0200 1e00 0000 0100 0100 ....(...........
00000020: 0200 0d00 0200 0000 0000 0000 0000 0000 ................

加粗的部分均为在文件头中和重定位有关系的字节

  • [6-7]字节表示重定位表项的个数=0002h
  • [18h-19h]字节表示重定位表的相对文件头的偏移位置位001eh
  • [1eh-25h]字节表示重定位表中的两项
    • 第一项:偏移量=0001h, Δ段地址=0002h
    • 第二项:偏移量=000dh, Δ段地址=0002h

获得程序首段地址:程序的首段地址=psp+10h

假设程序首段地址=1010h,第一项重定位项的重定位过程如下:

  1. 确定需要重定位的位置:(1010+0002):0001=1012:0001
  2. 更改该位置中的段地址值:word ptr 1012:[0001] += 1010h

具体例子

  1. 通过td调试hello2.exe文件

  1. 当前ds=es=psp的段地址,首段地址=psp+10h=5292h+10h=52A2h

  2. 根据重定位项的第一项:偏移量=0001h, Δ段地址=0002h,找到需要重定位的地方是在52A4:0001

  3. 然后进行重定位:word ptr 52A4:[0001]=0000h+首段地址=0000h+52A2h=52A2h

    也就是mov ax,52A2这条指令中的52A2