[汇编]《汇编语言》第12章 内中断
<h2 id="王爽汇编语言第四版-超级笔记">王爽《汇编语言》第四版 超级笔记</h2><p></p><div class="toc"><div class="toc-container-header">目录</div><ul><li>王爽《汇编语言》第四版 超级笔记<ul><li>第12章 内中断<ul><li>12.1 内中断的产生、中断处理程序</li><li>12.2 中断向量表、中断过程、中断处理程序和iret指令</li><li>12.3 除法错误中断的处理、编程处理0号中断</li><li>12.4 安装、do0程序</li><li>12.5 设置中断向量、单步中断、响应中断的特殊情况</li></ul></li></ul></li></ul></div><p></p>
<h3 id="第12章-内中断">第12章 内中断</h3>
<p>任何一个通用的CPU,比如8086,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。</p>
<p>这种特殊的信息,我们可以称其为:中断信息。中断的意思是指,CPU不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。</p>
<p>“中断信息”是要求CPU马上进行某种处理,并向所要进行的该种处理提供了必备的参数的通知信息。因为本书的内容不是微机原理与接口或组成原理,我们只能用一些便于理解的说法来描述一些比较复杂的机器工作原理,从而使学习者忽略一些和我们的学习重心无关的内容。</p>
<p>但笔者又需要对这些问题有一个严谨的交代,所以有了这些补充说明的文字。如果你不理解这些文字所讲的东西,就不必去理解了。</p>
<p>中断信息可以来自CPU的内部和外部,这一章中,我们主要讨论来自于CPU内部的中断信息。</p>
<h4 id="121-内中断的产生中断处理程序">12.1 内中断的产生、中断处理程序</h4>
<p>当CPU的内部有什么事情发生的时候,将产生需要马上处理的中断信息呢?</p>
<p>对于8086CPU,当CPU内部有下面的情况发生的时候,将产生相应的中断信息。</p>
<p>(1)除法错误,比如,执行div指令产生的除法溢出;<br>
(2)单步执行;<br>
(3)执行into指令;<br>
(4)执行int指令。</p>
<p>我们现在不要去管这4种情况的具体含义,只要知道CPU内部有4种情况可以产生需要及时处理的中断信息即可。虽然我们现在并不很清楚,这4种情况到底是什么,但是有一点是很清楚的,即它们是不同的信息。既然是不同的信息,就需要进行不同的处理。</p>
<p>要进行不同的处理,CPU首先要知道,所接收到的中断信息的来源。所以中断信息中必须包含识别来源的编码。8086CPU用称为中断类型码的数据来标识中断信息的来源。</p>
<p>中断类型码为一个字节型数据,可以表示256种中断信息的来源。以后,我们将产生中断信息的事件,即中断信息的来源,简称为中断源,上述的4种中断源,在8086CPU中的中断类型码如下。</p>
<p>(1)除法错误:0<br>
(2)单步执行:1<br>
(3)执行into指令:4<br>
(4)执行int指令,该指令的格式为int n,指令中的n为字节型立即数,是提供给CPU的中断类型码。</p>
<hr>
<p>CPU收到中断信息后,需要对中断信息进行处理。而如何对中断信息进行处理,可以由我们编程决定。我们编写的,用来处理中断信息的程序被称为中断处理程序。一般来说,需要对不同的中断信息编写不同的处理程序。</p>
<p>CPU在收到中断信息后,应该转去执行该中断信息的处理程序。我们知道,若要8086CPU执行某处的程序,就要将CS:IP指向它的入口(即程序第一条指令的地址)。可见首要的问题是,CPU在收到中断信息后,如何根据中断信息确定其处理程序的入口。</p>
<p>CPU的设计者必须在中断信息和其处理程序的入口地址之间建立某种联系,使得CPU根据中断信息可以找到要执行的处理程序。</p>
<p>我们知道,中断信息中包含有标识中断源的类型码。根据CPU的设计,中断类型码的作用就是用来定位中断处理程序。</p>
<p>比如CPU根据中断类型码4,就可以找到4号中断的处理程序。可随之而来的问题是,若要定位中断处理程序,需要知道它的段地址和偏移地址,而如何根据8位的中断类型码得到中断处理程序的段地址和偏移地址呢?</p>
<h4 id="122-中断向量表中断过程中断处理程序和iret指令">12.2 中断向量表、中断过程、中断处理程序和iret指令</h4>
<p>CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。那么什么是中断向量表呢?</p>
<p>中断向量表就是中断向量的列表。那么什么又是中断向量呢?</p>
<p>所谓中断向量,就是中断处理程序的入口地址。展开来讲,中断向量表,就是中断处理程序入口地址的列表。</p>
<p>中断向量表在内存中保存,其中存放着256个中断源所对应的中断处理程序的入口,如图12.1所示。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202109/1162354-20210907234631724-1687335400.png" alt="image" loading="lazy"></p>
<p>可以看到,CPU只要知道了中断类型码,就可以将中断类型码作为中断向量表的表项号,定位相应的表项,从而得到中断处理程序的入口地址。</p>
<p>可见,CPU用中断类型码,通过查找中断向量表,就可以得到中断处理程序的入口地址。在这个方案中,一个首要的问题是,CPU如何找到中断向量表?</p>
<p>现在,找到中断向量表成了通过中断类型码找到中断处理程序入口地址的先决条件。</p>
<p>中断向量表在内存中存放,对于8086PC机,中断向量表指定放在内存地址0处。从内存0000:0000到0000:03FF的1024个单元中存放着中断向量表。能不能放在别处呢?</p>
<p>不能,如果使用8086CPU,中断向量表就必须放在0000:0000~0000:03FF单元中,这是规定,因为8086CPU就从这个地方读取中断向量表。</p>
<p>那么在中断向量表中,一个表项占多大的空间呢?</p>
<p>一个表项存放一个中断向量,也就是一个中断处理程序的入口地址,对于8086CPU,这个入口地址包括段地址和偏移地址,所以一个表项占两个字,高地址字存放段地址,低地址字存放偏移地址。</p>
<hr>
<p>从上面的讲解中,我们知道,可以用中断类型码,在中断向量表中找到中断处理程序的入口。找到这个入口地址的最终目的是用它设置CS和IP,使CPU执行中断处理程序。</p>
<p>用中断类型码找到中断向量,并用它设置CS和IP,这个工作是由CPU的硬件自动完成的。CPU硬件完成这个工作的过程被称为中断过程。</p>
<p>CPU收到中断信息后,要对中断信息进行处理,首先将引发中断过程。硬件在完成中断过程后,CS:IP将指向中断处理程序的入口,CPU开始执行中断处理程序。</p>
<p>有一个问题需要考虑,CPU在执行完中断处理程序后,应该返回原来的执行点继续执行下面的指令。所以在中断过程中,在设置CS:IP之前,还要将原来的CS和IP的值保存起来。</p>
<p>在使用call指令调用子程序时有同样的问题,子程序执行后还要返回到原来的执行点继续执行,所以call指令先保存当前CS和IP的值,然后再设置CS和IP。</p>
<p>下面是8086CPU在收到中断信息后,所引发的中断过程。</p>
<p>(1)(从中断信息中)取得中断类型码;</p>
<p>(2)标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中);</p>
<p>(3)设置标志寄存器的第8位TF和第9位IF的值为0(这一步的目的后面将介绍);</p>
<p>(4)CS的内容入栈;</p>
<p>(5)IP的内容入栈;</p>
<p>(6)从内存地址为中断类型码x4和中断类型码x4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。</p>
<p>CPU在收到中断信息之后,如果处理该中断信息,就完成一个由硬件自动执行的中断过程(程序员无法改变这个过程中所要做的工作)。</p>
<p>中断过程的主要任务就是用中断类型码在中断向量表中找到中断处理程序的入口地址,设置CS和IP。因为中断处理程序执行完成后,CPU还要回过头来继续执行被中断的程序,所以要在设置CS、IP之前,先将它们的值保存起来。可以看到CPU将它们保存在栈中。</p>
<p>我们注意到,在中断过程中还要做的一个工作就是设置标志寄存器的TF、IF位,对于这样做的目的,我们将在后面的内容和下一章中进行讨论。因为在执行完中断处理程序后,需要恢复在进入中断处理程序之前的CPU现场(某一时刻,CPU中各个寄存器的值)。所以应该在修改标记寄存器之前,将它的值入栈保存。</p>
<p>我们更简洁地描述中断过程,如下:</p>
<p>(1)取得中断类型码N;<br>
(2)pushf<br>
(3)TF=0,IF=0<br>
(4)push CS<br>
(5)push IP<br>
(6)(IP)=(N<em>4),(CS)=(N</em>4+2)</p>
<p>在最后一步完成后,CPU开始执行由程序员编写的中断处理程序。</p>
<hr>
<p>由于CPU随时都可能检测到中断信息,也就是说CPU随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间之中。</p>
<p>而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。</p>
<p>中断处理程序的编写方法和子程序的比较相似,下面是常规的步骤:</p>
<p>(1)保存用到的寄存器;<br>
(2)处理中断;<br>
(3)恢复用到的寄存器;<br>
(4)用iret指令返回。</p>
<p>iret指令的功能用汇编语法描述为:</p>
<blockquote>
<p>pop IP<br>
pop CS<br>
popf</p>
</blockquote>
<p>iret通常和硬件自动完成的中断过程配合使用。在中断过程中,寄存器入栈的顺序是标志寄存器、CS、IP,而iret的出栈顺序是IP、CS、标志寄存器,刚好和其相对应,实现了用执行中断处理程序前的CPU现场恢复标志寄存器和CS、IP的工作。 iret指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序。</p>
<h4 id="123-除法错误中断的处理编程处理0号中断">12.3 除法错误中断的处理、编程处理0号中断</h4>
<p>我们通过对0号中断,即除法错误中断的处理,来体会一下前面所讲的内容。</p>
<p>当CPU执行div等除法指令的时候,如果发生了除法溢出错误,将产生中断类型码为0的中断信息,CPU将检测到这个信息,然后引发中断过程,转去执行0号中断所对应的中断处理程序。</p>
<p>我们看一下下面程序的执行结果,如图12.2所示(不同的操作系统下显示可能不同)。</p>
<blockquote>
<p>mov ax,1000h<br>
mov bh,1<br>
div bh</p>
</blockquote>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202109/1162354-20210907234811917-326622919.png" alt="image" loading="lazy"></p>
<p>可以看到,当CPU执行div bh时,发生了除法溢岀错误,产生0号中断信息,从而引发中断过程,CPU执行0号中断处理程序。</p>
<p>我们从图中可以看出系统中的0号中断处理程序的功能:显示提示信息“Divide overflow"后,返回到操作系统中。</p>
<hr>
<p>现在我们考虑改变一下0号中断处理程序的功能,即重新编写一个0号中断处理程序,它的功能是在屏幕中间显示“overflow!",然后返回到操作系统,如图12.3所示。</p>
<p><img src="https://img2020.cnblogs.com/blog/1162354/202109/1162354-20210907234842218-2141418934.png" alt="image" loading="lazy"></p>
<p>当CPU执行div bh后,发生了除法溢出错误,产生0号中断信息,引发中断过程,CPU执行我们编写的0号中断处理程序。</p>
<p>在屏幕中间显示提示信息“overflow!"后,返回到操作系统中。</p>
<p>编程:当发生除法溢出时,在屏幕中间显示“overflow!",返回DOS。</p>
<p>我们首先进行分析:</p>
<p>(1) 当发生除法溢出的时候,产生0号中断信息,从而引发中断过程。</p>
<p>此时,CPU将进行以下工作。</p>
<p>① 取得中断类型码0;<br>
② 标志寄存器入栈,TF、圧设置为0;<br>
③ CS、IP入栈;<br>
④ (IP)=(0x4),(CS)=(0x4+2)。</p>
<p>(2) 当中断0发生时,CPU将转去执行中断处理程序。</p>
<p>只要按如下步骤编写中断处理程序,当中断0发生时,即可显示“overflow!"。</p>
<p>① 相关处理;<br>
② 向显示缓冲区送字符串“overflow!”;<br>
③ 返回DOS。</p>
<p>我们将这段程序称为:do0。</p>
<p>(3) 现在的问题是:do0应存放在内存中。因为除法溢出随时可能发生,CPU随时都可能将CS:IP指向do0的入口,执行程序。</p>
<p>那么do0应该放在哪里呢?</p>
<p>由于我们是在操作系统之上使用计算机,所有的硬件资源都在操作系统的管理之下, 所以我们要想得到一块内存区存放do0,应该向操作系统申请。</p>
<p>但在这里出于两个原因我们不想这样做:</p>
<p>① 过多地讨论申请内存将偏离问题的主线;<br>
② 我们学习汇编的一个重要目的就是要获得对计算机底层的编程体验。所以,在可能的情况下,我们不去理会操作系统,而直接面向硬件资源。</p>
<p>问题变得简单而直接,我们只需找到一块别的程序不会用到的内存区,将do0传送到其中即可。</p>
<p>内存0000:0000~0000:03FF,大小为1KB的空间是系统存放中断处理程序入口地址的中断向量表。8086支持256个中断,实际上系统中要处理的中断事件远没有达到256个。所以在中断向量表中,有许多单元是空的。</p>
<p>中断向量表是PC系统中最重要的内存区,只用来存放中断处理程序的入口地址,DOS系统和其他应用程序都不会随便使用这段空间。</p>
<p>可以利用中断向量表中的空闲单元来存放我们的程序。一般情况下,从0000:0200至0000:02FF的256个字节的空间所对应的中断向量表项都是空的,操作系统和其他应用程序都不占用。我们在前面的课程中使用过这段空间。</p>
<p>根据以前的编程经验,我们可以估计出,do0的长度不可能超过256个字节。</p>
<p><strong>结论:我们可以将do0传送到内存0000:0200处。</strong></p>
<p>(4) 将中断处理程序do0放到0000:0200后,若要使得除法溢岀发生的时候,CPU转去执行do0,则必须将do0的入口地址,即0000:0200登记在中断向量表的对应表项中。</p>
<p>因为除法溢出对应的中断类型码为0,它的中断处理程序的入口地址应该从0x4地址单元开始存放,段地址存放在0x4+2字单元中,偏移地址存放在0x4字单元中。也就是说要将do0的段地址0存放在0000:0002字单元中,将偏移地址200H存放在0000:0000字单元中。</p>
<p>总结上面的分析,我们要做以下几件事情。</p>
<p>(1)编写可以显示“overflow!”的中断处理程序:do0;<br>
(2)将do0送入内存0000:0200处;<br>
(3)将do0的入口地址0000:0200存储在中断向量表0号表项中。</p>
<p>程序的框架如下。</p>
<p><strong>程序12.1</strong></p>
<pre><code>assume cs:code
code segment
start:do0安装程序
设置中断向量表
mov ax,4c00h
int 21h
do0: 显示字符串"overflow!"
mov ax,4c00h
int 21h
code ends
end start
</code></pre>
<p>可以看到,上面的程序分为两部分:</p>
<p>(1)安装do0,设置中断向量的程序;<br>
(2)do0。</p>
<p>程序12.1执行时,do0的代码是不执行的,它只是作为do0安装程序所要传送的数据。程序12.1执行时,首先执行do0安装程序,将do0的代码复制到内存0:200处,然后设置中断向量表,将do0的入口地址,即偏移地址200H和段地址0,保存在0号表项中。</p>
<p>这两部分工作完成后,程序就返回了。程序的目的就是在内存0:200处安装do0的代码,将0号中断处理程序的入口地址设置为0:200。</p>
<p>do0的代码虽然在程序中,却不在程序执行的时候执行。它是在除法溢出发生的时候才得以执行的中断处理程序。</p>
<p>do0部分代码的最后两条指令是依照我们的编程要求,用来返回DOS的。</p>
<p>现在,我们再反过来从CPU的角度看一下,什么是中断处理程序?</p>
<p>我们来看一下do0是如何变成0号中断的中断处理程序的。</p>
<p>(1)程序12.1在执行时,被加载到内存中,此时do0的代码在程序12.1所在的内存空间中,它只是存放在程序12.1的代码段中的一段要被传送到其他单元中的数据,我们不能说它是0号中断的中断处理程序;</p>
<p>(2)程序12.1中安装do0的代码执行完后,do0的代码被从程序12.1的代码段中复制到0:200处。此时,我们也不能说它是0号中断的中断处理程序,它只不过是存放在0:200处的一些数据;</p>
<p>(3)程序12.1中设置中断向量表的代码执行完后,在0号表项中填入了do0的入口地址0:200,此时0:200处的信息即do0的代码,就变成了0号中断的中断处理程序。因为当除法溢出(即0号中断)发生时,CPU将执行0:200处的代码。</p>
<h4 id="124-安装do0程序">12.4 安装、do0程序</h4>
<p>可以使用movsb指令,将do0的代码送入0:200处。程序如下。</p>
<pre><code>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
</code></pre>
<p>我们来看一下,用rep movsb指令的时候要确定的信息。</p>
<p>(1)传送的原始位置,段地址:code,偏移地址:offset do0;<br>
(2)传送的目的位置:0:200;<br>
(3)传送的长度:do0部分代码的长度;<br>
(4)传送的方向:正向。</p>
<p>更明确的程序如下。</p>
<pre><code>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 :显示字符串"overflow!"
mov ax,4c00h
int 21h
code ends
end start
</code></pre>
<p>问题是,我们如何知道do0代码的长度?</p>
<p>最简单的方法是,计算一下do0中所有指令码的字节数。但是这样做太麻烦了,因为只要do0的内容发生了改变,我们都要重新计算它的长度。</p>
<p>可以利用编译器来计算do0的长度,具体做法如下。</p>
<pre><code>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
</code></pre>
<p>"-”是编译器识别的运算符号,编译器可以用它来进行两个常数的减法。</p>
<p>比如,指令:mov ax,8-4,被编译器处理为指令:mov ax,4。</p>
<p>汇编编译器可以处理表达式。</p>
<p>比如,指令:mov ax,(5+3)x5/10,被编译器处理为指令:mov ax,4。</p>
<p>好了,知道了“-”的含义,对于用offset do0end-offset do0,得到do0代码的长度的原理,这里就不再多说了,相信到了现在,读者己可以自己进行分析了。</p>
<p>下面我们编写do0程序。</p>
<hr>
<p>do0程序的主要任务是显示字符串,程序如下。</p>
<pre><code>do0 :设置ds:si指向字符串
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;设置es:di指向显存空间的中间位置
mov cx,9 ;设置cx为字符串长度
s:mov al,
mov es:,al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end:nop
</code></pre>
<p>程序写好了,可要显示的字符串放在哪里呢?我们看下面的程序。</p>
<p><strong>程序12.2</strong></p>
<pre><code>assume cs:code
data segment
db "overflow!"
data ends
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 : mov ax,data
mov ds,ax
mov si,0 ;设置ds: si指向字符串
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;设置es:di指向显存空间的中间位置
mov cx,9 ;设置cx为字符串长度
s : mov al,
mov es:,al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end:nop
code ends
end start
</code></pre>
<p>上面的程序,看似合理,可实际上却大错特错。注意,“overflow!”在程序12.2的data段中。程序12.2执行完成后返回,它所占用的内存空间被系统释放,而在其中存放的“overflow!"也将很可能被别的信息覆盖。</p>
<p>而do0程序被放到了0:200处,随时都会因发生了除法溢岀而被CPU执行,很难保证do0程序从原来程序12.2所处的空间中取得的是要显示的字符串"overflow!”。</p>
<p>因为do0程序随时可能被执行,而它要用到字符串"overflow!”,所以该字符串也应该存放在一段不会被覆盖的空间中。正确的程序如下。</p>
<p><strong>程序12.3</strong></p>
<pre><code>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: jmp short do0start
db "overflow!"
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,9 ;设置cx为字符串长度
s:mov al,
mov es:,al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end:nop
code ends
end start
</code></pre>
<p>在程序12.3中,将“overflow!”放到do0程序中,程序12.3执行时,将标号do0到标号do0end之间的内容送到0000:0200处。</p>
<p>注意,因为在do0程序开始处的“overflow!”不是可以执行的代码,所以在“overflow!”之前加上一条jmp指令,转移到正式的do0程序。当除法溢出发生时,CPU执行0:200处的jmp指令,跳过后面的字符串,转到正式的do0程序执行。</p>
<p>do0程序执行过程中必须要找到“overflow!”,那么它在哪里呢?</p>
<p>首先来看段地址,“overflow!”和do0的代码处于同一个段中,而除法溢出发生时,CS中必然存放do0的段地址,也就是"overflow!"的段地址;再来看偏移地址,0:200处的指令为jmp short do0start,这条指令占两个字节,所以"overflow!”的偏移地址为202h。</p>
<h4 id="125-设置中断向量单步中断响应中断的特殊情况">12.5 设置中断向量、单步中断、响应中断的特殊情况</h4>
<p>下面,将do0的入口地址0:200,写入中断向量表的0号表项中,使do0成为0号中断的中断处理程序。</p>
<p>0号表项的地址为0:0,其中0:0字单元存放偏移地址,0:2字单元存放段地址。</p>
<p>程序如下:</p>
<blockquote>
<p>mov ax,0<br>
mov es,ax<br>
mov word ptr es:,200h<br>
mov word ptr es:,0</p>
</blockquote>
<hr>
<p>基本上,CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。</p>
<p>单步中断的中断类型码为1,则它所引发的中断过程如下。</p>
<p>(1) 取得中断类型码1;<br>
(2) 标志寄存器入栈,TF、IF设置为0;<br>
(3) CS、IP入栈;<br>
(4) (IP)=(1x4),(CS)=(1x4+2)。</p>
<p>如上所述,如果TF=1,则执行一条指令后,CPU就要转去执行1号中断处理程序。 CPU为什么要提供这样的功能呢?</p>
<p>我们在使用Debug的t命令的时候,有没有想过这样的问题,Debug如何能让CPU在执行一条指令后,就显示各个寄存器的状态?</p>
<p>我们知道,CPU在执行程序的时候是从CS:IP指向的某个地址开始,自动向下读取指令执行。也就是说,如果CPU不提供其他功能的话,就按这种方式工作,只要CPU一加电,它就从预设的地址开始一直执行下去,不可能有任何程序能控制它在执行完一条指令后停止,去做别的事情。</p>
<p>可是,我们在Debug中看到的情况却是,Debug可以控制CPU执行被加载程序中的一条指令,然后让它停下来,显示寄存器的状态。</p>
<p>Debug有特殊的能力吗?</p>
<p>我们只能说Debug利用了CPU提供的一种功能。只有CPU提供了在执行一条指令后就转去做其他事情的功能,Debug或是其他的程序才能利用CPU提供的这种功能做出我们使用T命令时的效果。</p>
<p>好了,我们来简要地考虑一下Debug是如何利用CPU所提供的单步中断的功能的。</p>
<p>首先,Debug提供了单步中断的中断处理程序,功能为显示所有寄存器中的内容后等待输入命令。</p>
<p>然后,在使用t命令执行指令时,Debug将TF设置为1,使得CPU工作于单步中断方式下,则在CPU执行完这条指令后就引发单步中断,执行单步中断的中断处理程序,所有寄存器中的内容被显示在屏幕上,并且等待输入命令。</p>
<p>接下来的问题是,当TF=1时,CPU在执行完一条指令后将引发单步中断,转去执行中断处理程序。注意,中断处理程序也是由一条条指令组成的,如果在执行中断处理程序之前,TF=1,则CPU在执行完中断处理程序的第一条指令后,又要产生单步中断,则又要转去执行单步中断的中断处理程序,在执行完中断处理程序的第一条指令后,又要产生单步中断,则又要转去执行单步中断的中断处理程序……</p>
<p>看来,上面的过程将陷入一个永远不能结束的循环,CPU永远执行单步中断处理程序的第一条指令。</p>
<p>CPU当然不能让这种情况发生,解决的办法就是,在进入中断处理程序之前,设置TF=0。</p>
<p>从而避免CPU在执行中断处理程序的时候发生单步中断。这就是为什么在中断过程中有TF=0这个步骤,我们再来看一下中断过程。</p>
<p>(1) 取得中断类型码N;<br>
(2) 标志寄存器入栈,TF=0、IF=0;<br>
(3) CS、IP入栈;<br>
(4) (IP)=(Nx4), (CS)=(Nx4+2)。</p>
<p>最后,CPU提供单步中断功能的原因就是,为单步跟踪程序的执行过程,提供了实现机制。</p>
<hr>
<p>一般情况下,CPU在执行完当前指令后,如果检测到中断信息,就响应中断引发中断过程。</p>
<p>在有些情况下,CPU在执行完当前指令后,即便是发生中断,也不会响应。对于这些情况,我们不一一列举,只是用一种情况来进行说明。</p>
<p>在执行完向ss寄存器传送数据的指令后,即便是发生中断,CPU也不会响应。这样做的主要原因是,ss:sp联合指向栈顶,而对它们的设置应该连续完成。</p>
<p>如果在执行完设置ss的指令后,CPU响应中断,引发中断过程,要在栈中压入标志寄存器、CS和IP的值。而ss改变,sp并未改变,ss:sp指向的不是正确的栈顶,将引起错误。</p>
<p>所以CPU在执行完设置ss的指令后,不响应中断。这给连续设置ss和sp指向正确的栈顶提供了一个时机。即我们应该利用这个特性,将设置ss和sp的指令连续存放,使得设置sp的指令紧接着设置ss的指令执行,而在此之间,CPU不会引发中断过程。</p>
<p>比如,我们要将栈顶设为1000:0,应该:</p>
<blockquote>
<p>mov ax,1000h<br>
mov ss,ax<br>
mov sp,0</p>
</blockquote>
<p>而不应该:</p>
<blockquote>
<p>mov ax,1000h<br>
mov ss,ax<br>
mov ax,0<br>
mov sp,0</p>
</blockquote>
<p>好了,现在我们回过来看一下,实验2中的“(3)下一条指令执行了吗?”现在你知道原因了吗?</p>
<p>Debug利用单步中断来实现T命令的功能,也就是说,用T命令执行一条指令后,CPU响应单步中断,执行Debug设置好的处理程序,才能在屏幕上显示寄存器的状态,并等待命令的输入。</p>
<p>而在mov ss,ax指令执行后,CPU根本就不响应任何中断,其中也包括单步中断,所以Debug设置好的用来显示寄存器状态和等待输入命令的中断处理程序根本没有得到执行,所以我们看不到预期的结果。CPU接着向下执行后面的指令mov sp,10h,然后响应单步中断,我们才看到正常的结果。</p><br><br>
来源:https://www.cnblogs.com/jpSpaceX/p/15215076.html
頁:
[1]