第十章 CALL和RET指令
本章概述:
call和ret指令都是转移指令,它们都修改IP,或同时修改CS和IP。它们经常被共同用来实现子程序的设计。
一、 ret和retf
1. ret指令用栈中的数据,修改IP的内容,从而实现近转移。
2. retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。
3. CPU执行ret指令时,进行下面两步操作:
1) (IP)=((ss)*16+(sp))
2) (sp)=(sp)+2
4. CPU执行retf指令时,进行下面4步操作:
1) (IP)=((ss)*16+(sp))
2) (sp)=(sp)+2
3) (CS)=((ss)*16+(sp))
4) (sp)=(sp)+2
5. CPU执行ret指令时,相当于进行:pop IP
6. CPU执行retf指令时,相当于进行:pop IP pop CS
二、 call指令
1. CPU执行call指令时,进行两步操作:
1) 将当前的IP或CS和IP压入栈中;
2) 转移。
2. call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同。
三、 依据位移进行转移的call指令
1. call 标号(将当前的IP压栈后,转到标号处执行指令)
2. CPU执行此种格式的call指令时,进行如下的操作:
1) (sp)=(sp)-2
2) ((ss)*16+(sp))=(IP)
3) (IP)=(IP)+16位位移。
16位位移=标号处的地址-call指令后的第一个字节的地址;
16位位移的范围为-32768~32767,用补码表示;
16位位移由编译程序在编译时算出。
3. CPU执行“call 标号”时,相当于进行:
push IP
jmp near ptr 标号
四、 转移的目的地址在指令中的call指令
1. call far ptr 标号 实现的是段间转移
2. CPU执行此种格式的call指令时,进行如下的操作。
1) (sp)=(sp)-2
((ss)*16+(sp))=(CS)
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
2) (CS)=标号所在段的段地址
(IP)=标号所在段中的偏移地址
3. CPU执行“call far ptr 标号”时,相当于进行:
push CS
push IP
jmp far ptr 标号
五、 转移地址在寄存器中的call指令
1. 指令格式:call 16位reg
功能:
(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(16位reg)
2. CPU执行“call 16位reg”时,相当于进行:
push IP
jmp 16位reg
六、 转移地址在内存中的call指令
转移地址在内存中的call指令有两种格式:
1) call word ptr 内存单元地址,相当于:
push IP
jmp word ptr 内存单元地址
2) call dword ptr 内存单元地址,相当于进行:
push CS
push IP
jmp dword ptr 内存单元地址
七、 call和ret的配合使用
1. 具有一定功能的程序段,称之为子程序。在需要的时候,用call指令转去执行,call指令转去执行子程序之前,call指令后面的指令的地址将存储在栈中,所以可在子程序的后面使用ret指令,用栈中的数据设置IP的值,从而转到call指令后面的代码处继续执行。
2. 可以利用call和ret来实现子程序的机制。子程序的框架:
标号:
指令
ret
八、 mul指令
1. mul指令是乘法指令,使用mul做乘法时,需要注意一下两点:
1) 两个相乘的数:两个相乘的数,要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg活内存字节单元中;如果是16位,一个默认在AX中,另一个放在16位reg或内存字单元中。
2) 结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果高位默认在DX中存放,低位在AX中存放。
2. 内存单元可以用不同的寻址方式给出:
1) mul byte ptr ds:[0]
2) mul word prt [bx+si+8]
九、 模块化程序设计
1. call和ret指令共同支持了汇编语言编程中的模块化设计。在实际编程中,程序的模块化是比不可少的。因为现实的问题比较复杂,对现实问题进行分析时,把它转化成相互联系、不同层次的子问题,是必须的解决方法。
2. call和ret指令对这种分析方法提供了程序实现上的支持。利用call和ret指令,我们可以用简捷的方法,实现多个相互联系、功能独立的子程序来解决一个复杂的问题。
十、 参数和结果传递的问题
1. 子程序一般都要根据提供的参数处理一定的事务,处理后,将结果(返回值)提供给调用者。
2. 用寄存器来存储参数和结果是最常见使用的方法。对于存放参数的寄存器和存放结果的寄存器,调用者和子程序的读写操作恰恰相反:调用者将参数送入参数寄存器,从结果寄存器中取到返回值;子程序从参数寄存器中取到参数,将返回值送入结果寄存器。
十一、 批量数据的传递
1. 如果子程序传递只有一个参数,放在某个寄存器中。但如果有两个参数,那么可以用两个寄存器来存放,可是如果需要传递的数据有3个、4个或更多直至N个,该怎么存放呢?寄存器的数量终究有限,我们不可能简单地用寄存器来存放多个需要传递的数据。对于返回值,也有同样的问题。
2. 在这种时候,我们将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序。对于具有批量数据的返回结果,也可用同样的方法。
3. 除了用寄存器传递参数外,还有一种通用的方法是用栈来传递参数。
十二、 寄存器冲突的问题
1. 子程序中使用的寄存器,很可能在主程序中也要使用,造成了寄存器使用上的冲突。
2. 解决这个问题的简捷方法是,在子程序的开始将子程序中所有用到的寄存器中的内容都保存起来,在子程序返回前再恢复。可以用栈来保存寄存器中的内容。
3. 编写子程序的标准框架如下:
子程序开始:子程序中使用的寄存器入栈
子程序内容
子程序中使用的寄存器出栈
返回(ret、retf)
十三、 实验10编写子程序
十进制数码字符对应的ASCII码 = 十进制数码值 + 30H
十四、 课程设计