第十章 CALL和RET指令

jmp & call指令

jmp段内转移:又称近转移

  1. jmp short 标号(位移量限制在[-127, 128])
  2. jmp reg(16bit)(ip=reg value)
  3. jmp near ptr 标号(位移量限制在16bit)
  4. jmp word ptr 内存单元地址(ip=内存单元地址)

jmp段间转移:又称远转移

  1. jmp far ptr 标号(cs=标号所在段的段地址,ip=标号所在的段内偏移量)
  2. jmp dword ptr 内存单元地址(cs=内存单元地址+2,ip=内存单元地址)

call段内转移

  1. call 标号:
    1. push ip
    2. jump near ptr ip
  2. call 16 reg
    1. push ip
    2. jump reg(16bit)
  3. call word ptr 内存单元地址
    1. push ip
    2. jump word ptr 内存单元地址

call段间转移

  1. call far ptr 标号:
    1. push cs
    2. push ip
    3. jmp far ptr 标号
  2. call dword ptr 内存单元地址
    1. push cs
    2. push ip
    3. jump dword ptr 内存单元地址(cs=内存单元地址+2,ip=内存单元地址)

程序段间传参

1)寄存器传递参数、返回值

2)内存传递参数、返回值

3)栈传递参数

教材的附注4有详细解释。不过为了更深刻、更全面的理解,找到了一篇不错的解读文章,循序渐进。

原文:

通过下面几个程序来一步步理解附注4用栈传递数据的思想:

备注:下面的程序,我们假设初始的sp=10H,ss=0001H

mov ax,1
push ax       ;(sp)=(sp-2)=10-2=E,  (ss:[sp])=(ss:[E])=1
mov ax,2
push ax       ;(sp)=(sp-2)=E-2=C,   (ss:[sp])=(ss:[C])=2
mov ax,3
push ax       ;(sp)=(sp-2)=C-2=A,   (ss:[sp])=(ss:[A])=3
call addsub   ;(sp)=(sp-2)=A-2=8,   (ss:[sp])=(ss:[8])=(下一个指令的ip)
add sp,6      ;(sp)=(A+6)=10    还原为初始值
 
addsub:
       mov ax,ss:[sp+2]    ;(ax)=(ss:[sp+2])=(ss:[8+2])=(ss:[A])=3
       add ax,ss:[sp+4]    ;(ax)=3+(ss:[sp+2])=3+(ss:[8+4])=3+(ss:[C])=5
       add ax,ss:[sp+6]    ;(ax)=5+(ss:[sp+2])=5+(ss:[8+6])=5+(ss:[E])=6
       ret                 ;(ip)=(栈顶的值),(sp)=(sp+2)=A

上面还原 sp 的方法为外平栈,下面这种方法用到 ret n ,即内平栈

mov ax,1
push ax       ;(sp)=(sp-2)=10-2=E,  (ss:[sp])=(ss:[E])=1
mov ax,2
push ax       ;(sp)=(sp-2)=E-2=C,   (ss:[sp])=(ss:[C])=2
mov ax,3
push ax       ;(sp)=(sp-2)=C-2=A,   (ss:[sp])=(ss:[A])=3
call addsub   ;(sp)=(sp-2)=A-2=8,   (ss:[sp])=(ss:[8])=(下一个指令的ip)
 
 
addsub:
       mov ax,ss:[sp+2]    ;(ax)=(ss:[sp+2])=(ss:[8+2])=(ss:[A])=3
       add ax,ss:[sp+4]    ;(ax)=3+(ss:[sp+2])=3+(ss:[8+4])=3+(ss:[C])=5
       add ax,ss:[sp+6]    ;(ax)=5+(ss:[sp+2])=5+(ss:[8+6])=5+(ss:[E])=6
       ret 6               ;(ip)=(栈顶的值),(sp)=(sp+2)=A,(sp)=(sp+6)=10  还原为初始值

但实际程序中,往往需要暂存寄存器,所以用sp定位方式也得变化,如下:

       mov ax,1
       push ax       ;(sp)=(sp-2)=10-2=E,  (ss:[sp])=(ss:[E])=1
       mov ax,2
       push ax       ;(sp)=(sp-2)=E-2=C,   (ss:[sp])=(ss:[C])=2
       mov ax,3
       push ax       ;(sp)=(sp-2)=C-2=A,   (ss:[sp])=(ss:[A])=3
       call addsub   ;(sp)=(sp-2)=A-2=8,   (ss:[sp])=(ss:[8])=(下一个指令的ip)
       add sp,6      ;(sp)=(A+6)=10    还原为初始值
 
