DOS汇编程序提高练习

2022-07-20 14:32:24 浏览数 (1)

【目的】

​ 汇编程序的编写和提高

【要求】
  1. 使用记事本编写.asm 源程序
  2. 对于按程序进行汇编及连接,产生.exe 文件
  3. 使用visio 绘制流程图
【内容】
【第一个实验】显示复制字符串

编写一个汇编程序,实现字符串的复制功能,并且将复制的字符串显示出来。(选做部分:倒

序显示复制字符串)

(1)实验流程图:

(2)实验源代码(粘贴源代码):

代码语言:javascript复制
DATAS SEGMENT ;定义DATAS 代码段
	string_str db 'The school of Information Science and Engineering Shandong University','$'
;定义string_str 字符串
DATAS ENDS ;

EXT SEGMENT ;定义EXT 附加段
	string_ext db 100 dup (?) ;为字符串string_ext 定义一个100 个字节大小的栈段
EXT ENDS ;

STACKS SEGMENT ;定义栈段
STACKS ENDS ; 

CODES SEGMENT ;CODES 代码段
	ASSUME CS:CODES,DS:DATAS,SS:STACKS,ES:EXT
;将CODES 和CS 连接,DATAS 和DS 连接,EXT 和ES 连接
START:
	MOV AX,DATAS ;把DATAS 的地址存入AX中
	MOV DS,AX ;用 DS 存储DATAS 的地址
	MOV AX,EXT ;把EXT 的地址存入AX 中
	MOV ES,AX ;用ES 存储EXT 的地址
	LEA SI,string_str ; 取string_str 的地址放置到SI 寄存器中
	LEA DI,string_ext ; 取string_ext 的地址放置到DI 寄存器中
	MOV CX,70 ;CX代表循环次数,由于字符总有70个字符,因此这里循环70次。
	STD ;将标志寄存器Flag的方向标志位DF置1,DF=1使串操作指令中操作数地址自动递减,说明字符串的处理是从高地址向低地址方向进行
	REP MOVSB ;使用字符串传送指令MOVSB,这条指令按字节传送数据,用SI 和DI 这两个寄存器控制字符串的源地址和目标地址
	LEA DX,string_ext ;将DX 和string_ext 的地址连接起来
	MOV AH,9
	INT 21H ;调用DOS 功能,输出字符串
	MOV AH,4CH ;令AH等于4CH
	INT 21H ;返回DOS
CODES ENDS 
	END START ;程序结束

(3)实验代码、过程、相应结果(截图)并对实验进行说明和分析:

实验结果如下图所示,实现了复制字符串并显示,符合题意。

【第一个实验】倒序显示复制字符串

(1)实验流程图:

(2)实验源代码(粘贴源代码):

代码语言:javascript复制
DATAS SEGMENT;定义DATAS 段
	string_str db 'The School of Information Science and Engineering Shandong University','$'
;定义string_str字符串
DATAS ENDS;
EXT SEGMENT;定义附加段
	string_ext db 100 dup(?);为string_ext定义一个100 个双字节空间
EXT ENDS
STACKS SEGMENT;定义栈段
STACKS ENDS
CODES SEGMENT;定义CODES段
	ASSUME CS:CODES,DS:DATAS,SS:STACKS,ES:EXT
	;将CODES 和CS 连接,DATAS 和DS 连接,EXT 和ES 连接
START:
	MOV AX,DATAS;把DATAS 的地址存入AX中
	MOV DS,AX ;用 DS 存储DATAS 的地址
	MOV AX,EXT;把EXT 的地址存入AX 中
	MOV ES,AX;用ES 存储EXT 的地址
	LEA SI,string_str; 取string_str 的地址放置到SI 寄存器中
	LEA DI,string_ext ; 取string_ext 的地址放置到DI 寄存器中
	MOV CX,69 ;CX代表循环次数,这里循环69次
	CLD;将标志寄存器Flag的方向标志位DF清零,这里令DF=0使串操作指令中操作数地址自动递增,说明字符串的处理是从低地址向高地址方向进行。
	REP MOVSB;使用字符串传送指令MOVSB,这条指令按字节传送数据,,用SI 和DI 这两个寄存器控制字符串的源地址和目标地址,即复制string_str的内容到string_ext
	LEA DX,string_ext;链接DX 与STRING_EXT
	MOV AH,9
	INT 21H;显示字符串
	MOV AH,2
	MOV DL,0DH
	INT 21H
	MOV DL,0AH
	INT 21H;换行输出
	MOV CX,69;使循环的次数为69
	MOV SI,68;令SI 为68,也就是最后一个字符(不包括$)
