一个关于实模式转到保护模式的问题
先看网上很多介绍的一个程序:
;名称:ASM1.ASM
;功能:演示实方式和保护方式切换(切换到16位代码段)
;----------------------------------------
INCLUDE 386SCD.INC
;----------------------------------------
;字符显示宏指令的定义
;----------------------------------------
EchoCh MACRO ascii
mov ah,2
mov dl,ascii
int 21h
ENDM
;----------------------------------------
DSEG SEGMENT USE16 ;16位数据段
;----------------------------------------
GDT LABEL BYTE ;全局描述符表
DUMMY Desc <> ;空描述符
Code Desc <0ffffh,,,ATCE,,> ;代码段描述符
DataS Desc <0ffffh,0,11h,ATDW,,> ;源数据段描述符
DataD Desc <0ffffh,,,ATDW,,> ;目标数据段描述符
;----------------------------------------
GDTLen = $-GDT ;全局描述符表长度
VGDTR PDesc <GDTLen-1,> ;伪描述符
;----------------------------------------
Code_Sel = Code-GDT ;代码段选择子
DataS_Sel = Datas-GDT ;源数据段选择子
DataD_Sel = DataD-GDT ;目标数据段选择子
;----------------------------------------
BufLen = 256 ;缓冲区字节长度
Buffer DB BufLen DUP(0) ;缓冲区
;----------------------------------------
DSEG ENDS ;数据段定义结束
;----------------------------------------
CSEG SEGMENT USE16 ;16位代码段
ASSUME CS:CSEG,DS:DSEG
;----------------------------------------
Start PROC
mov ax,DSEG
mov ds,ax
;准备要加载到GDTR的伪描述符
mov bx,16
mul bx
add ax,OFFSET GDT ;计算并设置基地址
adc dx,0 ;界限已在定义时设置好
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
;设置代码段描述符
mov ax,cs
mul bx
mov WORD PTR Code.BaseL,ax ;代码段开始偏移为0
mov BYTE PTR Code.BaseM,dl ;代码段界限已在定义时设置好
mov BYTE PTR Code.BaseH,dh
;设置目标数据段描述符
mov ax,ds
mul bx ;计算并设置目标数据段基址
add ax,OFFSET Buffer
adc dx,0
mov WORD PTR DataD.BaseL,ax
mov BYTE PTR DataD.BaseM,dl
mov BYTE PTR DataD.BaseH,dh
;加载GDTR
lgdt QWORD PTR VGDTR
cli ;关中断
EnableA20 ;打开地址线A20
;切换到保护方式
mov eax,cr0
or eax,1
mov cr0,eax
;清指令预取队列,并真正进入保护方式
JUMP16 Code_Sel, <OFFSET Virtual>
Virtual: ;现在开始在保护方式下运行
mov ax,DataS_Sel
mov ds,ax ;加载源数据段描述符
mov ax,DataD_Sel
mov es,ax ;加载目标数据段描述符
cld
xor si,si
xor di,di ;设置指针初值
mov cx,BufLen/4 ;设置4字节为单位的缓冲区长度
repz movsd ;传送
;切换回实模式
mov eax,cr0
and al,11111110b
mov cr0,eax
;清指令预取队列,进入实方式
JUMP16 <SEG Real> , <OFFSET Real>
Real: ;现在又回到实方式
DisableA20
sti
mov ax,DSEG
mov ds,ax
mov si,OFFSET Buffer
cld
mov bp,BufLen/16
NextLine: mov cx,16
NextCh: lodsb
push ax
shr al,1
call ToASCII
EchoCh al
pop ax
call ToASCII
EchoCh al
EchoCh ' '
loop NextCh
EchoCh 0dh
EchoCh 0ah
dec bp
jnz NextLine
mov ax,4c00h
int 21h
Start ENDP
;----------------------------------------
ToASCII PROC
and al,0fh
add al,90h
daa
adc al,40h
daa
ret
ToASCII ENDP
;----------------------------------------
CSEG ENDS ;代码段定义结束
;----------------------------------------
END Start
搞不懂的是这句:
adc dx,0 ;界限已在定义时设置好
首先是不知道为什么要使用这条指令,里面DX根本没初始化,那他原来的值是多少?呢?
[解决办法]
mov ax,DSEG
mov ds,ax
;准备要加载到GDTR的伪描述符
mov bx,16
mul bx
add ax,OFFSET GDT ;计算并设置基地址
adc dx,0 ;界限已在定义时设置好
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
上面的代码是在求当前数据段的物理地址:
mov bx,16
mul bx
上面两句是将AX的数据段的段值乘以16,获得数据段的物理地址。结果存放在dx:ax中。因此指向mul bx时dx得到了初始化。
add ax,OFFSET GDT ;计算并设置基地址
adc dx,0 ;界限已在定义时设置好
上面两句是计算GDT的物理地址,在数据段物理地址的基础上加上了GDT结构的偏移,第二句是怕相加之后产生进位,所以把进位加到了物理地址的高16位DX上,当然在这个程序里是不会产生进位的,因为GDT位于数据段的头一行,偏移是零。如此编码可以在GDT前面加入其它变量以后,计算的物理地址仍然有效。
顺便说一句,上面的代码出自《80x86汇编语言程序设计教程》第十章。