实验 12 编写 0 号中断的处理程序
编写 0 号中断的处理程序,使得在除法溢出发生时在屏幕中间显示字符串“divide error!”,然后返回 DOS 操作系统。
要求:仔细跟踪调试,在理解整个过程之前,不要进行后面课程的学习。
具体代码如下:
必须先执行安装 do0 程序的 do0.asm 程序代码,再执行会产生 0 号中断的 test.asm 测试程序代码。
一、不考虑中断处理程序的返回问题
1. 安装 do0 程序的程序代码:
assume cs:code
code segment
; do0 程序(0 号中断处理程序)的安装
start: mov ax,cs
mov ds,ax
mov si,offset do0 ; 设置 ds:si 指向源地址
mov ax,0
mov es,ax
mov di,200h ; 设置 es:di 指向目的地址
mov cx,offset do0end-offset do0 ; 设置 cx 为传输长度
cld ; 设置传输方向为正
rep movsb
; 设置中断向量表
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
mov ax,4c00h
int 21h
do0: jmp short do0start
db "divide error!"
do0start: mov ax,cs
mov ds,ax
mov si,202h ; 设置 ds:si 指向字符串
mov ax,0B800h
mov es,ax
mov di,12*160+36*2 ; 设置 es:di 指向显存空间的中间位置
mov cx,13 ; 设置 cx 为字符串长度
s: mov al,[si]
mov es:[di],al
mov byte ptr es:[di+1],2 ; 设置显示的字符串字体属性:绿色
inc si
add di,2
loop s
iret
do0end: nop
code ends
end start
2. 产生 0 号中断的测试程序的代码:
assume cs:codeseg
codeseg segment
; begin 代码选自书本 p240 的 12.6 节的内容
begin: mov ax,1000h
mov bh,1
div bh ; 执行本行除法运算代码,将产生 0 号中断
; 由硬件自动执行的中断过程,其入栈的 CS 和 IP 值,
; 不是指向产生中断信息的指令(此处为本行的 div 指令)的下一条指令,
; 而就是指向这条产生中断信息的当前指令(也就是本行的 div 指令),
; 这将导致从中断处理程序的末尾 iret 指令返回的时,
; 会不断执行这条产生中断过程的问题指令,并成为一个死循环。
mov ax,53A8h ; 本行代码用于验证引发 0 号中断的 div 指令中断处理程序的执行结果
mov ax,4c00h
int 21h
codeseg ends
end begin
二、考虑中断处理程序的返回问题
1. 安装 do0 程序的程序代码:
assume cs:code
code segment
; do0 程序(0 号中断处理程序)的安装
start: mov ax,cs
mov ds,ax
mov si,offset do0 ; 设置 ds:si 指向源地址
mov ax,0
mov es,ax
mov di,200h ; 设置 es:di 指向目的地址
mov cx,offset do0end-offset do0 ; 设置 cx 为传输长度
cld ; 设置传输方向为正
rep movsb
; 设置中断向量表
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
mov ax,4c00h
int 21h
do0: jmp short do0start
db "divide error!"
do0start: mov ax,cs
mov ds,ax
mov si,202h ; 设置 ds:si 指向字符串
mov ax,0B800h
mov es,ax
mov di,12*160+36*2 ; 设置 es:di 指向显存空间的中间位置
; 本程序在不考虑中断处理程序返回问题的程序代码基础中添加以下两行代码,
; 否则将陷入引发 0 号中断过程的问题指令的死循环 —— 不断执行引发 0 号中断过程的问题指令
mov bp,sp
add [bp],cx
mov cx,13 ; 设置 cx 为字符串长度
s: mov al,[si]
mov es:[di],al
mov byte ptr es:[di+1],2 ; 设置显示的字符串字体属性:绿色
inc si
add di,2
loop s
iret
do0end: nop
code ends
end start
2. 产生 0 号中断的测试程序的代码:
(1) 测试代码 1
assume cs:codeseg
codeseg segment
; begin 代码选自书本 p240 的 12.6 节的内容
begin: mov ax,1000h
mov bh,1
mov cx,offset s2-offset s1 ; 将产生中断过程的 div 指令长度存入 CX 寄存器,
; 提供给中断处理程序使用
s1: div bh ; 执行本行除法运算代码,将产生 0 号中断
; 由硬件自动执行的中断过程,其入栈的 CS 和 IP 值,
; 不是指向产生中断信息的指令(此处为本行的 div 指令)的下一条指令,
; 而就是指向这条产生中断信息的当前指令(也就是本行的 div 指令),
; 这将导致从中断处理程序的末尾 iret 指令返回的时,
; 会不断执行这条产生中断过程的问题指令,并成为一个死循环。
s2: mov ax,53A8h
mov ax,4c00h
int 21h
codeseg ends
end begin
(2) 测试代码 2
assume cs:codeseg
datasg segment
db "Test Over!",0
datasg ends
codeseg segment
; begin 代码选自书本 p240 的 12.6 节的内容
begin: mov ax,1000h
mov bh,1
mov cx,offset s2-offset s1 ; 将产生中断过程的 div 指令长度存入 CX 寄存器,
; 提供给中断处理程序使用
s1: div bh ; 执行本行除法运算代码,将产生 0 号中断
s2: call show
mov ax,4c00h
int 21h
; 从 show 标号开始到 call show_str(调用 show_str 子程序)都是对 show_str 子程序参数的初始化指令
show: mov ax,datasg
mov ds,ax
mov si,0
mov ax,0B800h ; 将显示缓冲区第 0 页的段地址存入 ES 段寄存器中
mov es,ax
mov dh,8 ; 行:第 8 行
mov dl,3 ; 列:第 3 列
mov cl,2 ; 属性:绿色字体
; 计算列偏移,结果存入 DI 寄存器中
dec dl
mov ax,2
mul dl
mov di,ax
; 计算行偏移,结果加到 DI 寄存器中
dec dh
mov ax,160
mul dh
add di,ax
; 将属性编码存入 AH 寄存器
mov ah,cl
; 将 0 存入 CX 寄存器,以便调用 show_str 子程序时,将字符串字符码赋给 CL 寄存器时,
; 可以通过 jcxz 指令判断是否到达字符串的末尾
mov cx,0
call show_str
ret ; 返回主调程序 begin
; show_str 子程序从开始到结束都在一个循环之中,可以将它看作是个递归调用程序,
; 功能相当于 C 语言中的递归函数
; 由于 (cx) 被初始化为数值 0,因此将 ds:si 字节单元传送给 CL 寄存器后,使用 jcxz 指令来判断:
; 如果 (cx)=0,说明已经到达字符串的末尾结束符 '\0',则跳至 ok 标号处返回主调程序
; 如果 (cx)≠0,说明未到达字符串的末尾结束符 '\0',则向显示缓冲区输出字符,继续执行 show_str 子程序
show_str: mov cl,[si]
jcxz ok
mov al,cl
mov es:[di],ax
inc si
add di,2
jmp short show_str ; 返回 show_str 子程序的开头,循环执行自己 —— 递归调用
ok: ret ; 返回 show_str 子程序的主调程序 show
codeseg ends
end begin
(3) 测试代码 3
assume cs:codeseg
datasg segment
db "Test Start:",0,"Test Over!",0
datasg ends
codeseg segment
; begin 代码选自书本 p240 的 12.6 节的内容
begin: mov si,0 ; 令 ds:si 指向第 1 个字符串“Test Start:”
mov dh,8 ; 行:第 8 行
call show
mov ax,1000h
mov bh,1
mov cx,offset s2-offset s1 ; 将引发中断过程的 div 指令的长度存入 CX 寄存器,
; 提供给中断处理程序使用
s1: div bh ; 执行本行除法运算代码,将产生 0 号中断
s2: mov si,12 ; 令 ds:si 指向第 2 个字符串“Test Over!”
mov dh,20 ; 行:第 20 行
call show
mov ax,4c00h
int 21h
; 从 show 标号开始到 call show_str(调用 show_str 子程序)都是对 show_str 子程序参数的初始化指令
show: mov ax,datasg
mov ds,ax
mov ax,0B800h ; 将显示缓冲区第 0 页的段地址存入 ES 段寄存器中
mov es,ax
mov dl,3 ; 列:第 3 列
mov cl,2 ; 属性:绿色字体
; 计算列偏移,结果存入 DI 寄存器中
dec dl
mov ax,2
mul dl
mov di,ax
; 计算行偏移,结果加到 DI 寄存器中
dec dh
mov ax,160
mul dh
add di,ax
; 将属性编码存入 AH 寄存器
mov ah,cl
; 将 0 存入 CX 寄存器,以便调用 show_str 子程序时,将字符串字符码赋给 CL 寄存器时,
; 可以通过 jcxz 指令判断是否到达字符串的末尾
mov cx,0
call show_str
ret ; 返回主调程序 begin
; show_str 子程序从开始到结束都在一个循环之中,可以将它看作是个递归调用程序,
; 功能相当于 C 语言中的递归函数
; 由于 (cx) 被初始化为数值 0,因此将 ds:si 字节单元传送给 CL 寄存器后,使用 jcxz 指令来判断:
; 如果 (cx)=0,说明已经到达字符串的末尾结束符 '\0',则跳至 ok 标号处返回主调程序
; 如果 (cx)≠0,说明未到达字符串的末尾结束符 '\0',则向显示缓冲区输出字符,继续执行 show_str 子程序
show_str: mov cl,[si]
jcxz ok
mov al,cl
mov es:[di],ax
inc si
add di,2
jmp short show_str ; 返回 show_str 子程序的开头,循环执行自己 —— 递归调用
ok: ret ; 返回 show_str 子程序的主调程序 show
codeseg ends
end begin