汇编语言笔记15外中断

汇编语言笔记15外中断

0x00 外中断简介

​ 在计算机系统中,cpu除了能够执行指令进行运算外,还能够对外部设备进行控制,接受他们的输入。然而外部设备的输入随时可能会发生,所以由外中断来处理

​ 外设的输入不直接送入内存和cpu,而是送入相关接口芯片的端口中(实际是芯片上的寄存器);cpu向外设的输出也不是直接送入外设,而是先送入端口中,再由相关芯片送到外设。同样cpu向外设输出的控制命令也是先送到相关芯片的端口中,再由芯片根据命令对外设实施控制。

外中断信息

​ cpu外部有需要处理的事情发生时,比如外设的输入到达,相关芯片将向cpu发出相应的中断信息。cpu在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。

​ 外中断分为两类:(1)可屏蔽中断:cpu可以不响应的外中断。是否响应由标志寄存器IF决定(DF 决定串传送 di si 递增还是递减 TF决定的是单步中断,别搞混了),如果IF为1,则响应可屏蔽中断,否则不响应可屏蔽中断

​ 8086cpu可以用sti 置IF=1 cli 置IF=0 (clear if set if)

​ (2)不可屏蔽中断:cpu必须响应的外中断,终端类型码为2

​ 外中断的中断过程和内中断一样:

​ (1)标志寄存器flag入栈 pushf

​ (2)IF=0 TF=0

​ (3)cs ip 入栈

​ (4)ip=4 * n cs=4 * n +2(n为中断类型码)

几乎所有由外设引发的中断都是可屏蔽中断

0x01 pc机键盘的处理过程

1 键盘输入

​ 键盘上的每一个键相当于一个开关,按下键,开关导通,产生一个扫描码,扫描码标识了按下的键的位置,扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60h

​ 松开按键时,也产生一个扫描码,同样送入60h端口 ,按下产生的扫面码称为通码,松开称为断码。

​ 通码的第7位为0,断码为1:即断码=通码+80h

QQ截图20200608144929

QQ截图20200608144948

2 引发9号中断

​ 键盘的输入到达60h端口时,相关的芯片就会向cpu发出中断类型码为9的可屏蔽中断信息。cpu检测到该中断信息后,如果IF=1,则响应中断,引发中断过程,执行int9的中断例程

3 执行int9中断例程

​ BIOS提供了int 9中断例程,用来经行基本的键盘输入输出处理,主要工作如下:

​ (1)读出60h端口中的扫描码

​ (2)如果时字符串的扫描码,将扫描码和它对应的ascill码送入内存中的BIOS键盘缓冲区;如果时控制键和切换键(比如crl shift)的扫描码,则将其转换为状态字节(用一个固定内存字节单元的各个位来表示控制键和切换键的状态,类似flag的做法)写入内存中储存状态字节的单元

​ (3)对键盘系统进行相关的控制,比如向相关芯片发送应答信息。

​ BIOS键盘缓冲区是系统启动后,BIOS用于存放int9中断例程所接受的键盘输入的内存区。该内存区可以存储15个键盘输入,键盘缓冲区中,一个字 的高字节存放扫描码,低字节存放对应的ascill码

​ 而键盘状态字节时0040:17,该字节记录了控制键和切换键的状态。各位代表的信息如下:

QQ截图20200608150002

0x02 编写int9中断

​ 1键盘产生扫描码 2扫描码送入60h端口 3 引发9号中断 4cpu执行9号中断例程处理键盘输入 前三个过程我们都无法改变,只能改变int9中断处理程序。

​ 我们不用完整地编写一个键盘中断的处理程序,因为要涉及一些硬件细节。但是我们可以自己写一个新的int9 做一些自己的事情,然后调用BIOS提供的int9中断处理例程

QQ截图20200608150800

assume cs:code
code segment
start:	mov ax,0b800h
		mov es,ax
		mov ah,'a'
s:		mov es:[2000],ah
		inc ah
		cmp ah,'z'
		jna s
		mov ax,4c00h
		int 21h
code ends
end start

​ 上述代码由于cpu高速运转,无法看清字母切换,因此直接显示‘z’,那么我们可以延时一段时间再切换,怎么延时呢?用两个16位寄存器来存放32位的循环次数:

assume cs:code
code segment
start:	mov ax,0b800h
		mov es,ax
		mov ah,'a'
s:		mov es:[2000],ah
		call sleep
		inc ah
		cmp ah,'z'
		jna s
		mov ax,4c00h
		int 21h
		
sleep:	push ax
		push bx
		mov ax,10h
		mov bx,0
s1:		sub bx,1
		sbb ax,0;巧妙的用sbb ax,0运算让每当 sub bx,1运行65536次 ax才减1
		cmp bx,0
		jne s1
		cmp ax,0
		jne s1
		pop bx
		pop ax
		ret
code ends
end start

QQ截图20200608152235

​ 成功达到了延时的目的,再就是实现按下Esc键改变显示的颜色,中断处理程序如下:

​ 1从60h端口读出键盘的输入 2调用BIOS的int9中断例程,处理其他硬件细节 3 判断是否为Esc的扫面码,如果是,改变显示的颜色后返回;如果不是,则直接返回

​ 针对2调用BIOS的int9中断,也就是我们在修改中断向量表之前要保存原来的int9的中断向量,在我们的中断处理程序中模拟中断的方式,(不能直接用int 9来调用,int 9被我们新的中断向量替代了)调用原来的中断处理程序,最后我们还要把int9的中断向量改回来。

​ 如何模拟int 调用原来中断呢:

QQ截图20200608152929

​ 我们要把这个过程实现了 也就是:

pushf;标志寄存器入栈
;后面这一串操作都是为了修改标志寄存器
pushf;再次入栈为了复制一份拿出来改
pop ax;拿出来
and ah,11111100b;IF TF=0;改
push ax
popf;改好入栈出栈送入标志寄存器

完整代码如下:

assume cs:code,ds:data,ss:stack
data segment
		db 16 dup (0)
data ends
stack segment
		db 128 dup (0)
stack ends
code segment
start:	mov ax,data
		mov ds,ax
		mov si,0
		mov ax,0
		mov es,ax
		mov ax,es:[4*9]
		mov ds:[0],ax
		mov ax,es:[4*9+2]
		mov ds:[2],ax;保存到ds
		mov word ptr es:[4*9],offset do9
		mov ax,cs
		mov word ptr es:[4*9+2],ax;安装do9
		
		mov ax,0b800h
		mov es,ax
		mov ah,'a'
s:		mov es:[2000],ah
		call sleep
		inc ah
		cmp ah,'z'
		jna s
		
		mov ax,0
		mov es,ax
		mov ax,ds:[0]
		mov es:[4*9],ax
		mov ax,ds:[2]
		mov es:[4*9+2],ax
		
		mov ax,4c00h
		int 21h
		
sleep:	push ax
		push bx
		mov ax,5h
		mov bx,0
s1:		sub bx,1
		sbb ax,0;巧妙的用sbb ax,0运算让每当 sub bx,1运行65536次 ax才减1
		cmp bx,0
		jne s1
		cmp ax,0
		jne s1
		pop bx
		pop ax
		ret
		
do9:	push ax
		push bx
		push es
		in al,60h
		
		pushf
		pushf
		pop bx
		and bh,11111100b
		push bx
		popf
		call dword ptr ds:[0];模拟int 调用原来的中断
		
		cmp al,1
		jne do9ret
		
		mov ax,0b800h
		mov es,ax
		inc byte ptr es:[2001];修改颜色,每次按下+1
do9ret:	pop es
		pop bx
		pop ax
		iret
code ends
end start

QQ截图20200608160155

​ 上述代码运行正确,上述代码没有把do9 放到 0:200h这里

QQ截图20200608161049

​ 由于我们在自己的中断处理程序调用原来的中断处理程序,在进入我们的中断时,已经设置了TF IF=0所以 不用在调用原中断时又设置,所以简化为 pushf 和 call dword ptr ds:[0]

QQ截图20200608161319

​ 我们是依次修改9号中段的中断向量的偏移地址和段地址,如果修改完偏移地址时即发生中断,那么将会跑飞(很危险),所以我们在修改中断向量的偏移地址之前应该屏蔽中断 修改完段地址后

再解除屏蔽,也就是 先cli 指令置IF为0 再 sti 指令置IF为1

0x03 安装新的int9中断

QQ截图20200608162414

assume cs:code
code segment
start:		mov ax,cs
			mov ds,ax
			mov si,offset do9
			mov ax,0
			mov es,ax
			mov di,204h;准备把200-203h四个字节用来储存原来的int9中断向量204用来保存当前颜色
			mov cx,offset do9e-offset do9
			cld
			rep movsb
			
			mov ax,es:[9*4]
			mov es:[200h],ax
			mov ax,es:[9*4+2]
			mov es:[202h],ax;把原来的中断向量写到0:200
			
			
			cli;屏蔽外设中断;
			mov word ptr es:[9*4],204h
			mov word ptr es:[9*4+2],0
			sti;安装完成
	
			mov ax,4c00h
			int 21h


do9:		push ax
			push es
			push bx
			push cx
			mov ax,0
			mov es,ax
			in al,60h
			pushf
			call dword ptr es:[200h]
			cmp al,3bh ;f1
			jne fin;不等于就跳到结束
			mov ax,0b800h
			mov es,ax
			mov bx,1
			mov cx,2000;一页2000字符80 25
s:			inc byte ptr es:[bx]
			add bx,2
			loop s
fin:		pop cx
			pop bx
			pop es
			pop ax
			iret
do9e:		nop
code ends
end start

​ 上面是我的代码运行就挂了,下面是书上给的参考代码,晚上再分析(还是头一次写错了代码)

//2020-6-8晚修正,上述代码已经修改正确 ,运行无误 ,错误之处在于,1串传送指令rep movsv 前没有设置flag DF位(应该用cld设置位0 di si同递增)2do9中调用原中断处理程序的200h写成了200 3中断的返回应该用iret 返回到之前的现场而不是 mov ax,4c00h int 21 程序返回

assume cs:code
stack segment
	db 128 dup (0)
stack ends
code segment
start:	mov ax,stack
	mov ss,ax
	mov sp,128

	push cs
	pop ds
	mov ax,0
	mov es,ax

	mov si,offset int9
	mov di,204h
	mov cx,offset int9end-offset int9
	cld
	rep movsb

	push es:[9*4]
	pop es:[200h]
	push es:[9*4+2]
	pop es:[202h]
	cli
	mov word ptr es:[9*4],204h
	mov word ptr es:[9*4+2],0
	sti
	mov ax,4c00h
	int 21h
int9:	push ax
	push bx
	push cx
	push es
	in al,60h
	pushf
	call dword ptr cs:[200h]
	cmp al,3bh
	jne int9ret
	mov ax,0b800h
	mov es,ax
	mov bx,1
	mov cx,2000
s:	inc byte ptr es:[bx]
	add bx,2
	loop s
int9ret:	pop es
	pop cx
	pop bx
	pop ax
	iret
int9end:	nop
code ends
end start

QQ截图20200608173318