ret和call都是转移指令,都是修改IP的值,或同时修改CS和IP。它们经常被一起用来实现子程序的设计。
ret和retf
ret指令用栈中的数据修改IP,实现的是近转移;retf指令用栈中的数据修改CS和IP的值,实现远转移。
格式:直接用 ret。
ret指令执行时,cpu进行2步操作:
1. ip = ss * 16 + sp2. sp = sp + 2
retf指令执行时,cpu进行4步操作:
1. ip = ss * 16 + sp2. sp = sp + 23. cs = ss * 16 + sp4. sp = sp + 2
可以看出,ret相当于汇编指令的
pop IP
retf相当于
pop IPpop CS
call指令
call指令也是一个转移指令,执行格式:call 目标(具体使用接下来说明),call的执行步骤:
- 将当前的IP或CS和IP入栈
- 转移
call不能实现短转移,但它实现转移的原理和jmp相同。
根据位移转移:call 标号,近转移,16位转移范围,也是使用相对的转移地址。
执行步骤:
- (SP)=(SP)-2
- ((SS)*16+(SP))=(IP)
- (IP)=(IP)+16
所以执行这条命令相当于执行
push ipjmp near ptr 标号
直接使用地址进行(远)转移:call far ptr 标号,执行步骤:
- (SP)=(SP)-2
- ((SS)*16+(SP))=(CS)
- (SP)=(SP)-2
- ((SS)*16+(SP))=(IP)
- (CS)=标号所在的段的段地址
- (IP)=标号的偏移地址
所以执行call far ptr 标号相当于执行
push cspush ipjmp far ptr 标号
使用寄存器的值作为call的跳转地址:call 16位reg
- (SP)=(SP)-2
- ((SS)*16+(SP))=(IP)
- (IP)=(16为reg)
相当于执行
push ipjmp 16位reg
使用内存中的值作为call的跳转地址:call word ptr 内存单元地址,当然还有call dword ptr 内存单元地址,这样进行的就是远转移。
联合使用ret和call
联合使用ret和call实现子程序的框架:
assume cs:codecode segmentmain:···call sub1···mov ax,4c00hint 21hsub1:···call sub2···retsub2:···retcode endsend main
mul指令
mul是乘法指令,使用时应注意,两个相乘的数,要么都是8位,要么都是16位,如果是8位,那么其中一个默认放在al中,另一个在一个8位reg或字节内存单元中;若是16位,则一个默认在ax中,另一个在16位reg或字内存单元中。如果是8位乘法, 则结果放在ax中,结果是16位;若是16位乘法,结果默认在ax和dx中,dx高位,ax低位,共32位。
格式:mul reg 或 mul 内存单元,支持内存单元的各种寻址方式。
如mul word ptr [bx+si+8]代表:
(ax)=(ax)*((ds)*16+(bx)+(si)+8)低16位(dx)=(ax)*((ds)*16+(bx)+(si)+8)高16位
例:计算100*10
mov al,100mov bl,10mul bl
参数的传递和模块化编程
看下面一段程序:计算data中第一行的数的立方存在第二行
assume cs:codedata segmentdw 1,2,3,4,5,6,7,8dd 0,0,0,0,0,0,0,0data endscode segmentstart:mov ax,datamov ds,axmov si,0mov di,16mov cs,8s:mov bx,[si]call cubemov [di],axmov [di].2,dxadd si,2add di,4loop smov ax,4c00hint 21hcube:mov ax,bxmul bxmul bxretcode endsend start
寄存器冲突
观察下面将data中的数据全转化为大写的代码:
assume cs:codedata segmentdb 'word',0db 'unix',0db 'wind',0db 'good',0data endscode segmentstart:mov ax,datamov ds,axmov bx,0mov cx,4s:mov si,bxcall capitaladd bx,5loop smov ax,4c00hint 21hcapital:mov cl,[si]mov ch,0jcxz okand byte ptr [si],11011111binc sijmp short capitalok:retcode endsend start
这段代码有一个问题出在,主函数部分使用cx设置循环次数4次,在循环中调用了子函数,而子函数中有一个判断语句jcxz也是用了cx,并且在之前修改了cx的值,造成逻辑错误。虽然修改的方法有很多,但我们应遵循以下的标准:
- 编写调用子程序的程序不必关心子程序使用了什么寄存器
- 编写子程序不用关心调用子程序的程序使用了什么寄存器
- 不会发生寄存器冲突
针对这三点,我们可以如下修改代码:
···capital:push cxpush sichange:mov cl,[si]mov ch,0jcxz okand byte ptr [si],11011111binc sijmp short changeok:pop sipop cxret···
虽然和上面的程序中没有冲突的是si,但我们保险起见,在子程序开始时将子程序用到的所有的寄存器的内容存入栈中,在返回之前在出栈回到相应寄存器中。这样无论调用子程序的程序使用了什么寄存器,都不会产生寄存器冲突。
