前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >DOS子程序汇编样例及详解

DOS子程序汇编样例及详解

作者头像
timerring
发布2022-08-03 18:45:55
6230
发布2022-08-03 18:45:55
举报
文章被收录于专栏:TechBlogTechBlog
【目的】
  1. 理解汇编语言中的ASSUME 伪指令和标准的汇编程序
  2. 掌握Debug-P/G/T 的关系和区别
  3. 掌握将十六进制数转换为十进制数的方法和程序
  4. 学习和改进两位数加法的程序
【样例要求】
  1. 使用记事本编写.asm 源程序
  2. 对于按程序进行汇编及连接,产生.exe 文件。若出错,则进行debug。
  3. 使用visio 绘制程序的流程图
【具体内容】

知识总结:Debug中 -P/-G/-T命令的区别

1、P和T都是执行,像这个语句add ax,bx ,你不管用哪个,都是执行这一句,但如果是call next 这个next是一个程序段,那么就不一样了,用P,直接就把这段程序执行完了,用T则进入内部一句一句的执行.这个和C语言的那些调试一样,有的进入函数内部,有的就执行完函数。

2、具体如下:

T命令:执行以CS:IP开始的一个或几个指令,并显示出执行每条指令后所有寄存器的内容。也称单步跟踪命令(step in),t命令是单步执行,遇到子程序,也会进入里面一步步执行再返回。

P命令:执行循环、重复的字符串指令、软件中断或子例程。单步执行命令(step over),p命令,大多数情况与t一样,只有当遇到call调用子程序的时候,p命令直接执行完这个程序。

G命令:连续执行内存代码,可以在g后面指定内存地址。格式: G [=<地址>[,<断点>]]

上式等价于: (1) G (2) G=<地址> (3) G=<地址>,<断点>

功能: 执行内存中的指令序列 注: (1) 从CS:IP所指处开始执行 (2) 从指定地址开始执行(3) 从指定地址开始执行,到断点自动停止。

【一】将键盘上输入的十六进制数转换成十进制数,并在屏幕上显示。
(1)流程图:

(2)源代码:
代码语言:javascript
复制
DATAS SEGMENT ;定义数据段代码
	STR1 DB 'Please enter a hexadecimal number',10,'$';定义提示字符串
	STR2 DB 10,'Please enter a right number',10,'$';定义错误提示字符串
DATAS ENDS

STACKS SEGMENT STACK
	DW 8 DUP(?) ;保留8个字变量的位置
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START: 
	MOV AX,DATAS
	MOV DS,AX ;将数据段的地址赋给DS
	MOV AX,STACKS
	MOV SS,AX ;将栈的段地址赋给SS
	MOV SP,20H ;指出栈顶
	LEA DX,STR1 ;利用LEA直接将STR1的内容赋值到DX中
	MOV AH,9H ;将9H赋值到AH中
	INT 21H ;输出提示字符串
	MOV BX,0 ;将0赋值到BX中
INPUT:
	MOV AH,1H
	INT 21H ;从键盘上输入一个字符,将其对应字符的ASCII码送入AL中,并在屏幕上显示该字符
	ADD DX,1 ;输入数字
	CMP AL,0DH
	JE HH ;若判断结果相等,即输入回车时则跳转至HH
JUDGE: 
	CMP AL,'f' ;比较输入的字符和f的ASCII码大小
	JA ERROR ;无符号大于则跳转至ERROR
	CMP AL,'a'
	JNB SIT1 ;无符号不小于则跳转至 SIT1
	CMP AL,'F' ;判断输入的字符是否是A~F
	JA ERROR ;无符号大于则跳转至ERROR
	CMP AL,'A'
	JNB SIT2 ;无符号不小于则跳转至SIT2
	CMP AL,'9' ;判断输入的字符是否是1~9
	JA ERROR ;无符号大于则跳转至ERROR
	CMP AL,'0'
	JNB SIT3 ;无符号不小于则跳转至SIT3
	JMP ERROR ;跳转至ERROR处
