您的位置:首页 > 其它

[Intel汇编-MASM]内中断

2014-12-20 14:52 134 查看
1. 内中断的产生:

1) 中断是指一种特殊的信息,一旦CPU收到中断信息就必须暂停当前的工作并转去执行处理该信息的代码(有时可以选择性地屏蔽中断信息),处理完成之后再回复中断前的环境并继续执行中断前的任务;

2) 内中断和外中断:

i. 内中断是指从CPU内部发出的中断信息,具体说就是指当前CPU正在执行的任务自己产生的中断,这种中断是无法屏蔽的(由于是CPU内部产生的,CPU只能屏蔽来自外部的中断);

ii. 外中断是指从CPU外部向CPU发出的中断信息,具体说就是非当前正在执行的任务产生的中断信息,这句话听起来很费解,可以这样理解,因为一切不是当前CPU正在执行的都不能叫做任务,也就是说来自外部的中断只能是由I/O设备产生的,因此外中断是由CPU外部的外设产生的;

!注意:回忆一下组成原理中的概念,微机就是指CPU和MM组成MPU,一切和它相连的都属于外设,而主存MM不会产生中断(在功能是是和CPU紧密相连的),因此一般意义上讲,CPU之外的(除了MM)都属于外设;

iii. 内中断无法屏蔽而外中断可以选择性屏蔽,主要是靠SF的标志位IF(即Interrupt Flag,中断允许标志),如果为1表示允许接受中断,为0就表示屏蔽中断,但也有不可屏蔽的外部中断(这类中断往往是非常紧急的事件);

3) 内中断产生的原因:主要有4类

i. 除法错误(执行div指令等发生溢出,不一定是除0异常,也可以是一个很大的数除以一个很小的数);

ii. 单步调试(即很多调试程序的单步调试功能,像Debug的T命令等);

iii. 执行into指令(这里不对这个做介绍);

iv. 著名的int指令,其实就是内部中断的一般化方式,可以由int指令产生上述所有类型的内部中断;

4) 中断类型码:

i. 用于识别中断的类型,严谨地说是用于识别中断源(即产生中断的事件);

ii. 每种类型的中断都有不同的含义,因此不同中断的处理方法也不同,所以要为终端类型编码;

iii. 这里用1个Byte对中断类型编码,称为中断类型码,因此最多只能有256种中断类型码;

iv. 上述4种内部中断的类型码分别为0、1、4和int n,其中int n中的n就是中断类型码,它可以指定为1Byte的任意数值,因此也可以通过int 0、int 1等产生除法错误、单步调试等所有的中断,因此int n是一种中断的一般化方式;

2. 中断向量表:

1) 一般中断的处理步骤中必然要有跳转去执行中断处理程序的步骤,因此需要通过中断类型码来获取中断处理程序的入口处地址;

2) 中断向量表:就是一张记录中断处理程序入口处指向的表,对于80X86CPU来说,中断向量表存放在内存地址0处,然后两个字存放一个中断向量(包括段地址和偏移地址,即一个中断处理程序的入口处地址),中断发生时就是根据中断类型码n(也就是中断向量表中的字索引)在中断向量表中找到相应的中断处理程序的入口处地址,然后在根据改地址修改cs:ip转去执行相应的中断处理程序;

3) 向量表中每一项(即两个字)都存放了一个中断处理程序的cs:ip值,高地址存放段地址,低地址存放偏移地址,表项按字进行索引,比如0号字存放0号中断的向量,n号字存放n号中断的向量,转化成内存字节的话就是n号中断的向量存放在0:2n处;

3. 中断过程:

1) 是指从CPU接收到中断信息一直到将cs:ip指向中断处理程序入口处的整个过程;

2) 该过程是由硬件自动完成的,程序员无法插手;

3) 用简洁语言描述中断过程:

(1)	get int number N
(2)	pushf
(3)	TF = 0, IF = 0
(4)	push cs
(5)	push ip
(6)	ip = 0:[N * 4], cs = 0:[N * 4 + 2]
!第3步中将IF置0就不允许中断处理程序中接受外部中断了,但是你也可以根据需要在中断处理程序中使用sti指令将IF置1,从而允许在处理中断过程中可以接受从外部进来的中断;

