(二十九) 内中断--汇编笔记

汇编语言

Posted by YiMiTuMi on April 23, 2020

内中断

任何一种通用的CPU,都具备,可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接受到的信息进行处理。这种特殊的信息,我们可以称其为:中断信息

中断的意思是指,CPU不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。

“中断信息”是要求CPU马上进行某种处理,并向所要进行的该种处理提供了必备的参数的通知信息。

内中断的产生

当CPU内部有下面的情况发生的时候,将产生相应的中断信息:

 1)除法错误,比如,执行div指令产生的除法溢出;

 2)单步执行;

 3)执行into指令;

 4)执行int指令;

8086CPU用称为中断类型码的数据来标识中断信息的来源。

中断类型码为一个字节型数据,可以表示256种中断信息的来源,称为中断源。

4种中断源的中断类型码:

 1)除法错误:0

 2)单步执行:1

 3)执行into指令:4

 4)执行int指令,该指令的格式为int n,指令中的n为字节型立即数,是提供给CPU的中断类型码。

中断处理程序

用来处理中断信息的程序被称为中断处理程序,一般来说,需要对不同的中断信息编写不同的处理程序。

CPU在收到中断信息后,就应该转去执行该中断信息的处理程序,就是要将CS:IP指向它的入口(即程序第一条指令的地址)

中断信息中包含有标识中断源的类型码,中断类型码的作用就是用来定位中断处理程序。

中断向量表

CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。

中断向量表就是中断向量的列表。中断向量,就是中断处理程序的入口地址。即中断向量表,就是中断处处理程序入口地址的列表。中断向量表在内存中保存,其中存放着256个中断源所对应的中断处理程序的入口。CPU只要知道了中断类型码,就可以将中断类型码作为中断向量表的表项号,定位相应的表项,从而得到中断处理程序的入口地址。

8086PC机,中断向量表指定放在内存地址0处。从内存 0000:0000 到 0000:03FF的1024个单元存放着中断向量表。

对于8086CPU,这个入口地址包括段地址和偏移地址,所以一个表项占两个字,高地址字存放段地址,低地址字存放偏移地址。

中断过程

可以用中断类型码,在中断向量表中找到中断处理程序的入口,用这个入口地址来设置CS和IP,使CPU执行中断处理程序。用中断类型码找到中断向量,并用它设置CS和IP,这个工作是由CPU的硬件自动完成的。CPU硬件完成这个工作的过程被称为中断过程。CPU收到中断信息后,要对中断嘻嘻进行处理,首先将引发中断过程。硬件在完成中断过程后,CS:IP将指向中断处理程序的入口,CPU开始执行中断处理程序。

CPU在执行完中断处理程序后,应该返回原来的执行点继续执行下面的指令。所以在中断过程中,在设置CS:IP之前,还要将原来的CS和IP的值保存起来。所以可以用call指令先保存当前CS和IP的值,然后再设置CS和IP。

8086CPU引发中断过程:

 1)(从中断信息)取得中断类型码

 2)标志寄存器的值入栈(因为在中断过程中要改变标志寄存器的值,所以要先保存在栈中);

 3)设置标志寄存器的第8位TF和第9位IF的值为0。

 4)CS的内容入栈

 5)IP的内容入栈

 6)从内存地址为中断类型码*4 和中断类型码 *4+2 的两个字节单元中读取中断处理程序的入口地址设置IP和CS。

简洁表示:

 1)取得中断类型码N

 2)pushf

 3)TF = 0, IF = 0

 4) push CS

 5) push IP

 6) (IP) = (N * 4), (CS) = (N * 4 + 2)

中断处理程序和 iret 指令

由于CPU随时都可能检测到中断信息,CPU随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间中。而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。

中断处理程序的编写方法:

 1)保存用到的寄存器

 2)处理中断

 3)恢复用到的寄存器

 4)用iret指令返回

iret指令的功能用汇编语法描述为:

pop IP

pop CS

popf

iret通常和硬件自动完成的中断过程配合使用。在中断过程中,寄存器入栈的顺序是标志寄存器、CS、IP,而iret的出栈顺序是IP、CS、标志寄存器,刚好和其相对应,实现了用执行中断处理程序前的CPU现场恢复标志寄存器和CS、IP的工作。

