一.ret指令用栈中的数据,修改IP的内容,从而实现近转移;api
CPU执行ret指令时,进行下面两步操做:框架
a) (1)(IP)=((ss)*16+(sp))oop
b) (2)(sp)=(sp)+2spa
二.retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移;设计
CPU执行retf指令时,进行下面两步操做:code
a) (1)(IP)=((ss)*16+(sp))orm
b) (2)(sp)=(sp)+2ip
c) (3)(CS)=((ss)*16+(sp))内存
d) (4)(sp)=(sp)+2字符串
三.能够看出,若是咱们用汇编语法来解释ret和retf指令,则:
a) CPU执行ret指令时,至关于进行:
pop IP
b).CPU执行retf指令时,至关于进行:
pop IP
pop CS
四.call 指令
CPU执行call指令,进行两步操做:
a) (1)将当前的 IP 或 CS和IP 压入栈中;
b) (2)转移。
call 指令不能实现短转移,除此以外,call指令实现转移的方法和 jmp 指令的原理相同。
解释:
n call 标号(将当前的 IP 压栈后,转到标号处执行指令)
n CPU执行此种格式的call指令时,进行以下的操做:
n (1) (sp) = (sp) – 2
((ss)*16+(sp)) = (IP)
n (2) (IP) = (IP) + 16位位移
call 标号
n 16位位移=“标号”处的地址-call指令后的第一个字节的地址;
n 16位位移的范围为 -32768~32767,用补码表示;
n 16位位移由编译程序在编译时算出。
五.综合实例演示:
看下面一段简单的代码:
Mov ax,0
Call s
Mov bx,0
S:add ax,1
Ret
解释以下:
Call s:实际上就是调用s处的子程序。并将Mov bx,0这条指令所对应的偏移地址入栈,此处为何要入栈呢?实际上就是为了方便ret指令取出ip。
Ret指令实际上就是返回s,能使程序从mov bx,0处执行。
六.call 和 ret 的配合使用
1.例子1:
assume cs:code
code segment
start: mov ax,1
mov cx,3
call s
mov bx,ax ;(bx) = ?
mov ax,4c00h
int 21h
s: add ax,ax
loop s
ret
code ends
end start
咱们来看一下 CPU 执行这个程序的主要过程:
n (1)CPU 将call s指令的机器码读入,IP指向了call s后的指令mov bx,ax,而后CPU执行call s指令,将当前的 IP值(指令mov bx,ax的偏移地址)压栈,并将 IP 的值改变为标号 s处的偏移地址;
n (2)CPU从标号 s 处开始执行指令,loop循环完毕,(ax)=8;
n (3)CPU将ret指令的机器码读入,IP指向了ret 指令后的内存单元,而后CPU 执行 ret 指令 ,从栈中弹出一个值(即 call 先前压入的mov bx,ax 指令的偏移地址)送入 IP 中。则CS:IP指向指令mov bx,ax;
n (4)CPU从 mov bx,ax 开始执行指令,直至完成。
2.例子2
n 咱们看一下程序的主要执行过程:
n (1)前三条指令执行后,栈的状况以下:
n 2)call 指令读入后,(IP) =000EH,CPU指令缓冲器中的代码为 B8 05 00;
CPU执行B8 05 00,首先,栈中的状况变为:
而后,(IP)=(IP)+0005=0013H。
n (3)CPU从cs:0013H处(即标号s处)开始执行。
n (4)ret指令读入后:(IP)=0016H,CPU指令缓冲器中的代码为 C3;CPU执行C3,至关于进行pop IP,执行后,栈中的状况为:
(IP)=000EH;
n (5)CPU回到 cs:000EH处(即call指令后面的指令处)继续执行。
3. 从上面的讨论中咱们发现,能够写一个具备必定功能的程序段,咱们称其为子程序,在须要的时候,用call指令转去执行。
4. 但是执行完子程序后,如何让CPU接着call指令向下执行?
5. call指令转去执行子程序以前,call指令后面的指令的地址将存储在栈中,因此能够在子程序的后面使用 ret 指令,用栈中的数据设置IP的值,从而转到 call 指令后面的代码处继续执行。
七.mul 指令
n 因下面要用到,咱们介绍一下mul指令,mul是乘法指令,使用 mul 作乘法的时候:
n (1)相乘的两个数:要么都是8位,要么都是16位。
8 位: AL中和 8位寄存器或内存字节单元中;
16 位: AX中和 16 位寄存器或内存字单元中。
n (2)结果
8位:AX中;
16位:DX(高位)和AX(低位)中。
n 格式以下:
mul reg
mul 内存单元
例子一:
n 例如:
n (1)计算100*10
100和10小于255,能够作8位乘法,程序以下:
mov al,100
mov bl,10
mul bl
结果: (ax)=1000(03E8H)
例子二:
n (1)计算100*10000
100小于255,可10000大于255,因此必须作16位乘法,程序以下:
mov ax,100
mov bx,10000
mul bx
结果: (ax)=4240H,(dx)=000FH
(F4240H=1000000)
8.参数和结果传递的问题
n 咱们设计一个子程序,能够根据提供的N,来计算N的3次方。
n 这里有两个问题:
n (1)咱们将参数N存储在什么地方?
n (2)计算获得的数值,咱们存储在什么地方?
很显然,咱们能够用寄存器来存储,能够将参数放到 bx 中 ;由于子程序中要计算 N×N×N ,可使用多个 mul 指令,为了方便,可将结果放到 dx 和 ax中。
n 子程序:
n 说明:计算N的3次方
n 参数: (bx)=N
n 结果: (dx:ax)=N∧3
cube:mov ax,bx
mul bx
mul bx
ret
n 用寄存器来存储参数和结果是最常使用的方法。对于存放参数的寄存器和存放结果的寄存器,调用者和子程序的读写操做偏偏相反:
n 调用者将参数送入参数寄存器,从结果寄存器中取到返回值;
n 子程序从参数寄存器中取到参数,将返回值送入结果寄存器。
9.批量数据的传递
n 前面的例程中,子程序 cube 只有一个参数,放在bx中。若是有两个参数,那么能够用两个寄存器来放,但是若是须要传递的数据有3个、4个或更多直至 N个,咱们怎样存放呢?
n 寄存器的数量终究有限,咱们不可能简单地用寄存器来存放多个须要传递的数据。对于返回值,也有一样的问题。
n 在这种时候,咱们将批量数据放到内存中,而后将它们所在内存空间的首地址放在寄存器中,传递给须要的子程序。
n 对于具备批量数据的返回结果,也可用一样的方法。
以下代码:
10.寄存器冲突的问题
n 设计一个子程序:
n 功能:将一个全是字母,以0结尾的字符串,转化为大写。
n 分析
应用这个子程序 ,字符串的内容后面定要有一个0,标记字符串的结束。子程序能够依次读取每一个字符进行检测,若是不是0,就进行大写的转化,若是是0,就结束处理。
因为可经过检测0而知道是否己经处理完整个字符串 ,因此子程序能够不须要字符串的长度做为参数。咱们能够用jcxz来检测0。
添加主框架
assume cs:code
data segment
db 'conversation',0
data ends
n 代码段中相关程序段以下:
mov ax,data
mov ds,ax
mov si,0
call capital
其中si运用了屡次,怎么避免呢?
从上而的问题中,实际上引出了个通常化的问题:子程序中使用的寄存器,极可能在主程序中也要使用,形成了寄存器使用上的冲突。
那么咱们如何来避免这种冲突呢 ?粗略地看,咱们能够有两个方案:
n (1)在编写调用子程序的程序时 ,注意看看子程序中有没有用到会产生冲突的寄存器,若是有,调用者使用别的寄存器;
n (2)在编写子程序的时候,不要使用会产生冲突的寄存器。
咱们编写子程序的标准框架以下:
子程序开始:子程序中使用的寄存器入栈
子程序内容
子程序使用的寄存器出栈
返回(ret、retf)
以下代码:
n capital: push cx
push si
change: mov cl,[si]
mov ch,0
jcxz ok
and byte ptr [si],11011111b
inc si
jmp short change
ok: pop si
pop cx
ret
n 要注意寄存器入栈和出栈的顺序。