《80X86汇编语言程序设计教程》十一 32位代码段和16位代码段切换实例

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《80X86汇编语言程序设计教程》十一 32位代码段和16位代码段切换实例相关的知识,希望对你有一定的参考价值。

1、  演示32位代码段与16位代码段之间的切换。实现的功能是以十六进制和ASCII码字符两种形式显示从内存地址100000H开始的16个字节的内容。

 

 

2、  源代码如下:

 

  1 ;DosTest.Asm
  2 ;16位偏移的段间转移指令的宏定义
  3 ;使用于16位段,用于跳转到32位目的段
  4 ;注意:标号偏移必须在16位二进制符号数数能表示的范围之内
  5 JUMP16    macro    selector,offsetv
  6         db    0eah                        ;操作码
  7         dw    offsetv                     ;16位偏移
  8         dw    selector                    ;段值或者选择子
  9 endm
 10 
 11 ;32位偏移的段间转移指令的宏定义
 12 ;使用于32位段,用于跳转到16位目的段
 13 JUMP32    macro    selector,offsetv
 14     db    0eah                            ;操作码
 15     dw    offsetv                         ;32位偏移
 16     dw    0
 17     dw    selector                        ;选择子
 18 endm
 19 
 20 ;存储段描述符结构类型的定义
 21 DESCRIPTOR    struc
 22     LimitL        dw    0                    ;段界限(0~15)
 23     BaseL         dw    0                    ;段基地址(0~15)
 24     BaseM         db    0                    ;段基地址(16~23)
 25     Attributes    dw    0                    ;段属性
 26     BaseH         db    0                    ;段基地址(24~31)
 27 DESCRIPTOR ends
 28 
 29 
 30 ;伪描述符结构类型的定义
 31 PDESC    struct
 32     Limit        dw    0                    ;16位界限
 33     Base         dd    0                    ;基地址
 34 PDESC ends
 35 
 36 ;7    6    5    4    3    2    1    0    7    6    5    4    3    2    1    0
 37 ;G    D    0    AVL    Limit(19…16)    P    DPL        DT        TYPE
 38 ;常量定义
 39 ATDR    =    0090h                        ;存在的只读数据段属性值(用于描述源数据段)
 40 ATDW    =    0092h                        ;存在的可读写数据段属性值(用于描述目的数据段)
 41 ATDWA   =    0093h                        ;存在的已访问可读写数据段属性值
 42 ATCE    =    0098h                        ;存在的只执行16位代码段属性值
 43 ATCE32  =    4098h                        ;存在的只执行32位代码段属性值
 44 DATALEN =    16                           ;源数据段长度
 45 
 46 
 47 ;须使用386特权指令
 48     .386P
 49 
 50 ;-----------------------------------
 51 ;数据段
 52 dseg    segment    use16                    ;16位段
 53     ;GDT表
 54     GDT           label    byte
 55     DUMMY         DESCRIPTOR<>                ;空描述符
 56     CODE32_SEL    =    08h                    ;32位代码段描述符选择子
 57     CODE32        DESCRIPTOR<CODE32LEN-1,,,ATCE32,>
 58     CODE16_SEL    =    10h                    ;16位代码段描述符选择子
 59     CODE16        DESCRIPTOR<0ffffh,,,ATCE,>
 60     DATAS_SEL     =    18h                    ;源数据段描述符选择子
 61     DATAS         DESCRIPTOR<DATALEN-1,,10h,ATDR,>;段基地址100000h
 62     DATAD_SEL     =    20h                    ;目的数据段描述符选择子
 63     DATAD         DESCRIPTOR<DATALEN*8-1,80a0h,0bh,ATDW,0>;段基地址0b80a0h
 64     STACKS_SEL    =    28h                    ;堆栈段描述符选择子
 65     STACKS        DESCRIPTOR<0ffffh,,,ATDWA,>;段基地址0000h,栈顶基址0ffffh
 66     NORMAL_SEL    =    30h                    ;规范段描述符选择子
 67     NORMAL        DESCRIPTOR<0ffffh,0,0,ATDW,>;段基地址0000h,栈顶基址0ffffh
 68     GDTLEN        = $ - GDT                   ;GDT表长度
 69     ;
 70     VGDTR         PDESC<GDTLEN-1,>            ;GDT伪描述符
 71     VARSS         dw    ?                     ;用于保存SS变量
 72 dseg    ends
 73 
 74 ;-----------------------------------
 75 ;实模式下代码段
 76 csegr    segment    use16    real
 77     assume    cs:csegr,ds:dseg
 78 start:                                      ;程序入口
 79     mov        ax,dseg
 80     mov        ds,ax
 81     ;
 82     mov        bx,16                        ;写VGDTR(GDT首地址转线性地址)
 83     mul        bx
 84     add        ax,offset GDT
 85     adc        dx,0
 86     mov        word ptr VGDTR.Base,ax
 87     mov        word ptr VGDTR.Base + 2,dx
 88     ;
 89     mov        ax,cseg32                    ;写32位代码段段基址
 90     mul        bx
 91     mov        CODE32.BaseL,ax    
 92     mov        CODE32.BaseM,dl
 93     mov        CODE32.BaseH,dh
 94     ;
 95     mov        ax,cseg16                    ;写16位代码段段基址
 96     mul        bx
 97     mov        CODE16.BaseL,ax    
 98     mov        CODE16.BaseM,dl
 99     mov        CODE16.BaseH,dh
