棧幀%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的棧幀