SIT1: 
	SUB AL,57H ;若位于a—f 之间,则AL-57H
	JMP TRA ;无条件跳转至TRA
SIT2: 
	SUB AL,37H ;若位于A-F 之间,则AL-37H
	JMP TRA ;无条件跳转至TRA
SIT3: 
	SUB AL,30H ;若为0—9,则AL-30H
	JMP TRA ;无条件跳转至TRA
TRA: 
	ADD DX,1
	MOV AH,0H ;将AH置零
	JE INPUT
	MOV CX,4H ;将循环次数设置为4
S: ROL BX,1 ;将bx左移四位
	LOOP S
	ADD BX,AX
	JMP INPUT ;跳转至输入阶段
HH: 
	MOV AX,BX ;将bx的值赋给ax
	MOV BX,10 ;设置除数为16位,用于解决四位十六进制数字。
	MOV CX,0
CIR: 
	MOV DX,0 ;输入的是四位及以下十六进制数字,因此被除数高位置零
	ADD CX,1 ;为输出时循环结束做准备
	DIV BX ;AX中的数字除以10,ax存储商数,dx中存储余数
	PUSH DX ;之后将余数入栈
	CMP AX,0 ;直到商为0时结束循环
	JNE CIR
NEXT: 
	POP AX ;将余数出栈
	MOV DL,AL ;转入DL 准备输出
	ADD DL,30H ;余数位于1—9 之间,因此需要将AL+30
	MOV AH,2
	INT 21H ;输出该十进制数字
	LOOP NEXT ;根据cx中的值进行循环输出的操作
	JMP STOP ;跳转至STOP
ERROR: ;错误情况处理
	LEA DX,STR2 ;获取STR至DX中
	MOV AH,9H
	INT 21H ;输出该提示语句
	JMP INPUT ;跳转至输入
STOP: 
	MOV AH,4CH
	INT 21H
	CODES ENDS
	END START
(3)结果分析:

当输入十六进制数时,显示其对应的十进制数字。符合题意。

当输入错误字符时,程序输出错误信息,并重新回到输入状态。符合题意。

【二】判断该年是否为闰年
(1)流程图:

(2)源代码:
代码语言:javascript
复制
data segment ;代码段开始
	infon db 0dh,0ah,'Please input a year:$' ;infon 用于字符串,0d 回车,oa 换行,然后显示'Please input a year:'
	Y db 0dh,0ah,'This is a leap year!$' ;Y 用于定义字符串,同上,回车换行后显示'This is a leap year!'
	N db 0dh,0ah,'This is not a leap year!$';N用于定义字符串,同上,回车换行显示'This is not a leap year!'
	w dw 0  ;声明空间存储输入年份解析后生成的年份数字
	buf db 8  ;定义缓冲区,准备接受8 个字符
		db ?  ;实际接受的字符数,初始化为空
		db 8 dup(?)  ;初始化,dup 是一条伪指令,用于重复初始化数据
data ends  ;代码段结束

stack segment ;栈段开始
	db 200 dup(0) ;定义一个200 字节的栈段,初始化的值0
stack ends ;栈段结束

code segment ;代码段开始
			assume ds:data,ss:stack,cs:code ;将代码段和cs 连接,data 和ds 连接,把stack 和ss 连接
	start: mov ax,data ;将data 的地址放到ax 中
		mov ds,ax ;将ax的内容存入ds 中,ds 存储data 的地址
		lea dx,infon ;在屏幕上显示提示信息
		mov ah,9
		int 21h ;提示输入一个字符串
		lea dx,buf ;将dx 与buf 的段地址链接
		mov ah,10
		int 21h ;提示键盘输入一个字符串
		mov cl,[buf+1] ;高位置零,保证cx 的数值与实际输入长度一致
		mov ch,0 ;让ch 等于0,保证cx 的值为[buf+1]对应字节的值
		lea di,buf+2 ;获取字符串首地址
		call datacate ;调用子程序datacate
		call ifyears ;调用子程序ifyears
		jc a1 ;当cf=1 时,跳转至A1 处执行
		lea dx,n ;获取n 的地址
		mov ah,9
		int 21h ;输出n 的提示信息,不是闰年
		jmp exit ;跳转至exit
