「汇编语言 第 3 版 王爽」- 参考答案:实验 16 编写包含多个功能子程序的中断例程

  CREATED BY JENKINSBOT

参考答案

第一步、编写多功能的中断例程

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 指令返回

参考文献

CSDN/汇编语言王爽第三版答案
百度文库/汇编语言实验答案 (王爽)