!一般上来说,如果处理的中断非常重要就尽量不要在处理过程中开中断!

4. 中断处理程序的一般流程和中断返回:

1) 中断处理过程和子程序很像,一下展示一下中断处理的一般流程:

i. 保存资源;

ii. 处理中断;

iii. 恢复冲突资源;

iv. 中断返回;

2) 中断返回必须通过iret指令实现:

i. iret即为interrupt return的缩写,即中断返回的意思;

ii. 它和中断过程中的远转移是遥相呼应的,因为中断过程中调用中断处理程序是通过同时修改cs:ip的远转移实现的,因此iret也是远转移;

iii. 它对应展开是:pop ip; pop cs; popf; 和它们入栈顺序对应;·

5. 中断处理程序的设计步骤:

1) 设计中断处理程序;

2) 加载中断处理程序:将写好的中断处理程序装载到内存的空闲区域;

3) 加载中断向量:将装载好的中断处理程序的入口处地址填入中断向量表中响应的位置处;

4) 在程序中调试该中断;

5) 示例:编程修改除法异常中断的处理程序(即0号中断)

assume cs:code

code segment

start:
; 设置源代码串首地址cs:offset do_int0
mov		ax, seg code
mov		ds, ax
mov		si, offset do_int0

; 设置目的代码串空间首地址0:200H
mov		ax, 0
mov		es, ax
mov		di, 200H

; 串传送,将写好的中断处理程序代码送到0:200H处
; 长度可以利用编译器提供的减法操作符计算(编译器提供了四则运算符号供程序员使用)
; 编译器提供的操作符和汇编指令add、sub有所不同
; 编译器提供的操作符是在编译阶段计算结果并填入源代码的(相当于预处理)
; 而编译指令add、sub等都是运行时计算的
mov		cx, offset do_int0_end - offset do_int0
cld ; 虽然mov不改变标志位,但是也要养成良好习惯,即使用标志位的前一步设置标志位!
rep		movsb

; 设置中断向量,将表项0:0和0:2分别置成中断处理程序入口处的低16位和高16位
; 尽量使用es段基做临时处理,因为cs、ds一般来首在程序中经常用到,但绝不要用ss段基!
; 因为栈对于程序来说至关重要,往往存储一些非常重要的临时数据,一旦无意中修改了ss,则可能造成数据丢失!
mov		ax, 0
mov		es, ax ; 用es而不要用ds、ss、cs
mov		word ptr es:[0], 200H
mov		word ptr es:[2], 0

; 试验除法异常运行结果
mov		ax, 15
mov		bl, 0
div		bl

; 装载程序退出
mov		ax, 4C00H
int		21H

; 除法异常中断的实现
do_int0:
jmp		short do_int0_start
; 注意!输出信息决不能定义在本程序的数据段或其它地方,必须定义在中断处理程序中!
; 因为本程序只负责装载中断处理程序和中断向量,程序退出后代码段、数据段等通通都会被释放!
; 为了能让程序退出后供其它程序也能使用我们编写的除法异常中断,就必须要让异常信息字符串
; 也一块儿定义在中断程序的代码区中!
; 因此这就可以当做是一段中断处理程序的小小数据区好了!:-D
db 'Overflow!', '$' ; 利用BIOS功能向中断输出字符串时必须以'$'结尾!
do_int0_start:
; 使ds:dx指向'Overflow!'的起始位置

; seg和offet是和环境有关的,因此这里seg do_int0并不是当前程序的代码段段基code
; 而是指其将在进的环境的段基,即(0:200H)的段基0200H
mov		ax, seg do_int0
mov		ds, ax
mov		dx, offset do_int0 + 2 ; offset同样也是和环境有关的

; 可以在Debug中看到seg tag和offset tag并不会直接翻译成一个绝对地址而是翻译成一个位移量
; seg计算的位移量是tag和0的差值,而offset计算的位移量是tag和tag所在段的差值!
; 这些都是动态计算的!并不是编译好就固定的!

; 调用第21H号中断的9号功能,即BIOS的终端字符串输出功能
; 输出字符串要求起始地址保存在ds:dx中,并且以'$'为结尾
mov		ah, 9
int		21H

; 中断程序不返回直接退出
mov		ax, 4C00H
int		21H
do_int0_end:
nop

code ends

end start


!注意:内存中0000:0000 ~ 0000:03FF这1024个单元(即1KB)用于存放中断向量表,因此最多能放256个表项,但是0000:0200至0000:02FF的256B的空间一般都是空的,应用程序和操作系统都不会占用,因此我们这里就利用这段空间来存放中断处理程序,并且很显然该处理程序没有超过256B;

!直接执行改程序可以看到输出"Overflow!"字符串,退出改程序后运行其它程序,如果其中也出现除法异常则也会调用该中断并显示"Overflow!"字符串,但是现在的电脑都是运行在保护模式下的,因此退出终端再打开则刚刚装载的中断向量和中断程序就不复存在了,如果要再次使用就必须重新运行此程序以重新加载一遍;

!由于发生除法异常通常都是直接退出程序报错的,因此这里中断就不返回了,同时也没必要保护冲突资源;

6. 单步中断和TF标志位:

1) 即之前介绍过的1号中断,Debug的T命令就是利用了该种类型的中断;

2) TF即Trap Flag,即陷阱标志位,只要该位为1,CPU就会立即响应单步中断,CPU中特别提供了陷阱门和调试寄存器等部件专门为单步调试工具提供相关调试方面的服务;

3) Debug的命令T就是将TF置1,然后CPU响应中断进入单步调试程序并显示各个寄存器的结果;

!现在可以明白为什么中断过程中有一步一定要将TF置0了吧!如果进入单步中断后TF仍然是1则又会产生单步中断,这就会陷入无限循环中,因此一定要在中断过程中将TF置0!

4) 响应单步中断的特殊情形:

i. 这就是之前讲到过的T命令作用域修改ss寄存器的指令时会连续执行两条指令再进入中断的情况;

ii. 原因是因为任何程序都会用到栈来存放数据,而当你修改ss:sp时就意味着程序的栈将要改变,因此修改ss和sp的步骤就不能断开,如果在修改ss之后就立马进入中断那么sp还没来得及改就已经破坏了栈中的数据,因此在指向错误的栈顶时就进入中断无疑产生无法估计的严重后果,因此CPU对修改ss寄存器的单步中断给出了一条特殊的机制,那就是连续执行两步,第二步专门留给修改sp的指令,因此如果要修改ss:sp就一定要先修改ss再修改sp,并且这两步一定要连续,中间不能夹其它指令!

7. int中断:

1) 可以利用int指令产生一个任意中断类型的内中断,因此也可以通过int 0、int 1来产生除法异常中断和单步中断;

2) 更重要的是可以用int指令调用一些BIOS和操作系统提供的中断,这里对于操作系统提供的中断就只介绍最低级的DOS系统提供的中断了,很多操作系统的API底层都是通过中断来实现的!

3) 自己编写中断并用int指令产生该中断并响应:这里介绍的是一般过程,包括资源保护和中断返回

要求:中断号为7CH,中断内容是求一字型数据的平方,参数是ax(要计算的数据),返回值保存在dx和ax中,其中dx存放高16位,ax存放低16位,应用中计算2×3456^2

接着,同样中断号是7CH,中断内容是将一以0为结尾的字符串转化为大写并输出到中断,参数ds:si指向字符串首地址

assume cs:code, ds:data

data segment
db 'welcome', 0
data ends

code segment

start:
mov		ax, 0
mov		es, ax
mov		word ptr es:[7CH * 4], 200H
mov		word ptr es:[7CH * 4 + 2], 0

mov		ax, seg do_int7C_sqr
mov		ds, ax
mov		si, offset do_int7C_sqr

mov		ax, 0
mov		es, ax
mov		di, 200H

mov		cx, offset sqr_end - offset do_int7C_sqr
cld
rep	movsb

mov		ax, 3456
int		7CH
add		ax, ax
adc		dx, dx

nop

mov		ax, seg do_int7C_toupr
mov		ds, ax
mov		si, offset do_int7C_toupr

mov		ax, 0
mov		es, ax
mov		di, 200H

mov		cx, offset toupr_end - offset do_int7C_toupr
cld
rep movsb

