标 题: 读书笔记重定位
作 者: haoxf
作者QQ号: 282074009
时 间: 2008-11-22,09:15
关于我:http://hi.baidu.com/dp282074009/blog...70df4d248.html

  在学习重定位之前,有必要知道重定位的用途。
  在实模式下,内存使用二维的编址方式。即地址的形式为“段基址:段内偏移”。这是一个逻辑地址,必须经过逻辑地址到绝对地址的转换后才能访问到所需的数据。那为什么要采用这种二维地址呢,为什么不直接用绝对地址,用二维地址又有什么好处?
  原因很简单,现在的操作系统是一个多任务的环境。即使在DOS的单用户单任务环境下,任何时刻内存中也不只存在一个程序。意思就是,不可能使每个程序都加载到它们希望的地址上(在Windows下,每个应用程序都有自己的4G地址空间。其自身当然能加载到其希望的地址上,但对于其加载的DLL,除第一个外剩余的往往都需要重定位)。如果使用绝对地址,则未加载到希望位置的程序将不能正确执行。对于使用二维地址编址,这个问题将大大简化。对于一个同段内的代码,其相对于段基地址的偏移是固定的,只需根据加载的实际地址修改段基址便可使程序正确运行。而段基址一般由相应的段寄存器给出,则只需修改段寄存器即可完成重定位。使用二维地址方式,大大减化了重定位问题。即使程序没有被加载到希望的位置也能得到正确地执行。二维地址xxxx:yyyy与绝对地址的转换关系如下:

            绝对地址 = xxxx0 + yyyy ;

  即绝对地址等于段值左移四位与段内偏移相加。对于80x86来说,实模式下处理器使用20位地址线,寻址空间为1M。其段值理应为20位,但Intel作了一个规定:程序段段基址的低4位必须为0,即分段时段基址必须是定于16字边界。有了这个规定,段基址只需16位即可表示。如一个二维地址为1000:1000,则其对应的绝对地址为10000+1000,即11000。
  实模式下最大物理地址为FFFF:FFFFh,即10FFEFh。从地址000000h到10FFFFh则是实模式下的地址空间,包含10FFF0h个字节。所以准备地说,实模式下的寻址范围不是1M,而是1114095字节,近似等于1088KB(比1088KB少16字节)。
  对于8086/8088而言,由于没有A20地址线,最大物理地址限定在FFFFFh。即上面提到的最大物理地址对于只有20位地址的8086/8088而言是不可送到的。如二维地址FFFF:FFFFh,只能转换成0FFEFh(最高位的1因为没有多余的地址线来表示它而丢失)。其相应的最大物理地址的逻辑地址是F000:FFFFh或FFFF:000Fh(注意,此处的多个二维地址映射到相同的物理地址,意为着段是可以重叠的)。当二维地址转换为绝对地址的过程中,出现地址过大而使高位地址丢失的现象,就是我们平时常听到的“内存环绕”(大于FFFFFh的绝对地址只能映射到1M内存范围内)。由于1M地址空间中有一部分用作它用,如BIOS区,显示缓冲区,各种接口卡区等,再加上DOS的驻留,应用程序真正能使用的只有640KB左右的内存。通常称为常规内存。
  以下介绍程序是如何装入内存,从而变成在计算机内可执行的形式的。
  在用汇编语言或高级语言编写的程序中,是通过符号名来访问子程序和数据的,我们把程序中符号名的集合叫做“名字空间”。汇编语言源程序经过汇编,或者高级语言源程序经过编译,得到的目标程序是以“0”作为参考地址的模块,然后多个目标模块由连接程序连接成一个具有统一地址的装配模块,以便最后装入内存中执行。我们把目标模块中的地址称为相对地址(或逻辑地址),而把相对地址的集合叫做“相对地址空间”或简单地叫做“地址空间”。
  装配模块虽然具有统一的地址空间,但它仍是以“0”作为参考地址,即是浮动的。要把它装入内存执行,就要确定装入内存的实际物理地址,并修改程序中与地址有关的代码,这一过程叫做地址重定位。
  地址空间的程序和数据经过地址重定位处理后,就变成了可由CPU直接执行的绝对地址程序。我们把这一地址集合称为“绝对地址空间”或“存储空间”。
  地址重定位完成的相对地址转换成内存的绝对地址工作又称为地址映射(map)、按照重定位的时机,可分为静态重定位和动态重定位。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

