目录
一、ret和retf
ret指令用栈中的数据,修改IP的内容,从而实现近转移
ref指令用栈中的数据,修改CS和IP的内容,从而实现远转移
CPU执行ret指令时,进行下面两步操作
(IP)=((SS)*16+SP)
(SP)=(SP)+ 2
CPU执行retf指令时,进行下面4步操作:
(IP)=((SS)*16+SP)
(SP)=(SP)+ 2
(CS)=((SS)*16+SP)
(SP)=(SP)+ 2
可以看出来,如果我们用汇编语法来解释ret和ref指令,则:
CPU执行ret指令时,相当于进行pop IP
CPU执行ref指令时,相当于进行:pop IP,pop CS
assume cs:code
stack segment
db 16 dup (0)
stack ends
code segment
mov ax,4c00h
int 21h
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,0
push ax
mov bx,0
ret
code ends
end start
上面程序,ret执行后,(IP) = 0,CS:IP指向代码段的第一条指令mov ax,4c00h
二、call指令
CPU执行call指令时,进行两步操作:
(1)将当前的IP或CS和IP压入栈中
(2)转移
2.1 依据位移进行转移的call指令
call 标号
(将当前的IP压入栈,转到标号处执行指令)
CPU执行此种格式的call指令,进行如下操作:
(1)(sp)=(sp)- 2
((ss)*16+(sp))=(IP)
(2)(IP)= (IP)+16位位移
可以看出CPU要执行call 标号的时候,相当于是进行:
push IP
jmp near ptr 标号
2.2 转移的目的地址在指令中的call指令
前面讲到的call指令,其对应的机器指令中并没有转移的目的地址,而是相当于当前IP的转移位移。
call far ptr 标号
实现的是段间转移
CPU执行此种格式的call指令时,进行如下的操作:
(1)
(sp)=(sp)- 2
((ss)*16+(sp))=(CS)
(sp)=(sp)- 2
((ss)*16+(sp))=(IP)
(2)
(IP)= 标号所在段的偏移地址
(CS)= 标号所在段的段地址
2.3 转移地址在寄存器中的call指令
指令格式:call 16位reg
相当于是进行:
push IP
jmp 16位reg
2.4 转移地址在内存中的call指令
转移地址在内存中的call指令有两种格式:
call word ptr 内存单元地址
CPU执行call word ptr 内存单元地址
时,相当于是进行:
push IP
jmp word ptr 内存单元地址
比如下面指令:
mov sp,10h
mov ax,0123h
mov dx:[0],ax
call word ptr ds:[0]
执行后,(IP)=0123H,(SP)=0EH
- call dword ptr内存单元地址
push IP
push CS
jmp word ptr 内存单元地址
三、call和ret的配合使用
下面程序返回前,bx中的值是多少?
assume cs:code
code segment
start:
mov ax,1
mov cx,3
call segment
mov bx,ax ;(bx)=?
mov ax,4c00h
int 21h
s: add ax,ax
loop segment
ret
code ends
end
-
CPU将call s指令的机器码读入,IP指向了call s后的指令mov bx,ax 然后CPU执行call s的指令,将当前IP值(mov bx,ax的偏移地址)压入栈,并将IP的值改变为标号s处的偏移地址
-
CPU从标号s处开始执行指令,loop循环完毕后,(ax)= 8
-
CPU将ret指令的机器码读入,IP指向ret指令后面的内存单元,然后CPU执行ret指令,从栈中弹出一个值(call s先前压入的mov bx,ax指令的偏移地址)送入IP中。则CS:IP指向指令mov bx,ax
-
CPU从mov bx,ax开始执行指令,直到完成
所以程序执行完后,(bx)=8,计算2的N次方,N由cx提供
我们可以使用call和ret来实现子程序(写一个具有一定功能的程序段),子程序的框架如下:
assume cs:code
code segment
main:
...
call subl ;调用子程序
...
mov ax,4c00h
int 21h
sub1:
...
call sub2 ; 调用子程序sub2
...
ret
sub2:
...
ret
code ends
end main
四、mul指令
mul是乘法指令,使用mul乘法的时候应该注意:
- 两个相乘的数:两个相乘的数位数应该一样(要么都是8位、要么都是16位),如果是8位,一个默认在AL,另外一个放在8位reg或是内存字节单元中,如果是16位,一个默认在AX,另外一个放在16位reg或是内存字节单元中
- 结果:如果是8位乘法,结果放在AX中,如果是16位,高位放入DX,低位放入AX
格式如下:
mul reg
mul 内存单元
可以用不同寻址方式给出:
mul byte ptr ds:[0]
含义:(ax) = (al) * ((ds)*16+0)
计算100*10
mov al,100
mov bl,10
mul bl
五、模块化程序设计
5.1 参数和结果传递的问题
子程序中一般都要根据参数处理一定的事物、处理后,将结果(返回值)提供给调用者,我们实际上就是在探讨,如何存储子程序需要的参数和产生的返回值
比如设置一个子程序,可以根据提供的N,计算N的3次方
这里就有两个问题,N存储在什么地方,计算得到的数值存储在什么地方?
很显然,可以用寄存器来进行存储,将参数放到bx中;因为子程序要计算NNN,可以使用多个mul指令,为了方便,将结果放在dx和ax中,子程序如下:
;说明:计算N的3次方
;参数:(bx)=N
;结果:(dx:ax)=N^3
cube:
mov ax,bx
mul bx
mul bx
ret
编程计算data段中第一组数据的3次方,结果保存在后面一组dword单元中
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
程序如下:
assume cs:code
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0 ;ds:si指向第一组word
mov di,16 ;ds:di指向第二组dword
mov cx,8
s: mov bx,[si]
call cube
mov [di],ax
mov [di].2,dx
add si,2 ;ds:si指向下一个word单元
add di,4 ;ds:di指向下一个dword单元
loop s
mov ax,4c00h
int 21h
cube: mov ax,bx
mul bx
mul bx
ret
code ends
end start
5.2 寄存器冲突问题
设计一个子程序,功能:将一个全是字母,以0结尾的字符串,转化为大写
程序要处理的字符串以0作为结尾符,这个字符串可以如下定义:
db ‘conversation’,0
这个子程序,字符串后面一定要有一个0,标记字符串的结束,字符串可以依次读取每个字符来进行检测,如果不是0,就进行大写的转化,是0就结束,可以用jcxz来检测0