call 和 ret指令

call和ret指令
(都为绝对地址)
call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。
(call指令保存的IP是call指令的下一个ip)
它们经常被共同用来实现子程序的设计。

ret和retf

ret指令用栈中的数据,修改IP的内容,从而实现近转移;

retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。

CPU执行ret指令
0时,进行下面的两步操作:

(1)(IP) = ((ss)*16 +(sp))

(2)(sp) = (sp)+2

CPU执行retf指令时,进行下面四步操作:

(1)(IP) = ((ss)*16) + (sp)

(2)(sp) = (sp) + 2

(3)(CS) = ((ss)*16) + (sp)

(4)(sp) = (sp) + 2

用汇编语法来解释ret和retf指令,则:

CPU执行ret指令时,相当于进行:

pop IP

CPU执行retf指令时,相当于进行:

pop IP

pop CS

例题

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code,ss:stack
stack segment
dw 8 dup(0)
stack ends
code segment
start:
mov ax, stack
mov ss, ax
mov sp, 0010H
mov ds, ax
call word ptr ds:[0EH]
inc ax
inc ax
inc ax
code ends
end start
该题中的程序的数据段和栈段使用了同一段内存 , 也就是说 ds 和 ss 是相同的
执行到 call word ptr ds:[0EH] 的时候 , 具体的流程如下 :
1. CPU取指令 : (call word ptr ds:[0EH])
2. ip自增上述指令的长度 , 指向了下一条指令 (inc ax)
3. 开始执行该指令
  3.1. push ip ; 将 ip 压入栈 , 也就是 : ss:[0EH] 保存 ip 的低 8 位 , ss:[0FH] 保存高 8 位
  3.2. jmp ds:[0EH] ( (ip) = ds:[0EH] , 也就是说 , 程序又从 ds:[0EH] 中取出数据赋值给 ip , 然后继续执行 )
4. 现在其实就开始执行 ip 之前保存的地址的指令了 , 也就是三个 inc ax
5. 因此最终 ax 值为 3

2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
assume cs:code,ds:data
data segment
dw 8 dup(0)
data ends
code segment
start:
mov ax, data
mov ss, ax
mov sp, 16
mov word ptr ss:[0], offset second
mov ss:[2], cs
call dword ptr ss:[0]
nop
second:
mov ax, offset second
sub ax, ss:[0CH]
mov bx, cs
sub bx, ss:[0EH]
code ends
end start
这个程序定义了数据段 , 其实也是栈段和数据段使用了同一段内存
首先也是初始化栈 :
mov ax, data
mov ss, ax
mov sp, 16
然后执行 :
mov word ptr ss:[0], offset s
将 s 标号处两个字节的地址移动到 ss:[0] 处
mov ss:[2], cs
将代码段地址的值保存在到 ss:[2] 处 , 同样两个字节
call dword ptr ss:[0]
CPU首先读取该指令
然后将 ip 增加该指令的长度
然后准备开始执行 (由于是 dword 因此 cs 和 ip 都要压栈)
执行首先要将当前的 cs 压栈 (保存在 ss:[0EH])
然后 ip (也就是 call 指令下一个指令的地址 , 也就是 nop 的地址) 压栈 (保存在 ss:[0CH]) 处
然后读取 ss:[0] 中的四个字节的数据 , 相当于 : 高地址为段地址 , 低地址为偏移地址
也就说 cs 为 (ss:[2]) , ip 为 (ss:[0])
这个 cs 和 ip 组成的地址就是 second 的地址
现在开始执行 second
mov ax, offset second
读指令 , ip自增 , 然后执行 , 因此执行完后 , ax 为该指令的偏移地址
然后 sub ax, ss:[0CH]
通过之前的分析 :
ss:[0CH] 处保存的是 : call dword ptr ss:[0] 这条指令的下一条指令 nop 的相对于代码段偏移地址
ax = (mov ax, offset second 的偏移地址) - ( nop 的偏移地址)
也就是 nop 指令的长度 , 也就是 1
接下来 :
mov bx, cs
将代码段的基址赋值给 bx
bx = (bx) - (ss:[0EH])
(ss:[0EH]) = (cs)
因此 bx = 0

实验

计算过程 (以书中的 (F4240H / 0AH) 为例)。

  1. int(H/N)*65536

    即: 000FH / 000AH = 0001H 余 0005H

    所以:int(H/N) = 1H , rem(H/N) = 0005H
    int(H/N)*65536 = 10000H

  2. [rem(H/N)*65536 + L] / N
    即: 0005H * 65536 = 50000H
    50000H + 4240H = 54240H

    所以:[rem(H/N)*65536 + L] / N = 54240H / 0AH = 86A0H 余 0000H

  3. 最终结果:
    10000H + 86A0H = 186A0H 且余数为0

分析

第一步中:除法的商为最终结果的商的高16位  ( int(H/N)*65536 )。

第二步中:上一步除法的余数(作为高16位)和被除数的低16位(作为低16位)共同参与32位的除法运算,所得的商为最终结果的商的低16位,所得余数为最终结果的余数。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mov ax,4240H
mov dx,000FH
mov cx,0AH
divdw:push bx
push ax ;将被除数的低16位入栈
mov ax,dx ;将被除数的高16位存入ax
mov dx,0
div cx ;即0000 000FH / 000AH,完成后ax为0001H, dx为0005H
mov bx,ax ;将上面除法结果的商存入bx
pop ax ;将被除数的低16位出栈,存入ax
div cx ;即54240H / 0AH,完成后ax860AH ,dx为0000H
mov cx,dx ;将余数存入cx
mov dx,bx ;将结果的商的高16为存入dx
pop bx
ret

注意

cpu在执行指令时先读取当前CS:ip0处的指令,然后IP1=IP0++,此时执行指令如果需要保存IP的话,保存的值已经是IP1了,而不是IP0
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
assume cs:code,ss:stack
stack segment
dw 8 dup(0)
stack ends
code segment
start:
mov ax, stack ;ip0
mov ss, ax ;ip1
mov sp, 0010H ;ip2
mov ds, ax ;ip3
call word ptr ds:[0EH] ;ip4 这一行保存入栈的ip是ip5
inc ax ;ip5
inc ax ;ip6
inc ax ;ip7
code ends
end start

执行到CS:IP4时,CPU首先将此处命令加载到指令缓冲区,然后IP寄存器++ --> IP5 ,然后执行命令: 入栈IP寄存器的值(IP5),然后再…