一、静态重定位

  DOS采用的是静态定位。静态重定位是在程序执行之前进行重定位,它根据装配模块将要装入的内存起始位置,直接修改装配模块中的有关使用地址的指令。
  例如,一个以“0”作为参考地址的装配模块,要装入以0100H为起始段基值的存储空间。显然,在装入之前要做某些修改,程序才能正确执行。例如,JMP 0500:0100h这条指令的意义,是跳转到绝对地址6000H处。现在跳转的实际地址为6100h,即在地址6000h(0500:0100h)的基础上加上装入的地址(1000),因此,JMP 0500:0100h这条指令中的直接地址码也要相应地加上起始地址,而成为JMP 0600:0100h。
  程序中涉及直接地址的每条指令都要进行这样的修改。需要修改的位置称为重定位项,所做的加实际装入模块起始地址修改中的块起始地址称为重定位因子。
  为支持静态重定位,连接程序在生成统一地址空间和装配模块时,应产生一个重定位项表,连接程序此时还不知道装配模块将要装入的实际位置,故重定位表所给出的需修改位置是相对地址所表示的位置。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  在详细讲解重定位之前,再讲一下DOS下EXE文件的结构:

              ┏━━━━━━━━━━━━━━━━━━┓  ┓
              ┃     00~01H:EXE文件标识:MZ或ZM  ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃     02~03H:末页字节数         ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃     04~05H:文件页数              ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃     06~07H:重定位表项数         ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃     08~09H:文件头节数             ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃     0A~0BH:所需最小节数        ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃     0C~0DH:期望节数              ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃     0E~0FH:SS初始值          ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┣ .EXE头部,至少为512字节;
              ┃     10~11H:SP初始值              ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃     12~13H:文件校验和          ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃     14~15H:IP初始值              ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃     16~17H:CS初始值          ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃     18~19H:重定位项偏移           ┃┓┃
              ┣━━━━━━━━━━━━━━━━━━┫┃┃
              ┃     1A~1BH:覆盖号           ┃┃┃
              ┣━━━━━━━━━━━━━━━━━━┫┃┃
              ┃              ... ...               ┃┃┃
              ┣━━━━━━━━━━━━━━━━━━┫┣┃;18~19H处的重定位项偏移指向这里;
              ┃         重定位项1          ┃┛┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃         重定位项2          ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┃
              ┃              ... ...               ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┛
              ┃         装入模块           ┃
       ┗━━━━━━━━━━━━━━━━━━┛
             DOS下.EXE文件结构



  EXE文件由标准的28字节头部,重定位表及装入模块组成(如上图)。头部及重定位表只在加载器装载程序时使用,使用完后即被丢弃。完成装载之后,DOS将控制权完全交给应用程序(DOS工作于实模式下,这也注定它是一个不安全的操作系统)。头部的 18~19H:重定位项偏移 标志了此文件中重定位表相对于文件开始处的偏移,06~07H:重定位表项数 给出重定位表的项数。DOS将根据重定位表对装入模块的相应位置进行修改。重定位表的结构如下:

       ┏━━━━━━━━━━━━━━━━━━┓  ┓
              ┃           段值          ┃  ┃
              ┣━━━━━━━━━━━━━━━━━━┫  ┣ 4字节;
              ┃                偏移           ┃  ┃
              ┗━━━━━━━━━━━━━━━━━━┛  ┛
               重定位项格式
        
  装入模块是一个浮动代码模块,就是说模块中表达段址的指令操作数在装入后需要修改。模块中这些段址操作数是以模块的起点0000:0000h来计算并写入的,现在装入后模块的实际起点为XXXX:0000h,显然应做加XXXXh的修改,这就是重定位操作,XXXXh称为重定位因子。
  重定位项表的每个表项为4字节,给出一个应予以修改的操作数的位置段值:偏移量。但表中给出的“段值”仍是一个相对段址。于是,对一个已装入内存的装入模块实施重定位操作应遵以下步骤:
  (1)按格式化区字节18~19H域中给出的重定位项表的状况信息块中的偏移位置,和字节06~07H域给出的重重定位项数,将重定位项表读入内存的一个缓冲区。
  (2)将重定位表每项的段址部分做加XXXXh的修改,以得到各修改位置的实际地址。
  (3)将实际地址(字)单元中的内容再做加XXXXh的修改。
  对所有的重定位项实施上述两步操作后,静态重定位才完成,尔后可启动程序执行。使用过的重定位项表内存副本随即被废弃。
  现在总结一下,80x86系列处理器在实模式下的寻址范围近似为1M,一般情况也是把它当作1M的。为了使程序代码能在内存的不同位置都能正确执行,Intel采用二维地址,或称作逻辑地址的表示方法,即“段基址:段内偏移”。二维地址的设计也正是为了方便程序的重定位。二维地址使绝大多数指令都不需要重定位操作便可正确执行。但少数指令,即指令中涉及同时改变段基址与段内偏移时,不得不进行重定位操作。操作的过程如上。DOS下使用的是静态重定位,即在程序执行之前进行重定位,它根据装配模块将要装入的内存起始位置,直接修改装配模块中的有关使用地址的指令。重定位的位置由EXE文件头中的重定位表给出。只有改变了段基址的指令才需要重定位,如远跳转JMP far ptr xxxx:yyyy,远调用call far ptr xxxx:yyyy,直接地址引用MOV ax,word ptr xxxx:yyyy等。
  静态重定位有着无需硬件支持的优点,但存在着如下的缺点:一是程序重定位之后就不能在内存中搬动了;二是要求程序的存储空间是连续的,不能把程序放在若干个不连续的区域内。静态重定位显然是不适合多任务操作系统的。

二、动态重定位
  相对于静态重定位,动态重定位不需要预先修改目标模块。
  动态重定位是指,不是在程序执行之前而是在程序执行过程中进行地址重定位。更确切地说,是在每次访问内存单元前才进行地址变换。动态重定位可使装配模块不加任何修改而装入内存,但是它需要硬件定位寄存器的支持。
  程序的目标模块装入内存时,与地址有关的各项均保持原来的相对地址不进行任何修改。如MOV ax,0500:0100h这条指令仍是相对地址500。当此模块被操作系统调度到处理机上执行时,操作系统将把此模块装入的实际起始起始地址减去目标模块的相对基地址,然后将其差值装入定位寄存器中。当CPU取得一条访问内存的指令时,地址变换硬件逻辑自动将指令中的相对地址与定位寄存器中的值相加,再依此和值作为内存绝对地址去访问该单元中的数据。
  由此可见,进行动态重定位的时机是在指令执行过程中,每次访问内存前动态地进行。采取动态重定位可带来两个好处:
  (1)、目标模块装入内存时无需任何修改,因而装入之后再搬迁也不会影响其正确执行,这对于存储器紧缩、解决碎片问题是极其有利的;
  (2)、一个程序由若干个相对独立的目标模块组成时,每个目标模块各装入一个存储区域,这些存储吉尔吉斯可以不是顺序相邻的,只要各个模块有自己对应的定位寄存器就行。
  动态重定位技术所付出的代价是需要硬件支持。

>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

  在多任务操作系统中,内存中存在多个进程实体。程序的重定位在所难免。对于保护模式下的Inel80x80处理器,其采用的段页式存储结构很好地解决了重定位问题。它不仅可以按程序的逻辑功能划分为多个段,还可以开启分页功能,使每个进程有多送4G(或更多)的地址空间。重定位只在动态链接时使用。分页机制还可以使程序只加载其正在执行的一部分到内存,而其他部分驻留在磁盘中,即使用所谓的虚拟存储技术,使内存容量大大“扩充”。想学习保护模式,杨秀文的《80x86汇编语言程序设计教程》当数中文的经典之作。Intel Architecture Software Developer's Manual则是Intel公司发布的关于处理器的官方资料,权威性不言而喻,可惜是英文的。
  以下是我编的一个查看分析exe文件的程序,还未编完,但对于本篇文章,查看重定位表还是可以的。错误是一定有的,所以请高手多多指教。

上传的附件 EXEanalyze.rar