1. 预备知识
- 前面提到,CPU 用 8 位中断类型码通过中断向量表找到相应的中断处理程序的入口地址。找到段地址和偏移地址后,用它来设置 CS 和 IP,CPU 会自动运行中断处理程序。该过程可概括为:
(1)取得中断类型码 N
(2)pushf,将各标志寄存器的内容入栈
(3)TF=0,IF=0(该设置后续章节介绍)
(4)push CS,保存原 CS 的值
(5)push IP,保存原 IP 的值
(6)(IP)=(4N),(CS)=(4N+2) - 中断处理程序和子程序类似,主要分为以下步骤:保存用到的寄存器,处理中断,恢复用到的寄存器,用 iret 指令返回。iret 指令包含恢复 CS:IP 的内容和恢复标志寄存器的内容。
2. 实验任务
编写 0 号中断的处理程序,使得在除法溢出发生时,在屏幕中间显式字符串“divide error!”,然后返回 DOS。
根据预备知识的内容,编写 0 号中断的处理程序流程为:编写中断处理程序 do0,将 do0 的代码放入内存 0000:0200 处(该区域 CPU 很少用到),将 do0 代码的入口地址 0000:0200 存储在中断向量表 0 号表项中。
2.1 编写中断处理程序 do0
由于在运算时随时会发生除法溢出,CPU 去执行 0 号中断处理程序时,待显式字符串必须放在 CPU 不使用的区域而防止被覆盖。可以考虑将该字符串放到 do0 程序中,do0start 为真正开始的程序:
do0:
jmp short do0start
db "divide error!"
do0start:
;中断处理程序
2.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:
;显式字符串"overflow"
mov ax,4c00h
int 21h
code ends
end start
传输长度为 do0 代码部分的长度,可通过 offset do0end-offset do0 计算得到,其中减号实现两个常数的减法。do0end 为仅包含空的段:
do0end:
nop
2.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 short do0start的长度为2字节,代码真正开始的位置为0000:0202h
mov ax,0b800h
mov es,ax ;B8000H~BFFFFH为显式缓冲区
mov di,12*160+36*2 ;在屏幕中间显式字符串"divide error!"
mov cx,13 ;设置cx为字符串长度
s:
mov al,ds:[si]
mov es:[di],al ;字符
mov ah,02h
mov es:[di+1],ah ;字符属性,绿色字体
inc si
add di,2 ;每次偏移2字节写入字符
loop s
mov ax,4c00h
int 21h
do0end:
nop
code ends
end start
编译链接生成可执行文件,运行可执行文件后,首先查看中断向量表的第 0 项,地址为 0000:0000。
此时,中断向量表的第 0 号项内容为 00 02 00 00,对应的地址为 0000:0200,然后查看 0000:0200 处的内容。
红色部分和蓝色部分分别为字符串的 16 进制表示和 ASCII 码。红色部分前面的内容 EB 0D 对应于源代码中的 jmp short do0start
共两个字节。do0start 部分的代码从 0000:020F 处开始,将对应的机器码翻译为汇编代码。
红色部分和蓝色部分分别为机器码和汇编代码的一一对应,即 do0start 部分的代码。
2.4 测试
编写除法溢出代码测试上述 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
编译链接生成可执行文件,运行可执行文件后,屏幕中间位置处显式绿色字体的 divide error!。
3. 总结
- 本文介绍了编写 0 号中断处理程序的内容,编写中断的处理程序主要分为以下步骤:编写中断处理程序,如本文的显式除法溢出的错误信息提示;将中断处理程序的代码放入 CPU 很少访问的内存区域,如本文将其放入内存 0000:0200 处;将中断处理程序的入口地址放入中断向量表的对应表项中,如本文处理 0 号中断将中断处理程序的入口地址放入内存的 0000:0000 处。
- 为了显示显式信息被覆盖,将显示字符串放入中断处理程序中。