当前位置:   article > 正文

《汇编语言》第10章 -CAll和RET指令 检测点及实验10答案_补全程序,使执行后,bx=3

补全程序,使执行后,bx=3

检测点10.1
补全程序,使从内存1000:000处开始执行指令。

assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,10h
mov ax,1000		;由程序下面的转移指令retf可知,首先入栈的是内存段地址
push ax
mov ax,0		;其次入栈的是偏移地址
push ax
retf
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

此问题较为简单,

检测点10.2
程序执行后,ax中的数值是多少?

ax=6		
  • 1

;分析
1)当cs:ip指向1000:3时,‘call s’指令的机器码进入指令缓冲器,ip指向下一条指令开始处(6);
2)CPU执行’call s’。将当前ip压入栈(6),并修改当前ip为标号s的偏移地址;程序跳转到1000:7处执行(即标号s处)。
3)执行’pop ax’。
所以,ax的值是6.

检测点10.3
程序执行后,ax中的数值是多少?

ax=1010h
  • 1

分析:
1)CPU执行mov ax,0,ax=0
2)CPU将call far ptr s指令机器码加载入指令缓冲器,ip指向下一指令(8)
之后CPU执行该指令,做push cs,push ip ,jmp s操作。执行后,栈中数据为0800,0010;cs:ip指向标号s处(pop ax
3)CPU执行pop ax,ax=8
4)CPU执行add ax,ax,ax=10h
5)CPU执行pop bx,bx=1000h
6)CPU执行add ax,bxax=1010h

检测点10.4
程序执行后,ax中的数值是多少?

ax=0bh
  • 1

分析:
1)CPU执行mov ax,6,ax=6
2)CPU执行call ax(执行过程与上述一致,不再赘述),执行后:栈中ss:sp=0500,cs:ip=1000:6
3)CPU执行mov bp,sp,bp=sp
4)CPU执行add ax,[bp],内存[bp]的默认段寄存器为ss。故ax=6+5=0bh

检测点10.5
(1)程序执行后,ax中的值为多少(不要用debug来验证结论)

assume cs:code
stack segment
dw 8 dup (0)
stack ends

code segment
start:
mov ax,stack
mov ss,ax
mov sp,10h		;使ss:sp指向栈顶(栈为空)
mov ds,ax		;使ds指向栈段
mov ax,0		
call word ptr ds:[0eh]
				;1)先让ip指向下一条指令,再将当前ip的的偏移地址(下一条指令的ip地址)
				入栈。再修改ip的值为(ds:[0eh]),即将ip修该为下一条指令处
inc ax
inc ax
inc ax
mov ax,4c00h
int 21h
code ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
ax=3
  • 1

(2)程序执行后,ax和bx中的数值是多少?

assume cs:code
data segment
dw 8 dup (0)
data ends
code segment
start:
mov ax,data
mov ss,ax
mov sp,16			;使ss:ip指向栈顶(此时栈空)
mov word ptr ss:[0],offset s
					;将标号s处的偏移地址送入ss:[0]
mov ss:[2],cs		;将cs的值送入ss:[2]
call dword ptr ss:[0]
					;将`nop`指令的偏移地址入栈,再将cs的值入栈;
					再将ip的值修改为(ss:[0]),将cs的值修改为(ss:[2])
					即挑战到标号s处
