📄 virtual.asm
字号:
TITLE 保护方式下的程序设计实例
; 本实例请不要在UCDOS下运行,Windows下也不可以运行,因为修改了系统地址寄存器。
; 本例综合运用保护方式下程序设计的基本知识,主要包括:
; 1)保护方式的进入和退出。
; 2)分页管理机制。
; 3)中断表。
; 4)通用保护异常。
; 5)特权级变化。
; 6)任务切换。
; 7)扩展内存的存取。
; 8)本实例要求您的机器至少要配置16MB内存。
; 9)阅读本例,重点是从保护方式的入口处开始:①→②→③→④→⑤→⑥。
PDT_ADDR equ 00300000H; 页目录表物理页地址。
PT_0_ADDR equ 00301000H; 页表0的物理页地址。
PTC_CODE equ 003FF000H; 代码所在区域的表项,对应页表第3FF页(倒数第一项)。
PTC_TEST equ 003FE000H; 供测试用表项,对应页表中第3FE页(倒数第二项)。
PTC_VIDEO equ 003FA000H; 显示缓冲区在页表中的表项,物理地址为0B800H。
PhTestMem equ 08FFF000H; 测试页表的表项用,PTC_TEST项对应该地址。
PhExeMem equ 00FFF000H; 分页寻址,代码物理页(第16MB处,代码复制而来)。
include dos.inc
.386p
GDTSEG Segment PARA USE16
gdt LABEL BYTE; 全局描述符表。
Dummy DESCRIPTOR <>; 空选择子。
Normal DESCRIPTOR <0ffffH,0,0,ATTDW,0>
NORMAL_SEL=Normal-gdt
ExtMem DESCRIPTOR <0FFFFh,0eaa0h,010h,ATTDR,0>; FFFF:EAB0处,只读。
EXTMEM_SEL=ExtMem-gdt
EchoBuffer DESCRIPTOR <0ffffH,8000H,0BH,ATTDW,0>
ECHO_BUFFER_SEL=EchoBuffer-gdt
CopyCodeBuffer DESCRIPTOR <0FFFFH,PhExeMem and 0FFFFH,PhExeMem shr 16,ATTDW,0>
COPY_SEL=CopyCodeBuffer-gdt; 第16MB的内存处的描述符。
V_P_Code DESCRIPTOR <0FFFFH,PTC_CODE AND 0FFFFH,PTC_CODE shr 16,ATTCER,0>
V_P_CODE_SEL=V_P_Code-gdt
V_P_Video DESCRIPTOR <0FFFFH,PTC_VIDEO AND 0FFFFH,PTC_VIDEO shr 16,ATTDW,0>
V_P_VIDEO_SEL=V_P_Video-gdt
V_P_PDTable DESCRIPTOR <0FFFH,PDT_ADDR AND 0FFFFH,PDT_ADDR shr 16,ATTDW,0>
V_P_PDT_SEL=V_P_PDTable-gdt; 页目录表。
V_P_PT_0 DESCRIPTOR <0FFFH,PT_0_ADDR AND 0FFFFH,PT_0_ADDR shr 16,ATTDW,0>
V_P_PT_0_SEL=V_P_PT_0-gdt; 页表0,以上为分页机制准备的。
Gdt_Init_Start LABEL BYTE; 以下为需初始化的GDT表项。
SwitchCon DESCRIPTOR<0FFFFH,ControlSeg,?,ATTCER,?>; 分支子程序。
SWITCH_SEL=SwitchCon-gdt
MainCode DESCRIPTOR <MAINCODELENGTH-1,MainCodeSeg,?,ATTCER,?>
MAINCODE_SEL=MainCode-gdt; 主代码。
MainData DESCRIPTOR <0FFFFH,MainDataSeg,?,ATTDW,?>; 提示信息及缓冲段。
MAINDATA_SEL=MainData-gdt
MainLdt DESCRIPTOR <MAINLDTLENGTH-1,MainLDTSeg,0,ATTLDT,?>
MAINLDT_SEL=MainLdt-gdt
MainTss DESCRIPTOR <MAINTSSLENGTH-1,MainTSSSeg,?,ATTTSS,?>
MAINTSS_SEL=MainTss-gdt
MainTSS_Alias DESCRIPTOR <MAINTSSLENGTH-1,MainTSSSeg,?,ATTDW,?>
MAINTSS_ALIAS_SEL=MainTss_Alias-gdt; 别名。
MainStack_0 DESCRIPTOR <MAINSTACK_POINTER_0-1,MainStackSeg_0,?,ATTDW,?>
MAINSTACK_0_SEL=MainStack_0-gdt
MainStack_2 DESCRIPTOR <MAINSTACK_POINTER_2-1,MainStackSeg_2,?,ATTDW+DPL2,?>
MAINSTACK_2_SEL=MainStack_2-gdt+RPL2
DosTss DESCRIPTOR <DOSTSSLENGTH-1,DosTSSSeg,?,ATTTSS,?>; 实方式任务。
DOSTSS_SEL=DosTss-gdt
DosLdt DESCRIPTOR <DOSLDTLENGTH-1,DosLDTSeg,?,ATTLDT,?>
DOSLDT_SEL=DosLdt-gdt
GDT_INIT_NUM=($-Gdt_Init_Start)/(SIZE DESCRIPTOR); 初始化项数。
GDTLENGTH=$-gdt
GDTSEG EndS
IDTSEG Segment para use16
; IDT只能为中断、陷阱或任务门。
; 主动调用中断,相当于陷阱事件,在堆栈中保存下一条指令的地址。
; 被动发生时相当于故障,在堆栈中保存了发生异常的指令地址,需处理。
; A、B、C、D、E类型的中断被动发生时,在堆栈中压入出错码。
idt LABEL BYTE
rept 13; 重复宏定义,0~12号向量。
GATE <Int_Pro,MAINCODE_SEL,0,ATTTGAT,0>; Int_Pro是默认中断程序。
EndM
GATE <Int_D,MAINCODE_SEL,0,ATTTGAT,0>; 0DH号向量,通用保护故障。
rept 19
GATE <Int_Pro,MAINCODE_SEL,0,ATTTGAT,0>; 14~32号向量。
EndM
INT_DOS21 GATE <?,DOSTSS_SEL,0,ATTTASKGAT,0>; 21H号向量。
rept 220
GATE <Int_Pro,MAINCODE_SEL,0,ATTTGAT,0>; 34~253号向量。
EndM
INT_PAGE GATE <IntPage,MAINCODE_SEL,0,ATTIGAT,0>; 254号向量。
GATE <Int_Pro,MAINCODE_SEL,0,ATTTGAT,0>; 255号向量。
IDTLENGTH=$-idt
IDTSEG Ends
MainTSSSeg Segment para use16
Main_TSS taskss <>
DB 0ffh; 控制端口I/O结束标志。
MAINTSSLENGTH=$
MainTSSSeg Ends
MainStackSeg_0 Segment para use16; 0级堆栈。
MAINSTACK_POINTER_0 =1024
db MAINSTACK_POINTER_0 dup(0)
MainStackSeg_0 Ends
MainStackSeg_2 Segment para use16; 2级堆栈。
MAINSTACK_POINTER_2 =1024
db MAINSTACK_POINTER_2 dup(0)
MainStackSeg_2 Ends
MainLDTSeg Segment para use16; Main任务的LDT。
No_Init LABEL BYTE
CallGate Gate <ReturnLevel_0_Entry,SWITCH_SEL,1,ATTCGAT+DPL2,0>
LEVEL2GATE_SEL=CallGate-No_Init+TIL+RPL2
MainLdt_Init_Start LABEL BYTE
GetExMemDe DESCRIPTOR <MAINCODELENGTH-1,MainCodeSeg,0,ATTCER,0>
VISITEXMEM_SEL=GetExMemDe-No_Init+TIL
Level2De DESCRIPTOR <LEVEL2LENGTH-1,CodeSeg_2,0,ATTCER+DPL2,0>
LEVEL2_SEL=Level2De-No_Init+TIL+RPL2
MAINLDT_INIT_NUM=($-MainLdt_Init_Start)/(SIZE DESCRIPTOR)
MAINLDTLENGTH=$
MainLDTSeg Ends
MainDataSeg Segment PARA use16
ExtMemMess db "1----The Content of Memory at FFFF:EAB0(since 10EAA0 for 16 bytes)is :",0dh,0ah
EMM_LEN=$-ExtMemMess
Buffer_ExtMem db 50 dup(0)
DPLConvert db "2----DPL is Changed(LEVEL0-->LEVEL2-->LEVEL0),Copy 12345678H from LEVEL2 stack!"
DPL_LEN=$-DPLConvert
Buffer_DPL db 16 dup(0)
Int_D_Mess db "3----General Protect happend!"
D_LEN =$-Int_D_Mess
Int_Pro_Mess db "4----Other Interrupt happened!"
P_LEN=$-Int_Pro_Mess
PageManage db "5----The Content of Page_0 Item:"
PAGE_LEN=$-PageManage
Buffer_Page db 8 dup(?)
MainDataSeg Ends
MainCodeSeg Segment PARA use16
assume cs:MainCodeSeg
Dis_play proc; 显示信息用。另外,这段代码还需复制到第16MB内存处(分页用)。
push di
cld
again:
lodsb
mov ah,34h; 颜色值。
stosw
loop again
pop di
retf
COPYLEN=$-Dis_Play; 这段代码的长度,即需要复制的字节数。
Dis_play endp
GetExMem proc; 读取扩展内存。
mov ax,EXTMEM_SEL
mov ds,ax
mov si,0
mov ax,MAINDATA_SEL
mov es,ax
lea di,Buffer_ExtMem
mov cx,16
cld
changeasc:
lodsb
call far ptr ToAscii; 转换成ASCII码。
stosw
mov al,' '
stosb
loop changeAsc
mov al,'$'
stosb
retf
GetExMem endp
ToAscii proc; AL的内容转化为ASCII码在AX中。
push ax
shr al,4
and al,0fh
add al,90h
daa
adc al,40h
daa
mov bl,al
pop ax
and al,0fh
add al,90h
daa
adc al,40h
daa
mov bh,al
mov ax,bx
retf
ToAscii endp
Int_Pro proc; 默认的中断执行程序。
; 主动中断,下列代码可正常返回。异常发生,请修改,确保EIP跳至下一条指令。
mov ax,MAINDATA_SEL
mov ds,ax
lea si,Int_Pro_Mess
mov ax,ECHO_BUFFER_SEL
mov es,ax
mov di,160*8
mov cx,P_LEN
call FAR ptr dis_play
iretd
Int_Pro Endp
Int_D proc; 向只读数据段写数据,模拟异常发生。为使程序能正确返回,修改了EIP。
mov ax,MAINDATA_SEL
mov ds,ax
Lea si,Int_D_Mess
mov ax,ECHO_BUFFER_SEL
mov es,ax
mov di,160*6
mov cx,D_LEN
call Far ptr dis_play
pop eax; 针对本例,被动发生,弹出出错码。
pop eax; 弹出EIP。
add eax,7; mov dword ptr ds:[0],0指令7个字节长。
push eax
iretd
Int_D Endp
IntPage Proc
mov ax,MAINCODE_SEL
mov ds,ax
lea si,Dis_play
mov ax,COPY_SEL
mov es,ax
xor di,di
mov ecx,COPYLEN
rep movsb; 复制Dis_play代码到16MB内存处,即00FFF000H处。
mov ax,V_P_PDT_SEL; 页目录表选择子。
mov es,ax
xor di,di
mov cx,1024
xor eax,eax
rep stosd; 初始化页目录表项,共1K。
mov dword ptr es:[0],PT_0_ADDR+USU+RWW+PL; 页表0存在等属性。
mov ax,V_P_PT_0_SEL; 页表0选择子。
mov es,ax
xor di,di
mov cx,1024
xor eax,eax
mov eax,3; 线性地址与物理地址页映射具有相同的空间。
init_item:
stosd; 初始化页表项,共1K。
add eax,1000H
loop init_item
mov di,(PT_0_ADDR shr 12)*4
mov dword ptr es:[di],PT_0_ADDR +USU+RWR+PL
mov di,(PTC_VIDEO shr 12)*4; 第3FAH作特别修改,重新建立映射。
mov dword ptr es:[di],0B8000H+USu+RWW+PL
mov di,(PTC_TEST shr 12)*4; 第3FEH作特别修改,重新建立映射。
mov dword ptr es:[di],PhTestMem+USU+RWW+PL
mov di,(PTC_CODE shr 12)*4; 第3FFH作特别修改,重新建立映射。
mov dword ptr es:[di],PhExeMem+USU+RWR+PL
mov eax,PDT_ADDR
mov cr3,eax; CR3为页目录表的物理地址。
mov eax,cr0
or eax,80000000H
mov cr0,eax; 分页机制的启动。
; 显示出页表中部分项的内容。
mov di,160*10; ×10,换10行。
mov cx,6; 6个表项。
mov ax,V_P_PT_0_SEL
mov ds,ax
mov bx,(PTC_VIDEO shr 12)*4
nextitem:
push ds
push bx
push cx
push di
mov edx,dword ptr ds:[bx]; 虽然没有明确的页映射关系,但在初始化页表项时保证了线性地址与物理地址空间页映射相对应。实际上仍是通过分页来寻址的。
mov ax,MAINDATA_SEL
mov es,ax
lea di,Buffer_Page
mov cx,4
page_toa_c:; 取表项中的内容。
rol edx,8
mov al,dl
Call_16 MAINCODE_SEL,ToAscii
stosw
loop page_toa_c
mov ax,MAINDATA_SEL
mov ds,ax
lea si,Buffer_Page
mov ax,V_P_VIDEO_SEL
mov es,ax; 页表0中3FA表项的物理页地址是0B8000H。
pop di
add di,160
mov cx,8
call_16 < V_P_CODE_SEL>,<offset Dis_Play>; 页表0的3FFH项,第16MB内存处。
pop cx
pop bx
add bx,4
pop ds
loop nextitem
mov eax,cr0
and eax,7fffffffh
mov cr0,eax; 关闭分页机制。
iretd
MAINCODELENGTH=$
IntPage Endp
MainCodeSeg Ends
CodeSeg_2 Segment para use16; DPL2
assume cs:CodeSeg_2
Level2:
push 12345678H
call_16 LEVEL2GATE_SEL, <offset ReturnLevel_0_Entry>; 由CPL=2转向DPL=0的代码段,必须通过CALL调用门才能实现。指令中偏移无效,用门内偏移来代替。
LEVEL2LENGTH=$
CodeSeg_2 Ends
DosLDTSeg Segment para use16; DOS任务,以便能使用实方式下的中断。
DosLdt_Init_Start label byte
DosData DESCRIPTOR<DOSDATALENGTH-1,DosDataSeg,0,ATTDW,0>
DOSDATA_SEL=DosData-DosLDT_Init_Start+TIL
DosStack DESCRIPTOR<DOSSTACKLENGTH-1,DosStackSeg,0,ATTDW,0>
DOSSTACK_SEL=DosStack-DosLDT_Init_Start+TIL
DosCode DESCRIPTOR<0ffffh,DosCodeSeg,0,ATTCER,0>
DOSCODE_SEL=DosCode-DosLDT_Init_Start+TIL
DOSLDT_INIT_NUM=($-DosLdt_Init_Start)/(SIZE DESCRIPTOR)
DOSLDTLENGTH=$-DosLdt_Init_Start
DosLDTSeg Ends
DosDataSeg Segment para use16
DosMess db 0dh,0ah, "Press any key to Exit!"
DOSMESSLEN=$-DosMess
DOSDATALENGTH=$
DosDataSeg Ends
DosStackSeg Segment para use16
DOSSTACK_POINTER =1024
db DOSSTACK_POINTER dup(0)
DOSSTACKLENGTH=$
DosStackSeg Ends
DosCodeSeg Segment para use16
assume cs:DosCodeSeg,ds:DATA_SEG,ss:DosStackSeg
Input:
pushad
push ds
push es
push fs
push gs
mov ax,NORMAL_SEL
mov ss,ax
mov sp,ax
mov eax,cr0
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -