检测点16.1
下面的程序将code段中a处的8个数据累加,结果存储到b处的双字中,补全程序。
assume cs:code
code segment
a dw 1, 2, 3, 4, 5, 6, 7, 8
b dd 0
start: mov si,0
mov cx,8
s: mov ax,a[si]
add word ptr b[0],ax ;低八位加加数ax
adc word ptr b[2],0 ;高八位+CF进位值
add si,2 ;si索引+2
loop s
mov ax,4c00H
int 21H
code ends
end start
检测点16.2
下面的程序将code段中a处的8个数据累加,结果存储到b处的双字中,补全程序。
assume cs:code, ds:data
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends
code segment
start: mov ax,data ;data段的地址值赋值给ax
mov ds,ax ;初始化ds数据段寄存器值
mov si,0
mov cx,8
s: mov al,a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
- 如果程序段中使用了数据标号,那么就必须在assume中声明段与段寄存器的关联。不然编译器无法得知类似a、b、c这样的标号是什么。
- 无论是否使用数据标号,如果要使用数据段数据,都需要在程序段中初始化ds寄存器
实验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}
实现代码
assume cs:code
code segment
start:
push cs ;int 7ch中断例程的安装程序
pop ds
mov si, offset int7ch_setscreen ;ds:si指向源地址(int7c的机器码)
mov ax, 0000H
mov es, ax ;es:di指向目的地址(0:204H向量表中)
mov di, 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存储到0:200H处
pop es:[200H]
push es:[7cH*4+2] ;将向量表中7ch号cs存储到0:202H处
pop es:[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 clear - int7ch_setscreen + 204H, frontcolor - int7ch_setscreen + 204H, backcolor - int7ch_setscreen + 204H, roll - int7ch_setscreen + 204H
set:
push bx
cmp ah, 3 ;判断功能编号是否大于3,是则退出
ja sret
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
;----
;子程序名称:clear
;功能:清屏,利用屏幕写满空格。
;入口参数:无
;返回值:无
;----
clear:
push bx ;保护寄存值
push cx
push es
mov bx, 0b800h ;es:bx指向显存缓冲区
mov es, bx
mov bx, 0
mov cx, 2000 ;满屏显示共80*25=2000字符
clearloop:
mov byte ptr es:[bx], ' ' ;显存缓冲区写满空格
add bx, 2
loop clearloop
pop es ;恢复寄存器值
pop cx
pop bx
ret
;----
;子程序名称:frontcolor
;功能:改变屏幕显示字符的前景色
;入口参数:al
;返回值:无
;----
frontcolor:
push bx
push cx
push es
mov bx, 0b800h
mov es, bx
mov bx,1
mov cx,2000
f_color:
and byte ptr es:[bx], 11111000b ;改变字符前景色
or es:[bx], al
add bx, 2
loop f_color
pop es
pop cx
pop bx
ret
;----
;子程序名称:backcolor
;功能:改变屏幕显示字符的背景色
;入口参数:al
;返回值:无
;----
backcolor:
push bx
push cx
push es
mov bx, 0b800h
mov es, bx
mov bx,1
mov cx,2000
b_color:and byte ptr es:[bx], 10001111b ;改变字符背景色
or es:[bx], 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
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_line:
push cx
mov cx,160
rep movsb
pop cx
loop copy_line
mov cx,80 ;最后一行清空
mov si,0
clear_lastline:
mov byte ptr es:[160*24+si],' '
add si,2
loop clear_lastline
pop ds
pop es
pop di
pop si
pop cx
ret
int7ch_end: nop
code ends
end start
本次实验的功能性子程序教材已经给出了实现,重点我们应该放在子程序调用程序段:int7ch_setscreen,与装载程序中的指令:org 204H(意为:通知编译器从204H为基础,来计算标号)。
思考一个问题,如果装载程序段中没有org 204H,会发生什么?
会导致call word ptr table[bx]时,无法正确定位到子程序的内存地址。本质原因为我们将一段代码放入了特定内存空间中。
而标号在被编译器转为具体偏移值时采用的基准地址值,并不会考虑这个因素。例如在debug中,如果程序没有写org 204H,那么标号的偏移量就是从0开始推算,而我们需要的是从204H开始推算。(可以利用debug查看call word ptr table[bx]编译后的table[bx]的实际值来验证)
如我本地测试时,call word ptr table[bx]最终被转为了call[bx+0206],因为jmp指令占用了两个字节,故table从206H开始。
回顾以往的装载程序段中,由于我们并没有使用到标号,所以并没有添加org xxx。如果应用到了标号,那么程序实则是有问题的。