LOOPPART:;定义循环部分
	MOV DL,[SI];将si起始的一个字符数据存入DL中;
	MOV AH,2
	INT 21H;将该字符输出
	DEC SI;将SI 自减,实现SI 的前移,逐个输出从而实现这种倒序输出
	LOOP LOOPPART;循环LOOPPART部分
	MOV AH,4CH;令AH等于4CH
	INT 21H;返回DOS
CODES ENDS
	END START;程序结束

(3)实验代码、过程、相应结果(截图)并对实验进行说明和分析:

实验结果如下图所示,实现了复制字符串并倒序显示,符合题目要求。

【第二个实验】

利用中断调用,在屏幕上显示1—9 之间随机数,中断号86H。

(1)实验流程图:

(2)实验源代码(粘贴源代码):

代码语言:javascript复制
DATAS SEGMENT ;定义DATAS 段
	string DB 0DH,0AH,'The Random Number is:','$';定义字符串提示显示随机数
DATAS ENDS 

STACKS SEGMENT STACK ;定义栈段
	DB 200 DUP(0) ;定义200 字节为0 的栈段
STACKS ENDS 

CODES SEGMENT ;定义CODES 段
	ASSUME DS:DATAS,SS:STACKS,CS:CODES
;将DATAS 和DS 连接,STACKS和SS 连接,CODES 和CS 连接

CREATENUM PROC ;定义子程序CREATENUM,产生随机数
	PUSH CX ;将CX 入栈
	PUSH DX ;将DX 入栈
	MOV AH,0 ;令AH的内容为0
	INT 1AH ;设置时钟的“滴答”计数,CX:DX=时钟“滴答”计数,并将之作为随机数生成的种子
	MOV AX,DX ;将DX中的内容存放至AX中
	MOV DX,0 ;将0存放至DX中
	MOV BX,10 ;将10存放至BX中
	DIV BX ;除10从而得到1~9 的随机数
	ADD DL,30H ;将DL中的内容转换为其对应的ASCII码
	MOV AH,02H
	INT 21H ;输出数字
	POP DX ;将CX 出栈
	POP CX ;将CX 出栈
	IRET ;中断返回
CREATENUM ENDP;子程序结束

START: 
	MOV AX,DATAS ;将DATAS的地址存入AX
	MOV DS,AX ;将AX的内容入DS中
	LEA DX,string ;将DX与string 地址链接
	MOV AH,9
	INT 21H ;调用DOS 功能,输出字符串
	MOV AX,0 ;将0存放至AX中
	MOV DS,AX ;段偏移地址指向0000
	MOV BX,86H*4 ;将中断86H 的地址赋给BX,其中每个中断都有CS和IP,一共4个字节,因此乘4
	CLI ;禁止中断发生,在CLI起效之后,所有外部中断都被屏蔽,保证当前运行的代码不被打断,起到保护代码运行的作用
	MOV WORD PTR DS:[BX],OFFSET CREATENUM ;字操作.将CREATENUM的偏移地址存入ES:BX中
	MOV WORD PTR DS:[BX 2],SEG CREATENUM ;字操作.将CREATENUM的段基地址存入ES:[BX 2]中
	STI ;允许中断发生,在STI起效之后,所有外部中断都被恢复,以打破被保护代码的运行,允许硬件中断转而处理中断的作用。
	INT 86H ;调用CREATENUM子程序
	MOV AH,4CH 
	INT 21H ;返回DOS
CODES ENDS 
	END START ;程序结束

(3)实验代码、过程、相应结果(截图)并对实验进行说明和分析:

​ 在此实验中,我采用当前时钟滴答计数,并将之作为随机数生成的种子,利用了中断调

用,多次实验结果如下图所示,产生随机数满足题目要求。

【第三个实验】

键盘输入10 个学生的成绩,编写一个程序统计60-69 分,70-79 分,80-89 分,90-99 分及

100 分的人数,分别存放在Score6,Score7,Score8,Score9 和Score10 单元中。