100     ;
101     mov        ax,ss                        ;写堆栈段段基址
102     mul        bx
103     mov        STACKS.BaseL,ax
104     mov        STACKS.BaseM,dl
105     mov        STACKS.BaseH,dh
106     mov        VARSS,ss                     ;保存实模式下段基址
107     ;
108     lgdt       fword ptr VGDTR              ;装载VGDTR到GDTR
109     ;
110     cli                                     ;关中断
111     call       ENABLEA20                    ;开地址线A20
112     ;
113     mov        eax,cr0                      ;CR0的PE位置1
114     or         eax,1
115     mov        cr0,eax
116     ;进入32位代码段
117     JUMP16     <CODE32_SEL>,<low offset SPM32>;切换到保护模式
118     ;此时已经回到实模式
119 TOREAL:
120     mov        ax,dseg
121     mov        ds,ax
122     mov        ss,VARSS                     ;恢复实模式下的SS
123     call       DISABLEA20                   ;关闭地址线A20
124     sti                                     ;开中断
125     mov        ah,07h                       ;等待按键终止程序
126     int        21h
127     mov        ah,4ch                        
128     int        21h
129     
130 
131 ;打开地址线A20号
132 ENABLEA20    proc
133     push       ax
134     in         al,92h
135     or         al,2
136     out        92h,al
137     pop        ax
138     ret
139 ENABLEA20 endp
140 
141 ;关闭地址线A20号    
142 DISABLEA20    proc
143     push       ax
144     in         al,92h
145     and        al,0fdh
146     out        92h,al
147     pop        ax
148     ret
149 DISABLEA20 endp    
150 
151 csegr    ends
152 
153 ;-----------------------------------
154 ;32位代码段
155 cseg32    segment    use32    pm32
156     assume    cs:cseg32
157 SPM32:
158     mov        ax,STACKS_SEL
159     mov        ss,ax                        ;装载堆栈段描述符选择子
160     mov        ax,DATAS_SEL
161     mov        ds,ax                        ;装载源数据段描述符选择子
162     mov        ax,DATAD_SEL
163     mov        es,ax                        ;装载目的数据段描述符选择子
164     ;以下开始以ASCII码形式显示源16个字节
165     ;目的数据段需要16 * (4 + 2) = 96个字节
166     xor        esi,esi                      ;设置指针和计数器
167     xor        edi,edi
168     mov        ecx,DATALEN                  ;16个数据,循环16次
169     cld                                     ;清方向标志位
170 NEXT:
171     lodsb                                   ;从源数据段装载一个byte数据到al并移动指针
172     push      ax
173     call      TOASCII                      ;低4位转ASCII码(一个byte)
174     mov        ah,7                         ;显示属性为黑底白字(再一个byte)
175     shl        eax,16                       ;暂存在eax高16位
176     pop        ax
177     shr        al,4                         ;高4位转ASCII码
178     call       TOASCII                        
179     mov        ah,7
180     stosd                                   ;eax的4个byte(dword)存入目的数据段并移动指针
181     mov        al,                        ;显示空格,属性为黑底白字,2个字节包含字符ASCII码和字符属性
182     stosw                                   ;ax的2个byte(word)入目的数据段并移动指针
183     loop       NEXT
184     ;变化到16位代码段
185     JUMP32     <CODE16_SEL>,<offset SPM16>
186     ;jmp       far ptr SPM16                ;这里取代完全没有问题
187 
188 ;把AL低4位的十六进制数转换成对应的ASCII码,保存在AL中       
189 TOASCII    proc
190     and        al,0fh
191     add        al,90h
192     daa
193     adc        al,40h
194     daa    
195     ret
196 TOASCII endp
197 
198     CODE32LEN  = $ - SPM32
199 cseg32    ends
200     
201 ;-----------------------------------
202 ;16位代码段
203     
204 cseg16    segment    use16    pm16
205     assume    cs:cseg16                    
206 SPM16:                                      ;跳转过来时ss、es的值都没有改变,实际上还是在保护模式
207     ;以下开始以十六进制数形式显示源16个字节
208     ;目的数据段需要16 * 2 = 32个字节
209     xor        si,si                        ;源数据段指针归位
210     mov        di,DATALEN * 3 * 2           ;这个语句是多余的,这里重新设置di没有意义
211     mov        ah,7                         ;显示属性为黑底白字
212     mov        cx,DATALEN
213 AGAIN:
214     lodsb                                   ;从源数据段装载一个byte数据到al并移动指针
215     stosw                                   ;ax的2个byte(word)入目的数据段并移动指针
216     loop      AGAIN
217     ;
218     mov        ax,NORMAL_SEL                ;装载规范段描述符选择子到ds和es            
219     mov        ds,ax                        ;这将引起高速缓存寄存器的刷新        
220     mov        es,ax
221     ;
222     mov        eax,cr0                        ;切换到实模式下
223     and        eax,0fffffffeh
224     mov        cr0,eax
225     ;切换回实模式
226     jmp        far ptr TOREAL
227 cseg16    ends
228     end        start

 

 

