实验2 多个逻辑段的汇编源程序编写与调试
一、实验目的
- 理解和掌握8086多个逻辑段的汇编源程序
- 理解和熟练应用灵活的寻址方式
- 通过汇编指令loop的使用理解编程语言中循环的本质,掌握其在嵌套循环中的正确使用
- 掌握使用debug调试8086汇编程序的方法
二、实验结论
1. 实验任务1
任务1-1
task1_1.asm源码:
assume ds:data, cs:code, ss:stack
data segment
db 16 dup(0)
data ends
stack segment
db 16 dup(0)
stack ends
code segment
start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, 16
mov ah, 4ch
int 21h
code ends
end start
调试到line17结束时的寄存器:

问题:
① 此时寄存器DS=076A,SS=076B,CS=076C;
② 假设程序加载后,code段的段地址是X,则data段的段地址是X-2h, stack的段地址是X-1h。
原因:数据段和栈段都预留了16B内存,又内存地址=段地址*16+偏移地址,所以相邻段地址相差1h
任务1-2
task1_2.asm源码:
assume ds:data, cs:code, ss:stack
data segment
db 4 dup(0)
data ends
stack segment
db 8 dup(0)
stack ends
code segment
start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, 8
mov ah, 4ch
int 21h
code ends
end start
调试至line17:

问题:
① 此时寄存器DS=076A,SS=076B,CS=076C;
② 假设程序加载后,code段的段地址是X,则data段的段地址是X-2h, stack的段地址是X-1h。
原因:系统分配段内存以16B为单位,分配的内存为16倍数,不足的会补上,所以此程序数据段和栈段还是预留16B空间;相邻段地址之间相差1h。
任务1-3
task1_3.asm源码:
assume ds:data, cs:code, ss:stack
data segment
db 20 dup(0)
data ends
stack segment
db 20 dup(0)
stack ends
code segment
start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, 20
mov ah, 4ch
int 21h
code ends
end start
执行至line17:

问题:
① 此时寄存器DS=076A,SS=076C,CS=076E;
② 假设程序加载后,code段的段地址是X,则data段的段地址是X-4h, stack的段地址是X-2h。
原因:分配段内存为16倍数,所以此程序数据段和栈段都预留32B空间;相邻段地址之间相差2h。
任务1-4
task1_4.asm源码:
assume ds:data, cs:code, ss:stack
code segment
start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, 20
mov ah, 4ch
int 21h
code ends
data segment
db 20 dup(0)
data ends
stack segment
db 20 dup(0)
stack ends
end start
执行至line17:

问题:
① 此时寄存器DS=076C,SS=076E,CS=076A;
② 假设程序加载后,code段的段地址是X,则data段的段地址是X+2h, stack的段地址是X+4h。
原因:段之间的地址顺序按照编译顺序分配。
任务1-5
① 实际分配给该段的内存空间大小是 (ceil)(N/16)*16 (ceil为向上取整)
② 只有task1_4.asm仍然可以正确执行。end start通知了编译器程序的入口在start处,去掉start,可能会导致CS段地址定位错误。
汇编程序运行方法:
1、找到一段起始地址为SA:0000(即起始地址的偏移地址为0)的容量足够的空闲内存区。
2、在这段内存区的前256个字节中,创建一个称为程序段前缀(PSP)的数据区,DOS利用PSP来和被加载程序进行通信。
3、从这段内存区的256字节处开始(在PSP的后面),将程序装入,程序的地址被设为SA+10H:0;空闲内存区从SA:0开始,0~255字节为PSP,从256字节处开始存放程序,为更好地区分PSP和程序,DOS将划分到不同的段中,如下这样的地址安排:
空闲内存区:SA:0
PSP区:SA:0
程序区:SA+10H:0
注意:PSP区和程序区虽然物理地址连续,却有不同的段地址。
4、将该内存区的段地址(SA)存入DS中,初始化其它相关寄存器后,设置CS:IP指向程序的入口(SA+10H:0)。程序装入时ds和cs地址的确定:
1.对于ds,程序装入时ds段地址指向PSP地址,要指向data段,需要在程序中自己赋值;
2.对于cs,cs由end start指定的start来确定段地址;如果没有start,cs:ip指向(SA+10H:0)
以task1_3为例,去掉end start的start后,

