现在的位置: 首页 > 综合 > 正文

再次讲解[LABEL_GO_BACK_REAL+3], ax

2018年01月24日 ⁄ 综合 ⁄ 共 2470字 ⁄ 字号 评论关闭

版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://myjfm.blogbus.com/logs/58307816.html

我们还是先把代码列出来吧,这样比较容易说清楚:

LABEL_SEG_CODE32:                ;从实模式跳转到此进入保护模式

      mov      ax, SelectorData      ;数据段选择子

      mov      ds, ax

      mov      ax, SelectorTest      ;测试段选择子

      mov      es, ax

      mov      ax, SelectorVideo     ;视频段选择子

      mov      gs, ax

      mov      ax, SelectorStack     ;堆栈段选择子

      mov      ss, ax

      mov      esp, TopOfStack

 

      ……

      jmp      SelectorCode16:0

      ……

 

[segment .s16code]

ALIGN    32

[BITS    16]

LABEL_SEG_CODE16:

      mov      ax, SelectorNormal

      mov      ds, ax

      mov      es, ax

      mov      fs, ax

      mov      gs, ax

      mov      ss, ax

 

      mov      eax, cr0

      and      al, 11111110b

      mov      cr0, eax

 

LABEL_GO_BACK_TO_REAL:

      jmp      0:LABEL_REAL_ENTRY

      ……

 

      我们可以看到,前面那个jmp并没有直接跳转到实模式,而是选择跳转到一个保护模式下的16位代码段,在16位保护模式代码段下的jmp才真正跳入到实模式。

      那么,你也许会问,为什么要这么麻烦,我们在实模式下不是直接从16位代码段跳到32位代码段的保护模式下了么,那为什么从保护模式就不能直接跳回去呢?

      弄明白这个问题很重要,下面我们慢慢详解。

      我们还是先来搞清楚一个概念:段描述符高速缓冲寄存器。

      在实模式下,段寄存器含有段值,为访问存储器形成物理地址时,处理器引用相应的某个段寄存器并将其值乘以16,形成20位的段基地址。在保护模式下,段寄存器含有段选择子,如上所述,为了访问存储器形成线性地址时,处理器要使用选择子所指定的描述符中的基地址等信息。为了避免在每次存储器访问时,都要访问描述符表而获得对应的段描述符,从80286开始每个段寄存器都配有一个高速缓冲寄存器,称之为段描述符高速缓冲寄存器或描述符投影寄存器,对程序员而言它是不可见的。每当把一个选择子装入到某个段寄存器时,处理器自动从描述符表中取出相应的描述符,把描述符中的信息保存到对应的高速缓冲寄存器中。此后对该段访问时,处理器都使用对应高速缓冲寄存器中的描述符信息,而不用再从描述符表中取描述符。

      各段描述符高速缓冲寄存器在实模式时之内容如下表所示:

      点击查看原始尺寸

      这些高速缓冲寄存器在实方式下仍发挥作用,只是内容上与保护模式下有所不同。如上表所示,其中“Y”表示“是”; “N”表示“否”;“B”表示字节;“U”表示向上扩展,“W”表示以字方式操作堆栈。段基地址仍是 32位,其值是相应段寄存器值(段值)乘以16,在把段值装载到段寄存器时刷新。由于其值是16位段值乘上16,所以在实模式下基地址实际上有效位只有 20位。每个段的32位段界限都固定为0FFFFH,段属性的许多位也是固定的。所谓固定是指在实方式下不可设置这些属性值,只能继续沿用保护方式下所设置的值。因此,在准备结束保护模式回到实模式之前,要通过加载一个合适的描述符选择子到有关段寄存器,以使得对应段描述符高速缓冲寄存器中含有合适的段界限和属性。GDT中的描述符Normal就是这样一个描述符,在返回实模式之前把对应选择子Normal加载到DS和ES就是此目的。

      CS段的段描述符高速缓冲寄存器的D位属性值为1表示代码当前运行在保护模式,D位属性值为0表示代码当前运行在实模式。而这个D位属性值在实模式下是固定的,也就是不容许改变的,只能在保护模式下改变。我们从上一篇日志看到,进入保护模式的标志是cr0的PE位置1,此时CPU就运行在了保护模式下,因此可以直接加载32位代码段的描述符,同时改变CS段描述符高速缓冲寄存器的D位属性值,这就是为什么可以直接从16位实模式代码段跳转到32位保护模式代码段的原因,因为跳转后的保护模式可以改变D位属性值。但是反过来就出问题了,由于实模式下D位属性值不容许改变,只能在保护模式下的时候就提前将D位属性值置0。那怎么置呢?我们想到一个方法就是先跳转到16位保护模式代码段,这样就可以置D位属性值为0,然后再由此跳入实模式。至此,这个问题我们算是弄明白了~

      接下来又一个问题,在16位保护模式下的代码段中的jmp怎么这么怪:

      LABEL_GO_BACK_TO_REAL:

            jmp      0:LABEL_REAL_ENTRY

      咦?段地址为什么是0阿?!

      其实这里只是暂时这么写罢了,我们在程序的更前方(还在实模式下的时候)有这样一段段代码:

      mov      ax, cs

      mov      ds, ax

      mov      es, ax

      mov      ss, ax

      mov      sp, 0100h

 

      mov      [LABEL_GO_BACK_TO_REAL + 3], ax

      其实这最后一句的作用就是为回到实模式的这个跳转指令指定正确的段地址,这条指令的机器码如下图:

      

      说明保护模式下的长跳转是5个字节长,且后两个字节是段基址,而这最后一句恰恰是将实模式下的段基址装入BYTE   4和BYTE   5。因此执行jmp      0:LABEL_REAL_ENTRY其实是变成了jmp      cs_real_mode:LABEL_REAL_ENTRY。

抱歉!评论已关闭.