3、  源代码有几处要说明的地方

  1)  原书中的“JUMP16 CODE32_SEL,<offset SPM32>”语句须改为“JUMP16     <CODE32_SEL>,<low offset SPM32> ”语句,原因是16位段不支持32位偏移(offset SPM32为32位立即数)

  2)  原书中的“CODE32LEN  = $”需要修改为“CODE32LEN     = $ - SPM32”,如果不是原书印刷等之类的错误,那么绝对是作者逻辑错误

 

 

4、  运行效果与相关说明

  1)  使用DiskGenuis复制.exe目标文件到DOS虚拟系统

  2)  打开虚拟机进入DOS7.1,使用“cls”指令清屏(否则将影响输出的视觉效果)

  3)  执行目标程序,将看到输出结果

技术分享图片 

  4)  使用adu.exe验证一下输出是正确的

技术分享图片 

 

 

5、  实现步骤的简单阐述

  1)  作切换到保护方式的准备

    2个16位数据段描述符、1个16位代码段描述符、1个16位堆栈段描述符、1个32位代码段描述符和1个规范段描述符。这里没必要再说了,只是注意下,32位代码段描述符在设置界限时采用的方法。另外,这个界限值不是长度,在以字节为粒度时是偏移量。

  2)  切换到保护方式一个32位代码段

    在上一个实例中已经提到过,这个JUMP宏实际上就是一条特殊的远跳转指令,这里要关注的一个东西是,跳转时的地址偏移问题。我简单说下:

    JUMP16用于16位段中,实现跳入32位段。由段间绝对跳转的性质可知,最大偏移是0FFFFH,也就是说,JUMP16实现的从16位段到32位段的跳转是有条件的:目标地址标号在32位段的段内偏移必须不大于0FFFFH

    JUMP32用于32位段中,实现跳入16位段。这个也只需要注意同一个地方,那就是16位段最大偏移为0FFFFH,所以跳转时必须保证高16位为0,作者在这里使用了宏定义把双字类型拆分成两个字类型的域,并把高字强行设置为0,对安全性有一定的提高

    从上面来看,跳转时不需要关注段寄存器的内容,此外,16位段与32位段之间的相互跳转与实模式还是保护模式没有半点关系,从这里也可以看到,实模式到保护模式的切换,在把VGDTR装载到GDTR寄存器以及将CR0的PE位置1后,就是一个简单的跳转指令,所以,也支持在模式切换的同时进行段类型的切换。

    如果能保证足够安全,可以完全不用作者的宏来实现这些跳转,就像在本例中最后由保护模式切换为实模式的“jmp far ptr TOREAL”那样,直接使用标号进行远跳,也是一样的,当然,这里要注意一些问题,这个问题也正体现之前一直说的这个远跳还是特殊的地方:这个宏中远跳指令可以重置自己设定的代码段值/代码段选择子和代码段内偏移分别到CS、IP/EIP。在保护模式下,装入段CS的是段选择子而不是段基地址,使用该指令来跳转是必须的,凡是装入的是段值而不是段选择子的情况,都可以使用远跳指令取代

  3)  把源数据转十六进制数码的ASCII码,并直接填入显存

    采用的是直接写屏的方式(参考“《80X86汇编语言程序设计教程》四 输入输出与中断”)。显存开始地址为0B8000H,这里的0b80a0h表示在3号显示方式下,屏幕第2行开头的位置。

  4)  切换到16位段代码

  5)  把源数据直接作为ASCII码填入显存

  6)  切回实模式

 

 

