📄 virtual.asm
字号:
and eax,0fffffffeh
mov cr0,eax
jmp_16 <seg dos_int>,<offset dos_int>
dos_int:
mov ax,DATA_SEG
mov ds,ax
LSS sp,dword ptr Sp_Value
LIDT RealIdtr
sti
mov ah,6
mov al,0
mov bh,0; f & b Color
mov cx,0
mov dx,184fh
int 10h ; 清屏。
mov ax,MainDataSeg
mov es,ax
mov ds,ax
lea bp,ExtMemMess
mov dx,0h
mov bh,0
mov cx,EMM_LEN+48
mov ah,13h
mov al,1
mov bl,034h
int 10H
cli
mov ax,DATA_SEG
mov ds,ax
LIDT qword ptr V_Idtr
mov eax,CR0
or eax,1
mov CR0,eax
JMP_16 < DOSCODE_SEL>,<offset Dos_V>
Dos_V:
mov ax,DOSSTACK_SEL
mov ss,ax
pop gs
pop fs
pop es
pop ds
popad
IRETD
jmp Input; 任务切换后,该地址保存于TSS的EIP成员中。
DOSCODELENGTH=$
DosCodeSeg Ends
DosTSSSeg Segment para use16
dd 0; trlink。
dd ? ; esp0。
dw ?,0; ss0。
dd ? ; esp1。
dw ?,0; ss1。
dd ? ; esp2。
dw ?,0; ss2。
dd 0; cr3。
dw Input,0; eip。
dd 0; psw。
dd 0; eax。
dd 0; ecx。
dd 0; edx。
dd 0; ebx。
dw DOSSTACKLENGTH,0; esp。
dd 0; ebp。
dd 0; esi。
dd 0; edi。
dw NORMAL_SEL,0; es。
dw DOSCODE_SEL,0; cs。
dw DOSSTACK_SEL,0 ; ss。
dw NORMAL_SEL,0 ; ds。
dw NORMAL_SEL,0 ; fs。
dw NORMAL_SEL,0 ; gs。
dw DOSLDT_SEL,0 ; ldt。
dw 0
dw $+2
db 0ffh
DOSTSSLENGTH=$
DosTSSSeg Ends
ControlSeg Segment PARA use16
assume cs:ControlSeg
VirtualEntry:; 保护方式的入口。
; ===============================================================
; ①为保护方式下任务切换作准备。
mov ax,MAINTSS_ALIAS_SEL; 别名。
mov fs,ax
mov dword ptr fs:Main_Tss.tresp0,MAINSTACK_POINTER_0
mov fs:Main_Tss.trss0,MAINSTACK_0_SEL
mov dword ptr fs:Main_Tss.tresp2,MAINSTACK_POINTER_2
mov fs:Main_Tss.trss2,MAINSTACK_2_SEL; 设置栈项为特权级变化作准备。
mov fs:Main_Tss.trldt,MAINLDT_SEL
mov ax,MAINDATA_SEL
mov ds,ax
mov ax,ECHO_BUFFER_SEL
mov es,ax
mov gs,ax
mov ax,MAINSTACK_0_SEL
mov ss,ax
mov esp,MAINSTACK_POINTER_0
mov ax,MAINTSS_SEL
ltr ax
mov ax,MAINLDT_SEL
lldt ax
; 说明: 上段代码为任务切换和特权级转换提供初始化环境,无论何种情况发生,返回后都必须要再检测每个段寄存器内容,给段寄存器赋值的用途正在于此。
; ===============================================================
; ②1MB以外内存的存取。
Call_16 VISITEXMEM_SEL,GetExMem
int 21h; 显示结果。
; 说明: 在MAIN任务中访问扩展内存,在DOS任务中完成显示。任务由MAIN切换到DOS任务中。先检测DOSTSS任务门,在非普通代码段中,只有CPL(MAIN任务)≤DPL(DOSTSS任务门)才能进行切换,任务切换大致要经过以下几步:
; ①检测DOSTSS区大小,不得小于103个字节。
; ②保存MAIN任务的相关寄存器的值,LDTR、CR3寄存器的值须手动保存于MAINTSS中。但本例中由于原来已设置过MAINTSS的LDT成员值,故没有必要再手动保存。
; ③把MAINTSS的选择子自动保存在DOSTSS的TrLink中。
; ④装载相应的寄存器,通过任务门的检查就可转向该任务中。
; ⑤DOS任务执行完毕,根据NT位判断是否需要解链处理。如果NT=1,则用DOSTSS中的TrLink恢复MAINTSS状态。注意,在恢复状态前要检测相关段寄存器的值,这是① 中初始化段寄存器的目的之一。
; ===============================================================
; ③任务内特权级的变化。
push MAINSTACK_2_SEL
push MAINSTACK_POINTER_2; 在0级堆栈中压入2级堆栈的SS: SP。
push LEVEL2_SEL
push offset Level2; 在0级堆栈中压入2级返回的指令地址CS:IP。
retf; 强制段间返回。
ReturnLevel_0_Entry:
pop ebx; level2 eip。
pop ebx; level2 32bit cs。
pop edx; 12345678H。
mov ax,MAINDATA_SEL
mov es,ax
lea di,Buffer_DPL
mov cx,4
toa_c:
rol edx,8
mov al,dl
Call_16 MAINCODE_SEL,ToAscii; 转换为ASCII。
stosw
loop toa_c
mov ax,MAINDATA_SEL
mov ds,ax
lea si,DPLConvert
mov ax,ECHO_BUFFER_SEL
mov es,ax
mov di,160*3
mov cx,DPL_LEN
call_16 MAINCODE_SEL,Dis_play
mov di,160*4; 换4行,下同。
mov cx,8
lea si,Buffer_DPL
mov cx,8
call_16 MAINCODE_SEL,Dis_play; 显示结果。
; 说明: 特权级变化是个复杂的过程。在本例中,程序进入保护方式后,CPL=0,要完成演示特权级变化的过程,需要让程序进入一个DPL=2的代码段中,即由特权级高向特权级低的方向转移。这种转移不可以通过CALL、JMP指令完成,必须要通过RET指令(包括从任务返回也是这样)。要营造一种由0级特权级向2级特权级转移的工作环境,通过RET返回,必须要在0级特权的堆栈中压入二级堆栈指针和程序要返回的地址,由于本例工作于16位方式,故只须压入16位值即可。
; retf指令执行后,程序转移到CodeSeg_2段的LeveL2入口处,这时CPL=2。
; 程序执行到Level2处时,实际上工作的堆栈也是二级堆栈。在二级堆栈中演示压入12345678H的值,然后通过CALL调用指令,转向ReturnLevel_0_Entry(DPL=0)处。这是一个典型的从CPL=2向DPL=0的由外层向内层转移过程。
; 要完成这个过程,必须满足以下几个条件:①只能通过调用门或任务切换才能完成由特权级由低向高的转移; ②必须通过特级级检查。检查的内容是CPL与调用门的DPL关系,只有CPL≤DPL才能通过检查; ③相应的堆栈指针必须设置好,即使不使用的堆栈; ④如果该过程要传递参数,必须在调用门描述符中说明(即双字计数器),本例为1。在本例中完成这个过程的步骤如下:
; ⑴在0级堆栈中保存2级堆栈的地址(32位)。
; ⑵复制双字计数器指定的参数,即12345678H。
; ⑶在0级堆栈中保存供返回用的特权二级的EIP、32位CS。
; 上述三步,与我们在③中开始处营造的环境相似。请注意,不管是16位段还是32位段,在向内层转换过程中的堆栈是以32位方式入栈的。
; 程序返回到0级代码处,即ReturnLevel_0_Entry处,为了取得当前堆栈(0级)中的12345678H,必须先弹出保存在堆栈中的二级EIP和CS,即程序中连续二个pop ebx,然后pop edx,弹出的就是从二级堆栈中复制过来的12345678H。
; 这时,您可能注意到,在保护方式下,好像维护堆栈平衡并不是一件十分重要的事情,的确如此!Windows操作系统就有这种特点。如本例中,二级堆栈中压入的12345678H就不平衡。但不管怎样,必须慎重对待堆栈中保存的返回IP(EIP)、CS。
; ===============================================================
; ④模拟异常发生,通过向只读数据段写而导致中断自然被动发生,即故障。
mov ax,EXTMEM_SEL
mov ds,ax
mov dword ptr ds:[0],0
; 说明: EXTMEM_SEL的段定义为只读,因此,凡是通过EXTMEM_SEL选择子来访问该段的程序必须都不能写。本例模拟向这个区域内写一个双字,将会发生通用保护故障(0DH类型)。在执行0DH类型的中断服务子程序时,先在堆栈中保存PSW、CS、EIP,它们都是32位(保护方式下的中断入栈都是32位)。显示相关信息后,程序要返回,返回的地址指令是“mov dword ptr ds:[0],0”,将产生无限循环。该指令为7个字节长,EIP=EIP+7保证中断服务子程序返回后的地址是⑤处。
; ===============================================================
; ⑤模拟异常发生,,主动调用。
INT 0FFH
; 说明: 该指令主动调用0FFH类型的中断服务子程序,不会产生出错码,并且在堆栈中保存的是下一条指令的地址,本例是⑥处,主动调用相当于一个陷阱。
; ===============================================================
; ⑥启动分页机制,通过陷阱门主动调用。
mov ax,MAINDATA_SEL
mov ds,ax
lea si,PageManage
mov ax,ECHO_BUFFER_SEL
mov es,ax
mov di,160*10
mov cx,PAGE_LEN
call_16 MAINCODE_SEL,Dis_play; 显示分页提示信息。
INT 0FEH
; 说明: 该指令通过陷阱门进入分页演示,IRETD指令返回,这里再补充一些内容。
; ①先做好页目录表、页表、物理页之间的对应关系,如4MB以内的物理内存对应页目录表0表项,同时对应页表0,4MB~8MB的物理内存对应页目录表1表项,同时对应页表1,依次类推,切不可“张冠李戴”。
; ②1MB以内的物理内存,最好保证逻辑页与物理页完全对应,避免由实方式向保护方式转变之后通过分页机制出现寻址混乱的现象。
; ③本例中,执行分页处的代码(第16M内存处)后,系统自动把页表0的3FFH表项的属性A位改为1,这与3FEH表项是不同的。
ToReal:; 为进入实方式作准备。
mov ax,NORMAL_SEL
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0; 退出保护方式。
and eax,0fffeh
mov cr0,eax
jmp FAR ptr ToDos
ControlSeg Ends
; 以下为实方式,初始化内容。
DATA_SEG Segment para use16
V_Gdtr G_IDC <GDTLENGTH-1,>; 全局描述符表。
V_Idtr G_IDC <IDTLENGTH-1,>; 中断描述符表。
RealIdtr G_IDC <3FFH,0>; 实方式下,中断表,0:0处。
Sp_Value dw ?
Ss_Value dw ?
DATA_SEG Ends
CODE_SEG Segment para use16
assume ds:DATA_SEG,CS:CODE_SEG
start:
mov ax,DATA_SEG
mov ds,ax
cld
push ds
call Init_Gdt; 初始化全局描述符表,即转化为实际的物理地址,下同。
call Init_Idt; 初始化中断描述符表。
assume ds:MainLDTSeg
mov ax,MainLDTSeg
mov ds,ax
lea si,MainLDT_Init_Start
mov cx,MAINLDT_INIT_NUM
call Init_Ldt; 初始化MAIN任务局部描述符表。
assume ds:DosLDTSeg
mov ax,DosLDTSeg
mov ds,ax
mov cx,DOSLDT_INIT_NUM
lea si,DosLDT_Init_Start
call Init_Ldt; 初始化DOS任务局部描述符表。
pop ds
assume ds:data_seg
mov Sp_Value,sp
mov Ss_Value,ss
LGDT V_Gdtr; GDTR
SIDT RealIdtr; 保存实方式下的IDT,这条指令最好放在LGDT之后。
cli
LIDT V_Idtr; IDTR
call Enabled_A20; 打开21号地址线。
mov eax,CR0
or eax,1
mov CR0,eax; 启动保护方式。
JMP_16 <SWITCH_SEL>,<offset VirtualEntry>; 实方式下预取。
ToDos:
call Disabled_A20; 关闭21号地址线。
mov ax,DATA_SEG
mov ds,ax
lss sp,dword ptr Sp_Value; 恢复实方式下的堆栈。
LIDT RealIdtr; 恢复实方式下的IDTR。
sti
mov ax,DosDataSeg
mov es,ax
lea bp,DosMess
mov dx,1700H
mov bh,0
mov cx,DOSMESSLEN
mov al,0
mov bl,0d2h
mov ah,13h
int 10h; 提示退出信息。
mov ah,0
int 16h
mov ah,6
mov al,0
mov bh,7; f & b Color
mov cx,0
mov dx,184fh
int 10h ; 清屏,恢复原屏幕。
mov ax,4c00h
int 21h
Init_Gdt proc near
push ds
assume ds:GDTSEG
mov ax,GDTSEG
mov ds,ax
mov cx,GDT_INIT_NUM
lea si,Gdt_Init_Start
repseg_gdt:
mov ax,[si].basel
mov bx,16
mul bx
mov [si].basel,ax
mov [si].basem,dl
mov [si].baseh,dh
add si,size DESCRIPTOR
loop repseg_gdt
assume ds:data_seg
pop ds
mov bx,16
mov ax,GDTSEG
mul bx
mov word ptr V_Gdtr.base,ax
mov word ptr V_Gdtr.base+2,dx
ret
Init_Gdt endp
Init_Ldt proc near
repseg_ldt:
mov ax,[si].basel
mov bx,16
mul bx
mov [si].basel,ax
mov [si].basem,dl
mov [si].baseh,dh
add si,size descriptor
loop repseg_ldt
ret
Init_Ldt endp
Init_Idt proc near
mov ax,IDTSEG
mov bx,16
mul bx
mov word ptr V_Idtr.base,ax
mov word ptr V_Idtr.base+2,dx
ret
Init_Idt Endp
Enabled_A20 proc near; 打开A20。
push ax
in al,92h
or al,2
out 92h,al
pop ax
ret
Enabled_A20 endp
Disabled_A20 proc; 关闭A20。
push ax
in al,92h
and al,0fdh
out 92h,al
pop ax
ret
Disabled_A20 endp
CODE_SEG ends
end Start
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -