1. 程式人生 > >c語言--棧幀結構分析

c語言--棧幀結構分析

這是一篇在自己寫了很久的文章了,因為要清理電腦,放在部落格上當備份。

寫在前邊:

棧幀結構對於初學C語言著,可能有些陌生。但是不能否認的是棧幀在C語言中扮演著很重要的角色,下邊就棧幀分析下記憶體。如果真對C語言感興趣的請耐心的看下邊的敘述。

棧幀說的簡單點就是呼叫函式的過程中,為這個函式開闢一個棧空間,用來儲存這個函式中的臨時變數。而這個儲存函式臨時變數的空間就被稱為棧空間。而這個棧空間就被稱為棧幀。

*******************************************************

索引:

在分析棧幀前還需要簡述幾個簡單的知識點。(在棧幀的分析中我們會用到的)

EIP:EIP又稱為

PC指標暫存器。EIP中儲存的是CPU正在執行指令的下一條指令。指令暫存器(extended instruction pointer),其記憶體放著一個指標,該指標永遠指向下一條等待執行的指令地址。 可以說如果控制了EIP暫存器的內容,就控制了程序——我們讓eip指向哪裡,CPU就會去執行哪裡的指令。eip可被jmp、call和ret等指令隱含地改變(事實上它一直都在改變)(ret指令就是把當前棧頂儲存的返回值地址 彈到eip中)

esp:棧頂暫存器,存放了指向函式棧幀棧頂的地址。其記憶體放著一個指標,該指標永遠指向系統棧最上面一個棧幀的棧頂。

Ebp:棧地暫存器(又可稱基址暫存器),存放了指向棧幀棧底的地址。

其記憶體放著一個指標,該指標永遠指向系統棧最上面一個棧幀的底部。

在這塊就需要簡單說一下函式的記憶體圖了:

 

(&&對這就個圖的介紹):記憶體中地址由小到大,先可以簡單的化成上邊的這樣一個區域,此次主要研究的是堆和棧區,下邊的記憶體暫不仔細去研究(下邊記憶體並不全面,只是取了幾個代表)。

棧區和堆區是相對而生的,堆區是向上生長的,棧區是向下生長的。堆區和地址空間的大小排列一樣,而這快容易出問題的應該算棧區了。而我們下邊會仔細去敘述一下堆區的。

而這裡需要強調的一點是堆區和棧區之間有一段空白,堆區和棧區不是相連的區域。

上邊說了這麼多的話語了,現在簡單粗暴一點,直接上程式碼。

我們執行的程式碼在

VC6.0環境下邊,原因只是因為編譯器環境簡單。

定義一個main函式,定義兩個引數a,b。在main函式中呼叫一個函式使得兩個數相加,暫且將這個函式稱為Add,最終返回到main函式中。打印出結果。

函式如下

 

暫時定義ab為這樣,為了等會更直觀的展現出來。程式碼很簡單現在進行分析。

程式執行進入組合語言過程中。(組合語言的指令不詳細介紹了)

棧向下生長,因此ebp棧底反而在地址大的地方,esp棧頂在地址小的地方。


這張圖片所描述的是棧區的放大空間。

棧是向下生長的,因此這塊需要注重的一個細節是ebp棧底指標反而指向地址大的地方,esp棧頂指標指向在地址小的地方。

 

調用出彙編程式碼,函式從main開始,前邊的預處理暫時不仔細去研究,簡單來描述,預處理給main分配了一個空間,並全部初始化了。如果真對這段彙編程式碼感興趣,可以查閱相關的組合語言的書籍。

 

當代碼進行到add時時在棧空間中已經進去了a,b,以及定義的ret。在這塊需要注意的是mov指令將a移動到【ebp-4】即ebp指標下移指向a,將a的地址放進去。b移動到【ebp-8】中即ebp指標指向了b的地址。

這塊需要說的和之前一樣棧中的生長方式是向下生長的,因此地址由高到低,一次存放。即實際上就是給a先開闢空間,再給b開闢空間。

 

此處push即壓棧的過程,而eaxcup裡的通用暫存器,此時的eax裡放的是esp的地址。即棧幀的壓棧過程是往棧頂壓棧,通用暫存器裡邊放的是esp的地址。此時就是最關鍵的地方了。在理論上的棧先進後出,就在這用直觀的程式碼顯示出來了。

esx,ebx,ecx,edx都是通用暫存器,在CPU中起一個儲存臨時變數的作用)

eax儲存的是【ebp-8】,即b的地址,即從組合語言來看就是將,b的地址壓棧進,esp頂部。而a也是這個原理根據彙編程式碼可以分析出,ab的下邊。即根據彙編程式碼可以分析畫出下邊的圖。

 

        此時的esp指向了壓棧a的地址空間。

這個過程返回到編輯器,呼叫一下暫存器,與記憶體就可以清晰的展現在面前。

 

此時的呼叫esp棧頂的地址,a,b的地址展現的很清楚了。而這塊還需要注意的是棧是由上向下生長的。

這塊需要知道一個組合語言的命令。

Call命令:(兩個作用)

1、將當前執行指令的下一條指令的下一條地址壓入棧中。

2、隨機call就會跳轉(jmp)至指定函式程式碼塊去執行。

 

此時的介面是呼叫玩call指令之後的圖,即現在的棧頂將add這個地址壓棧進去。可看見esp的最上端是add的地址。

 

上圖就是呼叫到add函式的程式碼。跟main有異曲同工之處。

此時的eip開始呼叫add()函式中的值。

 

即在空間圖上可以分析為在空間上在分配一個空間用來存放add函式的臨時變數。

 

Add函式就是跟上邊的main函式一樣的存放在屬於它自己的棧幀空間裡的。具體的就不詳細描述了。可以跟著上邊的main對比來看一下。

而在這塊 add函式返回的這塊的仔細的剖析一下。

 

在函式add中,a,b的和賦值給z,而將兩個數所加之和賦值給,通用暫存器eax中,暫時存放起來。

 

    在函式的結束部分,有這樣一段彙編程式碼。這個過程就是棧的釋放過程。

將ebp的地址賦給了esp.然後釋放了ebp的空間,即main:ebp被釋放。Ebp返回到main函式中的ebp地址中去。而原來的main:ebp空間是空的所以向上自動跳一格。

而最後的ret這塊需要強調一下。

Ret命令的兩個作用:

1、彈出pop棧頂

2、彈出棧頂的地址,並且將返回值放入EIP中。

則此時的ebpesp又返回到之前的地址。到現在,棧的形成與銷燬就完成了。

得出一個函式的棧空間都是自己開闢的,使用完後銷燬釋放空間。