a1:     lea dx,y ;获取y 的地址
		mov ah,9
		int 21h ;输出y 的信息,是闰年
exit:   mov ah,4ch 
		int 21h ; 因为AH 值为4C,代码段结束,返回DOS
datacate proc near ;说明datacate 子程序在主程序段内
		push cx ;将cx 压入栈中备份
		dec cx ;将cx 自减1,保证循环中使得si 指向最后一个字符(即回车符前的字符)
		lea si,buf+2 ;将si 与buf+2 的段地址链接(第三个字节存的才是从键盘输入的字符),获取buf 字符串的首地址
	tt1:inc si ;将si+1
		loop tt1 ;循环tt1 段代码
		pop cx ;将备份的cx 的值取出
		mov dh,30h ;用来将数字字符对应的ASCII 值转换为其代表的数字本身
		mov bl,10 ;让bl 的值等于10,在每进一位时使ax 乘10
		mov ax,1 ;让ax 的值等于1,其代表权值
	l1: push ax ;将ax 压入栈中备份
		push bx ;将bx 压入栈中备份
		push dx ;将dx 压入栈中备份
		sub byte ptr [si],dh ;将ASCII 码-30,转换成对应数字
		mov bl,byte ptr [si] ;获取该位的数字
		mov bh,0 ;BX 寄存器高位置零
		mul bx ;将cx 的值乘上bx中代表的权值,并存在ax 中
		add [w],ax ;把ax 的值加在结果上得到年份数字
	pop dx ;恢复dx 的值
	pop bx ;恢复bx 的值
		pop ax ;恢复ax 的值
		mul bl ;cx 的值乘10
		dec si ;si 中的内容自减1,让si 指向上个字符
		loop l1 ;循环l1 段,CX 控制循环次数
		ret ;子程序返回
datacate endp ;子程序结束

ifyears proc near ;说明datacate 子程序在主程序段内
		push bx ;将bx 压入栈中备份
		push cx ;将cx 压入栈中备份
		push dx ;将dx 压入栈中备份
		mov ax,[w] ;将年份放到ax 中
		mov cx,ax ;让年份转入CX 备份
		mov dx,0 ;将DX 置零
		mov bx,100 ;将bx 赋值100作为除数
		div bx ;将年份除以100
		cmp dx,0 ;将余数dx 的值与0 作比较
		jnz lab1 ;若结果不为0,跳转到lab1
		mov ax,cx ;将cx 的值放入ax 中
	mov bx,400 ;让除数的值等于400
		div bx ;将年份除以400
		cmp dx,0 ;将余数dx 的值与0 作比较
	jz lab2 ;若结果为0,则执行lab2
	clc ;将标记位c清零
	jmp lab3 ;跳转到lab3
lab1: mov ax,cx ;lab1 段代码:将cx 的值放入ax 中
	mov dx,0 ;dx置零
	mov bx,4 ;将bx 的内容赋值为4
	div bx ;将年份除以4
	cmp dx,0 ;将余数dx 的值与0 作比较
	jz lab2 ;若结果为0,则执行lab2
	clc ;将标记位c清零
		jmp lab3 ;跳转到lab3
lab2: stc ;标志位设置为1
lab3: pop dx ;恢复dx的值
	pop cx ;恢复cx的值
	pop bx ;恢复bx的值
	ret ;子程序返回
  ifyears endp ;子程序结束
code ends ;代码段结束
	end start ;程序结束
(3)结果分析:

这里选择有代表性的2022、2020、2000、1900作为样例进行测试。

2022不能被4整除,非闰年。符合题意。

2020能被4整除,闰年。符合题意。

1900 能被100 整除,但不能被400 整除,非闰年。符合题意。

1900 能被100 整除,也能被400 整除,闰年。符合题意。

【三】汇编实例学习和改进:两位数加法
1. 3+5 程序

(1)流程图:

(2)源代码:

代码语言:javascript
复制
DATAS SEGMENT;数据段开始
	five db 5 ;定义five,值为5的字节变量
DATAS ENDS;数据段结束

STACKS SEGMENT;栈段开始
	db 128 dup(?);定义栈为128 个双字节的不做初始化的空间
STACKS ENDS;栈段结束

CODES SEGMENT;代码段开始
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:;主程序开始
	MOV AX,DATAS;将段地址装入段寄存器
	MOV DS,AX;将DS 与DATAS 相连接
	MOV AL,FIVE;令AL 的值为FIVE,即5
	ADD AL,3;将寄存器中的值取出,加上3后放回
	ADD AL,30H;将AL存的值+30H,得到ASCII 码
	MOV DL,AL;将待输出字符的ASCII码传到DL中去
	MOV AH,2
	INT 21H;输出DL
	MOV AH,4CH
	INT 21H;返回DOS系统
CODES ENDS;代码段结束
END START;程序结束

(3)代码、过程、相应结果的说明和分析:

结果符合预期。

2. 两变量加法程序

(1)流程图:

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

代码语言:javascript
复制
DATAS SEGMENT ;DATAS 代码段开始
	num1 db 0 ;定义num1
	num2 db 0 ;定义num2
	add1 db '+$' ;定义add1,内容为“+”
	equ1 db '=$' ;定义equ1,内容为“=”
DATAS ENDS ;DATAS 代码段结束

STACKS SEGMENT ;栈段开始
	db 128 dup(?) ;定义一个128 字节的栈段
STACKS ENDS ;栈段结束

CODES SEGMENT ;代码段开始
ASSUME CS:CODES,DS:DATAS,SS:STACKS;把CODES 代码段和CS 链接起来,DATAS 和DS 连接起来,把STACKS 和SS 连接起来
START: ;主程序开始
	MOV AX,DATAS ;将DATAS 的地址放到AX 中
	MOV DS,AX ;将DS 与DATAS 连接
	MOV AH,1
	INT 21H ;从键盘上输入第一个字符
	SUB AL,30H ;AL 的值-30H,转换为对应的数字
	MOV num1,AL ;将AL 的值放入num1中
	LEA DX,add1 ;将DX 与add1 的段地址链接
	MOV AH,9
	INT 21H ;输出该内容
	MOV AH,1
	INT 21H ;从键盘上输入字符
	SUB AL,30H ;AL 的值-30H,转换为对应的数字
	MOV num2,AL ;将AL 的值放入num1中
	LEA DX,equ1 ;将DX 与equ1的段地址链接
	MOV AH,9
	INT 21H ;输出该内容
	MOV AL,num1 ;把num1内容放入AL 中
	ADD AL,num2 ;把AL内容加上num2
	ADD AX,30H ;将AX 的值+30H转换为对应的ASCII 码
	MOV DL,AL ;将AL 的值存入DL 中
	MOV AH,2
	INT 21H ;输出该值
	MOV AH,4CH 
	INT 21H ;AH 值为4C,返回DOS
CODES ENDS ;代码段结束
END START ;主程序结束

(3)代码、过程、相应结果的说明和分析:

这里先采用上个实验中的3和5作为测试样例,得到的结果如下:

但是由于在写程序只用了AL存储结果,因此该程序无法输出多于一位的结果数,这里采用6和7作为测试样例,结果符合预期。程序溢出,产生了bug,需要进一步的改进。

3. 两位数加法程序

