從彙編的角度看棧

2021-06-22 21:59:43 字數 1651 閱讀 9500

大家都知道,棧區是儲存函式,區域性變數的一塊記憶體區域。

那麼讓我們從彙編的角度,來看看函式的執行過程。首先,當我們使用pushl將資料入棧時,棧頂會移動,以容納新增加的值。實際上,我們能不斷將值入棧,棧會在記憶體中保持向下增長,知道存放**或資料的地方。那麼,我們如何知道棧頂位址呢?棧暫存器%esp總是包含乙個指向當前棧頂的指標。

在執行函式之前,乙個程式將函式的所有引數按逆序壓入棧中。接著程式發出一條call指令,表明程式希望開始執行哪乙個函式。call指令會去做兩件事;首先,它將下一條指令的位址即返回位址壓入棧中;然後,修改指令指標(%eip)以指向函式起始處。

引數 n

。。。引數2

引數1返回位址 

現在函式自己也有一些工作。首先通過pushl %ebp指令儲存當前基址指標暫存器%ebp。基址指標是乙個特殊的暫存器,用於訪問函式的引數和區域性變數。其次,它會用movl %esp, %ebp。將指標%esp複製到%ebp,這使你能夠把函式引數作為相對於基址指標的固定索引進行訪問。

在函式開始時將棧指標複製到基址指標暫存器可以讓你一直清楚引數的位置(區域性變數也是如此),即使在其它資料壓入彈出棧的情況下。%ebp將一直是棧指標在函式開始是的位置,所以可以說是對棧幀的常量引用。(棧幀包含乙個函式中所使用的所有棧變數,包括引數、區域性變數和返回位址。)   

引數 n

n×4+4(%ebp)

。。。引數2

12(%ebp)

引數18(%ebp)

返回位址

4(%ebp)

舊%ebp

(%ebp)  和 (%esp)

可以看到,每個引數都可以用%ebp通過基址定址方式訪問。

接下來,函式為其所需的所有區域性變數保留棧空間,只需將棧指標向外移動即可實現。假設要執行函式,我們需要兩個字的記憶體,只需要將棧指標向下移動兩個字即可。

sub  $8, %esp

這樣,我們久能就愛那個棧用於變數的儲存,而不需要擔心函式呼叫引起的入棧會破壞儲存的變數。因為函式呼叫是在棧幀上分配的,而變數僅僅在函式執行期間有效,而當函式返回時,棧幀久不復存在,這些變數也就不存在了。

假如我們有兩個字可用於本地儲存。那麼棧就是如下情況了:

引數 n

n×4+4(%ebp)

。。。引數2

12(%ebp)

引數18(%ebp)

返回位址

4(%ebp)

舊%ebp

(%ebp)

區域性變數 1

-4(%ebp)

區域性變數 2

-8(%ebp) 和 (%esp)

所以我們可以通過使用&ebp中的不同偏移量,通過基址定址訪問函式的所有資料。

當乙個函式執行完畢後,會做三件事。

(1) 將其返回值儲存到%eax。

(2) 將棧恢復到呼叫函式時的狀態(移除當前棧幀,並使呼叫**的棧幀重新生效)。

(3) 將控制權換給呼叫它的程式。這是通過ret指令實現的,該指令將棧頂的值彈出,並將指令指標暫存器%eip設定為該彈出值。

因此,要執行如下命令:

movl %ebp, %esp

popl %ebp

ret現在控制權已經回到,呼叫函式的那裡,你可以檢查%eax中的返回值。彈出其入棧的所有引數,將棧指標復位至其原先的位置(如果不需要引數值,按你可以用addl 將「4 *引數個數」加到%esp即可)。

從彙編角度看引用

引用型別到底是什麼?它和指標有什麼關係?它本身占用記憶體空間嗎?帶著這些疑問,我們來進行分析。先看 include include using namespace std void main 通過彙編檢視 如下 9 int x 1 00401048 mov dword ptr ebp 4 1 10 ...

C 從彙編角度詳解函式呼叫棧

先來看一段 int sum int a,int b intmain 有兩個問題 main函式呼叫sun,sum執行完之後怎麼知道回到哪個函式?sum函式執行完,回到main之後,怎麼知道從哪一行指令繼續執行?我們現在從彙編角度看這段 首先main還是會先開闢棧幀 mov dword ptr ebp ...

從彙編的角度看C 裡的引用和指標

c 裡有引用和指標,使用過c 的應該都知道它們的異同點。本文將從彙編的角度去觀察這2者的本質。使用也非常簡單,在左側編寫c 在右側就會直接顯示對應的組合語言,也可以根據需要的平台來選擇對應的編譯器 我們使用的c 如下,比較簡單 int main void 選擇的編譯器是x86 64 gcc 9.2,...