nop
s:
mov ax,offset s		;将标号s的偏移地址送入ax中
sub ax,ss:[0ch]		;将(ss:[0ch]-ax的值送入ax中
					注:ss:[0ch]'nop'的偏移,ax中是s的偏移,故执行后ax=1
mov bx,cs			;将cx送入bx中
sub bx,ss:[0eh]		;注:ss:[0e]处存放的是cs的值,二者相减,为0
mov ax,4c00h
int 21h
code ends 
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
ax=1,bx=0
  • 1

**

实验10 编写子程序

1.显示字符串
子程序描述:
名称:show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
参数:(dh )=行号(范围:0~24),(dl)=列号(范围:0-79),
(cl)=颜色,ds:si指向字符串的首地址;
返回:无

思路:
此子程序主要考虑显存地址与字符串的对应关系,前面做过类似实验。

以下,为我的代码;

"""说明:主程序主要提供参数和要显示的内容,具体操作由子程序完成。"""

assume cs:code
data segment
db 'Welcome to LOL!!Nice to meet you!!',0
#db 'Hello world!',0
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0			;使ds指向要显示的字符串首地址

mov dh,8			
#mov dh,13
mov dl,3	
#mov dh,7
mov cx,2			;设置要显示的行,列及颜色。
#mov dh,6			

call show_str
mov ax,4c00h
int 21h


#子程序
"""
名称:show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
所需参数:(dh )=行号(范围:0~24),(dl)=列号(范围:0-79),
	 (cl)=颜色,ds:si指向字符串的首地址;
返回:无
"""

show_str:
push ax
push bx
push cx
push dx
push si
push di
push ds
push es				;将子程序所有用到的寄存器入栈保存,方便调用。

#将参数转化为显存上的地址。其对应关系为:行起始=0a0h*行号;列起始=02h*列号;
mov al,0a0h			
mul dh				
mov bx,ax			;对应显存的行
mov al,2
mul dl				
mov di,ax			;对应显存的列
mov ax,0b800h
mov es,ax			;用es:[bx].[di]来寻址显存

mov ah,cl			;先将颜色用ah保存,因为一开始就要用cx进行判断

#完成字符串到显存的传送
trans:
mov cx,ds:[si]
jcxz back			;判断要传送的字符是否为‘0’,为0则直接返回。

mov al,ds:[si]
mov es:[bx].[di],al
					;传送字符到奇数列,对应显存上的ASCII码
mov es:[bx].1[di],ah
					;传送颜色到偶数列,对应显存上的颜色
inc si				
add di,2			;控制字符串及显存地址指向下一个单元

jmp short trans

back:
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret

code ends
end start
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

以下为运行结果,多次运行,改变条件,测试它的通用性;
在这里插入图片描述在这里插入图片描述
2.解决除法溢出问题。
问题描述:在用div做除法的时候,结果的商过大,超出了寄存器所能存储的范围。发生错误(除法溢出)。写一个子程序使除法运算不会溢出。
子程序描述:
名称:divdw
功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
参数:(ax)=dword型数据的低16位
(dx)=dword型数据的高16位
(cx)=除数
返回:(dx)=结果的高16位,(ax)=结果的低16位,(cx)=余数

思路:
在这里插入图片描述此子程序主要在于此公式的理解,此公式将可能有溢出的除法运算拆分为肯定不会溢出的运算。首先将被除数的高16位作为16位除法运算(H/N)的低16位,高16位补0。这样在(H/N)运算中,被除数和除数相差绝对不会超过65535;同理(H/N)运算后的余数也不会大。再将(H/N)运算后的商左移4位后作为原除法运算的高16位,将(H/N)运算后的[(余数左移4位+原被除数的低16位)/N]的结果的商作为原除法运算的低16位,余数作为原除法运算的余数。

*感觉表达不是太清楚,*?

以下,为我的代码:

"""由主程序提供要可能发生溢出的除法运算的数值,子程序完成不会溢出的返回值"""

assume cs:code

#此段用来保存子程序的返回值。
data segment
dw 8 dup (0)
data ends		
			
code segment
start:
mov ax,data
mov ds,ax			;使ds指向data段

mov ax,4240h
#mov ax,0e8e0h
mov dx,000fh
#mov dx,005eh
mov cx,0ah			;向子程序提供的参数,除数,被除数
call divdw

mov dx,ds:[0]
mov ax,ds:[2]
mov cx,ds:[4]		;取返回值。

mov ax,4c00h
int 21h

#子程序
"""
子程序描述:
名称:divdw
功能:进行不会产生溢出的除法运算,被除数为dword型,除数为word型,结果为dword型。
所需参数:(ax)=dword型数据的低16位
(dx)=dword型数据的高16位
(cx)=除数
返回:(dx)=结果的高16位,(ax)=结果的低16位,(cx)=余数
"""

divdw:
push ax
push cx
push dx
push ds				;保护寄存器的值

push ax				;保存L,ax传进来的是公式中的L值

mov ax,dx			;将公式中的H传入ax中,作为(H/N)中被除数的低16位
mov dx,0			;将(H/N)中被除数的高16位置0
div cx				;进行(H/N)

mov ds:[0],ax		;将(H/N)的商(int(H/N))保存到返回值段处,(作为无溢出除法运算商的高16位)

pop ax				;注:rem(H/N)*65536相当于左移4位,再加上L后,刚好L可作为
					下步除法运算的被除数的低16位;在ax中存放
					上步(H/N)的余数即为rem(H/N),作为新除法的被除数的高16位,在dx中存放

div cx				;进行(rem(H/N)*65536+L)/N除法运算

mov ds:[2],ax		;保存结果的商到返回值段(作为无溢出除法运算的商的低16位)
mov ds:[4],dx		;保存结果的余数到返回值段(作为无溢出除法运算的余数)

pop ds
pop dx
pop cx
pop ax
ret
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

以下为运行结果(在debug中查看)
(1)选取除数10(0ah),被除数1000000(0f4240h),结果100000(186a0h),余数0(0h)
的测试结果

在这里插入图片描述(2)选取除数10(0ah),被除数6220000(5ee8e0h),结果622000(97db0h),余数0(0h)
的测试结果

在这里插入图片描述(3)选取除数10(0ah),被除数65533(0fffdh),结果6553(1999h),余数3(3h)
的测试结果

在这里插入图片描述
(4)选取除数14(0eh),被除数778(30ah),结果55(37h),余数8(8h)
的测试结果

在这里插入图片描述经过多轮测试,此子程序可完成16位,32位的不会溢出的除法运算。

3.数值显示

assume cs:code
#此段用来存放数字的字符串
data segment
db 100 dup (0)
data ends

code segment
start:
mov ax,data
mov ds,ax
mov si,0					;使ds:si指向字符串首地址(作为show_str子程序的参数)

mov ax,12666				;将要显示的数字
#mov ax,1		

call dtoc					;调用dtoc子程序,进行数据转为ascii码

mov dh,8
mov dl,3
mov cl,2					;设置show_str的参数
call show_str				;调用show_str进行显示

mov ax,4c00h
int 21h

"""
子程序描述:
名称:dtoc
功能:将word型数据转变为表示十进制数的字符串字符串以0为结尾符。
参数:(ax)=word型数据
ds:si指向字符串的首地址
返回:无
"""
#子程序
dtoc:
push ax	
push bx
push cx
push dx
push si
push ds

mov bx,0				;将bx作为计数器。记录入栈次数。

change:
mov dx,0
mov cx,10
div cx					;进行16位除法

add dx,30h				;dx中为除法运算的余数
push dx
inc bx					;将表示10进制的字符串入栈保存,并使入栈次数加一

mov cx,ax				;将商作为判断是否结束的依据
jcxz rev
jmp short change

rev:
mov cx,bx				;将入栈次数作为循环次数
save:
pop ax					
mov ds:[si],al
inc si					;进行转存
loop save			

inc si
mov byte ptr ds:[si],0	;将表示10进制字符串的后边补0作为其结束符。

pop ds
pop si
pop dx
pop cx
pop bx
pop ax
ret

#子程序描述
"""
名称:show_str
功能:在指定的位置,用指定的颜色,显示一个用0结束的字符串。
所需参数:(dh )=行号(范围:0~24),(dl)=列号(范围:0-79),
	 (cl)=颜色,ds:si指向字符串的首地址;
返回:无
"""
show_str:
push ax
push bx
push cx
push dx
push si
push di
push ds
push es				;将子程序所有用到的寄存器入栈保存,方便调用。

#将参数转化为显存上的地址。其对应关系为:行起始=0a0h*行号;列起始=02h*列号;
mov al,0a0h			
mul dh				
mov bx,ax			;对应显存的行
mov al,2
mul dl				
mov di,ax			;对应显存的列
mov ax,0b800h
mov es,ax			;用es:[bx].[di]来寻址显存

mov ah,cl			;先将颜色用ah保存,因为一开始就要用cx进行判断

#完成字符串到显存的传送
trans:
mov cx,ds:[si]
jcxz back			;判断要传送的字符是否为‘0’,为0则直接返回。

mov al,ds:[si]
mov es:[bx].[di],al
					;传送字符到奇数列,对应显存上的ASCII码
mov es:[bx].1[di],ah
					;传送颜色到偶数列,对应显存上的颜色
inc si				
add di,2			;控制字符串及显存地址指向下一个单元

jmp short trans

back:
pop es
pop ds
pop di
pop si
pop dx
pop cx
pop bx
pop ax
ret

code ends
end start

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135

运行结果:
在这里插入图片描述在这里插入图片描述
注:(dtoc的参数ax最大值为65535,超出会报错。)


本节内容挺难顶的,不过还好,我给它全部搞完了。

(本章知识点记录)

第10章 call和ret指令

call和ret都是转移指令,都可以修改ip,或同时修改cs和ip。

10.1 ret和retf
ret指令用栈中的数据,修改ip的内容,实现近转移;
retf指令用栈中的数据,修改cs和ip的内容,实现远转移。

CPU执行ret指令时,进行下面两步操作:
(1)(IP)=((ss)*16+(sp))
(2)(sp)=(sp)+2
相当于进行 pop ip
CPU执行retf指令时,进行下面4步操作:
(1)(IP)=((ss)*16+(sp))
(2)(sp)=(sp)+2
相当于进行pop ip,popcs

10.2 call指令
CPU执行call指令时,进行两步操作:
(1)将当前的ip或cs和ip压入栈;
(2)转移。
不能进行短转移。

10.3依据位移进行转移的call指令
格式:call 标号(将当前的ip压栈后,转到标号处执行。)
进行的操作:
(1)(sp)=((sp)-2)
((ss)*16+(sp))=(ip)
(2)(ip)=(ip)+16位位移
相当于进行:push ip,jmp near ptr 标号

10.4转移的目的地址在指令中的call指令
格式:call far ptr 标号 实现段间转移。
进行的操作:
(1)((sp))=((sp)-2)
((ss)*16+(sp))=(cs)
((sp))=((sp)-2)
((ss)*16+(sp))=(ip)
(2)(ip)=(ip)+16位位移
相当于进行:push cs,push ip ,jmp far ptr 标号

10.5转移地址在寄存器中的call指令
格式:call 16位 reg
进行的操作:
(1)(sp)=(sp)-2
((ss)*16+(sp))=(ip)
(2)(ip)=(16位reg)
相当于进行:push IP,jmp 16位reg

10.6转移地址在内存中的call 指令
格式:call word ptr 内存单元地址 或者
call dword ptr 内存单元地址
(1)相当于进行:push ip,jmp near ptr 内存字单元
(2)相当于进行:push cs,push ip ,jmp dword ptr 内存双字单元

10.7 call和ret的配合使用
可以写一段具有一定功能的程序段,称其为子程序,在需要的时候,用call指令,转去执行,在子程序中使用ret指令,使程序转到call指令后面的代码处继续执行。(相当于函数的功能。)
call 和ret 子程序框架:
标号:
指令
ret

具有子程序的源程序框架:

assume cs:code
code segment
main:	:
		:
		call sub1			;调用子程序sub1
		:
		:
		mov ax,4c00h
		int 21h
sub1:	:
		:
		call sub2			;调用子程序sub2
		:
		:
		ret					;子程序返回
sub2:	:
		:
		ret					;子程序返回
code ends
end main		
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

10.8 mul指令
mul 是乘法指令;
(1)两个相乘的数:要么都是8位,要么都是16位。如果是8位,一个默认放在al中,另一个放在8位reg或者内存字节单元中;如果是16位,一个默认在ax中,另一个放在16位reg或内存字单元中。
(2)结果:8位乘法,结果默认放在ax中;16位乘法,结果高位默认在dx中,低位在ax中。

10.9 模块化程序设计
10.10 参数和结果传递问题
10.11 批量数据的传递
子程序一般都要根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者。实际上就是讨论如何存储子程序需要的参数和产生的返回值。
(1)用寄存器来存储参数和结果是最常用的方法。
调用者将参数送入参数寄存器,从结果寄存器中取到返回值;子程序从参数寄存器中取到参数,将返回值送入结果寄存器。
(2)将要传递的批量数据放到内存,然后将它们所在的内存空间首地址放在寄存器中,传递给需要的子程序;返回结果亦是如此。
(3)用栈来传递参数
这种技术和高级语言编译器的工作原理密切相关。
就是由调用者将需要传递给子程序的参数压入栈中,子程序从栈中取得参数。

**注:**指令:ret n :相当于进行pop ip , add sp,n
C语言中,局部变量也在栈中存储。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/80500
推荐阅读
相关标签
  

闽ICP备14008679号