虽然说这个实例是《80x86汇编语言程序设计》上的,但是自己照着抄下来->理解注释->调试居然搞了一下午时间。哎,不过终于过了。(有几个地方需要修改才可以编译通过,不知是否这是书上的错误?)
;功能:演示实模式与保护模式的切换,16进制显示从110000H开始的256个字节的值
;16位偏移的段间直接转移指令的宏定义,这是一个JMP指令到所描述的地址
JUMP MACRO selector,offsetv
DB 0EAH ;操作码
DW offsetv ;16位偏移
DW selector ;段值或者选择子
ENDM
;字符显示宏指令的定义
ECHOCH MACRO ASCII
MOV DL,ASCII
MOV AH,2
INT 21H
ENDM
;存储段描述符结构类型的定义(64bits)
DESCRIPTOR STRUC
limitL DW 0 ;段界限(0-15)
baseL DW 0 ;段基地址(0-15)
baseM DB 0 ;段基地址(16-23)
attributes dw 0 ;段属性(其中包含高4位段界限)
baseH DB 0 ;段基地址(24-31)
DESCRIPTOR ENDS
;伪描述符结构类型定义
PDESC STRUC
limitT dw 0 ;16 bit界限
base dd 0 ;基地址
PDESC ENDS
;常量定义(属性段定义)
;这个byte可以对照属性描述去理解含义。
;92H=1001 0010->表示这是一个可读写的数据段
;98H=1001 1000->这是一个只执行的代码段。
ATDW=92H ;存在的可读写数据段属性值(参考8-15bit定义)
ATCE=98H ;存在的只执行代码段属性值
;
.386p ;.386与.386p的区别是.386p可以使用特权指令
;--------------------------------------------------------------------------
;数据段
DSEG SEGMENTUSE16 ;16位段
GDT label byte ;全局描述符表GDT
DUMMY DESCRIPTOR <> ;空描述符
CODE DESCRIPTOR <0FFFFH,,,ATCE,> ;code descriptor
CODE_SEL=CODE-GDT ;代码段描述符的选择子
DATAS DESCRIPTOR <0FFFFH,0H,11H,ATDW,0>;这里描述的是一个base=110000h,limit=0FFFFh的段
DATAS_SEL=DATAS-GDT ;源数据段描述符的选择子
DATAD DESCRIPTOR <0FFFFH,,,ATDW,>
DATAD_SEL=DATAD-GDT ;目标数据段描述符的选择子
GDTLEN =$-GDT
;
VGDTR PDESC <GDTLEN-1,> ;伪描述符
;
BUFFERLEN=256 ;缓冲区字节长度
BUFFER DB BUFFERLEN DUP(0) ;缓存区
DSEG ENDS
;---------------------------------------------------------------------------------
;代码段
CSEG SEGMENTUSE16
ASSUME CS:CSEG, DS:DSEG
START:
MOV AX,DSEG
MOV DS,AX
;准备要加载到GDTR的伪描述符,把GDT的地址按照实模式方式装载
MOV BX,16
MUL BX ;AX*BX->AX DX
ADD AX,OFFSET GDT ;界限已在定义时设置稳妥
ADC DX,0 ;此时AX DX表示了GDT的实地址
MOV WORDPTR VGDTR.BASE,AX
MOV WORDPTR VGDTR.BASE+2,DX
;设置代码段描述符
MOVAX,CS
MULBX
MOV CODE.baseL,AX ;代码段开始偏移为0
MOV CODE.baseM,DL ;代码段节选已经在定义时设置
MOV CODE.baseH,DH
;设置目标数据段描述符,定义好目标数据段的地址
mov ax,ds
mul bx
add ax,offset BUFFER
ADC DX,0
MOV DATAD.baseL,AX
MOV DATAD.baseM,DL
MOV DATAD.BASEH,DH
;加载GDTR
LGDT FWORD PTR VGDTR
;
CLI ;关中断
CALL ENABLEA20 ;打开地址线A20
;切换到保护模式
MOV EAX,CR0;
OR EAX,1
MOV CR0,EAX
;清指令预取队列,并真正进入保护模式
JUMP <CODE_SEL>,<OFFSET VIRTUAL>
;
VIRTUAL: ;现在开始在保护模式下
mov AX,DATAS_SEL
MOVDS,AX ;加载源数据段描述符
MOV AX,DATAD_SEL
MOV ES,AX ;加载目标数据段描述符
CLD
XOR SI,SI ;设置指针初值
XOR DI,DI
MOV CX,BUFFERLEN/4 ;设置4字节为单位的缓冲区长度
REPZ MOVSD ;传送。REPZ 这个是串操作指令用法(根据zf标志位和cx的值来判断继续循环,还是不循环。zf=1 and cx!=0 的条件下继续循环 MOVSD指令是从ds:[si]送双字数据到es:[di]单元中
;切回实模式
MOV EAX,CR0
AND EAX,0FFFFFFFEH
MOV CR0,EAX
;清指令预取队列,进入实模式
JUMP <SEG REAL>,<OFFSET REAL>
;
REAL: ;现在回到实模式
CALL DISABLEA20
STI ;开中断
;
MOV AX,DSEG ;重置数据段寄存器
MOV DS,AX
MOV SI,OFFSET BUFFER
CLD ;显示缓冲区内容
MOV BP,BUFFERLEN/16
NEXTLINE:
MOV CX,16
NEXTCH:
LODSB
PUSH AX
SHR AL,4
CALL TOASCII
ECHOCH <AL>
POP AX
CALL TOASCII
ECHOCH <AL>
; ECHOCH <''>
LOOP NEXTCH
ECHOCH 0DH
ECHOCH 0AH
DEC BP
JNZ NEXTLINE
;
MOVAX,4C00H
INT 21H
;
TOASCII PROC
;把AL低4位的十六进制数字转换成对应的ASCII,保存在AL
AND AL,0FH
ADDAL,30H ;30H=011 0000,十六进制(0-9)的asscii刚好比其值大30h
CMPAL,39H
JBE TOASCII1
ADDAL,7 ;十六进制(A-F)的asscii值比其值大37h
TOASCII1:RET
TOASCII ENDP
;
ENABLEA20 PROC
;OPEN A20
PUSH AX
IN AL,92H
OR AL,2
OUT 92H,AL
POP AX
RET
ENABLEA20 ENDP
;
DISABLEA20 PROC
;CLOSED A20
PUSH AX
IN AL,92H
AND AL,0FDH ;0FDH=NOT 20H
POP AX
RET
DISABLEA20 ENDP
CSEG ENDS
END START