0除0出现错误代码如何解决(0号中断除数为0程序的改写)
本文较长,内容来自王爽老师的《汇编语言》一书,里面加上了一些个人理解,全部看完可能需要一点时间。
我们通过对 0号中断,即除法错误的中断处理,来体会一下前面所讲的内容。
当CPU执行div等除法指令的时候,如果发生了除法溢出错误,将产生中断类型码为 0 的中断信息,CPU将检测到这个信息,然后引发中断过程,转去执行 0 号中断所对应的中断处理程序。
mov ax,1000h
mov bh,1
div bh
现在我们考虑改变一下0号中断处理程序的功能,即重新编写一个0号中断处理程序,它的功能是在屏幕中间显示“overflow!”后,然后返回到操作系统。
分析(1)当发生除法溢出的时候,产生0号中断信息,从而引发中断过程。
此时,CPU将进行以下工作:
① 取得中断类型码0;
② 标志寄存器入栈,TF、IF设置为0;
③ CS、IP入栈;
④ (IP) = (0*4),(CS) = (0*4 2)
分析(2)可见 ,当中断 0 发生时,CPU将转去执行中断处理程序。
只要按如下步骤编写中断处理程序,当中断0发生时,即可显示“overflow!”。
① 相关处理。
② 向显示缓冲区送字符串“overflow!”。
③ 返回DOS
我们将这段程序称为do0。
分析(3)现在的问题是:do0 应放在内存中。
因为除法溢出随时可能发生,CPU随时都可能将 CS:IP指向 do0的入口,执行程序。
那么do0应该放在哪里呢?
由于我们是在操作系统之上使用计算机,所有的硬件资源都在操作系统的管理之下,所以我们要想得到一块内存存放do0,应该向操作系统申请。
但我们学习汇编的一个重要目的就是要获得对计算机底层的编程体验,所以,在可能的情况下,我们不去理会操作系统,而直接面向硬件资源。
问题变得简单而直接,我们只需找到一块别的程序不会用到的内存区,将do0传送到其中即可。
我们知道,内存0000:0000~0000:03FF,大小为1KB的空间是系统存放中断处理程序入口地址的中断向量表。
中断向量表在内存中存放,对于8086PC机,中断向量表指定放在内存地址0处。
从内存0000:0000到0000:03FF的1024个单元中存放着中断向量表。
8086 支持 256 个中断,但是,实际上,系统中要处理的中断事件远没有达到256 个 。所以在中断向量表中,有许多单元是空的。
中断向量表是PC系统中最重要的内存区,只用来存放中断处理程序的入口地址,DOS 系统和其他应用程序都不会随便使用这段空间。
我们可以利用中断向量表中的空闲单元来存放我们的程序。
一般情况下:
从0000:0200至0000:02FF的256个字节的空间所对应的中断向量表项都是空的,操作系统和其他应用程序都不占用。
我们估计,do0的长度不可能超过256个字节。
结论:我们可以将do0传送到内存0000:0200处。
我们将中断处理程序do0放到 0000:0200 后,若要使得除法溢出发生的时候,CPU转去执行do0,则必须将do0的入口地址,即0000:0200登记在中断向量表的对应表项中。
因为除法溢出对应的中断类型码为0,它的中断处理程序的入口地址应该从0×4地址单元开始存放,段地址存放在 0×4 2 字单元中,偏移地址存放在0×4字单元中。
也就是说要将do0的段地址0存放在 0000:0002 字单元中 ,将偏移地址200H存放在0000:0000字单元中。
总结上面的分析,我们要做以下几件事情:
(1)编写可以显示“overflow!”的中断处理程序:do0;
(2)将do0送入内存0000:0200处;
(3)将do0的入口地址0000:0200存储在中断向量表0号表项中。
程序1
assume cs:code
code segment
start: do0安装程序
设置中断向量表
mov ax,4c00h
int 21h
do0: 显示字符串“overflow!”
mov ax,4c00h
int 21h
code ends
end start
我们可以看到,上面的程序分为两部分:
(1)安装do0,设置中断向量
(2)do0
程序1执行时,do0的代码是不执行的,它只是作为do0安装程序所要传送的数据。
程序1执行时,首先执行do0安装程序,将 do0 的代码拷贝到内存 0:200处,然后设置中断向量表,将do0的入口地址,即偏移地址200H和段地址0,保存在0号表项中。
这两部分工作完成后,程序就返回了。
程序目的就是在内存0:200处安装do0 代码,将0号中断处理程序的入口地址设置为0:200。
do0的代码虽然在程序中,却不在程序执行的时候执行。它是在除法溢出发生的时候才得以执行的中断处理程序。
do0部分代码的最后两条指令是依照我们的编程要求,用来返回DOS的。
现在,我们在反过来从CPU的角度看一下,什么是中断处理程序?
我们来看一下do0是如何变成0号中断的中断处理程序的:
(1)程序1 在执行时,被加载到内存中,此时do0的代码在程序1 所在的内存空间中,它只是存放在程序1的代码段中的一段要被传送到其他单元中的数据,我们不能说它是0号中断的中断处理程序;
do0是如何变成0号中断的中断处理程序的:
(2)程序1中安装do0 的代码执行完后,do0的代码被从程序1的代码段中拷贝到0:200处。此时,我们也不能说它是0号中断的中断处理程序,它只不过是存放在0:200处的一些数据;
do0是如何变成0号中断的中断处理程序的:
(3)程序1中设置中断向量表的代码执行完后,在0号表项中填入了do0的入口地址0:200,此时0:200 处的信息,即do0 的代码,就变成了0号中断的中断处理程序。
因为当除法溢出(即0号中断)发生时,CPU将执行0:200处的代码。
如何让一个内存单元中的信息被CPU当作指令来执行?
将它的地址放入CS、IP中;
如何让一段程序成为N号中断的中断处理程序?
将它的入口地址放入中断向量表的N号表项中。
我们可以使用movsb指令,将do0的代码送入0:0200处。
我们来看一下,用rep movsb指令的时候需要确定的信息:
(1)传送的原始位置,段地址:code,偏移地址:offset do0;
(2)传送的目的位置:0:200;
(3)传送的长度:do0部分代码的长度;
(4)传送的方向:正向。
程序2
assume cs:code
code segmentx
start: 设置es:di指向目的地址
设置ds:si指向源地址
设置cx为传输长度
设置传输方向为正
rep movsb
设置中断向量表
mov ax,4c00h
int 21h
do0: 显示字符串“overflow!”
mov ax,4c00h
int 21h
code ends
end start
“-”是编译器识别的运算符号,编译器可以用它来进行两个常数的减法。
比如:mov ax,8-4,被编译器处理为指令: mov ax,4。
因此可以用offset do0end-offset do0,得到do0代码的长度。
下面我们编写do0程序。do0程序的主要任务是显示字符串,程序如下:
do0:设置ds:si指向字符串
mov ax,0b800h
mov es,ax
mov di,12*160 36*2;设置es:di指向显存空间的中间位置
mov cx,9 ;设置cx为字符串长度
s: mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end:nop
注意,“overflow!”在程序2的data段中。程序2执行完成后返回,它所占用的内存空间被系统释放,而在其中存放的“overflow!”也将很可能被别的信息覆盖;
而do0程序被放到了0:200处,随时都会因发生了除法溢出而被CPU 执行,很难保证 do0 程序从原来程序2所处的空间中取得的是要显示的字符串“overflow!”。
因为 do0 程序随时可能被执行,而它要用到字符串“overflow”,所以该字符串也应该存放在一段不会被覆盖的空间中。
在程序3中,我们将“overflow!”放到do0程序中,程序3执行时,将标号do0到标号
do0end之间的内容送到0000:0200处。
注意,因为在do0程序开始处的“overflow!”不是可以执行的代码,所以在“overflow!”之前加上一条jmp 指令,转移到正式的do0 程序。
当除法溢出发生时,CPU 执行0:200 处的jmp 指令,跳过后面的字符串,转到正式的do0 程序执行。
do0程序执行过程中必须要找到“overflow!”,那么它在哪里呢?
首先来看段地址,“overflow!”和do0的代码处于同一个段中,而除法溢出发生时,CS中必然存放do0的段地址,也就是“overflow!”的段地址;
再来看偏移地址,0:200处的指令为jmp short do0start ,这条指令占两个字节,所以“overflow!”的偏移地址为202h 。
下面,我们将do0的入口地址0:200,写入中断向量表的 0 号表项中,使do0成为0 号中断的中断处理程序。
0号表项的地址为0:0,其中0:0字单元存放偏移地址,0:2字单元存放段地址。
程序如下:
-----------------------------------------------------------------------------------------------------------------
程序3
assume cs:code
code segment
start:
;do0安装程序
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为传输长度,这里传输了包括‘overflow!’的内容
cld;设置传输方向为正
rep movsb
;设置中端向量表
mov ax,0
mov es,ax
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 "overflow!"
do0start:
mov ax,cs
mov ds,ax
mov si,202h
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
mov ax,4c00h
int 21h;中断处理程序完成后返回到DOS
do0end:nop
code ends
END START
————————————————
主程序
assume cs:code
code segment
main: ;当CPU执行div bh后,会发生除法溢出错误,产生0号中断信息
mov ax,1000H
mov bh,01
div bh
mov ax,4C00H
int 21H
code ends
end main
————————————————
整个过程的思路就是:
(1)编写可以显示“overflow!”的中断处理程序:do0;
(2)将do0这个程序本身送入内存0000:0200处,包括‘overflow!’这个字符串;
(3)将do0的入口地址0000:0200存储在中断向量表0号表项中。
(4)这个程序本身并未考虑对原有的0号中断程序的恢复问题,只是单纯进行了改写。
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com