mov		ax, seg data
mov		ds, ax
mov		si, 0
int		7CH

mov		ax, 4C00H
int		21H

do_int7C_sqr:
mul		ax
iret
sqr_end:
nop

do_int7C_toupr:
mov		al, 11011111B
check:
cmp		word ptr [si], 0
je		print
and		[si], al
inc		si
jmp		short check
print:
mov		word ptr[si], '$'
mov		dx, 0
mov		ah, 9
int		21H

iret
toupr_end:
nop

code ends

end start


8. 使用BIOS和DOS提供的中断例程:

1) BIOS:是一套烧制在主板ROM中的程序(Basic Input Output System,即基本输入输出系统),里面主要就是硬件检测和初始化程序以及各种各样的中断例程(包括各种内/外中断例程以及I/O操作中断例程,如支持键盘等的操作);

2) 除此之外DOS也提供了一些自己的中断例程,而其中一些和硬件设备相关的例程往往需要调用BIOS的中断例程;

3) BIOS中断例程以及DOS中断例程的安装过程:

i. 开机加电后CPU将cs:ip初始化成FFFF:0000,而在这个位置上有一条跳转指令使cs:ip跳转到ROM中的BIOS程序入口处;

ii. 执行BIOS的初始化程序,在初始化程序中会在0000:0000中加载BIOS的中断向量表,而BIOS的中断例程无需加载,因为其固化在ROM中;

!!注意:CPU会将ROM等存储空间全部映射到一个内存空间中统一管理,比如ROM的空间就会被映射到内存中的某段空间上,显存的空间也会被映射到内存的某段空间上,包括i.中的那条跳转指令其实也不是在真正的RAM上的,也是固化在一个部件上,只不过该部件也被映射到了主存上FFFF:0000开始的一段空间上了;

iii. BIOS初始化完成后,BIOS程序的最后一步会调用int 19H中断例程,该中断例程是操作系统的引导程序,可以启动引导操作系统的加载,而在操作系统初始化过程中则会装载自己提供的中断例程以及中断向量;

4) 设置光标位置的BIOS中断:

i. 示例:一般10H为BIOS的中断类型码

</pre><pre name="code" class="plain">mov		bh, 0 ; 第0页,也就是当前页
mov		dh, 5 ; 第5行
mov		dl, 12 ; 第12列
; 第10H号中断(BIOS中断)的第2号子程序
mov		ah, 2
int		10H
ii. 一般供一个程序员调用的中断例程中有很多子程序,具体使用哪段子程序由ah中的内容决定,这里不管是DOS还是BIOS例程,子程序号都是由ah的内容决定的;

iii. 这里2号子程序用于设置光标的位置,其中页号表示处于第几个屏幕缓冲区中,BIOS以控制台一整个屏幕为一页,0号页就是当前页,一般只在当前页显示,因为在其它页显示根本看不见,因此只在当前页显示才有意义;

5) 在光标处显示字符的BIOS中断:

mov		bh, 0 ; 第0页
mov		bl, 10111100B ; 颜色
mov		al, 'x' ; 要显示的字符
mov		cx, 3 ; 重复次数
mov		ah, 9 ; 9号子程序
int		10H
i. BIOS中断(10H号中断)的9号子程序用于在光标处显示字符;

ii. 颜色bl的每一位的意义:

7 6 5 4 3 2 1 0

BL R G B I R G B

闪烁 背景色 高亮 前景色

6) 示例:在第5行第12列显示3个红底高亮闪烁的'a'

assume cs:code

code segment
mov		bh, 0
mov		dh, 5
mov		bl, 12
mov		ah, 2
int		10H

mov		bh, 0
mov		bl, 11001010B
mov		al, 'a'
mov		cx, 3
mov		ah, 9
int		10H

mov		ax, 4C00H
int		21H
code ends

end
6) 三个常用的DOS中断例程:

i. 中断号为21H,子程序选择由ah决定;

ii. 程序返回:ah = 4CH,返回值保存在al中,而ax = 4C00H就是表示程序返回一个0;

iii. 在光标位置显示字符串:前面已经介绍过了,ah = 9,ds:dx指向字符串首地址,且字符串要以'$'作为结尾;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: