棧幀%ebp,%esp詳解
分類專欄: 彙編
首先應該明白,棧是從高地址向低地址延伸的。每個函數的每次調用,都有它自己獨立的一個棧幀,這個棧幀中維持着所需要的各種信息。寄存器ebp指向當前的棧幀的底部(高地址),寄存器esp指向當前的棧幀的頂部(地址地)。下圖爲典型的存取器安排,觀察棧在其中的位置
入棧操作:push eax; 等價於 esp=esp-4,eax->[esp];如下圖
出棧操作:pop eax; 等價於 [esp]->eax,esp=esp+4;如下圖
我們來看下面這個C程序在執行過程中,棧的變化情況
void func(int m, int n) {
int a, b;
a = m;
b = n;
}
main() {
...
func(m, n);
L: 下一條語句
...
}
在main調用func函數前,棧的情況,也就是說main的棧幀:
從低地址esp到高地址ebp的這塊區域,就是當前main函數的棧幀。當main中調用func時,寫成彙編大致是:
push m
push n; 兩個參數壓入棧
call func; 調用func,將返回地址填入棧,並跳轉到func
當跳轉到了func,來看看func的彙編大致的樣子:
__func:
push ebp; 這個很重要,因爲現在到了一個新的函數,也就是說要有自己的棧幀了,那麼,必須把上面的函數main的棧幀底部保存起 ; 來,棧頂是不用保存的,因爲上一個棧幀的頂部講會是func的棧幀底部。(兩棧幀相鄰的)
mov ebp, esp; 上一棧幀的頂部,就是這個棧幀的底部
;暫時先看現在的棧的情況
;到這裏,新的棧幀開始了
sub esp, 8 ; int a, b 這裏聲明瞭兩個int,所以esp減小8個字節來爲a,b分配空間
mov dword ptr [esp+4], [ebp+12]; a=m
mov dword ptr [esp], [ebp+8]; b=n
這樣,棧的情況變爲:
ret 8 ; 返回,然後8是什麼意思呢,就是參數佔用的字節數,當返回後,esp-8,釋放參數m,n的空間
由此可見,通過ebp,能夠很容易定位到上面的參數。當從func函數返回時,首先esp移動到棧幀底部(即釋放局部變量),然後把上一個函數的棧幀底部指針彈出到ebp,再彈出返回地址到cs:ip上,esp繼續移動劃過參數,這樣,ebp,esp就回到了調用函數前的狀態,即現在恢復了原來的main的棧幀
最近在看彙編碼,經常在程序的開頭看到ESP和EBP寄存器的出現,由於本人基礎知識的不牢靠,便上網查閱相關的資料,可惜網上的資料都不給力,都只是流於形式,沒有好好的解釋這兩個東西是什麼.終於通過google國外的網站,得到一個相當不錯的網頁,上面解釋的很清晰http://www.tenouk.com/Bufferoverflowc/Bufferoverflow2a.html 英文好的可以上去看看(我好像很喜歡這句話)
(1)ESP:棧指針寄存器(extended stack pointer),其內存放着一個指針,該指針永遠指向系統棧最上面一個棧幀的棧頂。
(2)EBP:基址指針寄存器(extended base pointer),其內存放着一個指針,該指針永遠指向系統棧最上面一個棧幀的底部。
在這裏要注意由於在intel系統中棧是向下生長的(棧越擴大其值越小,堆恰好相反)
(1)ESP:棧指針寄存器(extended stack pointer),其內存放着一個指針,該指針永遠指向系統棧最上面一個棧幀的棧頂。
(2)EBP:基址指針寄存器(extended base pointer),其內存放着一個指針,該指針永遠指向系統棧最上面一個棧幀的底部。
根據上述的定義,在通常情況下ESP是可變的,隨着棧的生產而逐漸變小,而ESB寄存器是固定的,只有當函數的調用後,發生入棧操作而改變。
在上述的定義中使用ESP來標記棧的底部,他隨着棧的變化而變化
pop ebp;出棧 棧擴大4byte 因爲ebp爲32位
push ebp;出棧,棧減少4byte
add esp, 0Ch;表示棧減小12byte
sub esp, 0Ch;表示棧擴大12byte
而ebp寄存器的出現則是爲了另一個目標,通過固定的地址與偏移量來尋找在棧參數與變量。而這個固定值者存放在ebp寄存器中,。但是這個值會在函數的調用過程發生改變。而在函數執行結束之後需要還原,因此,在函數的出棧入棧過程中進行保存。
下面根據彙編碼來對上面的內容進行解釋
現在利用VS2013的反彙編功能進行解釋注意設置好斷點
在上述的彙編碼中我們可以看到在函數開始的時候,習慣上以這麼兩端代碼開始
push ebp
mov ebp,esp
按照字面上理解,上面兩句話的意思是將ebp推入棧中,之後讓ebp等於esp
爲什麼這麼做呢?因爲ebp作爲一個用於尋址的固定值是有時間週期的。只有在某個函數執行過程中才是固定的,在函數調用與函數執行完畢後會發生改變。
在函數調用之前,將調用者的函數(caller)的ebp存入棧,以便於在執行完畢後恢復現場是還原ebp的值。下一步,foo必須爲它的局部變量分配空間,同時,也必須爲它可能用到的一些臨時變量分配空間。
sub esp, 0cch;減去的值根據程序而定
之後會根據情況看是否保存某些特定的寄存器(EBX,ESI和EDI)
之後ebp的值會保持固定。此後局部變量和臨時存儲都可以通過基準指針EBP加偏移量找到了
在函數執行完畢,控制流返回到調用者的函數(caller)之前會進行下述操作
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
Ret
所謂有始有終,這是會還原上面保存的寄存器值,之後還原esp的值(上一個函數調用之前的esp被保存在固定的ebp中)與ebp值。這一過程被稱爲還原現場之後通過ret返回上一個函數。