汇编网首页登录博客注册
unixandlinux的学习博客
博客首页博客互动【做检测题】论坛求助

我的博客

个人首页 |  我的文章 |  我的相册 |  我的好友 |  最新访客 |  文章收藏 |  论坛提问 |  友情链接 |  给我留言  
图片载入中
学习动态
最新评论
最新留言
好友圈
友情链接

[2023-08-24 20:21] 第 16 章 实验 16  编写包含多个功能子程序的中断例程

实验 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
评论次数(0)  |  浏览次数(301)  |  类型(课程实验) |  收藏此文  | 
 
 请输入验证码  (提示:点击验证码输入框,以获取验证码