# 2020-09-30 #「汇编语言 第 3 版 王爽」- 参考答案:实验 16 编写包含多个功能子程序的中断例程
参考答案
第一步、编写多功能的中断例程
assume cs:codeseg codeseg segment ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 新的中断程序 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; setscreen: jmp short select_function table dw offset clear_screen + 0200H, offset set_fg_color + 0200H, offset set_bg_color + 0200H, offset line_up + 0200H ; 实现子程序调用,用于跳转到不同的子程序 select_function: push bx cmp ah, 3 ; 检查子程序序号是否超过范围 ja _select_function_iret mov bh, 0 mov bl, ah add bx, bx call word ptr table[bx + 0200H] _select_function_iret: pop bx iret select_function_end: ; 实现清屏功能 clear_screen: push ax push ds push si push cx mov ax, 0B800h ; 设置显存地址 mov ds, ax mov si, 0 mov cx, 80 * 25 ; 总计写入次数 = 80 * 2 * 25 / 2 _loop_s0: mov byte ptr ds:[si], ' ' add si, 2 loop _loop_s0 pop cx pop si pop ds pop ax ret clear_screen_end: ; 设置前景色 set_fg_color: push bx push ds push si push cx mov bx, 0B800H mov ds, bx mov si, 1 mov cx, 80 * 2 * 25 / 2 loop_s1: and byte ptr ds:[si], 11111000b ; 抹掉原来前景 or ds:[si], al ; 设置前景 add si, 2 loop loop_s1 pop cx pop si pop ds pop bx ret set_fg_color_end: ; 设置背景色 set_bg_color: push bx push ds push si push cx mov cl, 4 ; al 取值为 0-7,不能直接设置,要移动到指定位置 shl al, cl mov bx, 0B800H mov ds, bx mov si, 1 mov cx, 80 * 2 * 25 / 2 loop_s2: and byte ptr ds:[si], 10001111b ; 抹掉原来背景 or ds:[si], al ; 设置背景 add si, 2 loop loop_s2 pop cx pop si pop ds pop bx ret set_bg_color_end: ; 向上移动一行 line_up: push ax push ds push si push es push di push cx mov ax, 0B800H ; 复制的起始地址 mov ds, ax mov si, 80 * 2 mov ax, 0B800H ; 复制的目的地址 mov es, ax mov di, 0 mov cx, 80 * 2 * 24 ; 复制的长度 cld rep movsb ; 最后一行要清空 mov ax, 0B800H mov ds, ax mov si, 80 * 2 * 24 mov cx, 80 loop_s3: mov byte ptr ds:[si], ' ' add si, 2 loop loop_s3 pop cx pop di pop es pop si pop ds pop ax ret line_up_end: setscreen_end: start: ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 安装中断程序 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov ax, cs ; 复制的起始地址 mov ds, ax mov si, offset setscreen mov ax, 0000H ; 复制的目的地址 mov es, ax mov di, 0200H mov cx, offset setscreen_end - offset setscreen cld rep movsb ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; 设置中断向量表 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov ax, 0000H mov ds, ax mov word ptr ds:[007Ch * 4], 0200h mov word ptr ds:[007Ch * 4 + 2], 0h mov ax, 4C00h int 21h codeseg ends end start
第二步、编写用于测试的程序
我们需要四个程序来测试中断例程的功能:
1)清屏:
assume cs:codeseg codeseg segment start: mov ah, 0 int 7Ch mov ax, 4C00h int 21h codeseg ends end start
2)设置前景色:
assume cs:codeseg codeseg segment start: mov al, 1 mov ah, 1 int 7Ch mov ax, 4C00h int 21h codeseg ends end start
3)设置背景色:
assume cs:codeseg codeseg segment start: mov al, 1 mov ah, 2 int 7Ch mov ax, 4C00h int 21h codeseg ends end start
4)向上滚动一行:
assume cs:codeseg codeseg segment start: mov ah, 3 int 7Ch mov ax, 4C00h int 21h codeseg ends end start
常见问题(FAQ)
我们将新的中断程序安装到从 0:0200 开始的内存段中,如何保证不会超出 03FF 地址?
1)首先,编译以得到二进制文件,这样我们就能得到程序的实际长度;
2)然后,运行 debug exp-16.exe 命令,查看程序长度;
3)得知中断程序的长度为 AFh,< (03FFh - 0200h + 1h),因此没有超过;
对于 table dw offset clear_screen + 0200H, ... 指令,为什么要 + 0200H 偏移?
1)我们使用 call word ptr 调用子程序,将直接设置 IP 寄存器。所以在 table 中应该保存的是内存地址,而不是偏移地址;
2)由于 offset 只能计算偏移地址,并且我们安装到 0200h 处,因此 offset + 0200h 就是用于设置 IP 的实际地址;
3)实际上,既然是偏移,我们还可以使用 JMP 指令,但是要处理好子程序返回(执行结束)的问题,这里就不再展开。
对于 call word ptr table[bx + 0200H] 指令,为什么要 + 0200H 偏移?
在正常情况下,汇编器以偏移地址零为开始来计算偏移。在程序载入内存后,内存的偏移地址也是从零开始。
当我们将程序安装到 0:0200h 地址处时,程序的偏移地址是从 200 开始的,所以相关的偏移量都要就行调整。
常见编码错误
1)子程序中没有使用 RET 指令返回
参考文献
K4NZ / 参考答案:实验 16 编写包含多个功能子程序的中断例程
CSDN/汇编语言王爽第三版答案
百度文库/汇编语言实验答案 (王爽)