【目的】
汇编程序的编写和提高
【要求】
- 使用记事本编写.asm 源程序
- 对于按程序进行汇编及连接,产生.exe 文件
- 使用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总结
- 功能号:00H 功能: 读取时钟“滴答”计数 入口参数:AH=00H 出口参数:AL=00H—未过午夜,否则,表示已过午夜 CX:DX=时钟“滴答”计数
- 功能号:01H 功能:设置时钟“滴答”计数 入口参数:AH=01H CX:DX=时钟“滴答”计数 出口参数:无
- 功能号:02H 功能:读取时间 入口参数:AH=02H 出口参数:CH=BCD码格式的小时 CL=BCD码格式的分钟 DH=BCD码格式的秒 DL=00H—标准时间,否则,夏令时 CF=0—时钟在走,否则,时钟停止
- 功能号:03H 功能: 设置时间 入口参数:AH=03H CH=BCD码格式的小时 CL=BCD码格式的分钟 DH=BCD码格式的秒 DL=00H—标准时间,否则,夏令时 出口参数: 无
- 功能号:04H 功能:读取日期 入口参数:AH=04H 出口参数:CH=BCD码格式的世纪 CL=BCD码格式的年 DH=BCD码格式的月 DL=BCD码格式的日 CF=0—时钟在走,否则,时钟停止
- 功能号:05H 功能:设置日期 入口参数:AH=05H CH=BCD码格式的世纪 CL=BCD码格式的年 DH=BCD码格式的月 DL=BCD码格式的日 出口参数: 无
- 功能号:06H 功能:设置闹钟 入口参数:AH=06H CH=BCD码格式的小时 CL=BCD码格式的分钟 DH=BCD码格式的秒 出口参数: CF=0—操作成功,否则,闹钟已设置或时钟已停止
- 功能号:07H 功能:闹钟复位 入口参数:AH=07H 出口参数:无
- 功能号:0AH 功能:读取天数计数,仅在PS/2有效
- 功能号:0BH 功能:设置天数计数,仅在PS/2有效
- 功能号: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工程文件 汇编程序设计编程