原文:https://www.pediy.com/kssd/pediy10/53710.html
///////////////////////////////////////////////////////////////////////////////////////////
北极星2003 注: 把多个实验报告合为一个专题
实验1:1楼
实验2:10/11楼
实验3:无
实验4:18楼
实验5:23/24楼
实验6:29楼
实验7:34楼
实验8:40楼
实验9:42楼
///////////////////////////////////////////////////////////////////////////////////////////
实验1 查看CPU和内存,用机器指令和汇编指令编程
1.预备知识:DEBUG的使用
在以后所有的实验中,都将用到DEBUG程序,首先学习一下它的用法。
(1)什么是DEBUG
DEBUG是DOS、WINDOWS都提供的实模式(8086方式)程序的调试工具。使用它,可以查看CPU各种寄存器中的内容,内存的情况和在机器码跟踪程序的运行。
(2)我们用到的DEBUG功能
用DEBUG的R命令查看、改变CPU寄存器的内容:
用DEBUG的D命令查看内存中的内容:
用DEBUG的E命令改写内存中的内容:
用DEBUG的U命令将内存中的机器指令翻译成汇编指令:
用DEBUG的T命令执行一条机器指令:
用DEBUG的A命令以汇编指令的格式在内存中写入一条机器指令:
DEBUG命令比较多,共有20多个,但上述6个命令是和汇编语言学习密切相关的。在以后的实验中,我们还会用到一个P命令。
(3)进入DEBUG
DEBUG是在DOS方式下使用的程序。我们在进入DEBUG前,应先进入到DOS方式。用以下方式可以进入DOS:
①重启计算机后,进入DOS方式,此时我们进入的是实模式的DOS。
②在WINDOWS中进入DOS方式,此时进入的是虚拟8086模式的DOS。
(4)用R命令查看、改变CPU寄存器的内容
我们已知道了AX、BX、CX、DX、SS、IP这6个寄存器,现在看一下它们之中的内容,如图2.1所示。其他寄存器SP、BP、SI、DI、DS、ES、SS、FLAGS(标志寄存器) 等先不予理会。
图2.31 使用R命令查看CPU中各个寄存器中的内容
注意CS和IP的值,CS=0CA2,IP=0100,也就是说,0CA2=0100处的指令为CPU当前要读取、执行的指令。在所有的寄存器的下方,DEBUG还列出了CS:IP所指向的内存单元处的所存放的机器码,并将它翻译为汇编指令。可以看到,CS:IP所指向的内存单元为0CA2:0100此处存放的机器码为027548,对应的汇编指令为ADD DH,[DI+48](这条指令的含义我们还不知道,先不必探究)。
DEBUG输出的右下角还有一个信息:“DS:0048=0”,以后会进行说明,这里同样不必深究。
还可以用R命令来改变寄存器中的内容,如图2.32所示。
图2.32 用R命令修改寄存器AX中的内容
若要修改一个寄存器中的值,比如AX中的值,可以用R命令后加寄存器名来进行,输入“R AX”后按ENTER键,将出现“:”作为输出提示,在后面输入要写入的数据后按ENTER键,即完成了对AX中内容的修改。若想查看一下修改的结果,可再用R命令查看,如图2.33所示。
图2.33 用R命令修改CS和IP中的内容
在图2.33中,一进入DEBUG,用R命令查看,CS:IP指向0B39:0100,此处存放的机器码为40,对应的汇编指令是INC AX.
接着,用R命令将CS修改为FF00,则CS:IP指向FF00:0200,此处存储的机器码为51,对应的汇编指令是PUSH CX。
(5)用DEBUG的R命令查看内存中的内容
用DEBUG的D命令,可以查看内存中的内容,D命令的格式较多。我们这里只介绍在以本次实验中用到的格式。
如果想知道内存10000H处的内容,可以用“D 段地址:偏移地址”的格式来查看,如图2.34所示。
图2.34 用D命令查看内存1000:0处的内容
使用“D 段地址:偏移地址”的格式,DEBUG将列出从指定的内存单元开始的128个内存单元的内容,图2.34中,在使用D 1000:0后,DEBUG列出了1000:0~1000:7F中的内容。
使用D命令,DEBUG将输出3部分内容,如图2.34所示。
中间是部分从指定地址开始的128个内存单元的内容,用十六进制的格式输出,每行的输出从16的整数倍的地址开始,最多输出16个单元的内容。从图中,我们可以知道,内存1000:0的内容是61:内存1000:10~1000:1F中的内容全部在第一行:内存1000:10中的内容是6D,内存1000:1处的内容是61:内存1000:10~1000:1F中的内容全部在第二行。注意在每行的中间有一个“-”,它将每行的输出分为两部分,这样便于查看。比如,要想从图中找出,1000:6B单元中内容,可以从1000:60找到行,“-”前面是1000:60~1000:67的8个单元,后面是1000:68~1000:6F的8个单元,这样我们就可以从1000:68单元向后数3个单元找到1000:6B单元,可以看到,1000:6B中的内容为67H。
左边是每行的起始地址。
右边是每个内存单元中的数据对应的可以显示的ASCII码字符。比如内存单元1000:0、1000:1、1000:2中存放的数据是72H、64H、73H,它对应的ASCII字符分别是”r”、”d”、”s”;内存单元1000:36中的数据是0AH,它没有对应可显示的ASCII字符,DEBUG应用”.”来代替。
注意:我们看到的内存中的内容,在不同的计算机中是不一样的;也可能每次用DEBUG看到的内容都不相同,因为我们用DEBUG看到的都是原来就在内存中的内容,这些内容受随时都有可能心迹的系统环境的影响。当然,我们也可以改变内存,寄存器中的内容。
使用D 1000:9查看1000:9处的内容,DEBUG将怎样输出呢?如图2.35所示。
图2.35 查看1000:9处的内容
DEBUG从1000:9开始显示,一直到1000:88,一共是128个字节。第一行中的1000:0~1000:8单元中的内容不显示。
在一进入DEUBG后,用D命令直接查看,将列出DEBUG预设的地址的内容,如图2.36。
在使用“D 段地址:偏移地址”之后,接着使用D命令,可以列出后续的内容,如图2.37所示。
图2.36 列出DEBUG预设的地址处的内容
图2.37 列出后续的内容
也可以指定D命令的查看范围,此时采用“D 段地址:起始偏移地址 结尾偏移地址“的格式。比如要看1000:0~1000:9中的内容,可以用”D 1000:0 9“实现。如图2.38所示。
图2.38 查看1000:0~1000:9单元中的内容
如果我们就想查看内存单元10000H中的内容,可用图2.39中的任何一种方法看到,因为图中的所有“段地址:偏移地址“都表示了10000H这一物理地址。
图2.39 用三种不同的段地址和偏移地址查看同一个物理地址中的内容
(6)用DEBUG的E命令改写内存中的内容
可以用E命令改写内存中的内容,比如,要将内容1000:0~1000:9单元中的内容分别写为0、1、2、3、4、5、6、7、8、9,可以用它“E 起始地址 数据 数据 数据。。。“的格式来进行,如图2.40所示。
图2.40 用E命令修改从1000:0开始的10个单元的内容
图2.40中,先用D命令查看1000:0~1000:F单元的内容,再用E命令修改从1000:0开始的10个单元的内容,最后用D命令查看1000:0~1000:F中的内容的变化。
也可以采用提高的方式来一个一个地改写内存中的内容,如图2.41所示。
图2.41 用E命令修改从1000:0开始的4个单元
如图2.41中,可以用E命令提高的方式来逐个地修改从某一地址开始的内存单元中的内容,以从1000:10单元开始为例,步骤如下:
①输入E 1000:10,按ENTER键。
②DEBUG显示起始地址1000:0010,和第一单元(即1000:0010单元)的原始内容:6D,然后光标停在“。“的后面提示输入想要写入数据,此时可以有两个选择:其一为输入数据(我们输入的是D),然后按空格键,则不对当前内存单元进行改写。
③当前单元处理完成后(不论是改写或没有改写,只要按了空格键,就表示处理完成),DEBUG将接着显示一个内存单元的原始内容,并提示读者进行修改,读者可以用同样的方法处理。
可以用E命令向内存中写入字符,比如:用E命令从内存1000:0开始 写入:数值1、字符‘a’、数值3、字符‘c’,可采用图2.42中所示的方法进行:
从图中2.42中可以看出,DEBUG对E命令的执行结果是,向1000:0、1000:2、1000:4单元中写入数值1,2,3,向1000:1、1000:3、1000:5单元中写入字符”a”,”b”,”c”的ASCII码值:61H,62H,63H。
也可以用E命令向内存中写入字符串,比如:用E命令从内存1000:0开始写入:数值1、字符串”a+b”、数值2、字符串”c++”、数值3、字符串”IBM”.如图2.43所示。
图2.42 用E命令向内存中写入字符
(7)用E命令向内存中写入机器码,用U命令查看内存中机器码的含义,用T命令执行内存中的机器码。
如何向内存中写入机器码呢?我们知道,机器码了是数据,当然可以用E命令将机器码写入内存。比如要从内存1000:0单元开始写入这样一段机器码:
机器码 对应的汇编指令
B8 01 00 MOV AX,0001
B9 02 00 MOV CX,0002
01C8 ADD AX,CX
可用如图2.44中所示的方式进行。
图2.44 用E命令将机器码写入内存
如何查看我们写入的或内存中原有的机器码所对应的汇编指令呢?可以用U命令进行。比如可以用U命令将从1000:0开始的内存单元中的内容翻译为汇编指令,并显示出来。如图2.45所示。
图2.45中,首先用E命令向从10000:0开始的内存单元中写入了8个字节的机器码:然后用D命令查看内存1000:0~1000:1F数据(从数据的角度看一下我们写入的内容):最后用U命令查看从1000:0开始内存单元中的机器指令和它们所对应的汇编指令。
U命令的显示输出分为3部分:第一条机器指令的地址、机器指令所对应的汇编指令。我们可以看到:
1000:0处存放的是我们写入的机器码B80100所组成的机器指令,对应的汇编指令是MOV AX,1:
1000:3处存放的是我们写入的机器码B80C00所组成的机器指令、对应的汇编指令是ADD CX,2:
1000:6处存放的是我们写入的机器码01C8所组成的机器指令、对应的汇编指令是ADD AX,CX:
1000:8处存放 的是内存中的机器码是034942所组成的机器指令、对应的汇编指令是ADD CX,[BX+DI+42].
图2.45 用U命令将内存单元中的内容翻译为汇编指令显示
由此,我们可以再次看到内存中的数据和代码没有任何区别,关键在于如何解释。
如何执行我们写入的机器指令?使用DEBUG的T命令可以执行一条或多条指令,简单使用T命令,可以执行CS:IP指向的指令。如图2.46所示。
图2.46 使用T命令执行CS:IP指向的指令
图2.46中,首先用E命令向从1000:0开始的内存单元中写入了8个字节的机器码:然后R命令查看CPU中寄存器的状,可以看到CS=0B39H,IP=0100H,指向内存0B39:0100若要用T从控制CPU执行我们写到1000:0的指令,必须先让CS:IP指向1000:0;用R命令修改CS、IP中的内容,使CS:IP指向1000:0
完成上面的步骤后,就可以使用T命令来执行我们写入的指令了(此时,CS:IP指向我们的指令所在的内存单元)。执行T命令后,CPU执行CS:IP指向的指令,则1000:0处的指令B8 01 00(MOV AX,0001)得到执行,指令执行后,DEBUG显示输出CPU中寄存器的状态。
注意,指令执行后,AX中的内容被改写为1,IP改变为IP+3(因为MOV AX,0001的指令长度为3个单元),CS:IP指向下一条指令。
接着图2.46,我们可以继续使用T命令执行下面的指令。如图2.47所示。
图2.47 用T命令继续执行
在图2.47中,用T命令继续执行后面的指令,注意每条指令执行后CPU相关寄存器内容的变化。
(8)用DEBUG的A命令以汇编指令的形式在内存中写入机器指令,直接以汇编指令的形式写入指令。为此,DEBUG提供了A命令。A命令的使用方法如图2.48所示。
图2.48 用A命令向从1000:0开始的内存单元中写入指令
图2.48中,首先用A命令,以汇编语言向从1000:0开始的内存单元中写入了几条指令,然后用D命令查看A命令的执行结果,可以看到,在使用A命令写入指令时,我们输入的是汇编指令,DEBUG将这些汇编指令翻译为对应的机器指令,将它们的机器码写入内存。
在使用A命令写入汇编指令时,在给出的起始地址后面直接按ENTER键表示操作结束。
如图2.49中,简单地用A命令,从一个预设的地址开始输入指令。
图2.49 从一个预设的地址开始输入指令
本次实验中需要用到的命令
查看、修改CPU中寄存器的内容:R命令
查看内存中的内容:D命令
修改内存中的内容:E命令(可以写入数据、指令、在内存中,它们实际上没有区别)
将内存中的内容解释为机器指令和对应的汇编指令:U命令
执行CS:IP指向的内存单元处的指令:T命令
以汇编指令的形式向内存中写入指令:A命令
在预备知识中,详细讲解了DEBGU基本功能和用法,在汇编语言的学习中,DEBGU是一个经常用到的工具,在预备知识中,应该一边看书,一边在机器上操作。
前面提到,我们的原则是:以后的,以后再说,所以在这里只讲了一些在本次实验需要用到的命令的相关使用方法,以后根据需要,我们会讲解其它的用法。
2.实验任务
(1)使用DEBGU,将下面的程序段写入内存,逐条执行,观察每条指令执行后,CPU中相关寄存器中的内容的变化。
机器码 汇编指令
B8 20 40 MOV AX,4E20H
05 16 14 ADD AX,1416H
BB 00 20 MOV BX,2000H
01 DB ADD AX,BX
89 C3 MOV BX,AX
01 D8 ADD AX,BX
B8 1A 00 MOV AX,001AH
BB 26 00 MOV BX,0026H
00 D8 ADD AL,BL
00 DC ADD AH,BL
00 C7 AD AH,AL
64 00 MOV AH,0
00 D8 ADD AL,BL
04 9C ADD AL,9CH
提示:可以用E命令和A命令,以两种方式将指令写入内存。注意用T命令执行时,CS:IP的指向。
(2)将下面的3条指令写入从2000:0开始的内存单元中,利用这3条指令计算2的8次方。
MOV AX,1
ADD AX,AX
JMP 2000:0003
(3)查看内存中的内容
PC机主板中的ROM中写有一个生产日期,在内存FFF00H~FFFFFH的某几个单元中,请找到这个生产日期并试图改变它。
提示:如果读者对实验的结果不理解,请仔细阅读第1章的1.15节。
(4)向内存从B8100H开始的单元中填写数据,如:
-E B810:0000 01 01 02 02 03 03 04 04
请读者:先填写不同的数据,观察产生的现象:再改变填写的地址,观察产生的现象。
提示:如果读者对实验结果不理解,请仔细阅读第1章中的1.15节。
总结:经过这次实验,我学到了许多宝贵的知识,现列举如下:
1. 学会了使用R命令查看CPU中的各个寄存器的内容
2. 学会了使用R命令修改各个寄存器中的内容
3. 学会了使用D命令查看某寄存器中的内容
4. 学会了使用D命令来列出DEBUG预设的地址及其后续地址内容
5. 学会了使用多种不同的段地址和偏移地址查看同一物理地址中的内容的方法
6. 学会了使用E命令修改从某地址开始的存储单元的内容
7. 学会了使用E命令向内存中写入字符、字符串、机器码的方法
8. 学会了使用U命令将内存单元中的内容翻译为汇编指令显示的方法
9. 学会了使用T命令执行CS:IP指向的指令的方法
10.学会了使用A命令向某地址开始的单元写入指令的方法
11.学会了使用A命令向一个预设的地址输入指令的方法
12.学会了向内存中输入一段指令并执行以及观察相应寄存器的内容的变化的方法
13.学会了使用3条指令计算2的N次方的方法
14.学会了使用通过查看ROM中数据来了解自己主板的生产日期的方法
15.更深入的了解了8086将各类存储器看作一个逻辑存储器的概念
16.更深入的了解了8086机内存地址空间分配原理。
这次实验,使我受益非常大。以上是我在做实验后的一点总结心得。
实验2 用机器指令和汇编指令编程
1. 预备知识:DEBUG的使用
前面实验中,讲了DEBUG一些主要命令的用法,这里,我们再补充一些关于DEBUG的知识。
(1) 关于D命令
从上次实验中,我们知道,D命令是查看内存单元的命令,可以用:
D 段地址:偏移地址的格式查看指定的内存单元的内容,上次实验中,D命令后面的段地址和偏移地址都是直接给出的。
现在,我们知道段地址是放在段寄存器中的,在D命令后面直接给出段地址,是DEBUG提供的一种直观的操作方式。D命令是由DEBUG执行的,DEBUG在执行“D 1000:0”这样的命令时,也会先将段地址1000送入段寄存器中。
DEBUG是靠什么来执行D命令的?当然是一段程序。
谁来执行这段程序?当然是CPU。
所以,DEBUG在其处理D命令的程序段中,必须有将段地址送入段寄存器中的代码。
段寄存器有4个:CS、DS、SS、ES,将段地址送入哪个段寄存器呢?
首先不能是CS,因为CS:IP必须指向DEBUG处理D命令的代码,也不能是SS,因为SS:SP要指向栈顶。这样只剩下DS和ES可以选择,放在哪里呢?我们知道,访问内存的指令如“MOV AX,[0]”等一般都默认段地址在DS中,所以DEBUG在执行如:“D 段地址:偏移地址”这种D命令时,将段地址送入DS中比较方便。
D命令也提供了一种符合CPU机理的格式:“D 段寄存器:偏移地址”,以段寄存器中的数据为段地址SA,列出从SA:偏移地址开始的内存区间中的数据。以下是4个例子:
①-R DS
:1000
-D DS:0 ;查看从1000:0开始的内存区间中的内容
②-R DS
:1000
-D DS:10 18 ;查看从1000:10~1000:18中的内容
③-D CS:0 ;查看当前代码段中的指令代码
④-D SS:0 ;查看当前栈段中的内容
(2) 在E、A、U命令中使用段寄存器
在E、A、U这些可以带有内存单元地址的命令中,也可以同D命令一样,用段寄存器表示内存单元的段地址。以下是3个例子:
①-R DS
:1000
-E DS:0 11 22 33 44 55 6 ;在从1000:0开始的内存区间中写入数据
②-U CS:0 ;以汇编指令的形式,显示当前代码段中的代码,0代码的偏移地址
③-R DS
:1000
-A DS:0 ;以汇编指令的形式,向从1000:0开始的内存单元中写入指令
(3) 下一条指令执行了吗?
在DEBUG中,用A命令写一段程序:
MOV AX,2000
MOV SS,AX
MOV SP,10 ;安排2000:0~2000:F为栈空间,初始化栈顶。
MOV AX,3123
PUSH AX
MOV AX,3366
PUSH AX ;在栈中压入两个数据
仔细看一下图3.18中单步执行的结果,读者发现了什么问题?
在用T命令单步执行MOV AX,2000后,显示出当前CPU各个寄存器的状态和下一步要执行的指令:MOV SS,AX;
在用T命令单步执行MOV SS,AX后,显示出当前CPU各个寄存器的状态和下一步要执行的指令。。。。。,在这里我们发现了一个问题:MOV SS,AX的下一条指令应该是MOV SP,10,怎么变成了MOV AX,3123H
MOV SP,10到哪里去了?它被执行了吗?
我们再仔细观察,发现:
在程序执行前,AX=0000,SS=0B39,SP=FFEE
在用T命令单步执行MOV AX,2000后,AX=2000;SS=0B39;SP=FFEE
在用T命令单步执行MOV SS,AX后,AX=2000;SS=2000;SP=0010
注意,在用T命令单步执行MOV SS,AX前,SS=0B39,SP=FFEE,而执行后SS=2000,SP=0010,SS变为2000是正常的,这正是MOV SS,AX的执行结果。可是SP变为0010是怎么回事?在这期间,能够将SP设为0010的只有指令MOV SP,10,看来,MOV SP,10一定是得到了执行。
那么,MOV SP,10是在什么时候被执行的呢?当然是在MOV SS,AX之后,因为它就是MOV SS,AX的下一条指令。显然,在用T命令执行MOV SS,AX的时候,它的下一条指令MOV SP,10也紧接着被执行了。
整理一下我们分析的结果:在用T命令执行MOV SS,AX的时候,它的下一条指令MOV SP,10也紧接着执行了。一般情况下,用T命令执行一条指令后,会停止继续执行,显示出当前CPU各个寄存器的状态和下一步要执行的指令,但T命令执行MOV SS,AX的时候,没有做到这一点。
不单是MOV SS,AX,对于如:MOV SS,BX,MOV SS,[0],POP SS等指令都会发生上面的情况,这些指令有哪些共性呢?它们都是修改栈段寄存器SS的指令。
为什么会这样呢?要想彻底说清楚这里面来龙去脉,在这里还为时过早,因为这涉及到我们在以后的课程中要深入研究的内容:中断机制,它是我们后半部分课程中的一个主题。现在我们只要知道这一点就可以了:DEBUG的T命令在执行修改寄存器SS的指令时,一条指令也紧接着被执行。
2 实验任务
(1) 使用DEBUG,将上面的程序段写入内存,逐条执行,根据指令执行后的实际运行情况填空。
MOV AX,FFFF
MOV DS,AX
MOV AX,2200
MOV SS,AX
MOV SP,0100
MOV AX,[0] AX=5BEAH
ADD AX,[2] AX=5CCAH
MOV BX,[4] BX=30FCH
ADD BX,[6] BX=6022H
PUSH AX SP=00FEH,修改的是字单元2200:00FE
PUSH BX SP=00FCH,修改的是字单元2200:00FC
POP AX SP=00FEH,AX=6022H
POP BX SP=0100H,BX=5CCAH
PUSH [4] SP=00FEH,修改的是字单元2200:00FE,内容是30F0H
PUSH [6] SP=00FCH,修改的是字单元2200:00FC,内容是2F32H
(2) 仔细观察图3.19中的实验过程,然后分析:为什么2000:0~2000:F中的内容会发生改变?
可能要再便有些实验才能发现其中的规律。如果读者在这里就正确回答了这个问题,那么要恭喜读者,因为读者有很好的惜玉怜香。大多数的读者对这个问题还是比较迷惑的,不过不要紧,因为随着课程的进行,这个问题的答案将逐渐变得显而易见。
图3.19用DEBUG进行实验的示例
总结:经过这次实验我学到了许多有用的东西:分别是:
1.我掌握了使用D、U、E、A命令中可以直接使用段寄存器作为表示段地址 2。我明白了在使用T命令执行修改SS段寄存器的指令时,它会紧接着执行后面的一条指令,即是说执行一次T命令将会连续执行两条指令。
不过有一点不明白的就是,为什么在定义一个堆栈段的时候,堆栈的内容会发生变化,难道是因为系统自动对它进行初始化了吗?有人可以告诉我吗?我的QQ是420716701.
[SIZE="4"]实验4 [BX]和loop的使用
(1) 编程,向内存0:200~0:23F依次传送数据0~63(3FH).
;----------------------------------------------
;ex5-1.asm
;-----------------------------------------------
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assume cs:code ;;
code segment ;;
mov ax,20h ;;
mov ds,ax ;;
mov bx,0 ;;
mov ax,0 ;;
mov cx,0ffh ;;
s: mov [bx],ax ;;
inc bx ;;
inc ax ;;
loop s ;;
mov ax,4c00h ;;
int 21h ;;
code ends ;;
end ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(2) 编程,向内存0:200~0:23F依次传送数据0~63(3FH),程序中只能使用9条指令,9条指令中包括“MOV AX,4C00H”和“INT 21H”。
;-------------------------------------------
;ex5-2.asm
;-------------------------------------------
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assume cs:code ;
code segment ;
mov ax,20h ;
mov ds,ax ;
mov bx,0 ;
mov cx,0ffh ;
s: mov [bx],bx ;
inc bx ;
loop s ;
mov ax,4c00h ;
int 21h ;
code ends ;
end ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(3) 下面的程序的功能是将“MOV AX,4C00H”之前的指令复制到内存0:200处,补全程序。上机调试,跟踪运行结果。
;------------------------------------------------
;;ex5-3.asm
;------------------------------------------------
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assume cs:code ;;;;;
code segment ;;;;;
mov ax,code ;;;;;
mov ds,ax ;;;;;
mov ax,0020h ;;;;;
mov es,ax ;;;;;
mov bx,0 ;;;;;
mov cx,18h ;;;;;
s:mov al,[bx] ;;;;;
mov es:[bx],al ;;;;;
inc bx ;;;;;
loop s ;;;;;
mov ax,4c00h ;;;;;
int 21h ;;;;;
code ends ;;;;;
end ;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1.把EX5-3.EXE加载入内存中
$debug ex5-3.exe
-r
AX=0000 BX=0000 CX=001D DX=0000 SP=0000 BP=0000 SI=0000 DI=0000
DS=13C7 ES=13C7 SS=13D7 CS=13D7 IP=0000 NV UP EI PL NZ NA PO NC
13D7:0000 B8D713 MOV AX,13D7
-u
13D7:0000 B8D713 MOV AX,13D7
13D7:0003 8ED8 MOV DS,AX
13D7:0005 B82000 MOV AX,0020
13D7:0008 8EC0 MOV ES,AX
13D7:000A BB0000 MOV BX,0000
13D7:000D B91800 MOV CX,0018
13D7:0010 8A07 MOV AL,[BX]
13D7:0012 26 ES:
13D7:0013 8807 MOV [BX],AL
13D7:0015 43 INC BX
13D7:0016 E2F8 LOOP 21
13D7:0018 B8004C MOV AX,4C00
13D7:001D 00FF ADD BH,BH
13D7:001F 50 PUSH AX
2.把0:200的内容反汇编结果如下:
13D7:000D B91800 MOV CX,0018
13D7:0010 8A07 MOV AL,[BX]
13D7:0012 26 ES:
13D7:0013 8807 MOV [BX],AL
13D7:0015 43 INC BX
13D7:0016 E2F8 LOOP 0010
13D7:0018 B8004C MOV AX,4C00
13D7:001B CD21 INT 21
13D7:001D 00FF ADD BH,BH
13D7:001F 50 PUSH AX
-u 0:200
0000:0200 46 INC SI
0000:0201 07 POP ES
0000:0202 07 ADC [BP+SI],AL
0000:0204 0A04 OR AL,[SI]
0000:0206 1002 ADC [BP+SI],AL
0000:0208 3A00 CMP AL,[BX+SI]
0000:020A A30354 MOV [5403],AX
0000:020D 00A3036E ADD [BP+DI+6E03],AH
0000:0301 00A30388 ADD [BP+DI+8803],AH
0000:0305 00A303A2 ADD [BP+DI+A203],AH
0000:0309 00A303FF ADD [BP+DI+FF03],AH
0000:0303 0310 ADD DX,[BX+SI]
0000:030F 02A90810 ADD CH,[BX+DI+1008]
-
3.运行程序至正常结束
13D7:001D 00FF ADD BH,BH
13D7:001F 50 PUSH AX
-u 0:200
0000:0200 46 INC SI
0000:0201 07 POP ES
0000:0202 07 ADC [BP+SI],AL
0000:0204 0A04 OR AL,[SI]
0000:0206 1002 ADC [BP+SI],AL
0000:0208 3A00 CMP AL,[BX+SI]
0000:020A A30354 MOV [5403],AX
0000:020D 00A3036E ADD [BP+DI+6E03],AH
0000:0301 00A30388 ADD [BP+DI+8803],AH
0000:0305 00A303A2 ADD [BP+DI+A203],AH
0000:0309 00A303FF ADD [BP+DI+FF03],AH
0000:0303 0310 ADD DX,[BX+SI]
0000:030F 02A90810 ADD CH,[BX+DI+1008]
-g 1d
Program terminated normally
-
4.再次将0:200的内容反汇编结果如下:
0000:0301 00A30388 ADD [BP+DI+8803],AH
0000:0305 00A303A2 ADD [BP+DI+A203],AH
0000:0309 00A303FF ADD [BP+DI+FF03],AH
0000:0303 0310 ADD DX,[BX+SI]
0000:030F 02A90810 ADD CH,[BX+DI+1008]
-g 1d
Program terminated normally
-u 0:200
0000:0200 B8D713 MOV AX,13D7
0000:0203 8ED8 MOV DS,AX
0000:0205 B82000 MOV AX,0020
0000:0208 8EC0 MOV ES,AX
0000:020A BB0000 MOV BX,0000
0000:020D B91800 MOV CX,0018
0000:0210 8A07 MOV AL,[BX]
0000:0212 26 ES:
0000:0213 8807 MOV [BX],AL
0000:0215 41 INC BX
0000:0216 E2F8 LOOP 0210
0000:021B CD21 INT 21
0000:021D 0AC4 OR AL,AH
0000:021F 5E POP SI
对比图1和图4,我们可以看出mov ax,4c00h前的指令序列已经被复制到了0:200处
实验总结:经过这次实验,我掌握了如何将一段内存的数据复制到另一段内存中去,以及如何将程序自身的指令序列复制到另一段内存中去.掌握了如何优化程序来将内存的内容复制到另一段内存去。
这次实验,花费我大量的时间和心血,终于完成了。
实验5 编写、调试具有多个段的程序
(1) 将下面的程序编译连接,用debug加载、跟踪,然后回答问题:
;----------------------------------------------------------
;ex5a1.asm
;------------------------------------------------------------
;AUTHOR:IT007
;DATE:2007/11/11
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends
stack segment
dw 0,0,0,0,0,0,0,0
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,data
mov ds,ax
push ds:[0]
push ds:[2]
pop ds:[2]
pop ds:[0]
mov ax,4c00h
int 21h
code ends
end start
①cpu执行程序,程序返回前,data段中的数据为多少?
答: 0123h,0456h,0789h,0abch,0defh,0fedh,0cabh,0987h
②CPU执行程序,程序返回前,cs=13D5,ss=13D4,ds=13D3.
③设程序加载后,code段的段地址为x,则data段的段地址为x-12h,stack段的段地址为x-2h.
(2) 将下面的程序编译连接,用debug加载、跟踪,然后回答问题:
;----------------------------------------------------------
;ex5a2.asm
;------------------------------------------------------------
;AUTHOR:IT007
;DATE:2007/11/11
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assume cs:code,ds:data,ss:stack
data segment
dw 0123h,0456h
data ends
stack segment
dw 0,0
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,data
push ds:[0]
mov ax,4c00h
int 21h
code ends
end start
①cpu执行程序,程序返回前,data段中的数据为多少?
答:23 01 56 04
②cpu执行程序,程序返回前, cs=13D5,ss=13D4,ds=13D3
.
③设程序加载后,code段的段地址为x,则data段的段地址为x-12h,stack段的段地址为x-2h.
④对于如下定义的段:
name segment
………
name ends
如果段中的数据占n个字节,则程序加载后,该段实际占有的空间为n个字节.
(3) 将下面的程序编译连接,用debug加载、跟踪,然后回答问题:
;----------------------------------------------------------
;ex5a3.asm
;------------------------------------------------------------
;AUTHOR:IT007
;DATE:2007/11/11
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assume cs:code,ds:data,ss:stack
code segment
start: mov ax,stack
mov ss,ax
mov sp,16
mov ax,data
push ds:[0]
push ds:[2]
pop ds:[2]
pop ds:[0]
mov ax,4c00h
int 21h
code ends
data segment
dw 0123h,0456h
data ends
stack segment
dw 0,0
stack ends
end start
①cpu执行程序,程序返回前,data段中的数据为多少?
答:23 01 56 04
②cpu执行程序,程序返回前,cs=13D3H,ss=13D7H,ds=13D6H.
③设程序加载后,code段的段地址为x,则段的段地址为x-10h,stack段的段地址为x-10h。
(4) 如果将1、2、3题中的最后一条伪指令”end start”改为”end”(也就是说,不指明程序的入口),则哪个程序仍然可以正确执行?请说明原因。
答:第3个程序可以正常运行,因为在前两个程序都未指定程序的入口,
而程序的前面一部分为数据,即非程序,当程序被加载入内存时,CS:
IP指 向这些数据,即把这些数据当作指令来执行,这样可能会引发
意想 不到的后果, 严重的甚至可能导致死机。而第3个程序CS:
IP指向程序的第一条指令,因 为数据段定义在程序的末尾,因而程
序可以正常执行。
(5) 程序如下,编写code段中的代码,将a段和b段中的数据依次相加,将结果存到c段中。
;----------------------------------------------------------
;ex5a5.asm
;------------------------------------------------------------
;AUTHOR:IT007
;DATE:2007/11/11
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assuem cs:code,ds:a,es:b
a segment
db 1,2,3,4,5,6,7,8
a ends
b segment
db 1,2,3,4,5,6,7,8
b ends
c segment
db 0,0,0,0,0,0,0,0
c ends
code segment
start:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;以下为我添加的代码
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov ax,a
mov ds,ax
mov ax,b
mov es,ax
mov bx,0
mov di,0
mov cx,8
mov ax,0
s: mov al,[bx]
add al,es:[bx]
mov [bx+32],al
inc bx
loop s
mov ax,4c00h
int 21h
code ends
end start
(6) 程序如下,编写code段中的代码,用push指令将a段中word数据,逆序存储到b段中。
;----------------------------------------------------------
;ex5a6.asm
;------------------------------------------------------------
;AUTHOR:IT007
;DATE:2007/11/11
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assume cs:code
a segment
dw 1,2,3,4,5,6,7,8
a ends
b segment
dw 0,0,0,0,0,0,0,0
b ends
code segment
start:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;以下为我添加的代码
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov ax,a
mov ds,ax
mov ax,b
mov ss,ax
mov sp,10h
mov bx,0
mov cx,8
s:
push [bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end start
总结:经过实验5,我掌握了以下知识:
①我学会了如何将一个段和附加寄存器关联,并将之设置为附加数据段
②我学会了如何使用END伪指令设置IP为一个程序的入口的偏移地址
③我学会了如何程序的数据的段并不是一定要放在程序的前面,它也可以放在程序的末尾,只是这时程序的IP即为程序的入口的偏移地址。
④我学会了如何依次将两个数组的元素相加,并把结果依次存储到另一个数组中去。
⑤我学会了如何使用MOV指令将一个字节单元的内存传送到8位寄存器中去。
⑥我学会了如何使用ADD指令将一个字节型单元的内容加到一个8位寄存器中去。
⑦我学会了如何读取附加段字节型单元的内容到一个8位寄存器中去。
⑧我学会了如何使用循环(至少执行一次的循环)。
⑨我学会了如何使用PUSH指令将一个数组的元素逆序COPY到一个堆栈(或说为数组应该也没什么大碍吧)中去。
心得:在这次实验当中,可以说作为一个初学者该犯的错误我都犯了,比如:试图将一个字型单元的内容读取到一个堆栈中去(这明显是不支持的嘛,PUSH指令怎么说也是一个字操作指令:),还有就是试图使用字操作指令进行字节操作(这明显是不能实现的嘛:),不过只要想相应的寄存器改为8位寄存器就支持了,还有就是忘了在程序的最后加上返回系统的指令(一个程序运行结束之后如果不返回调用它的程序,或操作系统或DEBUG,那么它该去哪里呢?我不知道,也许会发生意想不到的事情吧:)。
这次实验最大的收获就是终于掌握了独立编写一个汇编程序的能力,能解决一些日常遇到的小问题,如将一个数组逆序,将一个数组的元素COPY到另一个数组中去。在使用PUSH指令的过程中也让我更加深刻理解了堆栈的运行机制。
总结:经过实验5,我掌握了以下知识:
①我学会了如何将一个段和附加寄存器关联,并将之设置为附加数据段
②我学会了如何使用END伪指令设置IP为一个程序的入口的偏移地址
③我学会了如何程序的数据的段并不是一定要放在程序的前面,它也可以放在程序的末尾,只是这时程序的IP即为程序的入口的偏移地址。
④我学会了如何依次将两个数组的元素相加,并把结果依次存储到另一个数组中去。
⑤我学会了如何使用MOV指令将一个字节单元的内存传送到8位寄存器中去。
⑥我学会了如何使用ADD指令将一个字节型单元的内容加到一个8位寄存器中去。
⑦我学会了如何读取附加段字节型单元的内容到一个8位寄存器中去。
⑧我学会了如何使用循环(至少执行一次的循环)。
⑨我学会了如何使用PUSH指令将一个数组的元素逆序COPY到一个堆栈(或说为数组应该也没什么大碍吧)中去。
心得:在这次实验当中,可以说作为一个初学者该犯的错误我都犯了,比如:试图将一个字型单元的内容读取到一个堆栈中去(这明显是不支持的嘛,PUSH指令怎么说也是一个字操作指令:),还有就是试图使用字操作指令进行字节操作(这明显是不能实现的嘛:),不过只要想相应的寄存器改为8位寄存器就支持了,还有就是忘了在程序的最后加上返回系统的指令(一个程序运行结束之后如果不返回调用它的程序,或操作系统或DEBUG,那么它该去哪里呢?我不知道,也许会发生意想不到的事情吧:)。
这次实验最大的收获就是终于掌握了独立编写一个汇编程序的能力,能解决一些日常遇到的小问题,如将一个数组逆序,将一个数组的元素COPY到另一个数组中去。在使用PUSH指令的过程中也让我更加深刻理解了堆栈的运行机制。
实验6 实践课程中的程序
(1) 将课程中所有讲解过的程序上机调试,用DEBUG跟踪其执行过程,并在过程中进一步理解所讲内容。
(2) 编程:完成问题中的程序。
问题:编程,将datasg段中的每个单词的前四个字母改为大写字母:
;---------------------------
;pro9.asm
;---------------------------
;AUTHOR:没有风
;DATE:2007/11/18
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;功能:将4个单词的前4个字母转换为大写字母
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;语言:8086,编译工具:masm5.00
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
assume cs:codesg,ss:stacksg,ds:datasg
stacksg segment
dw 0,0,0,0,0,0,0,0
stacksg ends
datasg segment
str0 db '1. display '
str1 db '2. brows '
str2 db '3. replace '
str3 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
s0: push cx
mov si,0
mov cx,4
s: mov al,[bx+3+si]
and al,11011111b
mov [bx+3+si],al
inc si
loop s
add bx,16
pop cx
loop s0
mov ax,4c00h
int 21h
codesg ends
end start
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;总结:经过实战编写上面的程序,我
;1。学会了如何使用二重循环
;2。学会了如何定义并使用二维数组
;3。学会了如何使用行指针和列指针来遍历所要遍历的数组元素
;4。更深入的了解了如何使用一个寄存器来作为二重循环的计算器
;5。更深入的懂得了堆栈的原理及功能(用来暂存数据)
;6。学会了使用MOV指令逐字节的读取数组的元素来对数组赋值
;7。希望在上面的程序里加入三个功能:(1)把数组转换4个字母修改为全部字母(2)在转换字母前打印菜单(3)在转换字母后打印菜单
;8。希望日后往这个程序里添加里一些具体的有实际意义的功能,比如将它修改为一个学生管理系统,可以存储一定数量学生数据,并可以
; 将数据保存到硬盘中,以及从硬盘中读取这些数据,同时可以对这些数据进行增加、修改、删除等操作。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;日期:2007/11/18 20:28
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
汇编实验报告七
由于内容太多,在此仅给出源代码。想详细阅读报告的朋友可以下载附件。
;----------------------------------------------------------------------------------- ;exp7b.asm ;----------------------------------------------------------------------------------- ;AUTHOR:没有风 ;DATE:2007/12/3 ;----------------------------------------------------------------------------------- assume cs:code,ds:data,ss:stack,es:table stack segment ;定义一个堆栈段,用来暂存寄存器的值 dw 16 dup(?) stack ends data segment year db '1975','1976','1977','1978','1979','1980','1981','1982','1983' db '1984','1985','1986','1987','1988','1989','1990','1991','1992' db '1993','1994','1995' income dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514 dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000 employeenum dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226 dw 11542,14430,15257,17800 data ends table segment list db 21 dup ('year summ ne ?? ') ;定义一个长度为21的结构体数组,每个结构体有4个成员 table ends ;分别是年份、总收入、人数、人均收入 code segment start: mov ax,data mov ds,ax mov ax,table mov es,ax mov ax,stack mov ss,ax mov sp,32 mov bx,0 mov si,0 mov di,0 mov cx,21 s: mov ax,84[di] ;取84地址开始的元素,即被除数的低16位 mov dx,86[di] ;取86地址开始的元素,即被除数的高16位 div word ptr 168[si] ;求公司里每年的人均收入 push ax mov ax,[di] ;send the year to table segment mov es:[bx],ax mov ax,[di].2 mov es:[bx].2,ax mov ax,84[di] ;send the income to the table segment mov es:[bx].5,ax mov ax,84[di+2] mov es:[bx].7,ax mov ax,168[si] ;send the employee number to the table segment mov es:[bx+10],ax pop ax mov es:[bx].13,ax ;把商传送到table段中相应内存去,以C的风格访问结构体元素 add di,4 add si,2 add bx,16 loop s mov ax,4c00h int 21h code ends end start ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
实验8 分析一个奇怪的程序
分析下面的程序,在运行前思考:这个程序可以正确返回吗?
通过这个程序加深对相关内容的理解。
;-------------------------------------------------------------------------------
;exp8.asm
;DATE:2007/12/13
;-------------------------------------------------------------------------------
assume cs:codesg
codesg segment
mov ax,4c00h
int 21h
start:mov ax,0
; 占用一个字节的空间,关键,用来添加指令用
s:nop
; 占用一个字节的空间,关键,用来添加指令用
nop
;把标号s处的偏移地址(0008H)传送到DI寄存器
mov di,offset s
mov si,offset s2 ;把标号S2处的偏移地址(0020H)传送到SI寄存器
mov ax,cs:[si] ;把代码段处偏移地址为(si)的内容(0F6EBH,即指令JMP SHORT S翻译后为JMP 0008H)传送到AX寄存器,即把标号S2处的指令传送到AX寄存器(关键)
mov cs:[di],ax ;把AX内容传送到标号S处(S处有两个字节空间可以用来存入一条指令,关键),相当于在S处添加一条指令JMP SHORT S,机器码为0F6EBH,不过翻译后变为JMP 0000H(好奇怪哦,由原来的JMP 00008H变成这个样子了)
s0:jmp short s ;跳转到标号S处执行指令,实际上在上面已经把指令jmp short s复制到了标号S处,因为S为位移值,而不是一个偏移地址,所以执行该指令时,IP寄存器的内容被修改为:S处第二条指令的地址
s1:mov ax,0
int 21h ;(此处指令求被执行)
mov ax,0 ;(此处指令求被执行)
s2:jmp short s1 ;跳转到标号S1处执行指令,汇编为机器码为0EBF6H,经编译器翻译后为JMP 0018H,关键
nop
codesg ends
end start
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov di,offset s
mov si,offset s2
这两条指令的功能是把源内存单元的和目的内存单元的偏移地址传送到相应寄存器中去
mov ax,cs:[si]
mov cs:[di],ax
这两条指令的功能是将源内存单元的指令传送到目的内存单元中去
将源内存单元的指令jmp short s1 传送到目的去,确切的说应该是将机器指令EBF6H(jmp short s1被DEBUG翻译后的机器码)传送到目的内存单元去.这时,DEBUG把这个机器指令解释出来后,并不是我们原来看到的jmp short s1,因为F6H对应的有符号数为-10D,即当执行jmp short s返回s处执行EBF6H指令时,IP的内容加上JMP指令的长度为10,此时再减去10D,正好CS:IP指向mov ax,4c00h这条指令,接着往下执行程序顺利结束。因此程序可以正常返回。
学了好久,才终于将实验8写完,感觉问题是弄明白了,可是说出来又要让懂的人明白却不是那么容易的一回事。
;---------- ;exp9.asm ;---------- ;--------- ;purpose:print 3 strings in the middle of screen by different colors ;--------- assume cs:code,ds:data data segment string0 dw 0277h,0265h,026ch,0263h,026fh,026dh,0265h,0220h,0274h,026fh,0220h,026dh,0261h,0273h,026dh,0221h string1 dw 2477h,2465h,246ch,2463h,246fh,246dh,2465h,2420h,2474h,246fh,2420h,246dh,2461h,2473h,246dh,2421h string2 dw 7177h,7165h,716ch,7163h,716fh,716dh,7165h,7120h,7174h,716fh,7120h,716dh,7161h,7173h,716dh,7121h data ends code segment start: mov ax,data mov ds,ax mov ax,0b800h mov es,ax lea si,string0 mov di,071Eh mov cx,16 s0: mov bx,[si] mov es:[di],bx add di,2 add si,2 loop s0 lea si,string1 mov di,07BEh mov cx,16 s1: mov bx,[si] mov es:[di],bx add di,2 add si,2 loop s1 lea si,string2 mov di,085Eh mov cx,16 s2: mov bx,[si] mov es:[di],bx add di,2 add si,2 loop s2 mov ax,4c00h int 21h code ends end start
实验5.4 我写的程序
assume cs:code
a segment
db 1,2,3,4,5,6,7,8
a ends
b segment
db 1,2,3,4,5,6,7,8
b ends
ca segment
db 0,0,0,0,0,0,0,0
ca ends
code segment
start:
mov ax,a ;用两个循环实现
mov es,ax
mov ax,ca ;最终数据放到ds段中
mov ds,ax
mov bx,0
mov cx,8
s1: mov al,es:[bx] ;将a段中的先加到c段中去
add [bx],al
inc bx
loop s1
mov ax,b
mov es,ax
mov bx,0
mov cx,8
s2:mov al,es:[bx] ;将b段中的先加到c段中去
add [bx],al
inc bx
loop s2
mov ax,4c00h
int 21
code ends
end start
-----------------------
请问楼主的程序中 di 是用来干什么的?
assume cs:code,ds:data data segment db 'W','e','l','c','o','m','e',' ','t','o',' ','m','a','s','m','!' data ends code segment start: mov ax,data mov ds,ax mov ax,0b800h mov es,ax mov bx,071eh mov bp,0 mov cx,15 s: mov al,ds:[bp] mov ah,2h mov es:[bx],ax mov al,ds:[bp] mov ah,24h mov es:[bx+160],ax mov al,ds:[bp] mov ah,71h mov es:[bx+320],ax add bx,2 inc bp loop s code ends end start
在这次实验中,我们将要编写3个子程序,通过它们来认识几个常见的问题和掌握解决这些问题的方法。同前面的所有实验一样,这个实验是必须要独立完成的,在后面的课程中,将要用到这个实验中编写的3个子程序。
1. 显示字符串
问题
显示字符串是现实工作中经常要用到的功能,应该编写一个通用的子程序来实现这个功能。我们应该提供灵活的调用接口,使调用者可以决定显示的位置(行、列)、内容和颜色。
提示
(1) 子程序的入口参数是屏幕上的行号和列号,注意在子程序内部要将它们转化为显存中的地址,首先要分析一下屏幕上的行列位置和显存地址的对应关系:
(2) 注意保存子程序中用到的相关寄存器:
(3) 这个子程序的内部处理和显存的结构密切相关,但是向外提供了与显存结构无关的接口。通过调用这个子程序,进行字符串的显示时可以不必了解显存的结构,为编程提供了方便。在实验中,注意体会这种设计思想。
子程序描述
名称:show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数:(dh)=行号(取值范围0~24),(dl)=列号(取值范围0~79),
(cl)=颜色,ds:si指向字符串的首地址
返回:无
就用举例:在屏幕的8行3列,用绿色显示data段中的字符串。
;========================================================================== ;文件名:exp10a.asm ;目的:完成并测试在指定的位置,用指定的颜色,显示一个用0结束的字符串的子程序 ;========================================================================== assume cs:code,ds:data data segment str db '^_^Welcome to masm! ^_^',0 data ends code segment start: mov ax,data mov ds,ax mov dh,12 mov dl,30 mov cl,10001010b mov si,0 call show_str mov ax,4c00h int 21h ;============================================================== ;名称:show_str ;功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串 ;参数:(dh)=行号(取值范围0~24),(dl)=列号(取值范围0~79), ; (cl)=颜色,ds:si指向字符串的首地址 ;返回:无 ;============================================================== show_str: push dx push si push di push cx push ax mov ax,0b800h mov es,ax mov ax,160 mul dh mov dh,0 add ax,dx add ax,dx sub ax,2 mov di,ax mov ah,cl output: mov ch,ds:[si] mov cl,0 jcxz ok mov byte ptr es:[di],ch mov byte ptr es:[di+1],ah inc si inc di inc di jmp short output ok: pop ax pop cx pop di pop si pop dx ret code ends end start
;================================================================= ;文件名:exp10b.asm ;目的:8086下实现32位除法功能,解决16位除法溢出的问题 ;================================================================= assume cs:code,ss:stack stack segment dw 8 dup(0) stack ends code segment start: mov ax,stack mov ss,ax mov sp,16 mov ax,4c40h mov dx,0fh mov cx,0ah call divdw2 mov ax,4c00h int 21h ;================================================================================= ;名称:divdw ;功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型 ;参数:(ax)=dword型数据的低16位 ; (dx)=dword型数据的高16位 ; (cx)=除数 ;返回:(dx)=结果的高16位,(ax)=结果的低16位 ; (cx)=余数 ;================================================================================== divdw: push bx push ax mov ax,dx mov dx,0 div cx mov bx,ax pop ax div cx mov cx,dx mov dx,bx pop bx ret code ends end start
;===================================================== ;文件名:exp10c.asm ;目的:实现一通用的将十进制数输出到屏幕的子程序 ;===================================================== assume cs:code,ds:data,ss:stack data segment db 10 dup(0) data ends stack segment dw 128 dup(0) stack ends code segment start: mov ax,stack mov ss,ax mov sp,128 mov ax,12666 mov bx,data mov ds,bx mov si,0 call dtoc mov dh,8 mov dl,3 mov cl,2 call show_str mov ax,4c00h int 21h ;================================================================= ;名称:dtoc ;功能:将word型数据转变为表示十进制数的字符串,字符串以0为结尾符 ;参数:(ax)=word型数据 ; ds:si指向字符串的首地址 ;返回:无 ;================================================================= dtoc: push ax push bx push cx push si push di mov di,si mov bx,10 mov cx,0 s: push cx mov cx,ax jcxz enddtoc pop cx mov dx,0 div bx mov ds:[di],dl add byte ptr ds:[di],30h inc di inc cx jmp short s enddtoc: pop cx mov ds:[di],0 dec di mov ax,cx mov bl,2 div bl mov cl,al s1: mov al,ds:[di] mov bl,ds:[si] mov ds:[di],bl mov ds:[si],al inc si dec di loop s1 pop di pop si pop cx pop bx pop ax ret ;=========================================================== ;名称:show_str ;功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串 ;参数:(dh)=行号(取值范围0~24),(dl)=列号(取值范围0~79), ; (cl)=颜色,ds:si指向字符串的首地址 ;返回:无 ;=========================================================== show_str: push dx push si push di push cx push ax mov ax,0b800h mov es,ax mov ax,160 mul dh mov dh,0 add ax,dx add ax,dx sub ax,2 mov di,ax mov ah,cl output: mov ch,ds:[si] mov cl,0 jcxz ok mov byte ptr es:[di],ch mov byte ptr es:[di+1],ah inc si inc di inc di jmp short output ok: pop ax pop cx pop di pop si pop dx ret code ends end start