汇编语言(王爽第三版)实验16 编写包含多个功能子程序的中断例程
实验16 编写包含多个功能子程序的中断例程
安装一个新的int 7ch中断例程,为显示输出提供如下功能子程序:
(1) 清屏。
(2) 设置前景色。
(3) 设置背景色。
(4) 向上滚动一行。
入口参数说明:
(1) 用 ah 寄存器传递功能号:0 表示清屏,1表示设置前景色,2 表示设置背景色,3 表示向上滚动一行;
(2) 对于2、3号功能,用 al 传送颜色值,(al) ∈{0,1,2,3,4,5,6,7}
程序分析:
【1】这个中断例程怎么实现?是在程序中通过入口参数调用实现功能?还是我们按下屏幕上的键就可以设置?(如果这样就基本达到人机交互的要求了)后者由于int 9例程目前系统下,修改不了,故实现麻烦。
【2】还是按照老套路,安装在0:200H内存开始吧。因为程序也不大。
【3】由于这5个子程序作为整体,同时为int 7ch例程的一部分,所以它们都要安装在0:200H。这涉及到了setscreen中的直接定址表中的各个子程序偏移地址的值,是否需要修改它们?
【4】标号代表的是偏移地址,这些标号都是你所期望的吗?
程序实现:
版本一:安装程序如下
assume cs:code
code segment
start: ;int 7ch中断例程的安装程序
push cs
pop ds
mov si, offset int7ch_setscreen ;将ds:si指向源地址(int7c的机器码)
mov ax, 0000H
mov es, ax
;mov di, 200H
mov di, 204H ;将es:di指向目的地址(0:204H向量表中)
;前2个字用来存储原来的例程的入口地址
mov cx, offset int7ch_end - offset int7ch_setscreen ;设置传输长度
cld ;传输方向为正
rep movsb ;字节传输
mov ax, 0000H
mov es, ax
;将原来的例程的入口地址保存在0:200H处,共2个字单元。
push es:[7cH*4] ;将向量表中7ch号ip压栈
pop es:[200H] ;弹栈到0:200H处
push es:[7cH*4+2] ;将向量表中7ch号cs压栈
pop es:[202H] ;弹栈到0:202H处
;设置中断向量表,使7ch条目中断向量指向0000:204H
cli
mov word ptr es:[7cH*4], 204H
mov word ptr es:[7cH*4+2], 0000H
sti
mov ax, 4c00H
int 21H
;----
;中断程序名称:int7ch_setscreen
;功能:设置屏幕属性(1)清屏。(2) 设置前景色。(3) 设置背景色。(4) 向上滚动一行。
;入口参数:
;(1)ah传递功能号:0表示清屏,1表示设置前景色,2 表示设置背景色,3 表示向上滚动一行;
;对于2、3号功能,用 al 传送颜色值,(al) ∈{0,1,2,3,4,5,6,7}
;返回值:无
;----
int7ch_setscreen:
jmp short set
table dw cls - int7ch_setscreen + 204H,
frontcolor- int7ch_setscreen + 204H ,
backcolor - int7ch_setscreen + 204H,
roll - int7ch_setscreen + 204H
set: ;保护寄存器
push bx
cmp ah,3 ;判断传递的是否大于 3
ja sret ;如果大于3,退出
;根据功能号调用对应的功能子程序
mov bl,ah
mov bh,0
add bx,bx ;根据ah中的功能号计算对应子程序的地址在table表中的偏移
mov si, offset start - offset int7ch_setscreen
call word ptr table[bx+si+204H]
sret:
pop bx
iret
;----
;子程序名称:cls
;功能:清屏,利用屏幕写满空格。
;入口参数:无
;返回值:无
;----
cls: ;保护寄存器变量
push bx
push cx
push es
;es:bx指向显存缓冲区
mov bx, 0b800h
mov es, bx
mov bx, 0
mov cx, 2000 ;满屏显示80*25=2000字符
;显存缓冲区写满空格
clsloop:mov byte ptr es:[bx], ' '
add bx, 2
loop clsloop
;恢复寄存器变量
pop es
pop cx
pop bx
ret
;----
;子程序名称:frontcolor
;功能:改变屏幕显示字符的前景色
;入口参数:al
;返回值:无
;----
frontcolor: ;保护寄存器变量
push bx
push cx
push es
;es:bx指向显存缓冲区
mov bx, 0b800h
mov es, bx
mov bx,1
mov cx,2000
;改变字符前景色
f_color:and byte ptr es:[bx], 11111000b
or es:[bx], al ;利用入口参数al值改变前景色
add bx, 2
loop f_color
;恢复寄存器变量
pop es
pop cx
pop bx
ret
;----
;子程序名称:backcolor
;功能:改变屏幕显示字符的背景色
;入口参数:al
;返回值:无
;----
backcolor: ;保护寄存器变量
push bx
push cx
push es
;es:bx指向显存缓冲区
mov bx, 0b800h
mov es, bx
mov bx,1
mov cx,2000
;改变字符背景色
b_color:and byte ptr es:[bx], 10001111b
or es:[bx], al ;利用入口参数al值改变背景色
add bx, 2
loop b_color
;恢复寄存器变量
pop es
pop cx
pop bx
ret
;----
;子程序名称:roll
;功能:向上滚动一行,依次将n+1行内容复制到n行,最后一行空。
;入口参数:无
;返回值:无
;----
roll: ;寄存器保护
push cx
push si
push di
push es
push ds
;设置es:si(目标地址)和ds:si(源地址)都指向显存缓冲区
mov si,0b800h
mov es, si
mov ds, si
mov si,160 ;ds:si指向第n+1行
mov di,0 ;es:di指向第n行
cld ;设置方向为正
mov cx,24 ;共复制24行
copy_row:;逐行复制
push cx ;注意:保护cx值,下面必须要用到cx
mov cx,160
rep movsb ;按字节复制
pop cx
loop copy_row
;最后一行清空,写入空格
mov cx,80
mov si,0
cls_row:mov byte ptr es:[160*24+si],' '
add si,2
loop cls_row
;恢复寄存器变量
pop ds
pop es
pop di
pop si
pop cx
ret
int7ch_end: nop
code ends
end start
版本二。
assume cs:code
code segment
start: ;int 7ch中断例程的安装程序
push cs
pop ds
mov si, offset int7ch_setscreen ;将ds:si指向源地址(int7c的机器码)
mov ax, 0000H
mov es, ax
;mov di, 200H
mov di, 204H ;将es:di指向目的地址(0:204H向量表中)
;前2个字用来存储原来的例程的入口地址
mov cx, offset int7ch_end - offset int7ch_setscreen ;设置传输长度
cld ;传输方向为正
rep movsb ;字节传输
mov ax, 0000H
mov es, ax
;将原来的例程的入口地址保存在0:200H处,共2个字单元。
push es:[7cH*4] ;将向量表中7ch号ip压栈
pop es:[200H] ;弹栈到0:200H处
push es:[7cH*4+2] ;将向量表中7ch号cs压栈
pop es:[202H] ;弹栈到0:202H处
;设置中断向量表,使7ch条目中断向量指向0000:204H
cli
mov word ptr es:[7cH*4], 204H
mov word ptr es:[7cH*4+2], 0000H
sti
mov ax, 4c00H
int 21H
;通知编译器从204H开始重新计算标号
org 204H
;----
;中断程序名称:int7ch_setscreen
;功能:设置屏幕属性(1)清屏。(2) 设置前景色。(3) 设置背景色。(4) 向上滚动一行。
;入口参数:
;(1)ah传递功能号:0表示清屏,1表示设置前景色,2 表示设置背景色,3 表示向上滚动一行;
;对于2、3号功能,用 al 传送颜色值,(al) ∈{0,1,2,3,4,5,6,7}
;返回值:无
;----
int7ch_setscreen:
jmp short set
table dw cls, frontcolor, backcolor, roll
set: ;保护寄存器
push bx
cmp ah,3 ;判断传递的是否大于 3
ja sret ;如果大于3,退出
;根据功能号调用对应的功能子程序
mov bl,ah
mov bh,0
add bx,bx ;根据ah中的功能号计算对应子程序的地址在table表中的偏移
call word ptr table[bx]
sret:
pop bx
iret
;----
;子程序名称:cls
;功能:清屏,利用屏幕写满空格。
;入口参数:无
;返回值:无
;----
cls: ;保护寄存器变量
push bx
push cx
push es
;es:bx指向显存缓冲区
mov bx, 0b800h
mov es, bx
mov bx, 0
mov cx, 2000 ;满屏显示80*25=2000字符
;显存缓冲区写满空格
clsloop:mov byte ptr es:[bx], ' '
add bx, 2
loop clsloop
;恢复寄存器变量
pop es
pop cx
pop bx
ret
;----
;子程序名称:frontcolor
;功能:改变屏幕显示字符的前景色
;入口参数:al
;返回值:无
;----
frontcolor: ;保护寄存器变量
push bx
push cx
push es
;es:bx指向显存缓冲区
mov bx, 0b800h
mov es, bx
mov bx,1
mov cx,2000
;改变字符前景色
f_color:and byte ptr es:[bx], 11111000b
or es:[bx], al ;利用入口参数al值改变前景色
add bx, 2
loop f_color
;恢复寄存器变量
pop es
pop cx
pop bx
ret
;----
;子程序名称:backcolor
;功能:改变屏幕显示字符的背景色
;入口参数:al
;返回值:无
;----
backcolor: ;保护寄存器变量
push bx
push cx
push es
;es:bx指向显存缓冲区
mov bx, 0b800h
mov es, bx
mov bx,1
mov cx,2000
;改变字符背景色
b_color:and byte ptr es:[bx], 10001111b
or es:[bx], al ;利用入口参数al值改变背景色
add bx, 2
loop b_color
;恢复寄存器变量
pop es
pop cx
pop bx
ret
;----
;子程序名称:roll
;功能:向上滚动一行,依次将n+1行内容复制到n行,最后一行空。
;入口参数:无
;返回值:无
;----
roll: ;寄存器保护
push cx
push si
push di
push es
push ds
;设置es:si(目标地址)和ds:si(源地址)都指向显存缓冲区
mov si,0b800h
mov es, si
mov ds, si
mov si,160 ;ds:si指向第n+1行
mov di,0 ;es:di指向第n行
cld ;设置方向为正
mov cx,24 ;共复制24行
copy_row:;逐行复制
push cx ;注意:保护cx值,下面必须要用到cx
mov cx,160
rep movsb ;按字节复制
pop cx
loop copy_row
;最后一行清空,写入空格
mov cx,80
mov si,0
cls_row:mov byte ptr es:[160*24+si],' '
add si,2
loop cls_row
;恢复寄存器变量
pop ds
pop es
pop di
pop si
pop cx
ret
int7ch_end: nop
code ends
end start
如果不加入红色的代码,为什么会出错?(版本二)程序分析:
【1】table是什么?它是个标号,在编译器层面,它就是一个偏移地址。这个偏移地址怎么来的?是在编译时编译器计算出来的。
在这个例子中,装载到内存0:200H开始处,它应该代表了cs:[206H](我们所期望的),但是此时table由于装载程序编译的原因,table的偏移地址发生了改变。它不代表了cs:[206H]了。
为什么在装载程序中,offset int7ch_setscreen这种标号没有问题?它们在安装程序中只起到了机器码定位的作用。在安装程序中此标号的指令CPU是不执行的。
我们看下,如果没有下面指令,table代表的标号。
;通知编译器从204H开始重新计算标号
org 204H
将上面语句注释掉,在int7ch_setscreen程序中添加一条语句(在push bx后面)
mov bx, offset table ;作用:检测下table的偏移地址是多少?
将装载程序(假定为eee.asm)编译并连接后,执行eee.exe,将int 7ch程序装载到指定地方。测试int例程,测试程序如下(假定是xxx.asm):
assume cs:code
code segment
start: mov al, 2
mov ah, 0 ;调用清屏子程序。
int 7cH
mov ax, 4c00H
int 21H
code ends
end start
将xxx.asm编译并连接后,debug xxx.exe
单步执行它,观察bx值(调用int 7ch),结果如下(在我们的机器上是):
0000:020F BB4300 MOV BX,0043
说明table的偏移地址是:0043H,原因,在装载程序编译时,已经将table翻译成地址0043H的,那么其他的标号呢?一样,都不对了。
执行命令:d 0:200
-d 0:200
0000:0200 04 02 00 00 EB 08 61 00-7C 00 9A 00 B8 00 53 BB ......a.|.....S.
0000:0210 43 00 80 FC 03 77 0B 8A-DC B7 00 03 DB 2E FF 97 C....w..........
蓝色的是保存原来的int 7ch例程的ip和cs;
棕色的是跳转指令;
红色的应该是4个子程序的入口地址(指针),但是它们的值都不对。为什么?同样道理,编译装载程序时,给这些标号都翻译了地址了。
理解call [内存单元] 这个指令,push ip ;跳转到内存单元存储的内容处执行指令。
此时执行call word ptr table[bx]指令,由于offset table=0043H,bx=0H,故此指令转向到cs:[0043H+0H]地址单元存储的入口地址执行相应的指令,肯定不对了。
【2】怎么办?
方法一:如果要将一段代码放入特定的地址里的话,可以使用ORG来重新组织地址编排,就是说,它开始的代码都将放入ORG后写的地址中去,如果这下边有标号,将从ORG上指定的新地址开始按指令长度顺延。
这个方法靠谱:增加org伪指令,告诉编译器,这次编译下面的代码都是按照我所指定的偏移地址开始计算编号,并正确的翻译。
这样,装载程序在装载到0:200H处的时候,标号代表的地址就是按照我们程序预期的翻译正确了。同理,4个子程序及所有标号都正常了。
这样我们基本不用动4个子程序和新的int 7ch例程代码,完美实现了实验要求。
方法二:网上还有将table标号处所有的子程序的指针都修改,在中断例程中调用子程序时也修改了代码。也能实现此代码。
但是我们在子程序中插入个测试语句测试下子程序的标号,我们发现,子程序的标号并没有改变,还是装载程序编译时编译的地址。为什么还能实现程序?重申:jmp短转移和条件转移指令依靠的是相对位移,对于标号不敏感。
这个就有点为了解决问题而解决问题了。
【3】为什么原来的中断安装程序没有发生这个问题呢?一,直接指定入口地址,不存在子程序调用问题;二,其实在原来的程序中也存在这个问题的,标号不是我们所期望的,但是jmp短转移和条件转移指令依靠的是相对位移,对于标号不敏感。忽略了这个问题了。一句话就是缺org指令就完美了。
【4】这个实验,就是考验我们对于table这个标号的理解,还有就是编译过程。重点是table段的偏移地址和对于指向程序入口地址的内存call指令。
【5】使用cli和sti吧,养成了良好的习惯。
【6】其他子程序,按照书中代码编写就行了。要理解table直接定址表的意义。C语言中的指针数组。
【7】这本书最后一个大实验了。后面那个实验估计费劲了。这个写的不好,欢迎指正!谢谢。