(1)流程图:

(2)源代码:

代码语言:javascript
复制
DATAS SEGMENT
	infon1 db 0dh,0ah,'Please enter the first number:$';定义提示语句
	infon2 db 0dh,0ah,'Please enter the second number:$'
	infon3 db 0dh,0ah,'The sum is:$'
buf1 db 8 ;定义第一个缓冲区,存储第一个数字
db ?
db 8 dup(?)
buf2 db 8 ;;定义第二个缓冲区,存储第二个数字
db ?
db 8 dup(?)
DATAS ENDS

STACKS SEGMENT
	DB 128 dup(?);定义栈段代码
STACKS ENDS

CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
	MOV AX,DATAS
	MOV DS,AX
	lea dx,infon1
	MOV AH,9
	INT 21H
	lea dx,buf1
	mov ah,0ah
	int 21h ;输入第一个两位数
	MOV BL, [buf1+2];将十位存入bl
	SUB BL,'0';减去0对应的ASCII码,即转换为数字
	MOV BH, [buf1+3];将个位存入bh
	SUB BH,'0';减去0对应的ASCII码,即转换为数字
	lea dx,infon2
	mov ah,9
	int 21h
	lea dx,buf2
	mov ah,0ah
	int 21h ;输入第二个两位数
	
	lea dx,infon3 ;输出结果提示语
	mov ah,9
	int 21h
	MOV CL,[buf2+2] ;CL存入第一个数字的十位
	SUB CL,'0' ;减去0对应的ASCII码,即转换为数字
	MOV CH,[buf2+3] ;CH存入第一个数字的个位
	SUB CH,'0' ;减去0对应的ASCII码,即转换为数字
	add bl,cl ;将两个数十位相加
	add bh,ch ;将两个数个位相加
	cmp bh,10 ;个位与10 比较,考虑进位的问题
	jge units ;若小于10 则跳到units
units:;个位进位
	sub bh,10 ;个位减10,得到个位数字
	add bl,1 ;十位进1
	jmp tens ;继续判断十位数字加法
tens:;十位判断
	cmp bl,10 ;将十位数字与10 作比较
	jge carry ;若大于10 则跳到十位进位代码段carry
	jmp output ;否则跳至输出
carry:;十位进位代码段
	sub bl,10 ;十位有进位,将和的十位数字减10
	mov dl,31h;进位为1
	mov ah,02h
	int 21h
	jge output;跳至输出
output:
	mov dl,bl
	add dl,30h
	mov ah,02h
	int 21h ;把十位的值赋值到dl 并且输出该数字
	mov dl,bh
	add dl,30h
	mov ah,02h
	int 21h ;把个位的值赋值到dl 并且输出该数字
	mov ah,4ch
	int 21h;返回DOS
CODES ENDS;代码段结束
END START;主程序结束

(3)代码、过程、相应结果的说明和分析:

这里以98和88作为测试样例,结果符合预期。但是仍存在问题,就是由于编写程序中寄存器存储接收数字的逻辑,未能实现两位数加一位数的功能,若相加只能让一位数通过高位补零的方式完成,因此整个程序还有不断改进的空间。

【心得】

call指令和ret指令:CALL 指令调用一个过程,指挥处理器从新的内存地址开始执行。过程使用 RET(从过程返回)指令将处理器转回到该过程被调用的程序点上。 从物理上来说,CALL 指令将其返回地址压入堆栈,再把被调用过程的地址复制到指令指针寄存器。当过程准备返回时,它的 RET 指令从堆栈把返回地址弹回到指令指针寄存器。

LEA指令可以用来将一个内存地址直接赋给目的操作数,例如:lea eax,[ebx+8]就是将ebx+8这个值直接赋给eax,而不是把ebx+8处的内存地址里的数据赋给eax。