6、  特别说明

  1)  本例没有建立专用堆栈,但是在原堆栈上建立了堆栈段,所以保护模式下可进行堆栈操作

  2)  同上个实例一样,大量简化处理,没有IDT和LDT,DPL都设置为了0。

  3)  关于远跳的特殊之处的说明,各个段寄存器所配的高速缓冲寄存器在实模式下依然起作用。实模式下要求它的内容应该如下表:

 

 

 

段基地址

 

 

段界限

(固定)

其它段属性

G

R

W

E

CS

当前CS*16

0000FFFFH

Y

0

Y

B

U

Y

Y

Y

-

N

SS

当前SS*16

0000FFFFH

Y

0

Y

B

U

Y

Y

N

W

-

DS

当前DS*16

0000FFFFH

Y

0

Y

B

U

Y

Y

N

-

-

ES

当前ES*16

0000FFFFH

Y

0

Y

B

U

Y

Y

N

-

-

FS

当前FS*16

0000FFFFH

Y

0

Y

B

U

Y

Y

N

-

-

GS

当前GS*16

0000FFFFH

Y

0

Y

B

U

Y

Y

N

-

-

 

    其中:“Y”表示“是”,“N”表示“否”,“B”表示字节,“U”表示向高扩展段,“W”表示字操作堆栈。由于实模式下不可以设置高速缓存寄存器(也就是说,即使改变段寄存器中的段值,也不会引起高速缓存寄存器中内容的刷新),所以我们必须在保护模式下提前刷新它们到符合要求的值,再切换回保护模式。源代码中的“规范段描述符选择子”做的就是这样一个事情。我测试过,如果将它们去掉,程序在切换回实模式后将死机。此外,需要说明的是,源代码中的JMP32完全没必要用,这个纯粹就是跳转,根本不需要刷新高速缓存寄存器,这里也没有进行模式切换,我不知道作者放这是为了什么,感觉容易误导到读者

 

以上是关于《80X86汇编语言程序设计教程》十一 32位代码段和16位代码段切换实例的主要内容,如果未能解决你的问题,请参考以下文章

《80X86汇编语言程序设计教程》十五 任务切换实例

《80X86汇编语言程序设计教程》二十三 分页管理机制实例

《80X86汇编语言程序设计教程》二十四 进入与离开V86模式实例

《80X86汇编语言程序设计教程》十九 操作系统类指令与输入输出保护

将 ISR 链接到向量中断 80x86 32 位 AT&T 程序集

《80X86汇编语言程序设计教程》二十五 结语(读后感:这本书怎么样)