汇编语言(王爽第三版)实验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】这本书最后一个大实验了。后面那个实验估计费劲了。这个写的不好,欢迎指正!谢谢。

 

posted @ 2017-05-21 09:42  筑基2017  阅读(983)  评论(1编辑  收藏  举报