0%

《汇编语言》(第四版) 实验 16

《汇编语言(第四版)》 . 王爽著 . 清华大学出版社 . 2019

实验 16 编写包含多个功能子程序的中断例程

一个示例程序 - 理解数据标号

将 code 段中的 a 标号处的 8 个数据累加,结果存储到 b 标号处的字中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
assume cs:code
code segment
a: db 1,2,3,4,5,6,7,8
b: dw 0

start:mov si,offset a
mov bx,offset b
mov cx,8
s:mov al,cs:[si]
mov ah,0
add cs:[bx],ax
inc si
loop s

mov ax,4c00h
int 21h

code ends
end start

在上面的程序中,code、a、b、start、s 都是地址标号,仅仅表示了内存单元的地址,而数据标号(后面不带 : 的)还能够描述单元长度,在指令中可以代表一个段中的内存单元

使用数据标号后,将程序修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
assume cs:code
code segment
a db 1,2,3,4,5,6,7,8
b dw 0

start:mov si,0
mov cx,8
s:mov al,a[si] ;mov al,cs:0[si]
mov ah,0
add b,ax ;add cs:[8],ax
inc si
loop s

mov ax,4c00h
int 21h

code ends
end start

标号 a 描述了地址 code:0,和从这个地址开始,以后的内存单元都是字节单元,而标号 b 描述了地址 code:8,和从这个地址开始,以后的内存单元都是字单元

用 Debug 跟踪程序运行


对于程序中的 “b dw 0”,下面一些指令等价:

1
2
3
4
5
6
7
8
mov ax,b
mov ax,cs:[8]

mov b,2
mov word ptr cs:[8],2

inc b
inc word ptr cs:[8]

对于程序中的 “a db 1,2,3,4,5,6,7,8”,下面一些指令等价:

1
2
3
4
5
6
7
8
mov al,a[si]
mov al,cs:0[si]

mov al,a[3]
mov al,cs:0[3]

mov al,a[bx+si+3]
mov al,cs:0[bx+si+3]

检测点 16.1

将 code 段中 a 处的 8 个数据累加,结果存储到 b 处的双字中,补全程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
add word ptr b[2],0
add si,2
loop s
mov ax,4c00h
int 21h
code ends
end start
用 Debug 跟踪程序运行

如果考虑进位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:code
code segment
a dw 30000,2768,30000,2768,0,0,0,0
b dd 0
start:mov si,0
mov cx,8
s:mov ax,a[si]
add word ptr b[0],ax
adc word ptr b[2],0
add si,2
loop s
mov ax,4c00h
int 21h
code ends
end start
用 Debug 跟踪程序运行

在其他段中使用数据标号

一般不在代码段中定义数据,而将数据定义到其他段中,在其他段中我们也可以用数据标号来描述存储数据的单元的地址和长度,而地址标号只能在代码段中使用

下面的程序将 data 段中的 a 标号处的 8 个数据累加,结果存储到 b 标号处的字中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
mov ds,ax
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
用 Debug 跟踪程序运行


若想在代码段中直接使用数据标号访问数据,则需要用伪指令 assume 将标号所在的段和一个段寄存器联系起来,并且在程序中还要用指令对段寄存器进行设置

检测点 16.2

mov ax,data
mov es,ax

对比此程序与上一个程序
在用 assume 指令将段寄存器和某个段相联系后,在程序中还要使用指令对段寄存器进行设置

用 Debug 跟踪程序运行

讨论用查表的方法编写相关程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
;用 al 传送要显示的数据

showbyte: jmp short show

table db '0123456789ABCDEF' ;字符表

show: push bx
push es

mov ah,al
shr ah,1 ;mov cl,4
shr ah,1 ;shr ah,cl
shr ah,1
shr ah,1 ;右移 4 位,ah 中得到高 4 位的值
and al,00001111b ;al 中为低 4 位的值

mov bl,ah
mov bh,0
mov ah,table[bx] ;用高 4 位的值作为相对于 table 的偏移,取得对应的字符

