call和ret指令都是轉移指令,它們都修改IP,或同時修改CS和IP。它們經常被共同用來實現子程序的設計。
ret 和 retf
ret指令用棧中的數據,修改IP的內容,從而實現近轉移;
retf指令用棧中的數據,修改CS和IP的內容,從而實現遠轉移;
CPU執行ret指令時,進行下面兩步操作:
- (ip)=((ss)*16+(sp))
- (sp)=(sp)+2
CPU執行retf指令時,進行下面4步操作:
- (ip)=((ss)*16+(sp))
- (sp)=(sp)+2
- (cs)=((ss)*16+(sp))
- (sp)=(sp)+2
可以看出,如果我們用匯編語法來解釋ret和retf指令,則:
CPU執行ret指令時,相當於執行:
pop IP
CPU執行retf指令時,相當於執行:
pop IP
pop CS
依據位移進行轉移的call指令
call 標號(將當前的IP壓入棧后,轉到目標處執行指令)
CPU執行此種格式的call指令時,進行如下的操作:
- (sp)=(sp)-2
- ((ss)*16+(sp))=(ip)
- (ip)=(ip)+16位位移。
- 16位位移=標號處的地址 - call 指令后的第一個字節的地址;
- near ptr 指明此處的位移為16位位移;
- 16位位移的范圍為 -32768~32767,用補碼表示;
- 16位位移由編譯程序在編譯時算出。
CPU執行 call 標號 時,相當於進行:
push IP
jmp near ptr 標號
轉移的目的地址在指令中的call指令
call far ptr 標號 實現的是段間轉移。
CPU執行此種格式的call指令時,進行如下的操作。
- (sp)=(sp)-2
- ((ss)*16+(sp))=(cs)
- (sp)=(sp)-2
- ((ss)*16+(sp))=(ip)
- (CS)=標號所在段的段地址
- (IP)=標號在段中的偏移地址
CPU執行 call far ptr 標號 時,相當於進行:
push CS
push IP
jmp far ptr 標號
轉移地址在寄存器中的call指令
指令格式:call 16 位 reg
功能:
- (sp)=(sp)-2
- ((ss)*16+(sp))=(ip)
- (ip)=(16位reg)
CPU執行 call far ptr 標號 時,相當於進行:
push IP
jmp 16位 reg
轉移地址在內存中的call指令
轉移地址在內存中的call指令有兩種格式。
- call word ptr 內存單元地址
CPU執行 call word ptr 內存單元地址 時,相當於進行:
push IP
jmp word ptr 內存單元地址
- call dword ptr 內存單元地址
CPU執行 call dword ptr 內存單元地址 時,相當於進行:
push CS
push IP
jmp dword ptr 內存單元地址
mul 指令
mul 是乘法指令,使用mul 做乘法的時候注意以下兩點:
- 兩個相乘的數:兩個相乘的數,要么都是8位,要么都是16位。如果都是8位,一個默認放在AL中,另一個放在8位reg或內存字單元中;如果都是16位,一個默認在AX中,另一個放在16位reg或內存字單元中。
- 結構:如果是8位乘法,結構默認放在AX中;如果是16位乘法,結構高位默認在DX中存放,低位在AX中放。
寄存器沖突的問題
我們利用call和ret來實現子程序的機制。子程序的框架如下。
標號:
指令
ret
具有子程序的源程序的框架如下。
assume cs:code
code segment
main:
:
call sub1 ;調用子程序sub1
:
:
mov ax,4c00h
int 21h
sub1: ;子程序sub1開始
:
call sub2 ;調用子程序sub2
:
:
ret ;子程序返回
sub2: ;子程序sub2開始
:
ret ;子程序返回
code ends
end main
但可能引出一個一般化的問題:子程序中使用的寄存器,很有可能在主程序中也要使用,造成了寄存器使用上的沖突。
解決這個問題的簡捷方法是,在子程序的開始將子程序中所有用到的及寄存器中的內容都保存起來,在子程序返回前再恢復。 可以用棧來保存寄存器中的內容。
所以,我們編寫子程序的標准框架:
子程序開始: 子程序中使用的寄存器入棧
子程序的內容
子程序中使用的寄存器出棧
返回(ret,retf)