【输入10 个学生的成绩如下】:65 98 78 82 88 95 72 62 90 100 (1)实验流程图:

(2)实验源代码(粘贴源代码):

代码语言:javascript复制
DATAS SEGMENT;定义DATAS段
	SIGN DB 0DH,0AH,'PLEASE INPUT THE SCORES:','$';定义string 字符串,提示输入
	OUTSCORE6 DB 0DH,0AH,'SCORE6:','$';定义输出时的提示字符串
	OUTSCORE7 DB 0DH,0AH,'SCORE7:','$'
	OUTSCORE8 DB 0DH,0AH,'SCORE8:','$'
	OUTSCORE9 DB 0DH,0AH,'SCORE9:','$'
	OUTSCORE10 DB 0DH,0AH,'SCORE10:','$'
	SCOREOUT DB 0DH,0AH,'OUTSCORE:','$'
	SCORE6 DB 30H;将SCORE全部定义为0的ASCII码值30H
	SCORE7 DB 30H;
	SCORE8 DB 30H;
	SCORE9 DB 30H;
	SCORE10 DB 30H;
DATAS ENDS ;DATAS段结束

STACKS SEGMENT ;定义栈段
	DB 200 DUP(0);定义200 字节为0 的栈段
STACKS ENDS;栈段结束

CODES SEGMENT;定义CODES段
	ASSUME CS:CODES,DS:DATAS,SS:STACKS
;将DATAS 和DS 连接,STACKS和SS 连接,CODES 和CS 连接
START:
	MOV AX,DATAS;将DATAS的地址存入AX
	MOV DS,AX;将AX中的内容放入DS中
	LEA DX,SIGN;将DX 与SIGN连接
	MOV AH,9
	INT 21H;调用DOS 功能,输出SIGN
	MOV CX,10;CX代表循环次数,即循环10次

S:;定义S段
	MOV AH,1
	INT 21H;从键盘上输入一个字符,将其对应字符的ASCII 码送入AL 中,然后输出
	MOV DL,AL;将存储在AL 中的输入字符拷贝到DL 中
	INT 21H ;再输入一个字符,存在AL 中
	CALL COUNT;调用COUNT 子程序进行比较分类
	MOV DL,32;让DL 等于32及空格对应的ASCII 码
	MOV AH,2
	INT 21H;输出空格
	LOOP S;循环输入给定的成绩
	CALL OUTPUT ;调用OUTPUT子程序
	MOV AH,4CH
	INT 21H;返回DOS

COUNT PROC;定义COUNT 子程序
	PUSH DX;将DX入栈以保护数据
	CMP DL,31H;让DL(最高位)与31H即1的ASCII码比较
	JZ S10;若相等则跳转到S10
	CMP DL,36H;将DL(最高位)与36H即6的ASCII码比较
	JZ S6;若相等则跳转到S6
	CMP DL,37H;将DL与37H即7的ASCII码比较
	JZ S7;若相等则跳转到S7
	CMP DL,38H;将DL与38H即8的ASCII码比较
	JZ S8;若相等则跳转到S8
	CMP DL,39H;将DL与39H即9的ASCII码比较
	JZ S9;若相等则跳转到S9
	JMP OVER;跳转到OVER

S6:
	ADD [SCORE6],1;60-69区间的人数加一
	JMP OVER;跳转到OVER
S7:
	ADD [SCORE7],1;70-79区间的人数加一
	JMP OVER;跳转到OVER
S8:
	ADD [SCORE8],1;80-89区间的人数加一
	JMP OVER;跳转到OVER
S9:
	ADD [SCORE9],1;90-99区间的人数加一
	JMP OVER;跳转到OVER
S10:
	ADD [SCORE10],1;100区间的人数加一
	MOV AH,1
	INT 21H ;由于是100,需要多输入一位数字
	JMP OVER;跳转到OVER

over:;OVER 段
	POP DX;将DX出栈
	RET;返回主程序
COUNT ENDP;CONUT子程序结束

