1. 程式人生 > 其它 >過程呼叫

過程呼叫

技術標籤:組合語言

一、堆疊框架

堆疊框架(stack frame)也稱為活動記錄(activation record),它是為傳遞的引數、子例程的返回地址、區域性變數和儲存的暫存器保留的堆疊空間。堆疊框架是按以下步驟建立的:

  • 如果有傳遞的引數,則壓入堆疊
  • 子例程被呼叫,子例程的返回地址壓入堆疊
  • 子例程在開始執行時,EBP被壓入堆疊
  • EBP設為ESP的值,從這時開始,EBP就被作為定址所有子例程引數的基址指標使用了
  • 如果有區域性變數,ESP減去一個數值,以便在堆疊上為區域性變數保留空間
  • 如果任何暫存器需要儲存,則壓入堆疊

1.1 堆疊引數

有兩種基本型別的子例程引數:暫存器引數和堆疊引數。Irvine32和Irvine16庫使用暫存器引數,在下面著重介紹如何宣告和使用堆疊引數。

呼叫程式向子例程傳遞的值稱為argument(引數),當這些值被子例程接收的時候,它們被稱為parameter(引數)。

堆疊引數的使用過程是,在呼叫子例程之前,引數首先壓入堆疊。例如,假設DempMem使用堆疊引數,那麼可以使用下面的程式碼進行呼叫:

push TYPE array
push LENGTHOF array
push OFFSET array
call DumpMem

在進行子例程呼叫時,在堆疊上壓入了兩類引數:

  • 值引數(變數和常量的值)
  • 引用引數(變數的地址)

傳遞值:通過在堆疊上壓入變數值的一份副本的方式傳遞引數,這個過程稱為傳遞值,或簡稱為傳值。假設要呼叫一個子例程AddTwo,向該過程傳遞兩個32位整數,那麼過程如下:

.data
val1 DWORD 5
val2 DWORD 6

.code
push val1
push val2
call AddTwo

下面是call指令執行之前的堆疊示意圖:
在這裡插入圖片描述
C++中等價的函式呼叫是:

int sum=AddTwo(val1,val2);

傳遞引用:傳遞引用的引數是一個物件的地址。下面的語句呼叫了swap,通過引用傳遞兩個引數:

push OFFSET val1
push OFFSET val2
call Swap

下面是呼叫Swap前堆疊的示意圖:
在這裡插入圖片描述
這是等價的C/C++程式碼:

Swap(&val1,&val2);

傳遞陣列:在傳遞陣列引數時,高階語言總是傳遞引用,因為通過傳遞值的方式傳遞大量資料是完全不切合實際的,這時直接在堆疊上壓入資料,會降低程式的執行速度並且用光寶貴的堆疊空間。例如下面的語句向子例程ArrayFIll傳遞array的偏移地址:

.data
array DWORD 50 DUP

.code
push OFFSET array
call ArrayFill

堆疊引數的訪問(C/C++)

在呼叫函式時,C/C++程式使用標準的方法初始化和訪問引數。C/C++中的函式以序言(prologue)開始,序言部分的程式碼儲存了EBP暫存器,並使EBP指向當時堆疊的頂部,函式還有可能把一些暫存器壓棧,這些暫存器的值將在函式返回的時候恢復。函式以收尾(epilogue)程式碼結束,在這部分程式碼中,EBP暫存器被恢復,RET指令從函式返回。

還是以AddTwo為例,下面用C編寫的AddTwo函式接收兩個整數引數(通過傳值的方式傳遞)並返回其和:

int AddTwo(int x,int y){
	return x+y;
}

下面是等價的一個組合語言實現,首先,AddTwo把EBP壓棧以儲存其值:

push ebp

接下來,ebp設定為esp的值,因此ebp可作為AddTwo的堆疊框架的基址指標使用: