概述
文章講的是彙編語句中執行函數時堆棧的過程,其中比較重要的是 ESP
和 EBP
這倆個寄存器.
調用過程
假如讓我們來設計這個函數調用的堆棧過程,我覺得可以這樣思考
現看這個函數調用 ,調用 add
方法需要傳過去參數 ,所以必須有一個地方可以讓add
函數執行形成的堆棧可以取得到 main
傳過來 ,再一個 add
執行完之後還得返回main
中繼續執行 ,所以返回地址也是必須保存的.
上面左圖這是說明了,在一個進程中,堆是向下增長的, 右圖則是從調用者
和 被調用者
兩個角度在描述這個調用的過程..
我們常提到的IA32
寄存器就幾個 , 當我要去調用下一個函數的時候, 爲了騰出寄存器給即將調用的函數使用,所以調用之前我們必須先將寄存器中的數據入棧 ,這樣即將調用的函數就可以使用了 (我們後面會看到保存返回地址也是這個套路 ,先將返回地址保存在棧裏然後執行完函數之後的執行回到返回地址就好了)
其中需要注意的兩個寄存器 :
- ESP (stack point) : 指向當前棧幀的頂部
- EBP (base point) : 指向棧幀的底部
底部是不會動的,而頂部會一直增長
接下來看一個例子 :
上面的例子 ,可以看到 ESP
和 EBP
始終是指向當前棧幀的頂部和底部的, 我們可以看到大致的過程可以分爲 :
- 準備階段
- 過程體
- 結束階段
這裏注意幾個細節
每個過程開始的兩條指令
pushl %ebp
movl %esp, %ebp
意思就把調用人的 EBP
先入棧了 (方便後續執行找回來),然後將ESP
的值給到 EBP
(上一句我們已經把 EBP
的值存起來了,所以此時 EBP
就可以用來新的函數形成的堆棧的棧底了,秒啊!) ,那我如果要讓 EBP
回到我之前入棧的那個值呢?
popl %ebp
即可.
call 語句
call
語句做的事情有兩個 :
- 保存返回地址
- 跳轉執行函數
在上面例子中, 返回地址就是 :
movl %eax, -4(%ebp)
這一語句的地址
跳轉執行函數開始的語句是什麼
還是這兩句
pushl %ebp
movl %esp, %ebp
執行函數
上面的例子我們看到執行函數的過程中 ,爲了獲取傳進來的參數 ,可以通過 EBP
往上偏移 8或是12 ,具體的語句 :
movl 8(%ebp),%edx
movl (%edx),%ecx
返回
ret
實際會把前面入棧的返回地址
放在 EIP
寄存器 ,這樣就可以回到調用函數的下一句啦.(IA32中用EIP存放將要執行的地址)