jmp & call指令
jmp段内转移:又称近转移
- jmp short 标号(位移量限制在[-127, 128])
- jmp reg(16bit)(ip=reg value)
- jmp near ptr 标号(位移量限制在16bit)
- jmp word ptr 内存单元地址(ip=内存单元地址)
jmp段间转移:又称远转移
- jmp far ptr 标号(cs=标号所在段的段地址,ip=标号所在的段内偏移量)
- jmp dword ptr 内存单元地址(cs=内存单元地址+2,ip=内存单元地址)
call段内转移
- call 标号:
- push ip
- jump near ptr ip
- call 16 reg
- push ip
- jump reg(16bit)
- call word ptr 内存单元地址
- push ip
- jump word ptr 内存单元地址
call段间转移
- call far ptr 标号:
- push cs
- push ip
- jmp far ptr 标号
- call dword ptr 内存单元地址
- push cs
- push ip
- 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,将栈还原到初始状态。