iret指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序。

编写处理0号中断(除法溢出中断)

重新编写一个0号中断处理程序,它的功能是在屏幕中间显示“overflow”后,返回操作系统。

assume cs:code

code segment

	start:  mov ax, cs
			mov ds, ax
			mov si, offset do0   //设置ds:si指向源地址
			
			//安装 do0中断程序
			mov ax, 0
			mov es, ax
			mov di, 200H         //设置es:di指向目的地址
			mov cx, offset do0end - offset do0 //设置cx为传输长度
			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, 0800H 
			mov es, ax
			mov di, 12 * 160 + 36 * 2 //设置指向显存空间的位置
			  
			mov cx, 9
			
		s:  mov al, ds:[si]
			mov es:[di], al
			inc si
			add di, 2           //显示字符要保存颜色所以占2字节
			loop s
		
			mov ax, 4c00H
			int 21H

   do0end:  nop                 //方便计算do0程序所占的字节

code ends

end start

发生中断时CPU的工作:

 1)取得中断类型码0;

 2)标志寄存器入栈,TF、IF设置为0。

 3)CS、IP入栈,入栈CS先入,之后IP,所以高地址为CS,低地址为IP

 4)(IP) =(0 * 4), (CS) = (0 * 4 + 2)

步骤:

 1)设置初始化寄存器

 2)将do0程序写入到内存中

 3)实现do0程序,向缓冲去送字符串“overflow”

 4)设置中断向量表

 1)do0中断程序应该放在内存。因为除法溢出随时可能发生,CPU随时都可能将CS:IP 指向do0的入口,执行程序。

 2)一般情况下从0000:0200至0000:02FF的256个字节的空间所对应的中断向量表项都是空的,操作系统和其他应用程序都不占用。

 3)若要让除法溢出发成的时候,CPU转去执行do0,则必须将中断处理程序do0的入口地址即0000:0200登记到中断向量表的对应表项中。除法溢出对应的中断类型码为0,则它的中断处理程序的入口地址应该从0*4地址单元开始存放。

段地址放在(0 * 4)+ 2

偏移地址放在(0 * 4)

因为发生中断的时候CS、IP入栈,入栈CS先入,之后IP,所以高地址为CS,低地址为IP。

 4)用rep movsb指令将do0的代码送入0:0200处。(安装中断程序)

 5)利用编译器来计算do0的长度,“-”是编译器识别的运算符号,编译器可以用它进行两个常数的减法。

mov cx, offset do0end - offset do0 

 6)

data segment

	db "overflow!"

data ends

若以这种形式存放“overflow!”将会出错,当程序执行完,程序所占用的空间将被释放,而存放在内存中的数据“overflow!”也将有可能被别的信息覆盖。所以要将该字符串存放到一段不会被覆盖的空间中。

 7)将“overflow!”放到do0程序中,和程序一起存放到0000:0200的内存中,确保不会被覆盖掉。

 8)

 do0:  jmp short do0start
	   db "overflow!"

因为 db “overflow!” 不是可执行的代码,所以在前面加一条jmp指令,转移到正式的do0程序。

 9)因为”overflow!”和do0的代码在同一个段中,而溢出发生时,CS中必然存放do0的段地址,也就是”overflow!”的段地址。0:0200处的指令为jmp short do0start,这条指令占两个字节,所以”overflow!”的偏移地址为0202H。

注意:程序主要作用是将要实现的中断程序安装到内存中,当发生中断时,CPU自动去执行中断程序。

单步中断

CPU在执行完一条指令后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。

单步中断的中断类型码为1,则他引发的中断过程如下:

 1)取得中断类型码 1

 2)标志寄存器入栈,TF、IF设置为 0

 3)CS、IP入栈

 4)(IP)= (1 * 4),(CS)=(1 * 4 + 2)

如果TF = 1,则执行一条指令后,CPU就要去执行1号中断处理程序。

Debug利用了CPU提供的一种功能。只有CPU提供了在执行一条指令后就转去做其他事情的功能,Debug或是其它的程序才能利用CPU提供的这种功能做出我们使用T命令(单步调功能)时的效果。

Debug提供的单步中断功能:

首先,Debug提供了单步中断的中断处理程序,功能为显示所有寄存器中的内容后等待输入命令。

然后,在使用t命令执行指令时,Debug将TF设置为1,使得CPU工作于单步中断方式下,则在CPU执行完这条指令后就引发单步中断,执行单步中断的处理程序,当然在单步中断的处理程序执行前,将TF位置为0,防止无限循环等,所有寄存器的内容显示在屏幕上,并等待输入。

CPU提供单步中断功能的原因就是,为单步跟踪程序的执行过程,提供了实现机制。

int指令

int指令的格式:

int n  //n为中断类型码,它的功能是引发中断过程

CPU执行int n指令,相当于引发一个n号中断的中断过程:

 1)取中断类型码 n

 2)标志寄存器入栈,IF = 0,TF = 0(单步中断)

 3)CS、IP入栈

 4)(IP) = (n * 4), (CS) = (n * 4 + 2)

从此处转去执行n号中断处理程序。

可以在程序中使用int指令调用任何一个中断处理程序。

assume cs:code

code segment

	start:  mov ax, 0b800H //显存位置
			mov es, ax
			mov byte ptr es:[12 * 160 + 40 * 2], '!'
			int 0

code ends

end start

CPU执行int 0指令时,将引发中断过程,执行0号中断处理程序,而系统设置的0号中断是显示“Divide overflow”,然后返回到系统。

int指令的最终功能和call指令相似,都是调用一段程序。

一般情况下,系统将一些具有一定功能的子程序,以中断处理程序的方式提供给应用程序调用。在编程的时候,可以用int指令调用这些子程序。当然,也可以自己编写一些中断处理程序供别人使用。我们可以将中断处理程序简称为中断例程。

求word型数据的平方的中断例程

编写、安装中断7ch的中断例程:

功能:求word型数据的平方

参数:(ax) = 要计算的数据

返回值:dx、ax中存放结果的高16位和低16位。

例:求 2 * 3456^2

assume cs:code

code segment 

	start:  mov ax, 3456
			int 7ch
			add ax, ax
			adc dx, dx //进位加法
			mov ax, 4c00H
			int 21H

code ends

end start	

实现步骤:

 1)编写实现求平方功能的程序。

 2)安装程序,将其安装在0:200处

 3)设置中断向量表,将程序的入口地址保存在7ch表项中,使其成为中断7ch的中断例程。

安装程序:

assume cs:code

code segment

	start:  mov ax, cs
			mov ds, ax
			mov si, offset sqr     //设置ds:si指向源地址

			mov ax, 0
			mov es, ax
			mov di, 200H           //设置es:di指向目的地址
			mov cx, offset sqrend - offser sqr //计算cx的传输长度
			cld   //正方向传输
			rep movsb
			
			//设置中断向量表
			mov ax, 0
			mov es, ax
			mov word ptr es:[7ch * 4], 200H
			mov word ptr es:[7ch * 4 + 2], 0

			mov ax, 4c00H
			int 21H

	sqr:    mul ax
			iret

	sqrend: nop

code ends

end start

在中断例程sqr的最后,要使用iret指令。用汇编语法描述,iret指令的功能:

pop IP

pop CS

popf

CPU执行 int 7ch 指令进入中断例程之前,标志寄存器、当前的CS和IP被压入栈中,在执行完中断例程后,应该用iret指令恢复 int 7ch 执行前的标志寄存器和CS、IP的值,从而接着执行应用程序。

int指令和iret指令的配合使用于call指令和ret指令的配合使用具有相似的思路。

大小写转化中断例程

功能:将一个全是字母,以0结尾的字符串,转化为大写。

参数:ds:si指向字符串的首地址

例:

assume cs:code

data segment

	db 'conversation', 0

data ends

code segment 

	start:  mov ax, data
			mov ds, ax
			mov si, 0
			int 7ch

			mov ax, 4C00H
			int 21H

code ends

end start

安装程序:

assume cs:code