mov bx,0b800h
mov es,bx
mov es:[160*12+40*2],ah

mov bl,al
mov bh,0
mov al,table[bx] ;用低 4 位的值作为相对于 table 的偏移,取得对应的字符

mov es:[160*12+40*2+2],al

pop es
pop bx

ret

依据数据直接计算出所要找的元素的位置(直接定址表)

可以占用一些内存空间来换取运算的速度,将所要计算的 sin(x) 的结果都存储到一张表中,然后用角度值来查表,找到对应的 sin(x) 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
;用 ax 向子程序传递角度 
showsin: jmp short show

table dw ag0,ag30,ag60,ag90,ag120,ag150,ag180 ;字符串偏移地址表
ag0 db '0',0 ;sin(0)
ag30 db '0.5',0 ;sin(30)
ag60 db '0.866',0 ;sin(60)
ag90 db '1',0 ;sin(90)
ag120 db '0.866',0 ;sin(120)
ag150 db '0.5',0 ;sin(150)
ag180 db '0',0 ;sin(180)

show: push bx
push es
push si
mov bx,0b800h
mov es,bx

;以下用角度值/30*2 作为于 table 的偏移,取得对应的字符串的偏移地址,放在 bx 中
mov ah,0
mov bl,30
div bl
mov bl,al
mov bh,0
add bx,bx
mov bx,table[bx]

;以下显示 sin(x) 对应的字符串
mov si,160*12+40*2
shows:mov ah,cs:[bx]
cmp ah,0
je showret
mov es:[si],ah
inc bx
add si,2
jmp short shows
showret:
pop si
pop es
pop bx
ret

利用表,在两个数据集合之间建立一种映射关系,使我们能用查表的方法根据给出的数据得到其在另一集合中对应数据,这样做的目的一般来说有以下三个
1)为了算法的清晰与简洁
2)为了加快运算速度
3)为了使程序易于扩充

像这种可以通过依据数据,直接计算出所要找的元素的位置的表,我们称其为直接定址表

程序入口地址的直接定址表

在直接定址表中存储子程序的地址可以方便地实现不同子程序的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
;清屏
sub1: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000
sub1s: mov byte ptr es:[bx],' ' ;将字符 ASCII 码全设置为空格,达到清屏的效果
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret

;设置前景色
sub2: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub2s: and byte ptr es:[bx],11111000b
or es:[bx],al ;al 传递颜色值
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret

;设置背景色
sub3: push bx
push cx
push es
mov cl,4 ;设置背景色,移位以方便运算
shl al,cl
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub3s: and byte ptr es:[bx],10001111b
or es:[bx],al ;al 传递颜色值
add bx,2
loop sub3s
pop es
pop cx
pop bx
ret

;向上滚动一行
sub4: push cx
push si
push di
push es
push ds
mov si,0b800h
mov es,si
mov ds,si
mov si,160
mov di,0
cld
mov cx,24
sub4s: push cx
mov cx,160 ;一行 160 个字节
rep movsb ;下面一行整行向上覆盖掉上面一行
pop cx
loop sub4s ;注意 si 与 di 一直递增
mov cx,80
mov si,0
sub4s1: mov byte ptr [160*24+si],' ' ;处理最后一行
add si,2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret

将这些功能子程序的入口地址存储在一个表中,它们在表中的位置和功能号相对应。
对应关系为:功能号*2=对应的功能子程序在地址表中的偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
setscreen:  jmp short set

table dw sub1,sub2,sub3,sub4

set: push bx

cmp ah,3 ;判断功能号是否大于 3
ja sret
mov bl,ah
mov bh,0
add bx,bx ;根据 ah 中的功能号计算对应子程序在 table 表中的偏移

call word ptr table[bx]

sret: pop bx
ret

用根据功能号查找地址表的方法,程序的结构清晰,便于扩充。如果加入一个新的功能子程序,那么只需要在地址表中加入它的入口地址就可以了。