程序装入时DS指向PSP,CS:IP指向程序区开头(SA+10H:0)即(076A:0);由task1_3源码可知,段顺序为DS,SS,CS,所以此时CS:IP指向的实际为数据段,u命令反汇编也发现CS:IP处的值为数据段预留的0,而程序将其当作指令执行了,自然不能正确执行。
而task1_4中段顺序为CS,DS,SS,去掉start后CS:IP指向的程序区开头也是代码段,所以程序能正确执行。
2. 实验任务2
task2.asm源码:
ASSUME CS:CODE
CODE segment
mov ax, 0b800h
mov ds, ax
mov bx, 0f00h
mov cx, 50h ;每次写入两字节,重复80次
mov ax, 0403h
s: mov ds:[bx], ax
add bx, 2 ;每次循环bx加2
loop s
mov ah, 4ch
int 21h
CODE ends
end
实验结果:
程序运行前

运行后

3. 实验任务3
task3.asm源码:
assume cs:code
data1 segment
db 50, 48, 50, 50, 0, 48, 49, 0, 48, 49 ; ten numbers
data1 ends
data2 segment
db 0, 0, 0, 0, 47, 0, 0, 47, 0, 0 ; ten numbers
data2 ends
data3 segment
db 16 dup(0)
data3 ends
code segment
start:
mov bx, 0 ;对应位置的偏移地址
mov ax, data1
mov ds, ax
mov cx, 0ah
s: mov ax, [bx]
add ax, [bx+10h] ;data1和data2的数据相加
mov [bx+20h], ax ;放到data3段中
inc bx
loop s
mov ah, 4ch
int 21h
code ends
end start
反汇编:

运行前:

运行后:

三行分别为data1、data2、data3段,可以看出对应位置的data1和data2相加并存入了data3中。
4. 实验任务4
task4.asm源码:
assume cs:code
data1 segment
dw 2, 0, 4, 9, 2, 0, 1, 9
data1 ends
data2 segment
dw 8 dup(?)
data2 ends
code segment
start:
mov ax, data1
mov ds, ax
mov ax, data1+02h
mov ss, ax ;将data2用作栈段
mov sp, 20h ;栈顶指向高地址
mov cx, 8
mov bx, 0
s: push [bx]
add bx,2
loop s
mov ah, 4ch
int 21h
code ends
end start
反汇编:

运行前:

运行后:

两行分别为段data1和data2,可以看到已经将data1的内容逆序存储到了data2中;同时注意到data1中076A:0b~076A:0f处的值发生了变化,原因是程序中断,将此时的FALG、CS、IP存放在了栈空间(详见实验报告1).
5. 实验任务5
task5.asm源码:
assume cs:code, ds:data
data segment
db 'Nuist'
db 2, 3, 4, 5, 6
data ends
code segment
start:
mov ax, data
mov ds, ax
mov ax, 0b800H
mov es, ax
mov cx, 5
mov si, 0
mov di, 0f00h
s: mov al, [si]
and al, 0dfh
mov es:[di], al
mov al, [5+si]
mov es:[di+1], al
inc si
add di, 2
loop s
mov ah, 4ch
int 21h
code ends
end start
运行结果:

可以看出,底部出现了彩色的NUIST字样;同时data段预留的Nuist被转换成大写与预留的数字(2,3,4,5,6)交替存放在了目标地址。
line19的作用:
将0dfh转换成二进制为1101 1111,和其按位与之后第三位变为0,其他位不变;通过ASCII码表可知大小写字母的区别就在于第三位,所以将第三位变为0就是把小写字母转换成大写字母(大写字母不变)。

line4的字节数据的用途:
;修改line4字节数据
db 2,3,4,5,6
-->改成:
db 5 dup(2)或db 5 dup(5)
运行结果:


