1. 程式人生 > >Linux彙編教程11:函式與棧

Linux彙編教程11:函式與棧

Linux彙編教程1

開發一個程式,把所有的功能程式碼都在一塊,會讓程式變得難以維護。為了協助小組中的其他成員分工合作,我們需要把程式劃分成互相獨立的模組。一個模組問題不會牽連整個專案。一個程式有數千個函式構成,每一個函式實現一個功能,現在,我們開始學習函式部分。
一個函式有下面一個部分

函式名 —— 函式的名稱作為一個標籤,代表函式程式碼的起始位置。
函式引數 —— 函式引數是現實給函式處理的資料項
區域性變數 —— 區域性變數是函式進行處理時使用的資料儲存區,但函式返回是,變數的空間會被捨棄回收。函式中的區域性變數,程式中的任何其他函式無法直接利用。
靜態變數 —— 靜態變數是函式處理時用到的資料儲存區,使用後的空間不會被捨棄。
全域性變數 —— 全域性變數是函式之外的管理資料的儲存區。
返回值 —— 返回一些資料或資訊給呼叫了函式的部分。
返回地址 —— 返回地址不能直接在函式中使用。返回地址是但函式結束後接著執行程式碼的位置。call指令會為你處理返回地址,ret指令負責按照地址返回呼叫函式的地方。

下面一個部分,我們來講棧。

計算機的棧位於記憶體地址的最頂端。是用後進先出的方式,我們通過pushl指令來入棧,popl來出棧。我們的棧暫存器%esp包含指向當前棧頂的指標。當我們使用pushl入棧是,%esp中指標的值會減去4,當使用popl出棧的時候,%esp中指標的值會加4。
popl:

將棧頂資料彈出到某個記憶體位置或暫存器,相當於依次執行 movl (%esp), R/M和addl $4, %esp兩條指令

pushl:

將某個值入棧,相當於一次執行subl $4, %esp和 movl I/R/M, (%esp)兩條指令。
棧對於函式的區域性變數、引數、返回地址的實現十分重要。在執行函式之前,會將函式的所有引數按逆序壓入棧內。接著call指令會實現兩個作用:把下一條指令的地址(也就是返回地址)壓入棧中;修改%eip中的指令指標以指向函式起始處。接著開始執行之前,棧中的分佈類似下面:
返回地址 << (%esp)
引數1
引數2
……
引數n
函式的引數被壓入棧中,最後入棧的是返回地址。現在%esp裡的指標指向返回地址。接下來函式本身還有一些操作:由於我們需要使用基址暫存器%ebp進行對棧中資料的索引訪問,所以我們需要把%ebp中原有的資料做一個保留。首先,通過pushl %ebp指令把%ebp入棧。%ebp是一個特殊的暫存器,用於訪問函式和區域性變數;接著,執行movl %esp, %ebp指令,讓%ebp指標的值和現在的%esp一樣。現在的棧看起來是這樣:

原來的(%ebp) <<< (%esp) 和 (%ebp)
返回地址 <<< 4(%ebp)
引數1 <<< 8(%ebp)
引數2 <<< 12(%ebp)
……
引數n <<< (n+1)*4(%ebp)

現在就可以利用%ebp進行基址索引了,接下來,函式為需要的區域性變數開闢空間,我們只要移動棧指標就好了。如果我們需要3個字的記憶體,執行subl $12, %esp指令,這樣就有空間儲存變數,當函式返回時,這個堆疊也就消失了,這些變數也就沒有了。現在在棧中的情況是這樣:

區域性變數3 <<< -12(%ebp) 和 (%esp)
區域性變數2 <<< -8(%ebp)
區域性變數1 <<< -4(%ebp)
原來的(%ebp) <<< (%ebp)
返回地址 <<< 4(%ebp)
引數1 <<< 8(%ebp)
引數2 <<< 12(%ebp)
……
引數n <<< (n+1)*4(%ebp)
當一個函式執行結束後,函式還會進行三個步驟

將返回值儲存到%eax
移除當前棧幀,並使呼叫程式碼的棧幀恢復
將控制權交還給呼叫它的程式。利用ret指令

所以要讓函式返回,需要使用下面的指令:

movl %ebp, %esp
popl %ebp
ret

讓%esp中是值指向舊的%ebp中的值儲存的地方,在把就的舊的%ebp中的值彈出到%ebp中,現在%esp的值就指向了返回地址,在使用ret,將棧頂的值彈出,並將指令指標暫存器%eip設定為這個彈出值。