1. 程式人生 > >10 CALL和RET指令

10 CALL和RET指令

1、ret指令用棧中的資料,修改IP的內容,從而實現近轉移;

retf指令用棧中的資料,修改CS和IP的內容,從而實現遠轉移

CPU執行ret指令時,進行下面兩步操作:

(1)(IP) = ((ss)*16+(sp))

(2)(sp) = (sp)+ 2

CPU執行retf指令時,進行下面4步操作:

(1)(IP) = ((ss)*16 + (sp))

(2)(sp)=(sp)+2

(3)(CS)= ((ss)*16 + (sp))

(4)(sp) = (sp)+2

可以看出,如果我們用匯編語法來解釋ret和retf指令,則:

CPU指令ret指令時,相當於進行:

pop IP

CPU執行retf指令時,相當於進行:

pop IP

pop CS

2、call指令

CPU執行call指令時,進行兩步操作:

(1)將當前的IP或CS和IP壓入棧中:

(2)轉移

call指令不能實現短轉移,除此之外,call指令實現轉移的方法和jmp指令的原理相同

3、依據位移進行轉移的call指令

call 標號(將當前的IP壓棧後,轉到標號處執行指令)

CPU執行此種格式的call指令時,進行如下的操作:

(1)sp = sp -2

ss*16 + sp = ip

(2)ip = ip + 16

根據以上得出結論:CPU執行“call 標號”相當於進行:

push IP

jmp near ptr 標號

4、轉移的目的地址在指令中的call指令

前面講的call指令,其對應的機器指令中並沒有轉移的目的地址,而實相當於當前IP的轉移位移

call far ptr 標號 實現的時段間轉移

CPU執行此種格式的call指令時,進行如下的操作:

(1)sp = sp -2

ss*16 + sp = CS

sp = sp -2

ss*16 +sp = IP

(2)(CS) = 標號所在段的段地址

(IP) = 標號在段中的偏移地址

根據以上得出結論:CPU 執行“call  far ptr 標號”相當於進行:

push CS

push IP

jmp far ptr 標號

5、轉移地址在暫存器中的call指令

指令格式:call 16位 reg

功能:

sp = sp -2

ss*16 + sp = IP

IP = 16位 reg

彙編解釋:

push IP

jmp 16位 reg

6、轉移地址在記憶體中的call指令

CPU執行 call word ptr 記憶體單元地址時,相當於進行:

push IP

jmp word ptr 記憶體單元地址

CPU執行 call word ptr 記憶體單元地址時,相當於進行:

push CS

push IP                //出棧時候先出IP

jmp word ptr 記憶體單元地址

7、call和ret配合使用

相當於函式呼叫之後返回原來執行的下一條語句

8、mul指令(乘法指令)

(1)兩個相乘的數,要麼都是8位,要麼都是16位。如果是8位,一個預設在AL中,另一個放在8位reg或記憶體位元組單元中;如果是16位,一個預設在AX中,另一個放在16位reg或記憶體位元組單元中。

(2)結果:如果是8位乘法,結果預設放在AX中;如果是16位乘法,結果高位預設在DX存放,低位在AX中存放;

9、暫存器衝突的問題

如何避免衝突呢?可以有兩個方案(引出更NB的方案)

(1)在編寫呼叫子程式的程式時,注意看看子程式中有沒有用到會產生衝突的暫存器,如果有,呼叫者使用別的暫存器

(2)在編寫子程式的時候,不要使用會產生衝突的暫存器

分析一下兩種的可能性(打臉時刻)

(1)這將給呼叫子程式的程式編寫造成很大的麻煩,因為必須要小心檢查所呼叫的子程式中是否有將產生衝突的暫存器

(2)這個方案是不可能實現的,因為編寫子程式的時候無法知道將來的呼叫情況

可見,我們上面的兩個方案都不可行。我們希望:

(1)編寫呼叫子程式的程式的時候不必關心子程式到底是用了哪些暫存器;

(2)編寫子程式的時候不必很關心呼叫者使用了哪些暫存器;

(3)不會發生暫存器衝突

解決這個問題的簡捷方法是,在子程式的開始將子程式中所有用到的暫存器中的內容都儲存起來,在子程式返回前恢復,可以用棧來儲存暫存器中的內容

以後編寫子程式的框架如下:

子程式開始: 子程式使用的暫存器入棧

                       子程式內容

                       子程式中使用的暫存器出棧

                        返回(ret、retf)