data segment

	start:  mov ax, cs
			mov ds, ax
			mov si, offset Lan //设置 源地址
			
			mov ax, 0
			mov es, ax
			mov di, 200H
		
			mov cx, offset LanEnd - offset Lan
			cld
			rep movsb

			mov ax, 0
			mov es, ax
			mov wort ptr es:[7ch * 4], 200H   //IP
			mov wort ptr es:[7ch * 4 + 2], 0  //CS
			mov ax, 4C00H
			int 21H

      Lan:  push si
			push cx

  LanLoop:  mov cl, ds:[si]
			mov ch, 0
			jcxz ok
			and byte ptr ds:[si], 11011111B
			inc si
			jmp short LanLoop

	   ok:  pop si
			pop cx
			iret
		
   LanEnd:  nop

code ends

end start

对int、iret和栈的深入理解

用7ch中断例程完成loop指令。

例:再屏幕中间显示80个“!”

assume cs:code

code segment

	start:  mov ax, 0b800H
			mov es, ax
			mov di, 160 * 12

			mov bx, offset s - offset se
			mov cx, 80

		s: mov byte ptr es:[di], '!'
			add di, 2
			int 7ch
		
		se: nop
		
			mov ax, 4c00H
			int 21H

code ends

end start

模拟loop指令,7ch需要实现的功能:

 1)dec cx;

 2)如果(cx)!= 0,转到s处执行,否则向下执行。

转到标号s处显然应该设置(CS)= 标号s的段地址,(IP)= 标号s的偏移地址

int 7ch 引发中断过程后,进入7ch中断例程,在中断过程中,当前的标志寄存器、CS和IP都要入栈,此时压入的CS和IP中的内容,分别是调用程序的段地址(即s处的段地址)和 int 7ch后一条指令的偏移地址(即标号se的偏移地址)

所以s的偏移地址可以用,se + bx中存放的转移地址来获得。

3)用iret设置CS:IP指向,iret要根据栈中的CS和IP返回中断的下一条指令继续执行,所以只需要设置一下栈中的IP值就可以。

7ch中断例程:

 lp:   push bp
	   mov bp, sp
	   dec cx
	   jcxz lpret
	   add ss:[bp + 2], bx
	
lpret: pop bp
	   iret

此时,栈中的情况为:栈顶是bp,下面是se的偏移地址,再下面是s的段地址,再下面是标志寄存器的值。所以es的偏移地址为((ss)* 16 + (bp) + 2)

如果(cx)= 0,则并不需要修改栈中se的偏移地址,直接返回即可。CPU从标号se处向下执行。

BIOS 和 DOS所提供的中断例程

在系统板的ROM(只读内存)中存放着一套程序,称为BIOS(基本输入输出系统),BIOS中主要包含以下几部分内容:

 1)硬件系统的检测和初始化程序。

 2)外部中断和内部中断的中断例程。

 3)用于对硬件设备进行I/O操作的中断例程。

 4)其他和硬件系统相关的中断例程。

操作系统DOS也提供了中断例程,从操作系统的角度来看,DOS的中断例程就是操作系统像程序员提供的编程资源。

BIOS 和 DOS在所提供的中断例程中包含了许多子程序,这些子程序实现了程序员在编程的时候经常需要用到的功能。程序员在编程的时候,可以用int指令直接调用BIOS和DOS提供的中断例程。

和硬件设备相关的DOS中断例程中,一般都调用BIOS的中断例程。

BIOS 和 DOS 中断例程的安装过程

1)开机后,CPU一加电,初始化(CS)= 0FFFFH,(IP)= 0,自动从FFFF:0单元开始执行程序。FFFF:0处有一条跳转指令,CPU执行该指令后,转去执行BIOS中的硬件系统检测和初始化程序。

2)初始化程序将建立BIOS所支持的中断向量,即将BIOS提供的中断例程的入口地址登记在中断向量表中。注意,对于BIOS所提供的中断例程,只需将入口地址登记在中断向量表中即可,因为它们是固化到ROM中的程序,一直在内存中存在。

3)硬件系统检测和初始化完成后,调用int 19H 进行操作系统的引导。从此将计算机交由操作系统控制。

4)DOS启动后,除完成其他工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量。

BIOS中断例程应用(int 10H)

int 10H 中断例程是BIOS提供的中断例程,其中包含了多个和屏幕输出相关的子程序。

