最近在研究棧幀的結構,但總是有點亂,所以寫了一個小程序來看看esp和ebp在棧幀中的作用。這個程序如下:

這個程序很簡單,就是求兩個數的值,然后輸出即可。所以首先把它用gcc編譯鏈接成a.out,進入gdb進行調試。

首先在main和add兩處設置斷點。運行到第一個斷點,查看main的匯編代碼:

我們主要是觀察調用add后我們的esp和ebp的變化,於是輸入命令:c,繼續運行到add處,觀察add的匯編:

其實也就是運行到了main的call指令處進入add函數了。這時到了觀察esp和ebp的時刻了。

上圖其實已經很明顯了。我們來看下面的圖,這樣立體觀察(棧幀結構圖)就會瞬間明白了:

我把棧幀的每個字節都拆開來看就會有下面的結論,通過GDB,查找出這時esp和ebp的值,均為0xbffff0a8。
詳解如下:
我們知道調用call指令后會有下面的三件事:
- call指令保存返回地址:所謂保存返回地址(return address)其實就是 call指令將那一時刻的PC(%eip值,即call的下一條指令的地址)壓入棧(如上圖中的0x8048447)。還記得嗎?因為PC自增在先,指令執行在后。所以執行完add的所有代碼后,ret指令會恢復PC的值,程序就可以繼續執行main的剩余代碼了。
- add()保存main()的%ebp:將add()棧幀的base地址壓入到棧上。
這時查看從0xbffff0a8開始每個字節里的內容,從上面看出從0xbffff0a8開始向上的16個字節的內容(我把其中的8個放到了棧幀結構圖中了)。main棧幀的最后4個字節代表着返回地址,通過gdb的查看也可以明顯看出里邊的內容就是main中call指令的下一條指令的地址。當我們讀取0xbffff08a中的內容時(按雙字節讀)結果顯示0xbffff0d8.可見這里是小端存儲,讀取是從高地址開始讀取,其值表示main中的基址。這里有一個誤區就是:我們在數據結構中學的棧頂指針總是指向棧頂元素的下一個位置,但在這里確實指向棧頂元素。這里千萬要注意。