可以看到修改line4的字节数据,底部NUIST的颜色随之改变,同时NUIST的字母与line4的字节数据交替存放在目标地址,所以line4的字节数据的用途为色彩代码,每个字节决定一个字母的颜色。
6. 实验任务6
task6.asm源码:
assume cs:code, ds:data
data segment
db 'Pink Floyd '
db 'JOAN Baez '
db 'NEIL Young '
db 'Joan Lennon '
data ends
code segment
start:
mov ax, data
mov ds, ax
mov bx, 0 ;行偏移地址
mov cx, 4 ;四行
mov si, 0 ;单词内的字母指针
s1: ;进入外层循环
mov di, cx ;保存外层循环进行次数
mov cx, 4 ;每行首个单词都是4个字母,所以内存循环次数为4次
s2: ;进入内层循环
mov al, [bx+si] ;逐个取出字母
or al, 20h ;和20h按位或转换成小写
mov [bx+si], al ;放回原位置
inc si ;下个字母
loop s2 ;内层循环结束
add bx, 10h ;进入下一行
mov si, 0 ;字母指针清零
mov cx, di ;恢复外层循环测次数
loop s1 ;外层循环结束
mov ah, 4ch
int 21h
code ends
end start
分析:
类似task5的思想,将字母转换成小写只需将第三位变成1,所以和0010 0000b(即20h)按位或即可实现小写转换。
反汇编:


运行结果:

可以看出数据段中每行的首个单词都转换为小写了。
7. 实验任务7
task7.asm源码:
assume cs:code, ds:data, es:table
data segment
db '1975', '1976', '1977', '1978', '1979'
dd 16, 22, 382, 1356, 2390
dw 3, 7, 9, 13, 28
data ends
table segment
db 5 dup( 16 dup(' ') ) ;
table ends
code segment
start:
mov ax, data
mov ds, ax
mov ax, table
mov es, ax ;目标table地址
mov bx, 0 ;table的行地址
mov si, 0 ;data段中的数据指针
mov cx, 5 ;每次写5行
mov di, 0 ;table中每行中的指针
;写入年份
year: ;外循环,每次写一行
mov ax, cx ;保存循环次数
mov cx, 4 ;每行的年份占4字节,按字节写入需4次
year1: ;内循环,每次写一个字节即一个数字
mov dl, ds:[si] ;内存单元间不能直接移动,借dl按字取
mov es:[bx+di], dl
inc si ;data中的数据连续,按字取所以+1
inc di ;年份在table中每行的偏移地址为0~3
loop year1
add bx, 10h ;进入下一行
mov di, 0 ;di回到初位置
mov cx, ax ;恢复循环次数
loop year
;写入收入
mov cx, 5
mov bx, 0
mov di, 5
income:
mov ax, cx
mov cx, 2 ;收入按双字存储,但每次只能取单字,每行需要2次循环
income1:
mov dx, ds:[si] ;按字取
mov es:[bx+di], dx
add si, 2 ;按字取每次移动2字节
add di, 2
loop income1
add bx, 10h ;下一行
mov di, 5 ;回到每行写入的初位置
mov cx, ax
loop income
;写入雇员
mov cx, 5
mov bx, 0
mov di, 0ah
employee:
mov dx, ds:[si] ;按字取
mov es:[bx+di], dx
add si, 2
add bx, 10h
loop employee
;写入人均收入
mov cx, 5
mov bx, 0
mov di, 0dh
avgincome:
mov dx, es:[bx+7] ;被除数为双字即32位,用dx存高16位,ax存低16位
mov ax, es:[bx+5]
div word ptr es:[bx+0ah] ;除数为16位,商存在ax中,余数存在dx中
mov es:[bx+di], ax
add bx, 10h
loop avgincome
mov ah, 4ch
int 21h
code ends
end start
调试及运行结果
table段原始数据:

运行结果:

可以看到数据段中的数据已经按结构存到table段中了,且经验算人均收入的计算结果正确。
三、实验总结
-
end start指明了程序的入口,去掉start可能会导致CS:IP的地址错误,从而无法正确执行程序(详见)
-
掌握了汇编程序执行的具体方法,包括段地址的顺序(按编译顺序),装入时PSP的地址分配、装入时DS和CS:IP的地址如何确定(详见)等;
-
写代码过程中复习了一些忘掉的知识点:
(1)不能直接在内存单元之间mov,需要借助寄存器作为中介;
(2)mov的双方要确保位数相同,同理可以通过寄存器的位数来间接确定取字/字节;
(3)bx、bp、si、di用来存放偏移地址,且组合方式为(bx,bp)和(si,di)两两组合,同组的不能组合
(4)因为只有cx能存放循环次数,所以多层循环时需要进入内层循环时保存此时循环次数,跳出内层循环时 再恢复保存的循环次数;
(5)系统分配段内存按16B的倍数进行分配,不满足的会补到16B的倍数;
-
字母转大写:and 0dfh;
字母转小写:or 20h;

浙公网安备 33010602011771号