汇编实验四子程序设计.docx
《汇编实验四子程序设计.docx》由会员分享,可在线阅读,更多相关《汇编实验四子程序设计.docx(15页珍藏版)》请在冰豆网上搜索。
![汇编实验四子程序设计.docx](https://file1.bdocx.com/fileroot1/2022-12/16/bcdc3058-2ca5-4773-b078-dd2dc17bce6e/bcdc3058-2ca5-4773-b078-dd2dc17bce6e1.gif)
汇编实验四子程序设计
福建农林大学东方学院信息工程类实验报告
系:
计算机系专业:
电子信息工程年级:
10
姓名:
廖少冰学号:
1050302103实验课程:
汇编语言
实验室号:
_______实验设备号:
实验时间:
指导教师签字:
成绩:
实验四子程序设计
1.实验目的和要求
1、学习子程序的编写,主子程序的调用
2、不同模块间程序的调用和调试
2.实验用的软硬件环境
实验的硬件环境是:
IBM—PC机及其兼容机
实验的软件环境是:
操作系统:
DOS2.0以上;调试程序:
DEBUG.COM;文本编程程序:
EDIT.EXE、WPS.EXE;宏汇编程序:
MASM.EXE(或ASM.EXE);连接装配程序:
LINK.EXE;交叉引用程序:
CREF.EXE(可有可无)。
3.实验内容及实验数据记录
1、数据段中的3个字符,调用子程序将其逐个显示出来。
子程序的功能是显示一个字符。
单步执行,观察IP的变化
DATASEGMENT
MAGDB‘ABC’
DATAENDS
CODESEGMENT
ASSUMECS:
CODE,DS:
DATA
START:
MOVAX,DATA
MOVDS,AX
MOVSI,0
MOVCX,3
LL:
MOVDL,MAG[SI]
CALLMADD
INCSI
LOOPLL
MOVAH,4CH
INT21H
MADDPROC
MOVAH,02H
INT21H
RET
MADDENDP
CODEENDS
ENDSTART
2、阅读S31.ASM和S32.ASM两个模块中的程序,并分别汇编,然后连接成一个可执行文件S31.EXE。
具体步骤如下:
MASMS31.ASM(分别汇编)
MASMS32.ASM
LINKS31S32(将两个文件连接成为一个文件名为S31)
S31.EXE(运行)
3、编程:
利用主程序调用子程序,比较BUF1和BUF2缓冲区中不相等的字符,并将不相等的字符显示出来。
(也可以将找到的不相等字符逐个显示,用INT21H的02功能调用)
4、编程:
子程序搜索指定字符缓冲区中是否有n,如果有用y替代。
调用子程序将BUF1,BUF2,BUF3中的n全部用y替代
4.操作方法及实验步骤
1-1)文本编辑程序保存V1.asm,编译并连接后debug调试V1.exe。
跟踪如下
u反汇编观察CALL指令所在程序段偏移量为000F而且Call到哪显而易见是0019即MovAH,02与源程序一致而RET指令偏移量为001D但从中我们并不能得知其具体执行过程。
1-2)t单步跟踪,图为执行CALL指令前各个寄存器的状态及查看SS:
IP(d0769:
0000)即栈顶内容为零。
BP始终都为0实际上直到目前都没用到该堆栈。
1-3)继续单步执行,此时IP指向下一条指令CALL0019。
即执行该指令,结果如下可看到SP变为了FFFE
可知执行该指令的同时堆栈处于忙碌的变更中。
之后IP直接指向CALL所指0019。
d0769:
fffe查看栈顶内容如下高向低读为0012
由之前的反汇编得知0012正是INCSI指令的偏移量,而该指令位于Call指令的下一条。
1-4)由此我们推断RET指令的作用就是弹出栈顶内容至IP来使得程序接着运行执行CALL指令后的程序。
同样T单步执行观察如下
很明显执行RET后IP指向了0012。
SP又指向了栈底。
得以证实了CALL调用子程序是通过堆栈保护了现场进入子程序,之后再通过RET恢复到了原状态
2-1)单独阅读两个程序模块似乎毫无瓜葛,只知道S32.ASM模块中是实现的是二进制数转化为十六进制数并在显示器上显示的功能。
S31.ASM实现的是通过子程序TRAN的调用完成对主程序着中获取的两位非压缩BCD数的二进制转化。
按题目要求分别会变S31.asm和S32.asm如下
2-2)分别编辑程序存为S31.asm和S32.asm文件。
进入masm目录。
汇编连接如下均正常通过。
2-3)
运行程序S31.exe结果如下等待输入两位字符:
输入两个字符1和2立即就跟出了000C,输入字符4和5跟出了002D,程序实现了将2位非压缩BCD码转化为了16进制数并打印输出到显示器。
可见diso子程序虽然在另一个程序段中但在主程序中还是能被CALL唯一和在同一段内的调用方式不同的是需要指明是段内子程序,还是段外子程序。
指令中的NEARPTR或FARPTR正是此作用。
3-1)分析题目要求容易得知,程序可分为两部分,一是控制数据缓冲区的数据获取,而自然就是数据处理了亦是题目要求中的子程序实现的功能。
首先定义数据缓冲区有
BUF1DB'MYNAMEISZERO!
'
LEN1EQU$-BUF1
BUF2DB'YOUAREMYGIRL,RIGHT?
'
LEN2EQU$-BUF2
这里就有一个值得考虑的问题,就是缓冲区内的数据长度不等该怎么处理。
对以等长直接下两指针即可方便循环控制。
为方便起见,当然取短的不失为是好的选择,在与长的字符串中比较至短字符串长度LEN1结束并输出不等字符后指针DI所指向字符余下部分直接输出。
因此有如下示
输出BQ……
3-2)因而如何取较短的一者作为循环条件及SI和DI的初始成了问题。
比较可取的方法是先将LEN1假设为最短,传送给CX,同时将BUF1送AX,BUF2送BX。
然后比较CX与LEN2,分支中处理数据交换。
于是有
MOVCX,LEN1
MOVAX,BUF1
MOVBX,BUF2
CMPCX,LEN2
JBL
SUBCX,LEN1-LEN2
XCHGAX,BX
L:
MOVSI,AX
MOVDI,BX
问题得以解决。
既然这样设计,则字符显示功能就应该分离出子程序另作为又一个子程序,方便主程序和比较字符的子程序的调用。
3-3)于是有代码如下
DATASEGMENT
BUF1DB'MYNAMEISZERO!
'
LEN1EQU$-BUF1;计算BUF1长度
BUF2DB'YOUAREMYGIRL,RIGHT?
'
LEN2EQU$-BUF2;计算BUF2长度
DATAENDS
CODESEGMENT
ASSUMECS:
CODE,DS:
DATA
START:
MOVAX,DATA
MOVDS,AX
MOVCX,LEN1;假设BUF1最短送入CX
MOVAX,BUF1;AX存放短字符串首偏移//MOVAX,OFFSETBUF1
MOVBX,BUF2;BX存放长字符串首偏移//MOVBX,OFFSETBUF2
;MOVDH,LEN2-LEN1;下一个类似语句仅在分支中忽略了LEN2大于LEN1
CMPCX,LEN2
JBL
SUBCX,LEN1-LEN2
MOVDX,CX;暂时存放两字符串差//MOVDH,CL
XCHGAX,BX;LEN2较短则交换数据
L:
MOVSI,AX;SI定位较短者
MOVDI,BX;DI定位较长者
S:
CALLCMPP;调用子程序CMPP
INCSI
INCDI
LOOPS;比较字符未完成继续
Y:
INCDI;应该移至Call后才正确
MOVCX,DX;MOVCL,DH并移至Y之前
MOVAL,[DI];MOVDL,[DI]
CALLSHOW;较长字符串余下部分输出
CMPPPROC
MOVAL,[SI];MOVDL,[SI]
CMPAL,[DI];CMPDL,[DI]
JESKIP
CALLSHOW;字符不等调用子程序输出字符
MOVAL,[DI];MOVDL,[DI]
INT21H
SKIP:
RET
CMPPENDP
SHOWPROC
MOVAH,2;子程序调用dos的2号功能
INT21H
RET
SHOWENDP
CODEENDS
ENDSTART
4-1)要求子程序首先检索字符缓冲区内的N,并用Y替代。
首先是各个缓冲区定义如下:
DATASEGMENT
BUF1DB'MNnameisZERO!
'
BUF2DB'NOUAREMNGIRL,RIGHT?
'
BUF3DB'no,youareMNFriend!
'
LENDB$
DATAENDS
4-2)由于各个缓冲区大小不一,所以各缓冲区大小的获取很重要,它将是每一轮循环的控制参数CX。
可将它作为子程序的入口参数,来实现。
而另一入口参数自然就是各缓冲区的首字符的偏移量SI。
所以子程序实现:
DisplacePROC
S:
CMP[SI],'N'
JNENEXT
MOV[SI],'Y'
NEXT:
INCSI
LOOPS
RET
DisplaceNEDP
4-3)主程序实现入口参数的初始化过程,所以有BUF1的实现,其它如是:
MOVCX,BUF2-BUF1;一直犯同样的错误
MOVSI,BUF1;CX初始化字符串长度,SI初始为字符串入口
CALLDisplace;调用Displace子程序
4-4)所以有最初代码如下为便于观察,处理后打印输出各字符串
DATASEGMENT
BUF1DB'MYNAMEISZERO!
'
BUF2DB'NOUAREMNGIRL,RIGHT?
'
BUF3DB'NO,YOUAREMYFRIEND!
'
LENDB$
DATAENDS
CODESEGMENT
ASSUMECS:
CODE,DS:
DATA
START:
MOVAX,DATA
MOVDS,AX
MOVCX,BUF2-BUF1
MOVSI,BUF1
CALLDisplace
MOVCX,BUF3-BUF2
MOVSI,BUF2
CALLDisplace
MOVCX,LEN-BUF3
MOVSI,BUF2
CALLDisplace
MOVAH,4CH
INT21H
DisplacePROC
S:
CMP[SI],'N'
JNENEXT
MOV[SI],'Y'
NEXT:
MOVDL,[SI]
CALLShow
INCSI
LOOPS
RET
DisplaceNEDP
ShowPROC
MOVAH,2
INT21H
RET
ShowEDNP
5实验数据处理和分析
3-1)编辑最初代码编译出错
更正错误MOVAX,BUF1为MOVAX,OFFSETBUF1
MOVBX,BUF2为MOVBX,OFFSETBUF2
及录入错字,正常通过编译。
运行程序出错如下:
显然忘了退出到dos界面是一个原因,但不是主要原因。
检查程序发现对于较长字符串的余下部分的处理忘了循环。
修正后出现如下错误:
修改MOV4CH为MOVAH,4CH重新编译。
运行结果仍旧如上不停打印字符,显然问题很大。
3-2)还是单步跟踪来找错,debugV3.exe进入debug环境调试V3.exe。
如图发现虽然调用了dos的2号输出字符功能,但把本该传送给DL的字符错传给了AL,更正之。
并替换之前的DX的使用。
终于输出如下结果,为了便已对比,为程序添加换行输出功能。
4-1)编辑程序代码,编译出错,安提示定位到错误处修改之。
没引起注意,还是同样的错误,MOVCX,BUF2-BUF1、MOVSI,BUF1修改为
MOVCX,OFFSETBUF2-BUF1、MOVSI,OFFSETBUF1为了引以为戒,这以后干脆直接用LEA指令,以避免事后犯同样的错误。
而Constantexpected不知到是否和程序指令冲突了定义还是怎么,先修改之为LENTH。
错误23和25行均容易更正。
指定内存大小BYTEPTR即可。
4-2)之后才发现程序段未下结束标记
修改之,重新编译如下看来其它错误并非由以上引起。
同时LENTH还是不行。
所以猜想是后边的DB问题,恍然大悟,这里无须开辟空间,直接用伪指令EQU即可。
修改后:
LENEQU$。
重新编译。
至于后面的问题是由于修改之前的初始化过程LEA又犯了其它错误,LEA指令将地址载入目的操作数。
可是操作数明明可以是表达式的,为什么提示当前模式非法呢。
4-3)暂时用原始方法修改回来:
MOVCX,OFFSETBUF2-BUF1,其它同。
可又出现了如下错误:
17行中MOVCX,LEN-BUF1提示操作数不在同一段内或是常量,对比之前(上一题)的使用,我们忽略了这样一个问题,由于EQU并没在内存开辟新空间,为此将LEN视为常量,之前我们都是常量表达式出现,所以未出错。
这里BUF1是地址即内存操作数,所以猜想问题可能处在此处。
修改EQU指令为LENEQU$-BUF3,同时修改指令MOVCX,LEN-BUF3为MOVCX,LEN,问题应该可以解决。
31和36是子程序的标识,想是违反了命名规则。
修改为大写一试,错误并非由此引起。
仔细观察子程序的定义,原来是ENDP错写成EDNP。
修改之正常通过编译并连接运行。
4-4)经以上修改,最终完整程序代码如下
DATASEGMENT
BUF1DB'MNnameisZERO!
'
BUF2DB'NOUAREMNGIRL,RIGHT?
'
BUF3DB'no,youareMNFriend!
'
LENEQU$-BUF3
DATAENDS
CODESEGMENT
ASSUMECS:
CODE,DS:
DATA
START:
MOVAX,DATA
MOVDS,AX
MOVCX,OFFSETBUF2-BUF1
LEASI,BUF1;CX初始化BUF1长度,SI初始为BUF1入口
CALLRE;调用子程序进行字符检索和替换
MOVCX,OFFSETBUF3-BUF2
LEASI,BUF2;CX初始化BUF2长度,SI初始为BUF2入口
CALLRE;调用子程序进行字符检索和替换
MOVCX,LEN
LEASI,BUF3;CX初始化BUF3长度,SI初始为BUF3入口
CALLRE;调用子程序进行字符检索和替换
MOVAH,4CH
INT21H;退出到DOS界面
REPROC
S:
CMPBYTEPTR[SI],'N';检索字符‘N’
JNENEXT
MOVBYTEPTR[SI],'Y';找到‘N’则替换为‘Y’
NEXT:
MOVDL,[SI];将每一个遍历字符传送给DL准备显示
CALLSHOW;显示字符的子程序
INCSI
LOOPS
RET
REENDP;字符处理操作子程序
SHOWPROC
MOVAH,2
INT21H
RET
SHOWENDP;子程序调用DOS的2号显示功能
CODEENDS
ENDSTART
6.实验结果
3)对比字符串得知结果比较部分正确,但”RIGHT?
“部分只有输出'I'。
仔细检查代码发现在给CL赋值前CH并未初始化为0。
在Y:
前添加MOVCH,0;修改之,但不是问题所在,忽略了一个很大的问题就是由于CX是在分支里保存两串差值,导致了在LEN1小于LEN2时并没给CX第二次初始化为差值,所以有了该问题。
因此在MOVBX,OFFSETBUF2后添加指令MOVDH,LEN2-LEN1,将MOVCL,DH提前出Y之上。
修改后最终还露了’R‘,原因就是进入余下字符串前DI多加了1,将INCDI移动至CALL之后,重新编译运行的结果正确如下:
4)运行程序输出正确结果,至此该程序实现了所设计的功能。
7.质疑,建议,问题讨论,总结
在前几次实验的基础与学习中不断的吸取教训,此次实验完成的很顺利,设计的逻辑很少出现问题,唯一应该引起注意的是指令的语法规则在这次实验中是阻碍实验前行的一大障碍,粗心大意的编辑程序没有保持分析设计需求时的严谨态度,给自己添了原本不该出现的不少麻烦。
总之几次实验下来,受益匪浅,不仅是巩固了理论知识,还很大程度上提升了我分析并解决问题的能力,更重要的是每一次实验中的羁绊让我对生活和学习的态度的着重要性有了新的认识。
这将会是我人生中的一大财富。