实验任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
assume cs:code

code segment

start: mov ax,cs
mov ds,ax
mov si,offset int7c ;设置 ds:si 指向源地址
mov ax,0
mov es,ax
mov di,0200h ;设置 es:di 指向目的地址

mov cx,offset int7cend-offset int7c ;设置 cx 为传输长度

cld ;设置传输方向为正
rep movsb ;串传送操作

mov ax,0
mov es,ax
cli
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0 ;设置中断向量表
sti

mov ax,4c00h
int 21h

int7c: jmp short set

dw offset sub1 - offset int7c + 200h
dw offset sub2 - offset int7c + 200h
dw offset sub3 - offset int7c + 200h
dw offset sub4 - offset int7c + 200h
;int7c 在0:200处,通过上式的计算,可以得到子程序sub(i)入口的地址
;这里不能直接用 sub(i) 的标号找到 sub(i) 的入口地址
;因为上面的程序会将位于 cs:offset int7c 的新中断处理程序复制到 0:200 处

set: push bx

cmp ah,3
ja sret
mov bl,ah
mov bh,0
add bx,bx

call word ptr cs:[bx+202h] ;2 是 jmp short set 占的空间
;这里不能用之前的 table[bx]
;请深刻理解
;牢牢记住程序的本质
sret: pop bx
iret

;子程序 1:清屏
sub1: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000
sub1s: mov byte ptr es:[bx],' '
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret

;子程序 2:设置前景色
sub2: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub2s: and byte ptr es:[bx],11111000b
or es:[bx],al
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret

;子程序 3:设置背景色
sub3: push bx
push cx
push es
mov cl,4
shl al,cl
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub3s: and byte ptr es:[bx],10001111b
or es:[bx],al
add bx,2
loop sub3s
pop es
pop cx
pop bx
ret

;子程序 4:向上滚动一行
sub4: push cx
push si
push di
push es
push ds
mov si,0b800h
mov es,si
mov ds,si
mov si,160
mov di,0
cld
mov cx,24
sub4s: push cx
mov cx,160
rep movsb
pop cx
loop sub4s
mov cx,80
mov si,0
sub4s1: mov byte ptr [160*24+si],' '
add si,2
loop sub4s1
pop ds
pop es
pop di
pop si
pop cx
ret

int7cend: nop

code ends

end start

测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
assume cs:code
code segment
start: mov ah,1 ;功能号 1,设置前景色
mov al,1 ;前景色设置为蓝色
int 7ch
call delay

mov ah,2 ;功能号 2,设置背景色
mov al,7 ;背景色设置为白色
int 7ch
call delay

mov ah,3 ;功能号 3,向上滚动一行
int 7ch
call delay

mov ah,0 ;功能号 0,清空屏幕
int 7ch
call delay

mov ax,4c00h
int 21h

delay: push ax ;延时函数
push dx
mov dx,100h
mov ax,0 ;循环 100 0000 h 次
s1: sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0
jne s1
pop dx
pop ax
ret
code ends
end start
测试程序运行

前景色设置为蓝色


背景色设置为白色


向上滚动一行


清空屏幕


汇编语言实验合集

汇编语言实验合集

实验 1 查看 CPU 和内存,用机器指令和汇编指令编程
实验 2 用机器指令和汇编指令编程
实验 3 编程、编译、连接、跟踪
实验 4 [bx] 和 loop 的使用
实验 5 编写、调试具有多个段的程序
实验 6 实践课程中的程序
实验 7 寻址方式在结构化数据访问中的应用
实验 8 分析一个奇怪的程序
实验 9 根据材料编程
实验 10 编写子程序
课程设计 1
实验 11 编写子程序
实验 12 编写 0 号中断的处理程序
实验 13 编写、应用中断例程
实验 14 访问 CMOS RAM
实验 15 安装新的 int9 中断例程
实验 16 编写包含多个功能子程序的中断例程
实验 17 编写包含多个功能子程序的中断例程
课程设计 2