实验 16 编写包含多个功能子程序的中断例程
安装一个新的 int 7ch 中断例程,为显示输出提供如下功能子程序。
(1) 清屏。
(2) 设置前景色。
(3) 设置背景色。
(4) 向上滚动一行。
入口参数说明如下:
(1) 用 AH 寄存器传递功能号:0 表示清屏,1 表示设置前景色,2 表示设置背景色,3 表示向上滚动一行。
(2) 对于 1、2 号功能,用 AL 寄存器传送颜色值,(al)∈{0,1,2,3,4,5,6,7}。
具体代码:
注意,以下最前面的两种错误代码,仅编写功能号 1 的子程序以作测试。
1. 错误代码:
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset int7ch
mov ax,0
mov es,ax
mov di,200h ; 将 int 7ch 指令所引发中断的中断例程安装到 0:200 处
mov cx,offset int7chend-offset int7ch
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
int7ch: call setscreen
iret
setscreen:jmp short set
; 因为地址(无论是段地址还是偏移地址)占用 2 个字节,所以此处必须用 dw 来声明
table: dw sub1 ; error
set: push bx
cmp ah,3 ; 判断功能号是否大于 3
ja sret
mov bl,ah
mov bh,0
add bx,bx ; 根据 AH 寄存器存储的功能号,计算对应子程序在 table 表中的偏移
mov si,table
call word ptr cs:[bx+si] ; error:调用对应的功能子程序
sret: pop bx
ret
sub1: push bx
push cx
push es
mov bx,0B800h
mov es,bx
mov bx,1
mov cx,2000
sub1s: and byte ptr es:[bx],11111000B ; 保留屏幕原来的闪烁、背景色、高亮,清空前景色(0)
or es:[bx],al ; 1. 将前景色设置得与 AL 寄存器存储的颜色相同 ; 2. 保留屏幕原来的闪烁、背景色、高亮属性信息。
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret
int7chend: nop
code ends
end start
2. 错误代码:
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset int7ch
mov ax,0
mov es,ax
mov di,200h ; 将 int 7ch 引发中断的中断例程安装到 0:200 处
mov cx,offset int7chend-offset int7ch
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
int7ch: call setscreen
iret
setscreen: jmp short set
; 因为地址(无论是段地址还是偏移地址)占用 2 个字节,所以此处必须用 dw 来声明
table dw sub1 ; error
set: push bx
cmp ah,3 ; 判断功能号是否大于 3
ja sret
mov bl,ah
mov bh,0
add bx,bx ; 根据 AH 寄存器中存储的功能号,计算对应子程序在 table 表中的偏移
call word ptr table[bx] ; error:调用对应的功能子程序
sret: pop bx
ret
sub1: push bx
push cx
push es
mov bx,0B800h
mov es,bx
mov bx,1
mov cx,2000
sub1s: and byte ptr es:[bx],11111000B ; 保留屏幕原来的闪烁、背景色、高亮,清空前景色(0)
or es:[bx],al ; 1. 将前景色设置得与 AL 寄存器存储的颜色相同 ; 2. 保留屏幕原来的闪烁、背景色、高亮属性信息。
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret
int7chend: nop
code ends
end start
对上述两种错误代码的特别说明:
(1) 用 db、dw、dd 等伪指令声明的数据,都会在程序编译阶段被以 idata(立即数,即常数)形式替换,所以以下代码中的 sub1、sub2、sub3、sub4 等 4 个标号在编译完成后,将被替换成编译时这些标号的偏移地址。
(2) 无论数据标号还是非数据标号,当作为标号使用时,在 mov 传送指令中都会在编译阶段被以 idata 形式替换,但在 call、jmp(注意 jcxz 及 je、jne、jb、jnb、ja、jna 等指令实际也是另一种形式的 jmp 指令)等转移指令中,由于这些指令本身是用转移指令的下一条指令与目标指令之间的位移来计算出跳转所至目标位置,并且计算的执行也是在编译阶段,因此标号能够被正确地指向目标位置。
基于上述两点,下面的代码将发生错误:
table dw sub1,sub2,sub3,sub4
...
mov si,table ; table 是数据标号
mov bx,0
mov si,table ; 如果 table 为非数据标号,则:mov si,offset table
call word ptr cs:[bx+si] ; 或:call word ptr table[bx]
...
sub1: ...
...
sub2: ...
...
sub3: ...
...
sub4: ...
...
table 标号无法指向正确的位置,cs:[bx+si] 也不再是 table 定址表内元素所在的内存单元。并且“table dw sub1,sub2,sub3,sub4”代码中,sub1 等 4 个元素会在安装编译后被安装程序中该标号的偏移地址值所替换,这导致在中断例程被执行时发生错误 —— table 定址表中的 sub1、sub2 等已经无法指向正确的位置。
另外,其后的“call word ptr table[bx]”指令中的 table 并不是被用作标号,而是表示一个内存单元 —— 这时,在安装程序的编译阶段,此处的 table 也会被以 idata 形式替换,这同样导致该 call 指令在中断例程被执行时,无法跳至正确的位置。
3. 正确代码:
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset int7ch
mov ax,0
mov es,ax
mov di,200h ; 将 int 7ch 引发中断的中断例程安装到 0:200 处
mov cx,offset int7chend-offset int7ch
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
int7ch: call setscreen
iret
setscreen: jmp short set
; 因为地址(无论是段地址还是偏移地址)占用 2 个字节,所以此处必须用 dw 来声明
; 此处 200h 是 int 7ch 中断例程被安装到的偏移地址
table dw sub1-int7ch+200h,sub2-int7ch+200h,sub3-int7ch+200h,sub4-int7ch+200h
set: push bx
cmp ah,3 ; 判断功能号是否大于 3
ja sret
mov bl,ah
mov bh,0
add bx,bx ; 根据 AH 寄存器存储的功能号计算对应子程序在 table 表中的偏移
mov si,0
mov es,si
mov si,es:[7Ch*4] ; 将 int 7Ch 中断例程的偏移地址存入 SI 寄存器
add si,table-int7ch ; 将 table 标号的偏移量加入到 SI 寄存器中
; 注:上述从“mov si,0”到“add si,table-int7ch”共 4 条指令,可以用一条指令取代:
; add bx,200h+table-int7ch
; 取代后,下面“call word ptr cs:[bx+si]”应改为“call word ptr cs:[bx]”
call word ptr cs:[bx+si] ; 调用对应的功能子程序
sret: pop bx
ret
sub1: push bx
push cx
push es
mov bx, 0B800h
mov es,bx
mov bx,0
mov cx,2000
sub1s: mov byte ptr es:[bx],' '
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret
sub2: push bx
push cx
push es
mov bx,0B800h
mov es,bx
mov bx,1
mov cx,2000
sub2s: and byte ptr es:[bx],11111000B ; 保留屏幕原来的闪烁、背景色、高亮,清空前景色(0)
or es:[bx],al ; 1. 将前景色设置得与 AL 寄存器存储的颜色相同 ; 2. 保留屏幕原来的闪烁、背景色、高亮属性信息。
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
sub3: push bx
push cx
push es
mov cl,4
shl al,cl ; 将 AL 寄存器存储的颜色信息移动到前景色属性位置,清空其他位为 0
mov bx,0B800h
mov es,bx
mov bx,1
mov cx,2000
sub3s: and byte ptr es:[bx],10001111B ; 保留屏幕除前景色外的其他属性,前景色则被清空为 0
or es:[bx],al ; 将 AL 寄存器前景色位置的颜色属性赋给屏幕显存,屏幕其他属性则保留
add bx,2
loop sub3s
pop es
pop cx
pop bx
ret
sub4: push cx
push si
push di
push es
push ds
mov si,0B800h
mov es,si
mov ds,si
mov si,160 ; ds:si 指向第 n+1 行。注意,一行 80 个字符,一个字符占用 2 字节
mov di,0 ; es:di 指向第 n 行
cld
mov cx,24 ; 共复制 24 行
sub4s: push cx
mov cx,160
rep movsb ; 复制 1 行:执行“mov es:[di],ds:[si]”和 (cx)=(cx)-1,直到 cx=0 为止
pop cx
loop sub4s ; 共复制 24 行
mov cx,80
mov si,0
sub4s1: mov byte ptr [160*24+si],' ' ; 最后一行清空
add si,2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret
int7chend: nop
code ends
end start
4. 正确代码:
说明:将 int 7ch 中断的中断例程位置由位于安装程序代码的后面移到最前面,就可以避免在 table 定址表中添加各子程序标号与中断例程起始位置的偏移地址进行减法运算的代码,而只需将表中子程序标号加上中断例程本身偏移地址 200h 即可 —— 注意,(bx) 也要加上 200h 这个偏移地址。
assume cs:code
code segment
int7ch: call setscreen
iret
setscreen:jmp short set
; 因为地址(无论是段地址还是偏移地址)占用 2 个字节,所以此处必须用 dw 来声明
table dw sub1+200h,sub2+200h,sub3+200h,sub4+200h
set: push bx
cmp ah,3 ; 判断功能号是否大于 3
ja sret
mov bl,ah
mov bh,0
add bx,bx ; 根据 AH 寄存器存储的功能号计算对应子程序在 table 表中的偏移
add bx,200h
call word ptr table[bx] ; 调用对应的功能子程序
sret: pop bx
ret
sub1: push bx
push cx
push es
mov bx, 0B800h
mov es,bx
mov bx,0
mov cx,2000
sub1s: mov byte ptr es:[bx],' '
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret
sub2: push bx
push cx
push es
mov bx,0B800h
mov es,bx
mov bx,1
mov cx,2000
sub2s: and byte ptr es:[bx],11111000B ; 保留屏幕原来的闪烁、背景色、高亮,清空前景色(0)
or es:[bx],al ; 1. 将前景色设置得与 AL 寄存器存储的颜色相同 ; 2. 保留屏幕原来的闪烁、背景色、高亮属性信息。
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
sub3: push bx
push cx
push es
mov cl,4
shl al,cl ; 将 AL 寄存器存储的颜色信息移动到前景色属性位置,清空其他位为 0
mov bx,0B800h
mov es,bx
mov bx,1
mov cx,2000
sub3s: and byte ptr es:[bx],10001111B ; 保留屏幕除前景色外的其他属性,前景色则被清空为 0
or es:[bx],al ; 将 AL 寄存器前景色位置的颜色属性赋给屏幕显存, ; 屏幕其他属性则保留
add bx,2
loop sub3s
pop es
pop cx
pop bx
ret
sub4: push cx
push si
push di
push es
push ds
mov si,0B800h
mov es,si
mov ds,si
mov si,160 ; ds:si 指向第 n+1 行。注意,一行 80 个字符,一个字符占用 2 字节
mov di,0 ; es:di 指向第 n 行
cld
mov cx,24 ; 共复制 24 行
sub4s: push cx
mov cx,160
rep movsb ; 复制 1 行:执行“mov es:[di],ds:[si]”和 (cx)=(cx)-1,直到 cx=0 为止
pop cx
loop sub4s ; 共复制 24 行
mov cx,80
mov si,0
sub4s1: mov byte ptr [160*24+si],' ' ; 最后一行清空
add si,2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret
int7chend: nop
start: mov ax,cs
mov ds,ax
mov si,offset int7ch
mov ax,0
mov es,ax
mov di,200h ; 将 int 7ch 引发中断的中断例程安装到 0:200 处
mov cx,offset int7chend-offset int7ch
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
code ends
end start
5. 正确代码:
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset int7ch
mov ax,0
mov es,ax
mov di,200h ; 将 int 7ch 中断的中断例程安装到 0:200 处
mov cx,offset int7chend-offset int7ch
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
int7ch: call setscreen
iret
setscreen: jmp short set
; 因为地址(无论是段地址还是偏移地址)占用 2 个字节,所以此处必须用 dw 来声明
; 此处 200h 是 int 7ch 中断例程被安装到的偏移地址
table dw sub1-int7ch+200h,sub2-int7ch+200h,sub3-int7ch+200h,sub4-int7ch+200h
set: push bx
cmp ah,3 ; 判断功能号是否大于 3
ja sret
mov bl,ah
mov bh,0
add bx,bx ; 根据 AH 寄存器存储的功能号计算对应子程序在 table 表中的偏移
add bx,200h
mov dx,offset int7ch
sub bx,dx
call word ptr table[bx] ; 调用对应的功能子程序
sret: pop bx
ret
sub1: push bx
push cx
push es
mov bx, 0B800h
mov es,bx
mov bx,0
mov cx,2000
sub1s: mov byte ptr es:[bx],' '
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret
sub2: push bx
push cx
push es
mov bx,0B800h
mov es,bx
mov bx,1
mov cx,2000
sub2s: and byte ptr es:[bx],11111000B ; 保留屏幕原来的闪烁、背景色、高亮,清空前景色(0)
or es:[bx],al ; 1. 将前景色设置得与 AL 寄存器存储的颜色相同 ; 2. 保留屏幕原来的闪烁、背景色、高亮属性信息。
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
sub3: push bx
push cx
push es
mov cl,4
shl al,cl ; 将 AL 寄存器存储的颜色信息移动到前景色属性位置,清空其他位为 0
mov bx,0B800h
mov es,bx
mov bx,1
mov cx,2000
sub3s: and byte ptr es:[bx],10001111B ; 保留屏幕除前景色外的其他属性,前景色则被清空为 0
or es:[bx],al ; 将 AL 寄存器前景色位置的颜色属性赋给屏幕显存, ; 屏幕其他属性则保留
add bx,2
loop sub3s
pop es
pop cx
pop bx
ret
sub4: push cx
push si
push di
push es
push ds
mov si,0B800h
mov es,si
mov ds,si
mov si,160 ; ds:si 指向第 n+1 行。注意,一行 80 个字符,一个字符占用 2 字节
mov di,0 ; es:di 指向第 n 行
cld
mov cx,24 ; 共复制 24 行
sub4s: push cx
mov cx,160
rep movsb ; 复制 1 行:执行“mov es:[di],ds:[si]”和 (cx)=(cx)-1,直到 cx=0 为止
pop cx
loop sub4s ; 共复制 24 行
mov cx,80
mov si,0
sub4s1: mov byte ptr [160*24+si],' ' ; 最后一行清空
add si,2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret
int7chend: nop
code ends
end start
6. 正确代码:
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset int7ch
mov ax,0
mov es,ax
mov di,200h ; 将 int 7ch 所引发中断的中断例程安装到 0:200 处
mov cx,offset int7chend-offset int7ch
cld
rep movsb
mov ax,0
mov es,ax
mov word ptr es:[7Ch*4],0h ; 中断例程偏移地址
mov word ptr es:[7Ch*4+2],20h ; 中断例程段地址
mov ax,4C00h
int 21h
; 将中断例程偏移地址和段地址互换之后,中断例程程序代码内部的 (bx) 和 table 定址表中各子程序地址,
; 就都不用再加上中断例程的偏移地址后才能正确运行了。
int7ch: call setscreen
iret
setscreen:jmp short set
; 因为地址(无论是段地址还是偏移地址)占用 2 个字节,所以此处必须用 dw 来声明
table dw sub1-int7ch,sub2-int7ch,sub3-int7ch,sub4-int7ch
set: push bx
cmp ah,3 ; 判断功能号是否大于 3
ja sret
mov bl,ah
mov bh,0
add bx,bx ; 根据 AH 寄存器存储的功能号计算对应子程序在 table 表中的偏移
mov dx,offset int7ch
sub bx,dx
call word ptr table[bx] ; 调用对应的功能子程序
sret: pop bx
ret
sub1: push bx
push cx
push es
mov bx, 0B800h
mov es,bx
mov bx,0
mov cx,2000
sub1s: mov byte ptr es:[bx],' '
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret
sub2: push bx
push cx
push es
mov bx,0B800h
mov es,bx
mov bx,1
mov cx,2000
sub2s: and byte ptr es:[bx],11111000B ; 保留屏幕原来的闪烁、背景色、高亮,清空前景色(0)
or es:[bx],al ; 1. 将前景色设置得与 AL 寄存器存储的颜色相同 ; 2. 保留屏幕原来的闪烁、背景色、高亮属性信息。
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
sub3: push bx
push cx
push es
mov cl,4
shl al,cl ; 将 AL 寄存器存储的颜色信息移动到前景色属性位置,清空其他位为 0
mov bx,0B800h
mov es,bx
mov bx,1
mov cx,2000
sub3s: and byte ptr es:[bx],10001111B ; 保留屏幕除前景色外的其他属性,前景色则被清空为 0
or es:[bx],al ; 将 AL 寄存器前景色位置的颜色属性赋给屏幕显存, ; 屏幕其他属性则保留
add bx,2
loop sub3s
pop es
pop cx
pop bx
ret
sub4: push cx
push si
push di
push es
push ds
mov si,0B800h
mov es,si
mov ds,si
mov si,160 ; ds:si 指向第 n+1 行 注意,一行 80 个字符,一个字符占用 2 字节
mov di,0 ; es:di 指向第 n 行
cld
mov cx,24 ; 共复制 24 行
sub4s: push cx
mov cx,160
rep movsb ; 复制 1 行:执行“mov es:[di],ds:[si]”和 (cx)=(cx)-1,直到 cx=0 为止
pop cx
loop sub4s ; 共复制 24 行
mov cx,80
mov si,0
sub4s1: mov byte ptr [160*24+si],' ' ; 最后一行清空
add si,2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret
int7chend: nop
code ends
end start
7. 测试代码:
; 功能:测试 int 7ch 中断的中断例程子程序 setscreen。
; 说明:先要运行 exp16_d.EXE 程序,以安装 int 7ch 中断的中断例程,然后再执行本程序代码以测试之。
assume cs:code
code segment
start: mov ah,0 ; 传递给 int 7ch 中断例程的参数:功能号 0
mov al,2 ; 传递给 int 7ch 中断例程的参数:显示缓冲区输出字符的属性信息
int 7ch ; 执行 int 7ch 指令以引发中断,调用其中断例程子程序
mov ax,4c00h
int 21h
code ends
end start