汇编语言实验 13:直接定址表

311 阅读8分钟

1. 预备知识

  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]
  1. 一般情况下,我们不在代码段定义数据,而是将数据定义到其他段。如果想在代码段中直接使用标号访问数据,则需使用伪指令 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] ;在代码段中直接使用标号访问数据
    ...
  1. 用查表的方法可简化程序的编写。

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. 总结

  1. 不带冒号的标号不仅表示内存单元的地址,还包含了内存单元的长度

  2. 欲在代码段中直接使用标号访问数据,需使用伪指令 assume 将标号所在段与一个段寄存器相关联

  3. 在编写包含多个功能的子程序时,可将各子程序的入口地址存在表中,然后通过编号获取地址并完成特定功能

  4. 参考:汇编语言/王爽著.——北京:清华大学出版社,2003