0%

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

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

实验 6 实践课程中的程序

实验任务(1)

将课程中所有讲解过的程序上机调试,用 Debug 跟踪其执行过程,并在过程中进一步理解所讲内容。

程序 1

在汇编程序中,用 ‘…’ 的方式指名数据是以字符的形式给出的,编译器将把它们转化为相对应的 ASCII 码。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:code,ds:data

data segment
db 'unIX'
db 'foRK'
data ends

code segment

start: mov al,'a'
mov bl,'b'
mov ax,4c00h
int 21h

code ends

end start
用 Debug 跟踪程序运行


ASCII ((American Standard Code for Information Interchange): 美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准 ISO/IEC 646。ASCII 第一次以规范标准的类型发表是在 1967 年,最后一次更新则是在 1986 年,到目前为止共定义了 128 个字符

ASCII 码使用指定的 7 位或 8 位二进制数组合来表示 128 或 256 种可能的字符。标准 ASCII 码也叫基础 ASCII 码,使用 7 位二进制数(剩下的 1 位二进制为 0)来表示所有的大写和小写字母,数字 0 到 9、标点符号,以及在美式英语中使用的特殊控制字符

常用的 ASCII 码

0x30 ~ 0x39 : 字符 1 ~ 9
0x41 ~ 0x5A : 大写字母 A ~ Z
0x61 ~ 0x7A : 小写字母 a ~ z

程序 2

在 codesg 中填写代码,将 datasg 中的第一个字符串转化为大写,第二个字符串转化为小写。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:codesg,ds:datasg

datasg segment
db 'BaSiC'
db 'iNfOrMaTiOn'
datasg ends

codesg segment

start:
?

codesg ends

end start

通过查 ASCII 码标准表,我们可以很轻易地看出来,小写字母的 ASCII 码值比大写字母的 ASCII 码值大 20H,那么如果我们能够判断当前字母是大写还是小写,就可以按这个规律进行大小写之间的改变

可惜的是,到这个实验为止,我们还没有接触到能够对字母的大小写进行判断的指令,那么我们必须找到新的规律

从 ASCII 码的二进制形式着手,我们发现,大写字母与其小写字母的二进制表示的唯一区别是第 5 位不相同,例如,小写字母 a 为 0110 0001,大写字母 A 为 0100 0001,那么如果有一个字母,将它的第 5 位置 1 即为小写,置 0 即为大写,这样就避免了判断

注意,将某一位置 0 或置 1 可以使用 andor 指令

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
assume cs:codesg,ds:datasg

datasg segment
db 'BaSiC'
db 'iNfOrMaTiOn'
datasg ends

codesg segment

start: mov ax,datasg
mov ds,ax ;设置 ds 指向 datasg 段
mov bx,0 ;ds:bx 指向 'BaSiC' 的第一个字母
mov cx,5 ;设置循环次数 5,为 'BaSiC' 的长度

s: mov al,[bx]
and al,11011111B ;将 al 中的 ASCII 码的第 5 位置为 0,即转化为大写字母
mov [bx],al
inc bx
loop s

mov bx,5 ;ds:bx 指向 'iNfOrMaTiOn' 的第一个字母
mov cx,11 ;设置循环次数 11,为 'iNfOrMaTiOn' 的长度

s0: mov al,[bx]
or al,00100000B ;将 al 中的 ASCII 码的第 5 位置为 1,即转化为小写字母
mov [bx],al
inc bx
loop s0

mov ax,4c00h
int 21h

codesg ends

end start
用 Debug 跟踪程序运行

程序 3

[bx+idata] 来指明内存单元,这种寻址方式被称为寄存器相对寻址,它为高级语言实现数组提供了便利机制

用 Debug 查看内存,结果如下

2000:1000BE 00 06 00 00 00...

写出下面的程序执行后,ax、bx、cx 中的内容

1
2
3
4
5
6
mov ax, 2000H
mov ds,ax
mov bx,1000H
mov ax,[bx]
mov cx,[bx+1]
mov cx,[bx+2]

AX 中为 00BEH,BX 中为 1000H,CX 中为 0606H

注意,数据寄存器的大小为一个字,传送进去的数据也应为一个字大小,并且注意是小端序

用 Debug 跟踪程序运行

程序 4

使用 [bx+idata] 的方式进行数组的处理,在 codesg 中填写代码,将 datasg 中的第一个字符串转化为大写,第二个字符串转化为小写。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
assume cs:codesg,ds:datasg

datasg segment
db 'BaSiC'
db 'MinIX'
datasg ends

codesg segment

start:
?

codesg ends

end start

为了简化程序,将两个循环合并成一个循环,可以用 [0+bx][5+bx] 的方式在同一个循环中定位这两个字符串中的字符,0 和 5 给定两个字符串的起始偏移地址,bx 给定从起始偏移地址开始的相对地址


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
assume cs:codesg,ds:datasg

datasg segment
db 'BaSiC'
db 'MinIX'
datasg ends

codesg segment

start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,5

s: mov al,[bx] ;定位第一个字符串中的字符,也可写作 mov al,0[bx]
and al,11011111b
mov [bx],al
mov al,[5+bx] ;定位第二个字符串中的字符,也可写作 mov al,5[bx]
or al,00100000b
mov [5+bx],al
inc bx
loop s

mov ax,4c00h
int 21h

codesg ends

end start
用 Debug 跟踪程序运行

程序 5

si 和 di 是 8086CPU 中和 bx 功能相近的寄存器,但 si 和 di 不能够分成两个 8 位寄存器来使用

用 si 和 di 实现将字符串 ‘welcome to masm!’ 复制到它后面的数据区中。


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
assume cs:codesg,ds:datasg

datasg segment
db 'welcome to masm!'
db '................'
datasg ends

codesg segment

start: mov ax,datasg
mov ds,ax
mov si,0
mov di,16

mov cx,8
s: mov ax,[si]
mov [di],ax
add si,2
add di,2
loop s

mov ax,4c00h
int 21h

codesg ends

end start

变址寄存器:SI(source index)、DI(destination index)
ds:si 指向要复制的源字符串,用 ds:di 指向复制的目的空间,用循环来完成复制,一次循环复制 2 个字节

用 Debug 跟踪程序运行

程序 6

用更少的代码,实现问题 7.2 中的程序。


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
assume cs:codesg,ds:datasg

datasg segment
db 'welcome to masm!'
db '................'
datasg ends

codesg segment

start: mov ax,datasg
mov ds,ax
mov si,0
mov cx,8

s: mov ax,0[si]
mov 16[si],ax
add si,2
loop s

mov ax,4c00h
int 21h

codesg ends

end start

si 与 di 是存在一定的关系的,可以用 si+16 表示 di,这样可以省去一个寄存器

程序 7

用 Debug 查看内存,结果如下

2000:1000BE 00 06 00 00 00...

写出下面的程序执行后,ax、bx、cx 中的内容

1
2
3
4
5
6
7
8
9
10
mov ax,2000H
mov ds,ax
mov bx,1000H
mov si,0
mov ax,[bx+si]
inc si
mov cx,[bx+si]
inc si
mov di,si
add cx,[bx+di]

AX 中为 00BEH,BX 中为 1000H,CX 中为 0606H

可参考程序 3

用 Debug 跟踪程序运行

程序 8

[bx+si+idata][bx+di+idata] 的含义相似,表示一个偏移地址为 (bx)+(si)+idata 或 (bx)+(di)+idata 的内存单元,这种寻址方式被称为相对基址变址寻址

用 Debug 查看内存,结果如下

2000:1000BE 00 06 00 6A 22...

写出下面的程序执行后,ax、bx、cx 中的内容

1
2
3
4
5
6
7
8
9
10
mov ax,2000H
mov ds,ax
mov bx,1000H
mov si,0
mov ax,[bx+2+si]
inc si
mov cx,[bx+2+si]
inc si
mov di,si
add bx,[bx+2+di]

AX 中为 0006H,BX 中为 226AH,CX 中为 6A00H

用 Debug 跟踪程序运行


总结比较前面用到的寻址方式

  1. [idata] 用一个常量来表示地址,可用于直接定位一个内存单元,称为直接寻址
  2. [bx] 用一个变量来表示内存地址,可用于间接定位一个内存单元,称为寄存器间接寻址
  3. [bx+idata] 用一个变量和常量表示地址,可在一个起始地址的基础上用变量间接定位一个内存单元,称为寄存器相对寻址
  4. [bx+si] 用两个变量表示地址,称为基址变址寻址
  5. [bx+si+idata] 用两个变量和一个常量表示地址,称为相对基址变址寻址

程序 9

编程,将 datasg 段中每个单词的头一个字母改为大写字母


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
assume cs:codesg,ds:datasg

datasg segment
db '1. file '
db '2. edit '
db '3. search '
db '4. view '
db '5. options '
db '6. help '
datasg ends

codesg segment

start: mov ax,datasg
mov ds,ax
mov bx,0
mov cx,6
s: mov al,[bx+3]
and al,11011111b
mov [bx+3],al
add bx,16
loop s

mov ax,4c00h
int 21h

codesg ends

end start

用 bx 定位每行的起始地址,用 3 定位要修改的列,每行的起始地址相差 16,用 [bx+idata] 的方式来对目标单元进行寻址

用 Debug 跟踪程序运行

程序 10

编程,将 datasg 段中每个单词改为大写字母。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
assume cs:codesg,ds:datasg

datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends

codesg segment

start:
?

codesg ends

end start

用 bx 定位每行的起始地址,用 si 定位要修改的列,每行的起始地址相差 16,用 [bx+si] 的方式来对目标单元进行寻址


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
assume cs:codesg,ds:datasg

datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends

codesg segment

start: mov ax,datasg
mov ds,ax
mov bx,0

mov cx,4

s0: mov si,0
mov cx,3

s: mov al,[bx+si] ;内层循环覆盖了外层循环的循环计数值 CX
and al,11011111b
mov [bx+si],al
inc si
loop s

add bx,16
loop s0

mov ax,4c00h
int 21h

codesg ends

end start

在上面的程序中,我们进行了二重循环,却只使用了一个循环计数器,造成在进行内层循环的时候,覆盖了外层循环的循环计数值,所以程序是有问题的

我们应该在每次开始内层循环的时候,将外层循环的 cx 中的数值保存起来,在执行外层循环的 loop 指令前,再恢复外层循环的 cx 数值

比如,用寄存器 dx 来临时保存 cx 中的数值


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
assume cs:codesg,ds:datasg

datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends

codesg segment

start: mov ax,datasg
mov ds,ax
mov bx,0

mov cx,4

s0: mov dx,cx ;将外层循环的 cx 值保存在 dx 中
mov si,0
mov cx,3 ;cx 设置为内层循环的次数

s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s

add bx,16
mov cx,dx ;用 dx 中存放的外层循环的计数值恢复 cx
loop s0 ;外层循环的 loop 指令将 cx 中的计数值减 1

mov ax,4c00h
int 21h

codesg ends

end start

用别的寄存器暂存 cx 中的值只是权宜之计,CPU 中寄存器的数量有限,且大都有各自的任务要做,而程序中经常会出现暂存数据的需求

1
2
3
4
5
6
7
datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
dw 0 ;定义一个字,用来暂存 cx
datasg ends

1
mov ds:[40H],cx                     ;将外层循环的 cx 值保存在 datasg:40H 单元中

可以考虑将暂存的数据放到内存单元中,需要的时候,从内存单元恢复,但如果需要同时保存多个数据的时候,就得记下数据放到了哪个内存单元中,这样会使得程序混乱

一般来说,在需要暂存数据的时候,我们都应该使用栈


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
assume cs:codesg,ds:datasg,ss:stacksg

datasg segment
db 'ibm '
db 'dec '
db 'dos '
db 'vax '
datasg ends

stacksg segment ;定义一个段,用来做栈段,容量为 16 个字节
dw 0,0,0,0,0,0,0,0
stacksg ends

codesg segment

start: mov ax,stacksg
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0

mov cx,4

s0: push cx ;将外层循环的 cx 值压栈
mov si,0
mov cx,3 ;cx 设置为内层循环的次数

s: mov al,[bx+si]
and al,11011111b
mov [bx+si],al
inc si
loop s

add bx,16
pop cx ;从栈顶弹出原 cx 的值,恢复 cx
loop s0 ;外层循环的 loop 指令将 cx 中的计数值减 1

mov ax,4c00h
int 21h

codesg ends

end start
用 Debug 跟踪程序运行

实验任务(2)

编程,将 datasg 段中每个单词的前 4 个字母改为大写字母。


用二重循环 + 相对基址变址寻址解决
用 bx 定位每行的起始地址,用 si 定位要修改的列,用 [bx+3+si] 的方式来对目标单元进行寻址


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
assume cs:codesg,ds:datasg,ss:stacksg

stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends

datasg segment
db '1. display '
db '2. brows '
db '3. replace '
db '4. modify '
datasg ends

codesg segment

start: mov ax,stacksg
mov ss,ax
mov sp,16 ;设置栈空间

mov ax,datasg
mov ds,ax

mov bx,0
mov cx,4 ;设置外层循环 cx

s1: push cx ;cx 压栈
mov si,0
mov cx,4 ;设置内层循环 cx

s2: mov al,[bx+3+si]
and al,11011111b
mov [bx+3+si],al ;将前 4 个字母改为大写字母
inc si
loop s2

add bx,16 ;换行
pop cx ;退栈,恢复原 cx
loop s1

mov ax,4c00h
int 21h

codesg ends

end start
用 Debug 跟踪程序运行


汇编语言实验合集

汇编语言实验合集

实验 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