addsub:              ;这里我们假设子程序中还有其他指令,所以要暂存相关寄存器
       push bx       ;(sp)=(sp-2)=8-2=6,   (ss:[sp])=(ss:[6])=(bx)
       push cx       ;(sp)=(sp-2)=6-2=4,   (ss:[sp])=(ss:[4])=(cx)
       push dx       ;(sp)=(sp-2)=4-2=2,   (ss:[sp])=(ss:[2])=(dx)
 
       ;....假设省略了其他指令,注意下面用sp定位之前传递入栈的参数方式变化                 
       mov ax,ss:[sp+6+2]    ;(ax)=(ss:[sp+8])=(ss:[2+8])=(ss:[A])=3
       add ax,ss:[sp+6+4]    ;(ax)=3+(ss:[sp+2])=3+(ss:[2+A])=3+(ss:[C])=5
       add ax,ss:[sp+6+6]    ;(ax)=5+(ss:[sp+2])=5+(ss:[2+C])=5+(ss:[E])=6
       
       pop dx                ;(sp)=(sp+2)=2+2=4
       pop cx                ;(sp)=(sp+2)=4+2=6
       pop bx                ;(sp)=(sp+2)=6+2=8
       ret                   ;(ip)=(栈顶的值),(sp)=(sp+2)=8+2=A

那么上述用sp定位方法很不方便,因为每次都有数据需要进栈暂存,解决方法如下:

 ;这里为了方便我们假设初始的 SS=0,SP=20H
       mov ax,1
       push ax       ;(sp)=(sp-2)=20-2=1E,    (ss:[sp])=(ss:[1E])=1
       mov ax,2 
       push ax       ;(sp)=(sp-2)=1E-2=1C,    (ss:[sp])=(ss:[1C])=2
       mov ax,3
       push ax       ;(sp)=(sp-2)=1C-2=1A,    (ss:[sp])=(ss:[1A])=3
       call addsub   ;(sp)=(sp-2)=1A-2=18h,   (ss:[sp])=(ss:[18h])=(下一个指令的ip)
       add sp,6      ;(sp)=(1A+6)=20h    还原为初始值
 
addsub:              ;下面是用bp来定位栈中数据的方法      
       push bp       ;(sp)=(sp-2)=18-2=16h,   (ss:[sp])=(ss:[16h])=(bp)
       mov bp,sp     ;(bp)=(sp)=16h
       sub sp,10h    ;(sp)=16-10=6h, 这里可以随便减去一个值
 
                     ;这里我们假设子程序中还有其他指令,所以要暂存相关寄存器
       push bx       ;(sp)=(sp-2)=6-2=4,   (ss:[sp])=(ss:[6])=(bx)
       push cx       ;(sp)=(sp-2)=4-2=2,   (ss:[sp])=(ss:[4])=(cx)
       push dx       ;(sp)=(sp-2)=2-2=0,   (ss:[sp])=(ss:[2])=(dx)
 
                           ;下面用bp定位栈中数据,bp永远从+4开始,与上面入栈多少数据无关              
       mov ax,ss:[bp+4]    ;(ax)=(ss:[bp+4])=(ss:[16h+4])=(ss:[1A])=3
       add ax,ss:[bp+6]    ;(ax)=3+(ss:[bp+4])=3+(ss:[16+6])=3+(ss:[1C])=5
       add ax,ss:[bp+8]    ;(ax)=5+(ss:[bp+8])=5+(ss:[16+8])=5+(ss:[1E])=6
       
       pop dx                ;(sp)=(sp+2)=0+2=2
       pop cx                ;(sp)=(sp+2)=2+2=4
       pop bx                ;(sp)=(sp+2)=4+2=6
 
       mov sp,bp             ;等价于add sp,10h, 目的是还原sp,(sp)=16h
       pop bp                ;(sp)=(sp+2)=18h  
       ret                   ;(ip)=(栈顶的值),(sp)=(sp+2)=18+2=1A

在这个基础上,再去理解附注4就很简单了(这里我对原文进行了一些修改,添加了完整代码与部分注释):

C程序:
void add(int,int,int)

main() {
    int a = 1;
    int b = 1;
    int c = 1;
    add(a,b,c);
    c++;
}

void add(int a, int b, int c) {
    c=a+b;
}

编译后的汇编程序(注意:c语言中局部变量也放在栈中存储):
mov bp,sp
sub sp,6
mov word ptr [bp-6],0001  ;int a
mov word ptr [bp-4],0002  ;int b
mov word ptr [bp-2],0000  ;int c
push [bp-2] ; c入栈
push [bp-4] ; b入栈
push [bp-6] ; a入栈

call ADDR
add sp,6
int word ptr [bp-2]

ADDR: 
    push bp ; 暂存bp寄存器数据,避免子程序需要使用该寄存器并修改了值,造成主程序的崩溃
    mov bp,sp
    mov ax,bp[bp+4] ; ax   = a变量的值 (bp[0-2]为bp的值, bp[3-4]为主程序中指令add sp,6的偏移量)
    add ax,bp[bp+6] ; ax   = ax + b变量的值
    mov [bp+8],ax   ; 变量c = ax
    mov sp,bp
    pop bp ; 还原bp寄存器的值
    ret

附注4最后那个C程序转换的汇编程序中:

mov sp,bp 为的就是还原sp,避免万一子程序中有栈操作改变了sp,所以最后必须还原,相当于清除自身临时变量。
否则没有这句话保护可能导致整个程序崩溃。至于 add sp,6 为的是平衡栈,因为之前sp减了6,将栈还原到初始状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值