下面给出一个用于模拟异常和演示异常处理的实例。该实例的逻辑功能是,在屏幕上显示一条提示用户以按键方式选择异常类型的字符,然后模拟指定的异常。该实例演示内容包括:除法出错故障处理、溢出陷阱处理、段不存在故障处理、堆栈段出错处理和通用保护故障处理;还有作为一个独立任务方式出现的陷阱处理程序。
(6)实模式下执行的启动和结束程序代码段和数据段。
在切换到保护模式后,就进入临时代码段。为了简单,演示任务不发生特权级变换。演示步骤如下:
(1)从临时代码段转移到演示代码段。
(2)做演示准备。把演示任务的LDT选择子装入LDTR,并填入TSS,装载任务寄存器TR,建立演示任务堆栈,设置其它数据段寄存器。
(3)接收要模拟的异常类型号。通过软中断指令INT调用读键盘任务完成该步骤。读键盘任务只有在接收到指定的字符后才结束。接收的字符是0、4、B、C和D。
按接收的字符模拟异常。即根据键入的字符,执行有关程序片段。在这些片段中,有意安排了能引起有关故障或陷阱的指令。
结束演示,转临时代码段,返回DOS。
程序清单如下:
名称:ASM7.ASM
功能:模拟异常和演示异常处理
编译:TASM ASM7.ASM
连接:TLINK ASM7.OBJ
----------------------------------------------------------------------------
INCLUDE 386SCD.INC
----------------------------------------------------------------------------
GDTSeg SEGMENT PARA USE16 全局描述符表数据段(16位)
----------------------------------------------------------------------------
全局描述符表GDT
GDT LABEL BYTE
空描述符
DUMMY Desc <>
规范段描述符及选择子
Normal Desc <0ffffh,,,ATDW,,>
Normal_Sel = Normal-GDT
视频缓冲区段描述符(DPL=3)及选择子
VideoBuf Desc <0ffffh,8000h,0bh,ATDW,,>
VideoBuf_Sel = VideoBuf-GDT
----------------------------------------------------------------------------
EFFGDT LABEL BYTE
临时代码段描述符及选择子
TempCode Desc <0ffffh,TempCodeSeg,,ATCE,,>
TempCode_Sel = TempCode-GDT
演示代码段描述符及选择子
DemoCode Desc <DemoCodeLen-1,DemoCodeSeg,,ATCE,,>
DemoCode_Sel = DemoCode-GDT
演示任务局部描述符表段描述符及选择子
DemoLDT Desc <DemoLDTLen-1,DemoLDTSeg,,ATLDT,,>
DemoLDT_Sel = DemoLDT-GDT
演示任务TSS段描述符及选择子
DemoTSS Desc <DemoTSSLen-1,DemoTSSSeg,,AT386TSS,,>
DemoTSS_Sel = DemoTSS-GDT
缓冲数据段描述符及选择子
XBuffer Desc <BufferLen-1,BufferSeg,,ATDW,,>
XBuffer_Sel = XBuffer-GDT
读键盘任务局部描述符表段描述符及选择子
GKeyLDT Desc <GKeyLDTLen-1,GKeyLDTSeg,,ATLDT,,>
GKeyLDT_Sel = GKeyLDT-GDT
读键盘任务TSS段描述符及选择子
GKeyTSS Desc <GKeyTSSLen-1,GKeyTSSSeg,,AT386TSS,,>
GKeyTSS_Sel = GKeyTSS-GDT
显示陷阱处理程序代码段描述符及选择子
EchoCode Desc <EchoCodeLen-1,EchoCodeSeg,,ATCE,,>
EchoCode_Sel = EchoCode-GDT
显示出错码过程代码段描述符及选择子
SubCode Desc <SubCodeLen-1,SubCodeSeg,,ATCE,,>
SubCode_Sel = SubCode-GDT
其它中断或异常处理程序代码段描述符及选择子
Other Desc <OtherCodeLen-1,OtherCodeSeg,,ATCE,,>
Other_Sel = Other-GDT
----------------------------------------------------------------------------
GDTLen = $-GDT 全局描述符表长度
GDNum = ($-EFFGDT)/(SIZE Desc) 需处理基地址的描述符个数
----------------------------------------------------------------------------
GDTSeg ENDS 全局描述符表段定义结束
----------------------------------------------------------------------------
IDTSeg SEGMENT PARA USE16 中断描述符表数据段(16位)
----------------------------------------------------------------------------
IDT LABEL BYTE 中断描述符表
0号陷阱门描述符(对应除法出错故障)
Gate <DivBegin,Divide_Sel,,AT386TGate,>
从1--3的3个陷阱门描述符
REPT 3
Gate <OtherBegin,Other_Sel,,AT386TGate,>
ENDM
4号陷阱门描述符(对应溢出陷阱)
Gate <OFBegin,OF_Sel,,AT386TGate,>
从5--0ah的的6个陷阱门描述符
REPT 6
Gate <OtherBegin,Other_Sel,,AT386TGate,>
ENDM
0bh号陷阱门描述符(对应段不存在故障)
Gate <SNPBegin,SNP_Sel,,AT386TGate,>
0ch号陷阱门描述符(对应堆栈段故障)
Gate <SSEBegin,SSE_Sel,,AT386TGate,>
0dh号陷阱门描述符(对应通用保护故障)
Gate <GPBegin,GP_Sel,,AT386TGate,>
从0eh--0edh的240个陷阱门描述符
REPT 240
Gate <OtherBegin,Other_Sel,,AT386TGate,>
ENDM
对应0feh号陷阱门描述符(对应显示中断处理程序)
Gate <EchoBegin,EchoCode_Sel,,AT386TGate,>
0ffh号任务门描述符(对应读键盘中断处理任务)
Gate <,GKeyTSS_Sel,,ATTaskGate,>
----------------------------------------------------------------------------
IDTLen = $-IDT
----------------------------------------------------------------------------
IDTSeg ENDS 中断描述符表段定义结束
----------------------------------------------------------------------------
读键盘任务局部描述符表段
----------------------------------------------------------------------------
GKeyLDTSeg SEGMENT PARA USE16
----------------------------------------------------------------------------
GLDT LABEL BYTE
代码段描述符及选择子
GKeyCode Desc <0ffffh,GKeyCodeSeg,,ATCE,,>
GKeyCode_Sel = GKeyCode-GLDT+TIL
堆栈段描述符及选择子
GKeyStack Desc <GKeyStackLen-1,GKeyStackSeg,,ATDWA,,>
GKeyStack_Sel = GKeyStack-GLDT+TIL
----------------------------------------------------------------------------
GKeyLDNum = ($-GLDT)/(SIZE Desc) 需初始化基地址的描述符个数
GKeyLDTLen = $ 局部描述符表段长度
----------------------------------------------------------------------------
GKeyLDTSeg ENDS
----------------------------------------------------------------------------
读键盘任务TSS段
----------------------------------------------------------------------------
GKeyTSSSeg SEGMENT PARA USE16
DD 0 链接字
DD ? 0级堆栈指针
DW ?,?
DD ? 1级堆栈指针
DW ?,?
DD ? 2级堆栈指针
DW ?,?
DD 0 CR3
DW GKeyBegin,0 EIP
DD 0 EFLAGS
DD 0 EAX
DD 0 ECX
DD 0 EDX
DD 0 EBX
DW GKeyStackLen,0 ESP
DD 0 EBP
DD 0 ESI
DD 0 EDI
DW Normal_Sel,0 ES
DW GKeyCode_Sel,0 CS
DW GKeyStack_Sel,0 SS
DW Normal_Sel,0 DS
DW Normal_Sel,0 FS
DW Normal_Sel,0 GS
DW GKeyLDT_Sel,0 LDTR
DW 0 调试陷阱标志
DW $+2 指向I/O许可位图的偏移
DB 0ffh I/O许可位图结束字节
GKeyTSSLen = $
GKeyTSSSeg ENDS
----------------------------------------------------------------------------
读键盘任务堆栈段
----------------------------------------------------------------------------
GKeyStackSeg SEGMENT PARA USE16
GKeyStackLen = 1024
DB GKeyStackLen DUP(0)
GKeyStackSeg ENDS
----------------------------------------------------------------------------
读键盘任务代码段
----------------------------------------------------------------------------
GKeyCodeSeg SEGMENT PARA USE16
ASSUME CS:GKeyCodeSeg,DS:RDataSeg,ES:BufferSeg
----------------------------------------------------------------------------
GKeyBegin PROC FAR
push ds
push es
push fs
push gs
mov ax,Normal_Sel
mov ss,ax 准备转实方式
mov eax,cr0
and al,11111110b
mov cr0,eax 转实方式
JUMP16 <SEG GetKey>,<OFFSET GetKey>
GetKey: mov ax,RDataSeg 实方式
mov ds,ax
mov ebp,esp 恢复实方式部分现场
lss sp,DWORD PTR SPVar
lidt QWORD PTR NORVIDTR
sti
mov dx,OFFSET Mess
mov ah,9
int 21h 显示提示信息
GetKey1: mov ah,0
int 16h 读键盘
cmp al,'0'
jz GetKey2
cmp al,'4'
jz GetKey2
and al,11011111b 小写转大写
cmp al,'B'
jb GetKey1
cmp al,'D'
ja GetKey1 只有[0,4,b,c,d]有效
GetKey2: mov dl,al
mov ah,2
int 21h 显示所按字符
mov ax,BufferSeg
mov es,ax
mov BYTE PTR es:KeyASCII,dl 保存到缓冲数据段
cli 准备返回保护方式
lidt QWORD PTR VIDTR
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 <GKeyCode_Sel>,<OFFSET GetKeyV>
GetKeyV: mov ax,GKeyStack_Sel 又进入保护方式
mov ss,ax
mov esp,ebp
pop gs
pop fs
pop es
pop ds
iretd
jmp GKeyBegin
GKeyBegin ENDP
----------------------------------------------------------------------------
GKeyCodeLen = $
GKeyCodeSeg ENDS
----------------------------------------------------------------------------
其它中断或异常处理程序的代码段
----------------------------------------------------------------------------
OtherCodeSeg SEGMENT PARA USE16
ASSUME CS:OtherCodeSeg
----------------------------------------------------------------------------
OtherBegin PROC FAR
mov si,OFFSET MessOther
int 0feh 显示提示信息
mov WORD PTR es:[0],ax
jmp $ 进入无限循环
OtherBegin ENDP
----------------------------------------------------------------------------
OtherCodeLen = $
OtherCodeSeg ENDS
----------------------------------------------------------------------------
除法出错故障处理程序代码段
----------------------------------------------------------------------------
DivCodeSeg SEGMENT PARA USE16
ASSUME CS:DivCodeSeg
----------------------------------------------------------------------------
DivBegin PROC FAR
mov si,OFFSET Mess0
mov di,0
int 0feh 显示提示信息
shr ax,1 处理模拟的除法错误
iretd 返回
DivBegin ENDP
----------------------------------------------------------------------------
DivCodeLen = $
DivCodeSeg ENDS
----------------------------------------------------------------------------
溢出陷阱处理程序代码段
----------------------------------------------------------------------------
OFCodeSeg SEGMENT PARA USE16
ASSUME CS:OFCodeSeg
----------------------------------------------------------------------------
OFBegin PROC FAR
mov si,OFFSET Mess4
mov di,0
int 0feh 显示提示信息
iretd 返回
OFBegin ENDP
----------------------------------------------------------------------------
OFCodeLen = $
OFCodeSeg ENDS
----------------------------------------------------------------------------
段不存在故障处理程序代码段
----------------------------------------------------------------------------
SNPCodeSeg SEGMENT PARA USE16
ASSUME CS:SNPCodeSeg
----------------------------------------------------------------------------
SNPBegin PROC FAR
mov si,OFFSET MessB
mov di,0
int 0feh 显示提示信息
pop eax 弹出出错代码
CALL16 SubCode_Sel,SubBegin 显示出错代码
pop eax
add eax,2 按模拟的引起段不存在指令
push eax 调整返回地址
iretd
SNPBegin ENDP
----------------------------------------------------------------------------
SNPCodeLen = $
SNPCodeSeg ENDS
----------------------------------------------------------------------------
堆栈段故障处理程序代码段
----------------------------------------------------------------------------
SSECodeSeg SEGMENT PARA USE16
ASSUME CS:SSECodeSeg
----------------------------------------------------------------------------
SSEBegin PROC FAR
mov si,OFFSET MessC
mov di,0
int 0feh 显示提示信息
pop eax 弹出出错代码
CALL16 SubCode_Sel,SubBegin 显示出错代码
pop eax
add eax,4 按模拟的引起堆栈段错误的
push eax 指令调整返回地址
iretd
SSEBegin ENDP
----------------------------------------------------------------------------
SSECodeLen = $
SSECodeSeg ENDS
----------------------------------------------------------------------------
通用保护故障处理程序代码段
----------------------------------------------------------------------------
GPCodeSeg SEGMENT PARA USE16
ASSUME CS:GPCodeSeg
----------------------------------------------------------------------------
GPBegin PROC FAR
push ebp
mov ebp,esp
push eax
push esi
push edi 保护现场
mov si,OFFSET MessD
mov di,0
int 0feh 显示提示信息
mov eax,[bp+4] 从堆栈中取出出错代码
CALL16 SubCode_Sel,SubBegin 显示出错代码
pop edi
pop esi
pop eax 恢复部分现场
add DWORD PTR [ebp+8],2 按模拟的故障指令调整返回
pop ebp 地址
add esp,4 废除堆栈中的出错代码
iretd
GPBegin ENDP
----------------------------------------------------------------------------
显示出错码过程代码段
----------------------------------------------------------------------------
SubCodeSeg SEGMENT PARA USE16
ASSUME CS:SubCodeSeG
----------------------------------------------------------------------------
SubBegin PROC AX中含出错代码
push ax 保护现场
push cx
push dx
push si
push di
mov si,OFFSET ErrCode
mov dx,ax
mov cx,4
SubR1: rol dx,4 把16位出错代码转换成4位
mov al,dl 十六进制数的ASCII码并保存
and al,0fh
add al,30h
cmp al,'9'
jbe SubR2
add al,7
SubR2: mov [si],al
inc si
loop SubR1
mov si,OFFSET ErrMess
Mov di,80*2 从第二行行首开始
int 0feh 显示出错码
pop di 恢复现场
pop si
pop dx
pop cx
pop ax
retf 返回
SubBegin ENDP
----------------------------------------------------------------------------
SubCodeLen = $
SubCodeSeg ENDS
----------------------------------------------------------------------------
GPCodeLen = $
GPCodeSeg ENDS
----------------------------------------------------------------------------
实现显示的陷阱处理程序代码段
入口参数--DS:SI指向显示信息串,ES:DI指向显示缓冲区
----------------------------------------------------------------------------
EchoCodeSeg SEGMENT PARA USE16
ASSUME CS:EchoCodeSeg
----------------------------------------------------------------------------
EchoBegin PROC FAR
pushad 保护现场
cld
mov ah,7
mov al,20h
mov cx,80
push di
rep stosw 清所在显示行
pop di
Echo1: lodsb
or al,al
jz Echo2
stosw 显示指定信息串
jmp Echo1
Echo2: popad 恢复现场
iretd
EchoBegin ENDP
----------------------------------------------------------------------------
EchoCodeLen = $
EchoCodeSeg ENDS
----------------------------------------------------------------------------
缓冲区数据段
----------------------------------------------------------------------------
BufferSeg SEGMENT PARA USE16
KeyASCII DB ?
Buffer DB 128 DUP(?)
BufferLen = $
BufferSeg ENDS
----------------------------------------------------------------------------
演示任务局部描述符表段
----------------------------------------------------------------------------
DemoLDTSeg SEGMENT PARA USE16
----------------------------------------------------------------------------
DLDT LABEL BYTE
演示任务TSS段作为数据段的描述符及选择子
ToDemoTSS Desc <DemoTSSLen-1,DemoTSSSeg,,ATDW,,>
ToDemoTSS_Sel = ToDemoTSS-DLDT+TIL
演示任务堆栈段描述符及选择子
DemoStack Desc <DemoStackLen-1,DemoStackSeg,,ATDWA,,>
DemoStack_Sel = DemoStack-DLDT+TIL
演示任务数据段描述符及选择子
DemoData Desc <DemoDataLen-1,DemoDataSeg,,ATDW,,>
DemoData_Sel = DemoData-DLDT+TIL
除法出错故障处理程序代码段描述符及选择子
Divide Desc <DivCodeLen-1,DivCodeSeg,,ATCE,,>
Divide_Sel = Divide-DLDT+TIL
溢出陷阱处理程序代码段描述符及选择子
OverFlow Desc <OFCodeLen-1,OFCodeSeg,,ATCE,,>
OF_Sel = OverFlow-DLDT+TIL
段不存在故障处理程序代码段描述符及选择子
SNPCode Desc <SNPCodeLen-1,SNPCodeSeg,,ATCE,,>
SNP_Sel = SNPCode-DLDT+TIL
堆栈段出错故障处理程序代码段描述符及选择子
SSECode Desc <SSECodeLen-1,SSECodeSeg,,ATCE,,>
SSE_Sel = SSECode-DLDT+TIL
通用保护故障处理程序代码段描述符及选择子
GPCode Desc <GPCodeLen-1,GPCodeSeg,,ATCE,,>
GP_Sel = GPCode-DLDT+TIL
为模拟段不存在故障而安排的数据段描述符及选择子
TestNPS Desc <0ffffh,,,ATDW-80h,,>
TestNPS_Sel = TestNPS-DLDT+TIL
----------------------------------------------------------------------------
DemoLDNum = ($-DLDT)/(SIZE Desc) LDT描述符个数
DemoLDTLen =$
----------------------------------------------------------------------------
DemoLDTSeg ENDS
----------------------------------------------------------------------------
演示任务TSS段
----------------------------------------------------------------------------
DemoTSSSeg SEGMENT PARA USE16
DemoTaskSS TSS <>
DB 0ffh
DemoTSSLen = $
DemoTSSSeg ENDS
----------------------------------------------------------------------------
演示任务的堆栈段
----------------------------------------------------------------------------
DemoStackSeg SEGMENT PARA USE16
DemoStackLen = 1024
DB DemoStackLen DUP(0)
DemoStackSeg ENDS
----------------------------------------------------------------------------
演示任务的数据段
----------------------------------------------------------------------------
DemoDataSeg SEGMENT PARA USE16
Mess0 DB 'Divide Error (Exception 0)',0
Mess4 DB 'Overflow (Exception 4)',0
MessB DB 'Segment Not Present (Exception 11)',0
MessC DB 'Stack Segment (Exception 12)',0
MessD DB 'General Protection (Exception 13)',0
MessOther DB 'Other Exception',0
ErrMess DB 'Error Code = '
ErrCode DB 4 DUP(0),'H',0
DemoDataLen = $
DemoDataSeg ENDS
----------------------------------------------------------------------------
演示任务的代码段
----------------------------------------------------------------------------
DemoCodeSeg SEGMENT PARA USE16
ASSUME CS:DemoCodeSeg,DS:DemoDataSeg
----------------------------------------------------------------------------
DemoBegin PROC FAR
mov ax,DemoLDT_Sel
lldt ax 装载LDTR
mov ax,DemoStack_Sel 置堆栈
mov ss,ax
mov esp,DemoStackLen
mov ax,ToDemoTSS_Sel
mov gs,ax 把演示任务LDT选择子填入TSS
mov WORD PTR gs:DemoTaskSS.TRLDTR,DemoLDT_Sel
mov ax,DemoTSS_Sel
ltr ax 装载TR
mov ax,DemoData_Sel
mov ds,ax 装载其它数据段寄存器
mov ax,VideoBuf_Sel
mov es,ax
mov ax,XBuffer_Sel
mov fs,ax
mov ax,XBuffer_Sel
mov gs,ax
int 0ffh 接收要模拟的异常类型号
mov al,BYTE PTR fs:KeyASCII 按接收的字符模拟异常号
cmp al,'0'
jnz Demo4
mov ax,2000
mov cl,2 模拟除法出错故障
div cl 该指令长2字节
jmp Over
Demo4: cmp al,'4'
jnz Demo11
mov al,100
add al,50
into 模拟溢出陷阱
JMP OVER
Demo11: cmp al,'B'
jnz Demo12
mov ax,TestNPS_Sel 模拟段不存在故障
mov gs,ax 该指令长2字节
JMP Over
Demo12: cmp al,'C'
jnz Demo13
mov ebp,esp 模拟堆栈出错故障
mov al,[ebp] 该指令长4字节
jmp Over
Demo13: mov ax,DemoTSS_Sel 模拟通用保护故障
mov gs,ax 该指令长2字节
Over: 转临时代码段
JUMP16 TempCode_Sel,<OFFSET ToDos>
DemoBegin ENDP
----------------------------------------------------------------------------
DemoCodeLen = $
DemoCodeSeg ENDS
----------------------------------------------------------------------------
TempCodeSeg SEGMENT PARA USE16 临时任务的代码段
ASSUME CS:TempCodeSeg
----------------------------------------------------------------------------
Virtual PROC FAR
JUMP16 DemoCode_Sel,DemoBegin 转演示任务
ToDos: mov ax,Normal_Sel 恢复实方式段描述符高速缓存
mov ds,ax
mov es,ax
mov fs,ax
mov gs,ax
mov ss,ax
mov eax,cr0 准备返回实模式
and al,11111110b
mov cr0,eax
JUMP16 <SEG Real>,<OFFSET Real>
Virtual ENDP
----------------------------------------------------------------------------
TempCodeSeg ENDS
============================================================================
RDataSeg SEGMENT PARA USE16 实方式数据段
VGDTR PDesc <GDTLen-1,> GDT伪描述符
VIDTR PDesc <IDTLen-1,> IDT伪描述符
NORVIDTR PDesc <3ffh,> 用于保存原IDTR值
SPVar DW ? 用于保存实方式下的SP
SSVar DW ? 用于保存实方式下的SS
Mess DB 'Press a key[0,4,B,C,D]:$'提示信息
RDataSeg ENDS
----------------------------------------------------------------------------
RCodeSeg SEGMENT PARA USE16 实方式代码段
ASSUME CS:RCodeSeg,DS:RDataSeg
----------------------------------------------------------------------------
Start PROC
mov ax,RDataSeg
mov ds,ax
cld
call InitGDT 初始化全局描述符表GDT
call InitIDT 初始化中断描述符表IDT
mov ax,GKeyLDTSeg
mov fs,ax
mov cx,GKeyLDNum
mov si,OFFSET GLDT
CALL InitLDT
mov ax,DemoLDTSeg
mov fs,ax
mov cx,DemoLDNum
mov si,OFFSET DLDT
CALL InitLDT
mov SSVar,ss 保存堆栈指针
mov SPVar,sp
lgdt QWORD PTR VGDTR 装载GDTR
sidt QWORD PTR NORVIDTR 保存IDTR
cli 关中断
lidt QWORD PTR VIDTR 装载IDTR
mov eax,cr0
or al,1
mov cr0,eax
JUMP16 <TempCode_Sel>,<OFFSET Virtual>
Real: mov ax,RDataSeg
mov ds,ax
lss sp,DWORD PTR SPVar 又回到实方式
lidt QWORD PTR NORVIDTR
sti
mov ax,4c00h
int 21h
Start ENDP
----------------------------------------------------------------------------
InitGDT PROC
push ds
mov ax,GDTSeg
mov ds,ax
mov cx,GDNum
mov si,OFFSET EFFGDT
InitG: mov ax,[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR [si].BaseL,ax
mov BYTE PTR [si].BaseM,dl
mov BYTE PTR [si].BaseH,dh
add si,SIZE Desc
loop InitG
pop ds
mov bx,16
mov ax,GDTSeg
mul bx
mov WORD PTR VGDTR.Base,ax
mov WORD PTR VGDTR.Base+2,dx
ret
InitGDT ENDP
----------------------------------------------------------------------------
InitIDT PROC
mov bx,16
mov ax,IDTSeg
mul bx
mov WORD PTR VIDTR.Base,ax
mov WORD PTR VIDTR.Base+2,dx
ret
InitIDT ENDP
----------------------------------------------------------------------------
入口参数:FS:SI=第一个要初始化的描述符,CX=要初始化的描述符数
----------------------------------------------------------------------------
InitLDT PROC
ILDT: mov ax,WORD PTR FS:[si].BaseL
movzx eax,ax
shl eax,4
shld edx,eax,16
mov WORD PTR fs:[si].BaseL,ax
mov BYTE PTR fs:[si].BaseM,dl
mov BYTE PTR fs:[si].BaseH,dh
add si,SIZE Desc
loop ILDT
ret
InitLDT ENDP
----------------------------------------------------------------------------
RCodeSeg ENDS
END Start
2.关于实例七的说明
上述模拟与演示程序的许多内容与实例六相同,下面就各异常处理程序和读键盘任务的实现作些说明:
(1)除法出错故障处理程序的实现
从源程序可见,除法出错是在执行故意安排的被除数为2000,而除数为2的无符号除指令时引起的。作为演示,除法出错故障处理程序先显示一条提示信息,然后把存放被除数AX的内容右移一位,然后就返回。由于除法出错为故障类异常,所以在故障处理结束后,仍执行该无符号除指令。显然将再次引起同样的故障,仍把被除数右移一位。由于每次处理时都把被除数减半,所以几次故障后就不发生该故障了。
(2)溢出陷阱处理程序的实现
作为演示的溢出陷阱处理程序较简单。先显示一条提示信息,然后就返回。因为溢出异常为陷阱类异常,所以在陷阱处理结束后,就直接返回到引起陷阱指令的下一条指令。
(3)段不存在故障处理程序的实现
从源程序可见,段不存在故障是在执行故意安排的把一个选择子送段寄存器GS的指令时引起的。该选择子索引的描述符中的存在位P被置为0,表示对应段不在内存。在正常情况下,段不存在故障处理程序要把对应的段装入内存,再把描述符内的P位修改为1,于是,在故障处理结束后,引起故障的指令可得到顺序执行。为了简单,这里安排的故障处理程序先显示一条提示信息,然后显示出错码,最后调整堆栈中的返回地址并返回。段不存在故障提供一个出错码,该故障处理程序利用POP指令把它用堆栈中弹出,这样堆栈指针就指向返回地址。由于段不存在异常属于故障类异常,所以返回点仍是引起故障的指令。因此,演示程序调整了堆栈中的返回地址,使其返回到引起故障的指令的下一条指令。
(4)堆栈段出错故障处理程序的实现
引起堆栈出错故障的原因有多种,实例通过执行故意安排的偏移超过段界限的堆栈段访问指令来模拟堆栈段出错故障的产生。作为演示的堆栈出错故障处理程序比较简单,先显示一条信息,然后显示出错码,最后调整堆栈中的返回地址并返回。
(5)通用保护障处理程序的实现
引起通用保护故障处理程序的原因有多种,实例通过把一个指向系统段描述符的选择子装入数据段寄存器GS来模拟通用保护故障的产生。作为演示的通用保护故障处理程序,象上述两个故障处理程序一样比较简单,先显示一条提示信息,然后显示出错码,最后调整堆栈中的返回地址并返回,但在废除堆栈中的出错码和调整堆栈中的返回地址时采用了其它方法。
(6)异常处理程序的一般说明
在实例中,通向上述各种异常处理程序的门都是陷阱门。所以,在发生异常而转入这些异常处理程序时,都不发生任务切换。于是,这些异常处理仍作为演示任务的一部分。
正常情况下,异常处理程序应该注意现场的保护和恢复,但为了简单,作为演示的异常处理程序没有能够切实地保护现场。注意,这些异常处理程序所采用的处理方法与所模拟的指令有关,不适用于一般情况。
(7)显示出错代码的过程
实例采用一个过程用于显示出错代码,该过程的入口参数是AX含出错码。利用该过程不仅缩短程序,而且也用于表现异常处理程序的实现。
(8)读键盘任务的实现
在实例的IDT中,0FFH号门描述符是任务门,指向一个独立的任务。该任务的功能是读键盘,接收一个指定范围内的字符。演示任务通过指令“INT 0FFH”来调用它,接收一个代表需要模拟异常的字符。
为了简单,该任务在实模式下读键盘,接收指定范围内的字符。为此,该任务每次经历如下步骤:(1)转到实模式。此前要作必要的准备,转到实模式后,要恢复必须的实模式下的部分现场。(2)接收指定的字符。调用DOS功能显示提示信息,调用BIOS中断读键盘,如果在指定范围内,那么就显示,并保存在约定的数据段中。(3)转回到保护模式,此前也要作必要的准备。
尽管在任务切换时,自动利用TSS保护和恢复现场,但由于该任务相当于一个读键盘的过程,所以在开始任务时,还通过堆栈保护必要的现场,在结束任务时恢复现场。请特别注意,安排在该任务代码段中的IRETD指令之后的转移指令的作用。当执行IRETD指令时,由于NT位为1,所以按反向链进行任务切换,同时保存各寄存器到当前的TSS,为了下次进入时仍能从头开始执行此任务,所以在IRETD后加一条转移到该任务开头的指令。