1. 预备知识
- 以前,我们在代码段中使用标号来标记指令、数据或段的起始地址,这类标号仅表示内存单元的地址。我们还可以使用一种标号,这种标号不但表示内存单元的地址,还包含了内存单元的长度。
a db 1,2,3,4,5,6,7,8
b dw 0
上述标号 a 和 b 后面都没有冒号,它们同时描述内存地址和单元长度。如标号 a 描述地址 code:0 和从这个地址开始以后的内存单元都是字节单元;标号 b 描述地址 code:8 和从这个地址开始以后的内存单元都是字单元。因此,我们可以借由这类标号表示内存单元的地址。
mov ax,b ;相当于 mov ax,cs:[8]
mov b,2 ;相当于 mov word ptr cs:[8],2
inc b ;相当于 inc word ptr cs:[8]
- 一般情况下,我们不在代码段定义数据,而是将数据定义到其他段。如果想在代码段中直接使用标号访问数据,则需使用伪指令 assume 将标号所在段与一个段寄存器相关联。
assume cs:code,ds:data
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends
start:
mov ax,data
mov ds,ax ;完成关联
...
add b,ax ;在代码段中直接使用标号访问数据
...
mov al,a[si] ;在代码段中直接使用标号访问数据
...
- 用查表的方法可简化程序的编写。
2. 实验任务 1:显示数据
以十六进制的形式在屏幕中间显示给定的字节型数据。
2.1 实验分析
1 个字节需用两个十六进制数(0~F)表示。如要在屏幕显示 2Bh,则需将 2 映射为 ASCII 码值 32h(2 + 30h),将 B 映射为 ASCII 码 42h(11 + 37h),二者的映射方式不同。
为了简化程序,我们应在数字 0~15 和 0~F 之间建立新的映射关系。具体做法是建立一张表,表中依次存储 0~F,然后通过数字 0~15 直接查找对应的字符。
table db '0123456789ABCDE'
子程序如下:
;al传入待显示数据
showbyte:
jmp short show
table db '0123456789ABCDE'
show:
push bx
push es ;保护现场
mov ah,al
mov cl,4
shr ah,cl ;右移4位得到高4位的值
and al,00001111b ;按位与操作得到低4位的值
mov bl,ah
mov bh,0
mov ah,table[bx] ;高4位值作为偏移取得对应字符
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah
;显示高位字符
mov bl,al
mov bh,0
mov al,table[bx] ;低4位值作为偏移取得对应字符
mov es:[160*12+40*2+2],al
;显示低位字符
pop es
pop bx ;恢复现场
ret
整体代码为:
assume cs:code
code segment
start:
mov al,2Bh
call showbyte
mov ax,4c00h
int 21h
showbyte:
jmp short show
table db '0123456789ABCDE'
show:
push bx
push es ;保护现场
mov ah,al
mov cl,4
shr ah,cl ;右移4位得到高4位的值
and al,00001111b ;按位与操作得到低4位的值
mov bl,ah
mov bh,0
mov ah,table[bx] ;高4位值作为偏移取得对应字符
mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah
;显示高位字符
mov bl,al
mov bh,0
mov al,table[bx] ;低4位值作为偏移取得对应字符
mov es:[160*12+40*2+2],al
;显示低位字符
pop es
pop bx ;恢复现场
ret
code ends
end start
2.2 实验结果
3. 实验任务 2:编写包含多个功能的子程序
编写一个子程序,并提供如下显示功能:
用 ah 传递功能号:0 表示清屏,1 表示设置前景色,2 表示设置背景色,3 表示向上滚动一行
对于 2、3 号功能,用 al 传递颜色值,(al)∈{0,1,2,3,4,5,6,7}
3.1 实验分析
3.1.1 清屏
为实现清屏,即将当前屏幕中的全部字符设置为空格:
cls_screen:
push bx
push cx
push es ;保护现场
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000 ;当前屏幕含2000个字符
help:
mov byte ptr es:[bx],' '
;写入空格
add bx,2
loop help
pop es
pop cx
pop bx ;恢复现场
ret
3.1.2 设置前景色
在设置前景色时,颜色值存放在 AL 中。字符属性表如下:
[7] [6 5 4] [3] [2 1 0]
闪烁 背景(RGB) 高亮 前景(RGB)
前景色对应于最后三位,首先实现与运算符清除字符属性区域的后三位(and 11111000b),然后将 AL 的内容存入字符属性区域:
set_fore:
push bx
push cx
push es ;保护现场
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
help_set_fore:
and byte ptr es:[bx],11111000b
;清除后三位的值
or es:[bx],al
;设置前景色
add bx,2
loop help_set_fore
pop es
pop cx
pop bx ;恢复现场
ret
3.1.3 设置背景色
在设置背景色时,颜色值存放在 AL 中,对应于字符属性表的第 2~4 位,和设置前景色类似,首先使用与运算清除相关位,再设置背景色。
set_back:
push bx
push cx
push es ;保护现场
mov cl,4
shl al,cl
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
help_set_back:
and byte ptr es:[bx],10001111b
;清空对应位
shl al,1
shl al,1
shl al,1
shl al,1
;左移4位,将AL的内容移到前4位
or es:[bx],al
;设置背景色
add bx,2
loop help_set_back
pop es
pop cx
pop bx ;恢复现场
ret
3.1.4 向上滚动一行
向上滚动一行,借助 rep movsb 指令每次传送一行数据,即 160 个字节,共传送 24 行:
roll:
push cx
push si
push di
push es
push ds
mov si,0b800h
mov es,si
mov ds,si ;ES和DS均指向显示缓冲区
mov si,160 ;ds:si指向第n+1行
mov di,0 ;es:di指向第n行
cld ;设置传输方向为正
mov cx,24 ;共移动24行
help_roll:
push cx
mov cx,160
rep movsb ;源地址为ds:si,目的地址为es:di,共传送160字节
pop cx
loop help_roll
mov cx,80 ;每列共80个字符
mov si,0
help_help_roll:
mov byte ptr [160*24+si],' '
;最后一行使用空格填充
add si,2
loop help_help_roll
pop ds
pop es
pop di
pop si
pop cx
ret
3.1.5 设置直接定址表
将各子程序的入口地址存储在表中,并且它们在表中的位置与其功能号相对应,满足功能号*2=对应子程序的入口地址。
setscreen:
jmp short set
table:
dw cls_screen,set_fore,set_back,roll
set:
push bx
cmp ah,3 ;判断功能号是否大于3
ja setret
mov bl,ah
mov bh,0
add bx,bx ;根据功能号得到对应子程序在表中的偏移
call word ptr table[bx]
;根据偏移调用对应的子程序
setret:
pop bx
ret
用 AH 传递功能号、对于功能 2 和功能 3 使用 AL 传递颜色属性。整体代码为:
assume cs:code
code segment
start:
mov ah,3
mov al,2h
call setscreen
mov ax,4c00h
int 21h
setscreen:
jmp short set
table:
dw cls_screen,set_fore,set_back,roll
set:
push bx
cmp ah,3 ;判断功能号是否大于3
ja setret
mov bl,ah
mov bh,0
add bx,bx ;根据功能号得到对应子程序在表中的偏移
call word ptr table[bx]
;根据偏移调用对应的子程序
setret:
pop bx
ret
cls_screen:
push bx
push cx
push es ;保护现场
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000 ;当前屏幕含2000个字符
help_cls_screen:
mov byte ptr es:[bx],' '
;写入空格
add bx,2
loop help_cls_screen
pop es
pop cx
pop bx ;恢复现场
ret
set_fore:
push bx
push cx
push es ;保护现场
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
help_set_fore:
and byte ptr es:[bx],11111000b
;清除后三位的值
or es:[bx],al
;设置前景色
add bx,2
loop help_set_fore
pop es
pop cx
pop bx ;恢复现场
ret
set_back:
push bx
push cx
push es ;保护现场
mov cl,4
shl al,cl ;左移4位,将AL的内容移到前4位
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
help_set_back:
and byte ptr es:[bx],10001111b
;清空对应位
or es:[bx],al
;设置背景色
add bx,2
loop help_set_back
pop es
pop cx
pop bx ;恢复现场
ret
roll:
push cx
push si
push di
push es
push ds
mov si,0b800h
mov es,si
mov ds,si ;ES和DS均指向显示缓冲区
mov si,160 ;ds:si指向第n+1行
mov di,0 ;es:di指向第n行
cld ;设置传输方向为正
mov cx,24 ;共移动24行
help_roll:
push cx
mov cx,160
rep movsb ;源地址为ds:si,目的地址为es:di,共传送160字节
pop cx
loop help_roll
mov cx,80 ;每列80个字符
mov si,0
help_help_roll:
mov byte ptr [160*24+si],' '
;最后一行使用空格填充
add si,2
loop help_help_roll
pop ds
pop es
pop di
pop si
pop cx
ret
code ends
end start
3.2 实验结果
0 号功能,清屏:
1 号功能,设置前景色:
2 号功能,设置背景色:
3 号功能,向上滚动一行:
4. 总结
-
不带冒号的标号不仅表示内存单元的地址,还包含了内存单元的长度
-
欲在代码段中直接使用标号访问数据,需使用伪指令 assume 将标号所在段与一个段寄存器相关联
-
在编写包含多个功能的子程序时,可将各子程序的入口地址存在表中,然后通过编号获取地址并完成特定功能
-
参考:汇编语言/王爽著.——北京:清华大学出版社,2003