OUTPUT PROC;定义OUTPUT子程序
	LEA DX,OUTSCORE6;将DX与OUTSCORE6的段地址链接
	MOV AH,9
	INT 21H;调用DOS 功能,输出OUTSCORE6
	MOV DL,[SCORE6];将[SCORE6]的值存入DL
	MOV AH,2
	INT 21H;输出在60-69区间段的人数
	MOV DL,10
	INT 21H;输出空格

	LEA DX,OUTSCORE7;将DX 与OUTSCORE7地址链接
	MOV AH,9
	INT 21H;调用DOS 功能,输出OUTSCORE7
	MOV DL,[SCORE7];将[SCORE7]的值存入DL
	MOV AH,2
	INT 21H;输出70-79区间段的人数
	MOV DL,10
	INT 21H;输出空格

	LEA DX,OUTSCORE8;将DX 与OUTSCORE8地址链接
	MOV AH,9
	INT 21H;调用DOS 功能,输出OUTSCORE8
	MOV DL,[SCORE8];将[SCORE8]的值存入DL
	MOV AH,2
	INT 21H;输出80-89区间段的人数
	MOV DL,10
	INT 21H;输出空格

	LEA DX,OUTSCORE9;将DX 与OUTSCORE9地址链接
	MOV AH,9
	INT 21H;调用DOS 功能,输出OUTSCORE9
	MOV DL,[SCORE9];将[SCORE9]的值存入DL
	MOV AH,2
	INT 21H;输出90-99区间段的人数
	MOV DL,10
	INT 21H;输出空格

	LEA DX,OUTSCORE10;将DX 与OUTSCORE10地址链接
	MOV AH,9
	INT 21H;调用DOS 功能,输出OUTSCORE10
	MOV DL,[SCORE10];将[SCORE10]的值存入DL
	MOV AH,2
	INT 21H;输出100区间段的人数
	MOV DL,10
	INT 21H;输出空格

	RET;返回主程序
	OUTPUT ENDP;OUTPUT子程序结束
CODES ENDS
	END START;主程序结束

(3)实验代码、过程、相应结果(截图)并对实验进行说明和分析:

以题目中的65 98 78 82 88 95 72 62 90 100为测试样例,实验结果如下图所示,符合题意。

【总结心得】

1、CLD与STD总结:

CLD即(CLear Direction flag)

功能: 将标志寄存器Flag的方向标志位DF清零。在字串操作中使变址寄存器SI或DI的地址指针自动增加,字串处理由前往后。

STD即(SeT Direction flag)

功能:

与CLD相反功能指令是STD,即将方向标志位DF置1。在字串操作中使SI或DI的地址指针自动递减,字串处理由后往前。STD用于将方向标志设置为1,使得Si和/或DI将自动递减到当其中一个字符串指令执行时指向下一个字符串元素。如果方向标志被设置,SI/DI对于字节字符串将减1,对于字符串将减2。

2、MOVSB总结

MOVSB即字符串传送指令,这条指令按字节传送数据。通过SI和DI这两个寄存器控制字符串的源地址和目标地址,比如DS:SI这段地址的N个字节复制到ES:DI指向的地址,复制后DS:SI的内容保持不变。

3、CLI和STI总结

CLI汇编指令全称为Clear Interupt,该指令的作用是禁止中断发生,在CLI起效之后,所有外部中断都被屏蔽,这样可以保证当前运行的代码不被打断,起到保护代码运行的作用。

STI汇编指令全称为Set Interupt,该指令的作用是允许中断发生,在STI起效之后,所有外部中断都被恢复,这样可以打破被保护代码的运行,允许硬件中断转而处理中断的作用。

4、REP总结

REP 指令即“重复前缀指令”, REP 前缀一次只能应用于一条字符串指令。要重复指令块,需要使用 LOOP 指令或其它循环结构。因此需要一个寄存器来控制串长度。这个寄存器就是CX,指令每次执行前都会判断CX 的值是否为0(为0 结束重复,不为0,CX 的值减1),以此来设定重复执行的次数。