mul 指令: 两个相乘的数, 要么都是 8 位, 要么都是 16 位. 如果是 8 位, 一个默认放在 AL 中, 另一个放在 8 位 reg 或内存字节单元中; 如果是 16 位, 一个默认再 AX 中, 另一个放在 16 位 reg 或内存子单元中。结果:如果是 8 位乘法, 结果默认放在 AX 中; 如果是 16 位乘法, 结果高位默认在 DX 中存放, 低位在 AX 中存放。

div 指令:一般格式为:div reg或div 内存单元,reg和内存单元存放的是除数,除数可分为8位和16为2种。被除数:默认放在AX或DX和AX,如果除数为8位,被除数则为16位,默认在AX中存放;如果除数 为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。

​ 整体回顾此次实验,最开始直接浏览题时即使有思路,但是不知道如何去用汇编实现这个思路,各种地址,取址,移位等操作,有时输出的结果和自己预想中的不一样,出现乱码,此时还会一条一条地debug程序,整个过程毫无疑问是非常考验耐心的,且设计程序时有了一定的思路在实现中遇到很多考虑不周的地方,导致再分析修改的思路,前前后后发现又回到了起点。

​ 不过后来我又仔细地阅读了两遍老师写的实验PDF,我采用先阅读理解出老师给出的实验二判断闰年的那个代码,然后查了一些不熟悉的指令用法,阅读源码给我带来的收获颇丰,之前大概知道lea,push,pop等指令的用法,但具体却用的不灵活,能实现的功能有限,理解老师的源码时会有种恍然大悟的感觉:原来是这么用的。再模仿着实验二模块化的思想,顺着老师PDF的一步一步引导慢慢实现了其他实验。同时在这个过程中也查阅了不熟悉的指令用法与具体使用,整体上感觉比我前面几次实验收获更大。

​ 在第一个实验中,最开始我想的是比较常规的做法,即先将十六进制转换为二进制,再将二进制转换为十进制输出,但后来在具体实验过程中发现过于复杂冗余,且消耗的内存资源较多,实现起来并不方便。之后就回到直接除以十取余的转换方法。在判断输入字符时,需要多次跳转,因此借助老师实验二中模块化的思想,也照着采用模块化的定义方法,实现了最终的功能。

​ 在有了前两个实验的基础上,写第三个实验不像最初看题时的感觉了,在3+5实验的基础上,我做了一点改进,写了一个初步的两变量加法程序,但是由于在写程序只用了AL存储结果,因此该程序无法输出多于一位的结果数,采用6和7作为测试样例,程序溢出,产生了bug,需要进一步的改进。在最终的两位数加法程序中,采用了多个寄存器,分开个位和十位数字,并求和,再分开判断个位和十位是否需要进位,写到最后我发现汇编和之前学过的C语言写程序很相似,只不过汇编通过取址等操作以及寄存器实现。在实验运行出现预计的结果时,内心是异常喜悦的。

​ 现在回过头来看我写的第一个实验报告,有种莫名的感叹,虽然仅过去三周,但是让我对汇编的理解产生了翻天覆地的变化,我深切地感受到,学习一门语言不只是要逐个学习每一条指令就算掌握了这门语言,更重要地是通过实际地在应用中使用它,往往会有更好地理解,虽然现在只是学习了一点汇编的基础知识,但是对汇编的理解却有了很大的变化。

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

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

山东大学微处理器原理实验3工程文件 子程序汇编实验

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-07-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 【目的】
  • 【样例要求】
  • 【具体内容】
    • 【一】将键盘上输入的十六进制数转换成十进制数,并在屏幕上显示。
      • (1)流程图:
      • (2)源代码:
      • (3)结果分析:
    • 【二】判断该年是否为闰年
      • (1)流程图:
      • (2)源代码:
      • (3)结果分析:
    • 【三】汇编实例学习和改进:两位数加法
      • 1. 3+5 程序
      • 2. 两变量加法程序
      • 3. 两位数加法程序
  • 【心得】
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档