一个供程序员调用的中断例程中往往包括多个子程序,中断例程内部用传递进来的参数来决定执行哪一个子程序。BIOS和DOS提供的中断例程,都用ah来传递内部子程序的编号。

例:int 10H中断例程设置光标位置功能

mov ah, 2     //置光标
mov bh, 0     //第0页
mov dh, 5     //dh中放行号
mov dl, 12    //dl中放列好
int 10H

(ah)= 2表示调用第10h号中断例程的2号子程序,功能为设置光标位置,可以提供光标所在的行号、列号和页号。

行号:80 * 25 字符模式下:0 ~ 24

列号:80 * 25 字符模式下:0 ~ 79

(bh)= 0,(dh)= 5,(dl)= 12,设置光标到第0页,第5行,第12列。

bh中页的含义:内存地址空间中,B8000H ~ BFFFFH共32KB的空间,为80 * 25彩色字符模式的显示缓冲区。一屏的内容在显示缓冲区中共占4000个字节。

显示缓冲区分为8页,每页4KB(大约4000B),显示器可以显示任意一页的内容。一般情况下,显示第0页的内容。也就是说,通常情况下,B8000H ~ B8F9FH中的4000个字节的内容将出现在显示器上。

int 10H中断例程在光标位置显示字符功能:

mov ah, 9       //在光标处显示字符
mov al, 'a'     //字符
mov bl, 7       //颜色属性
mov bh, 0       //第0页
mov cx, 3       //字符重复个数
int 10H

(ah)= 9表示调用第10H号中断的=例程的9号子程序,功能为在光标位置显示字符,可以提供要显示的字符、颜色属性、页号、字符重复个数作为参数。

bl中的颜色属性的格式:

		7	6 5 4	3	2 1 0

含义	   BL	R G B   I   R G B

	   闪烁   背景   高亮  前景

和显存中的属性字节的格式相同。

例:在屏幕的第5行12列显示3个红底高亮闪烁绿色的‘a’

assume cs:code

code segment

	//设置光标位置
	mov ah, 2    //2号子程序
	mov bh, 0    //页
	mov dh, 5    //行
	mov dl, 12   //列
	int 10H
	
	//在光标处显示字符
	mov ah, 9    //9号子程序
	mov al, 'a'  //显示字符
	mov bl, 11001010b  //颜色属性
	mov bh, 0    //第0页
	mov cx, 3    //字符重复个数
	int 10H

	mov ax, 4c00H
	int 21h

code ends

end 

闪烁效果必须在全屏DOS方式下才能看到。

DOS中断例程应用(int 21H)

int 21H 程序返回功能:

int 21H 中断例程是DOS提供的中断例程,其中包含了DOS提供给程序员在编程时调用的子程序。

int 21H中断例程的 4ch号功能,即程序返回功能:

mov ah, 4ch  //程序返回
mov al, 0	 //返回值
int 21H

(ah) = 4cH 表示调用第21号中断例程的4ch号子程序,功能为程序返回,可以提供返回值作为参数。

使用:

mov ax, 4c00H
int 21H

int 21H中断例程在光标位置显示字符串的功能:

ds:dx 指向字符串,要显示的字符串需要用“$”做结束符

mov ah, 9 功能号9,表示在光标位置显示字符串

int 21H

(ah)= 9 表示调用第21H号中断例程的9号子程序,功能为在光标位置显示字符串,可以提供要显示字符串的地址作为参数。

例:在屏幕的5行12列显示字符串

assume cs:code

data segment

	db 'Welcome to masm', '$'

data ends

code segment
	
			//设置光标位置
	start:  mov ah, 2    //2号子程序
			mov bh, 0    //第0页
			mov dh, 5    //行号
			mov dl, 12   //列号
			int 10h

			mov ax, data
			mov ds, ax
			mov dx, 0     //设置ds:dx指向字符串的首地址 data:0
			mov ah, 9     //设置9号子程序
			int 21H

code ends

end start

其中“$”本身并不显示,只是起到边界的作用。

如果字符串比较长,遇到行尾,程序会自动换行,如果到了最后一行,还能自动上卷一行。

DOS为程序员提供了许多可以调用的子程序,都包含在int 21H中断例程中。

丁香花 – 忧愁思念