5、时钟服务INT 1AH总结

  1. 功能号:00H 功能: 读取时钟“滴答”计数 入口参数:AH=00H 出口参数:AL=00H—未过午夜,否则,表示已过午夜 CX:DX=时钟“滴答”计数
  2. 功能号:01H 功能:设置时钟“滴答”计数 入口参数:AH=01H CX:DX=时钟“滴答”计数 出口参数:无
  3. 功能号:02H 功能:读取时间 入口参数:AH=02H 出口参数:CH=BCD码格式的小时 CL=BCD码格式的分钟 DH=BCD码格式的秒 DL=00H—标准时间,否则,夏令时 CF=0—时钟在走,否则,时钟停止
  4. 功能号:03H 功能: 设置时间 入口参数:AH=03H CH=BCD码格式的小时 CL=BCD码格式的分钟 DH=BCD码格式的秒 DL=00H—标准时间,否则,夏令时 出口参数: 无
  5. 功能号:04H 功能:读取日期 入口参数:AH=04H 出口参数:CH=BCD码格式的世纪 CL=BCD码格式的年 DH=BCD码格式的月 DL=BCD码格式的日 CF=0—时钟在走,否则,时钟停止
  6. 功能号:05H 功能:设置日期 入口参数:AH=05H CH=BCD码格式的世纪 CL=BCD码格式的年 DH=BCD码格式的月 DL=BCD码格式的日 出口参数: 无
  7. 功能号:06H 功能:设置闹钟 入口参数:AH=06H CH=BCD码格式的小时 CL=BCD码格式的分钟 DH=BCD码格式的秒 出口参数: CF=0—操作成功,否则,闹钟已设置或时钟已停止
  8. 功能号:07H 功能:闹钟复位 入口参数:AH=07H 出口参数:无
  9. 功能号:0AH 功能:读取天数计数,仅在PS/2有效
  10. 功能号:0BH 功能:设置天数计数,仅在PS/2有效
  11. 功能号:80H 功能描述:设置声音源信息 入口参数:AH =80H AL =声音源 =00H——8253可编程计时器,通道2 =01H——盒式磁带输入 =02H——I/O通道上的"Audio In" =03H——声音产生芯片 出口参数: 无

​ 有了前几次实验的经历,这次的实验写起来也相对地熟练一些,这次汇编程序的编写和提高的实验也带给我了很多的收获,通过实践的方法使用了MOVSB、CLD与STD、CLI和STI、REP指令等等,但是仍然遇到了一些问题。

​ 在第一个实验中,我认真读了实验资料中的几个串操作类指令的例子,通过这些例子,我大概有了关于复制并显示字符串的思路,我按照思路先画出了实验的流程图,并且通过MOVSB、REP指令写出了程序的实现片段,然后就面临着两个实验共有的问题,如何设定复制字符串的方向,我在网上查阅了相关的资料,看到了有关于CLD和STD的实现,但是有关于ES寄存器的部分让我有点陌生,然后我又复习了王爽的书中有关于这一部分的讲解,将标志寄存器Flag的方向标志位DF清零。在字串操作中使变址寄存器SI或DI的地址指针自动增加,字串处理由前往后,反之,STD即将方向标志位DF置1。在字串操作中使SI或DI的地址指针自动递减,字串处理由后往前。在弄懂程序的基础上,对我写的代码片段进行了完善,最终实现了正序/倒序显示复制字符串的要求。

​ 在第二个实验中,依照老师给出的通过时钟计数,并将之作为随机数生成的种子的方法,按照思路我学习了关于时钟服务INT 1AH的用法,同时又去查询了一次中断表,不得不感叹中断在汇编程序中作用之大,同时也学会了自己设置中断的方法,掌握了CLI 指令:即禁止中断发生,在CLI起效之后,所有外部中断都被屏蔽,保证当前运行的代码不被打断,起到保护代码运行的作用,和STI指令:即允许中断发生,在STI起效之后,所有外部中断都被恢复,以打破被保护代码的运行,允许硬件中断转而处理中断的作用。

​ 第三个实验与以前C语言的实验程序很相似,我先画出了程序的流程图,大概将程序按照功能的模块化思想进行了分类,分为主程序,count计数子程序以及output输出子程序,主要通过比较、跳转至对应的人数SCORE加一的顺序实现,整体来说比较顺利。但是在最后输入成绩时由于S6-9的设置导致S10也只能输入两位数字,虽然不影响最终统计结果,但是我通过给S10多加了一位输入解决了这个小bug。

​ 整体来说,这次实验做的比较顺利,在实验过程中也让我收获颇丰,通过这门课也让我真正地了解到处理器的架构、指令集、寄存器等知识,通过汇编语言也让我从实践的角度强化了对这些知识的理解。

初学汇编,可能存在错误之处,还请各位不吝赐教。

受于文本原因,本文相关实验工程无法展示出来,现已将资源上传,可自行下载。

山东大学微处理器原理实验4工程文件 汇编程序设计编程

0 人点赞