目录
call和ret指令都是转移指令,它们都
修改IP,或同时修改CS和IP
。
10.1 ret 和 retf
-
ret指令用栈中数据,
修改IP的内容,从而实现近转移;执行下面两步操作:
- (1) (IP)=((ss)*16+(sp))
- (2) (sp)=(sp)+2 retf指令用栈中数据, 修改CS和IP的内容,从而实现远转移;执行下面4步操作:
- (1) (IP)=((ss)*16+(sp))
- (2) (sp)=(sp)+2
- (3) (CS)=((ss)*16+(sp))
- (4) (sp)=(sp)+2
10.2 call 指令
-
CPU执行call指令时,进行两步操作:
- (1) 将当前的IP或CS和IP压入栈中
- (2) 转移
call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同。
10.3 依据位移进行转移的 call 指令
-
“call 标号”,执行时进行如下操作:
- (1) (sp)=(sp)-2 ((ss)*16+(sp))=(IP)
- (2) (IP)=(IP)+16位位移
16位位移 = 标号处的地址 - call指令后第一个字节的地址;
16位位移的范围为:-32768~32767
16位位移由编译程序在编译时算出
用汇编语法来解释此种格式的call指令,相当于“push IP ;jmp near ptr 标号”
10.4 转移的目的地址在指令中的 call 指令
-
“call far ptr 标号”,执行时进行如下操作:
- (1) (sp)=(sp)-2 ((ss)*16+(sp))=(CS) (sp)=(sp)-2 ((ss)*16+(sp))=(IP)
- (2) (CS)=标号所在段的段地址 (IP)=标号在段中的偏移地址
用汇编语法解释此种格式的call指令,相当于“push CS;push IP;jmp far ptr 标号”
10.5 转移地址在寄存器中的 call 指令
-
“call reg(16位)”,执行时进行如下操作:
- (1) (sp)=(sp)-2 ((ss)*16+(sp))=(IP)
- (2) (IP)=(16位reg)
用汇编语法解释此种格式的命令,相当于“push IP; jmp 16位reg”
10.6 转移地址在内存中的 call 指令
-
“call word ptr 内存单元地址”,用汇编语法来解释此种格式的 call 指令,相当于:
- push IP
- jmp word ptr 内存单元地址 “call dword ptr 内存单元地址”,用汇编语法来解释此种格式的 call 指令,相当于:
- push CS
- push IP
- jmp dword ptr 内存单元地址
这一节的监测点10.5中问题一,如果你通过debug单步跟踪是得不到正确结果的,关键步骤就是“call word ptr ds:[0eh]”如果你单步执行,中断时,必然将eflags cs ip都要入栈保存,哪怕你没通过push指令进行入栈操作,这就破坏了栈数据导致程序不能按照原代码执行,得不到结果。直接执行或者通过debug的g命令,运行到最后再查看ax中数据,就会得到正确结果ax=3
10.7 call 和 ret 的配合使用
写一个具有一定功能的程序段,称其为子程序,在需要时,用call指令转去执行。可是执行完子程序后,如何让CPU接着call指令向下执行? call指令转去执行子程序之前,call指令后面的指令地址将存储在栈中,所以可在子程序的后面使用ret指令,用栈中的数据设置IP的值,从而转到call指令后面的代码处继续执行。
我们可以利用call和ret来实现子程序的机制。
学到后面发现jmp和call两个指令我还是有点模糊,特别是jmp指令,我总容易忽略它。这里补充点东西强化以下对这两个指令的理解。
jmp | call | ret |
---|---|---|
转移指令 | 转移指令 | 转移指令 |
可以只修改IP或同时修改CS、IP | 可以只修改IP或同时修改CS、IP | 可以只修改IP或同时修改CS、IP |
分为段内转移(只修改IP)和段间转移(同时修改CS和IP) | 分为段内转移(只修改IP)和段间转移(同时修改CS和IP) | ret只修改IP,retf同时修改CS、IP |
针对修改IP范围的不同,段内转移分为:短转移(8位)和近转移(16位) | 段内转移只有近转移一种(16位) | 与call类似,不会依据修改IP的范围再区分 |
转移之后无需返回 |