文章目录
- 前言
- [1. 编程处理0号中断](#1. 编程处理0号中断)
-
- [1.1 效果演示](#1.1 效果演示)
- [1.2 分析所要编写的中断处理程序](#1.2 分析所要编写的中断处理程序)
-
- [1.2.1 引发中断](#1.2.1 引发中断)
- [1.2.2 中断处理程序](#1.2.2 中断处理程序)
- [1.2.3 中断处理程序do0应该存放的位置](#1.2.3 中断处理程序do0应该存放的位置)
- [1.2.4 中断向量表的修改](#1.2.4 中断向量表的修改)
- [1.2.5 总结](#1.2.5 总结)
- [1.3 程序框架](#1.3 程序框架)
- [1.4 注意事项](#1.4 注意事项)
- [1.5 从CPU的角度看中断处理程序](#1.5 从CPU的角度看中断处理程序)
- [1.6 一些问题的思考与解答](#1.6 一些问题的思考与解答)
- [2. 安装](#2. 安装)
-
- [2.1 使用movsb指令](#2.1 使用movsb指令)
- [2.2 明确执行rep movsb前所设置的信息](#2.2 明确执行rep movsb前所设置的信息)
- [2.3 do0代码的长度](#2.3 do0代码的长度)
- 结语
前言
📌
汇编语言是很多相关课程(如数据结构、操作系统、微机原理)的重要基础。但仅仅从课程的角度出发就太片面了,其实学习汇编语言可以深入理解计算机底层工作原理,提升代码效率,尤其在嵌入式系统和性能优化方面有重要作用。此外,它在逆向工程和安全领域不可或缺,帮助分析软件运行机制并增强漏洞修复能力。
本专栏的汇编语言学习章节主要是依据王爽老师的《汇编语言》来写的,和书中一样为了使学习的过程容易展开,我们采用以8086CPU为中央处理器的PC机来进行学习。
1. 编程处理0号中断
1.1 效果演示
现在我们考虑改变一下0号中断处理程序的功能,即重新编写一个0号中断处理程序,它的功能是在屏幕中间显示"overflow!"然后返回到操作系统,如下图所示。
当CPU 执行div bh
后,发生了除法溢出错误,产生0号中断信息,引发中断过程,CPU 执行我们编写的0号中断处理程序。在屏幕中间显示提示信息"overflow!"后,返回到操作系统中。
1.2 分析所要编写的中断处理程序
1.2.1 引发中断
当发生除法溢出的时候,产生0号中断信息,从而引发中断过程。
此时,CPU将进行以下工作:
① 取得中断类型码0;
② 标志寄存器入栈,TF、IF设置为0;
③ CS、IP入栈;
④ (IP) = (0*4),(CS) = (0*4+2)
1.2.2 中断处理程序
可见 ,当中断 0 发生时,CPU将转去执行中断处理程序。
只要按如下步骤编写中断处理程序,当中断0发生时,即可显示"overfow!"。
① 相关处理。
② 向显示缓冲区送字符串"overfow!"。
③ 返回DOS
我们将这段程序称为do0。
1.2.3 中断处理程序do0应该存放的位置
现在的问题是:do0 应放在内存中。
因为除法溢出随时可能发生,CPU随时都可能将 CS:IP指向 do0的入口,执行程序。
那么do0应该放在哪里呢?
由于我们是在操作系统之上使用计算机,所有的硬件资源都在操作系统的管理之下,所以我们要想得到一块内存存放do0,应该向操作系统申请。
但在这里出于两个原因我们不想这样做:
-
原因之一:过多地讨论申请内存将偏离问题主线;
-
原因之二:我们学习汇编的一个重要目的就是要获得对计算机底层的编程体验。所以,在可能的情况下,我们不去理会操作系统,而直接面向硬件资源。
问题变得简单而直接,我们只需找到一块别的程序不会用到的内存区,将do0传送到其中即可。
前面讲到,内存0000:0000~0000:03FF,大小为1KB的空间是系统存放中断处理程序入口地址的中断向量表。8086支持256个中断,但是,实际上,系统中要处理的中断事件远没有达到256个。所以在中断向量表中,有许多单元是空的。
中断向量表是PC系统中最重要的内存区,只用来存放中断处理程序的入口地址,DOS系统和其他应用程序都不会随便使用这段空间。可以利用中断向量表中的空闲单元来存放我们的程序。
一般情况下,从0000:0200至0000:02FF的256个字节的空间所对应的中断向量表项都是空的,操作系统和其他应用程序都不占用。我们在前面的内容中使用过这段空间。
根据以前的编程经验,我们可以估计出,do0的长度不可能超过256个字节。
结论:我们可以将do0传送到内存0000:0200处。
1.2.4 中断向量表的修改
我们将中断处理程序do0放到 0000:0200 后,若要使得除法溢出发生的时候,CPU转去执行do0,则必须将do0的入口地址。即0000:0200登记在中断向量表的对应表项中。
因为除法溢出对应的中断类型码为0,它的中断处理程序的入口地址应该从0×4地址单元开始存放,段地址存放在 0×4+2 字单元中,偏移地址存放在0×4字单元中。
也就是说要将do0的段地址0存放在 0000:0002 字单元中,将偏移地址200H存放在0000:0000字单元中。
1.2.5 总结
总结上面的分析,我们要做以下几件事情:
(1)编写可以显示"overfow!"的中断处理程序:do0
(2)将do0送入内存0000:0200处
(3)将do0的入口地址0000:0200存储在中断向量表0号表项中
1.3 程序框架
程序的框架如下:
assembly
assume cs:code
code segment
start:
do0安装程序
设置中断向量表
mov ax,4c00h
int 21h
do0:
显示字符串"Welcome to Fishc.com!"
mov ax,4c00h
int 21h
code ends
end start
我们可以看到,上面的程序分为两部分:
-
(1)安装do0,设置中断向量的程序
-
(2)do0
1.4 注意事项
上面的程序框架执行时,do0的代码是不执行的,它只是作为do0安装程序所要传送的数据。
执行do0安装程序,将 do0 的代码拷贝到内存 0:200处,然后设置中断向量表,即偏移地址200H和段地址0,保存在0号表项中。
这两部分工作完成后,程序就返回了。
程序的目的就是在内存0:200处安装do0 的代码,将0号中断处理程序的入口地址设置为0:200。
do0的代码虽然在程序中,却不在程序执行的时候执行。它是在除法溢出发生的时候才得以执行的中断处理程序。
do0部分代码的最后两条指令是依照我们的编程要求,用来返回DOS的。
1.5 从CPU的角度看中断处理程序
现在,我们在反过来从CPU的角度看一下,什么是中断处理程序?
do0变成0号中断的中断处理程序的过程:
-
(1)这个程序框架在执行时,被加载到内存中,此时do0的代码在程序所在的内存空间中,它只是存放在程序的代码段中的一段要被传送到其他单元中的数据,我们不能说它是0号中断的中断处理程序;
-
(2)程序中安装do0 的代码执行完后,do0的代码被从程序的代码段中拷贝到0:200处。此时,我们也还不能说它是0号中断的中断处理程序,它只不过是存放在0:200处的一些数据;
-
(3)程序中设置中断向量表的代码执行完后,在0号表项中填入了do0的入口地址0:200,此时0:200 处的信息,即do0 的代码,就变成了0号中断的中断处理程序。
1.6 一些问题的思考与解答
我们如何让一个内存单元成为栈顶?
答:将它的地址放入SS、SP中;
我们如何让一个内存单元中的信息被CPU当作指令来执行?
答:将它的地址放入CS、IP 中;
那么,我们如何让一段程序成为N号中断的中断处理程序?
答:将它的入口地址放入中断向量表的N号表项中。
2. 安装
下面的内容中,我们讨论每一部分程序的具体编写方法。
2.1 使用movsb指令
我们可以使用movsb指令,将do0的代码送入0:0200处。
程序如下:
assembly
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
2.2 明确执行rep movsb前所设置的信息
我们来看一下,用rep movsb
指令的时候需要确定的信息:
-
(1)传送的原始位置,段地址:code,偏移地址:
offset do0
; -
(2)传送的目的位置:0:200;
-
(3)传送的长度:do0部分代码的长度;
-
(4)传送的方向:正向。
更明确的程序如下:
assembly
assume cs:code
code segment
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, do0部分代码的长度 ;设置cx为传输长度
cld ;设置传输方向为正
rep movsb
;设置中断向量表
mov ax, 4c00h
int 21h
do0:
;显示字符串"Welcome to Fishc.com!"
mov ax, 4c00h
int 21h
code ends
end start
2.3 do0代码的长度
问题是,我们如何知道do0代码的长度?
最简单的方法是,计算一下do0 所有指令码的字节数。但是这样做太麻烦了,因为只要do0的内容发生了改变,我们都要重新计算它的长度。
其实,我们可以利用编译器来计算do0的长度,具体做法如下:
assembly
assume cs:code
code segment
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 ax,4c00h
int 21h
do0:
;显示字符串"overflow!"
mov ax,4c00h
int 21h
do0end:
nop
code ends
end start
"-"是编译器识别的运算符号,编译器可以用它来进行两个常数的减法。
比如:mov ax,8-4
,被编译器处理为指令: mov ax,4
。
另外,编译器还可以处理表达式。
比如指令: mov ax,(5+3)\*5/10
,被编译器处理为指令: mov ax,4
。
好了,知道了"-"的含义,对于用 ofset do0end-ofset do0
,得到 do0 代码的长度的原理,这里就不再多说了,相信到了现在,大家已经可以自己进行分析了。
后面的文章中我们将编写 do0 程序。
结语
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下。
也可以点点关注,避免以后找不到我哦!
Crossoads主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是作者前进的动力!