1. 预备知识
-
当 CPU 内部出现以下情况时,将产生相应的中断信息:除法错误,单步执行,执行 int 0 指令,执行 int 指令。上述四种中断信息对应的类型码分别为 0、1、4 和 n,其中 n 对应于执行 int n 指令。
-
用来处理中断信息的程序称为中断处理程序,CPU 用 8 位中断类型码并通过中断向量表找到相应的中断处理程序的入口地址。
-
中断向量表存放了 256 个中断源,固定存放在 0000:0000~0000:03E8 单元。在中断向量表中,一个表项占两个字,高地址字存放段地址、低地址字存放偏移地址。找到段地址和偏移地址后,用它们来设置 CS 和 IP,CPU 会自动执行中断处理程序。整个流程为:
-
取得中断类型码 n
-
pushf,将标志寄存器入栈
-
TF=0、IF=0(后续介绍该设置的作用)
-
push CS,保护原 CS 的值
-
push IP,保护原 IP 的值
-
(IP)=(4*n),(IP)=(4*n+2)
-
-
中断处理程序和子程序类似,主要分为以下步骤:(1)保存用到的寄存器;(2)处理中断;(3)恢复用到的寄存器;(4)iret,返回。
-
offset 关键字用于取得标号处的偏移地址,如 offset s。
-
movsb 为汇编语言中的传送指令,按字节传送数据。该指令以 es:di 为传送的目的地址、ds:si 为源地址,cx 存放传送的字节数。同时,cld 和 std 影响标志寄存器的 DF 位,进而设置传送的方向。cld 设置 DF=0,si 和 di 每次自增一;std 设置 DF=1,si 和 di 每次自减一。
-
上面提到,8086 共支持 256 个中断,但实际上远远没有使用到该数量的中断事件。所以在中断向量表中,许多单元都是空的。一般情况下,从 0000:0200~0000:0300 的 256 个字节的空间是空闲的,操作系统和其他应用程序都不占用。
2. 实验任务
编写 0 号中断处理程序,使得在除法溢出时,在屏幕中间显示字符串 divide error!。
2.1 实验分析
根据预备知识,首先将该实验任务拆分成以下几个步骤:编写中断处理程序 do0,将 do0 代码放入内存 0000:0200 处,将 do0 代码的入口地址 0000:0200 存储在中断向量表的 0 号表项。
2.1.1 编写中断处理程序 do0
由于在运算时随时会发生除法溢出,CPU 去执行 0 号中断处理程序时,待显示字符串必须存放在 CPU 不使用的区域而防止被覆盖。将该字符串放到 do0 程序中,do0start 为真正开始的程序:
do0:
jmp short do0start
db "divide error!"
do0start:
mov ax,cs
mov ds,ax
mov si,202h ;指令jmp的长度为2字节,字符的偏移地址为0202h
mov ax,0b800h
mov es,ax ;段寄存器ES指向显示缓冲区
mov di,12*160+36*2 ;写入时的偏移
mov cx,13 ;设置cx为字符串长度
s:
mov al,ds:[si]
mov es:[di],al
mov byte ptr es:[di+1],2h
;写入字符和字符属性
inc si ;偏移1字节取字符
add di,2 ;偏移2字节写字符
loop s
mov ax,4c00h
int 21h
2.1.2 将 do0 代码放入内存 0000:0200 处
借助 movsb 指令,将 do0 代码放入内存 0000:0200 处:
assume cs:code
code segment
start:
;设置es:di指向目的地址
;设置ds:si指向源地址
;设置cx为传输长度
;设置传输方向
rep movsb
;设置中断向量表
mov ax,4c00h
int 21h
do0:
;在屏幕中间显示字符串
mov ax,4c00h
int 21h
code ends
end start
目的地址为 0200:0000,源地址为 cs:offset do0。
mov ax,0
mov es,ax
mov di,200h ;设置es:di指向目的地址
mov ax,cs
mov ds,ax
mov si,offset do0 ;设置ds:si指向源地址
本节是将 do0 代码写入内存,传输长度为 do0 代码部分的长度,可通过关键字 offset 取得偏移地址后计算相对位移。
offset do0end-offset do0
其中,减号实现两个常数的减法,do0end 用于标识 do0 代码的结尾,内容为空:
do0end:
nop
2.1.3 设置中断向量表
将 do0 代码的入口地址写入中断向量表的 0 号表项中。
mov ax,0
mov es,ax
mov word ptr es:[0],200h ;低地址写入偏移地址
mov word ptr es:[2],0 ;高地址写入段地址
整体代码为:
assume cs:code
code segment
start:
mov ax,0
mov es,ax
mov di,200h ;设置es:di指向目的地址
mov ax,cs
mov ds,ax
mov si,offset do0 ;设置ds:si指向源地址
mov cx,offset do0end-offset do0 ;设置cx为传输长度
cld ;设置传输方向为正
rep movsb
mov ax,0
mov es,ax
mov word ptr es:[0],200h ;低地址写入偏移地址
mov word ptr es:[2],0 ;高地址写入段地址(设置中断向量表完成)
mov ax,4c00h
int 21h
do0:
jmp short do0start
db "divide error!" ;将字符串存放到do0内
do0start:
mov ax,cs
mov ds,ax
mov si,202h ;指令jmp的长度为2字节,字符的偏移地址为0202h
mov ax,0b800h
mov es,ax ;段寄存器ES指向显示缓冲区
mov di,12*160+36*2 ;写入时的偏移
mov cx,13 ;设置cx为字符串长度
s:
mov al,ds:[si]
mov es:[di],al
mov byte ptr es:[di+1],2h
;写入字符和字符属性
inc si ;偏移1字节取字符
add di,2 ;偏移2字节写字符
loop s
mov ax,4c00h
int 21h
do0end:
nop
code ends
end start
2.2 实验结果
运行上述程序。首先,查看中断向量表的 0 号表项,表示的内存单元地址为 0000:0200。
然后,查看存放在 0000:0200 处的字符串。
最后,jmp 指令长度为 2 字节、字符串长度为 13 字节,查看存放在 0000:020F 处的代码。
最后,使用以下除法溢出的代码测试 0 号中断。
assume cs:code
code segment
start:
mov ax,1000h
mov bh,1
div bh
;除数为8位,被除数默认放在ax中,
;此时结果中al存储商,ah存储余数,结果1000h对于存储商的寄存器al溢出
code ends
end start
屏幕显示内容如下:
3. 总结
-
当 CPU 内部出现除法错误、单步执行、执行 int 指令时会发生中断,CPU 转而先去处理中断信息
-
CPU 根据中断类型码找到中断向量表的指定项,并用其设置 CS 和 IP 的值,CPU 执行中断处理程序
-
实验改写了除法溢出相关的 0 号中断,包括编写中断处理程序、程序安装、设置中断向量表等步骤
-
参考:汇编语言/王爽著.——北京:清华大学出版社,2003