ebp 函数堆栈esp_对于ESP、EBP寄存器的理解
esp是棧指針,是cpu機(jī)制決定的,push、pop指令會(huì)自動(dòng)調(diào)整esp的值;
ebp只是存取某時(shí)刻的esp,這個(gè)時(shí)刻就是進(jìn)入一個(gè)函數(shù)內(nèi)后,cpu會(huì)將esp的值賦給ebp,此時(shí)就可以通過ebp對(duì)棧進(jìn)行操作,比如獲取函數(shù)參數(shù),局部變量等,實(shí)際上使用esp也可以;
既然使用esp也可以,那么為什么要設(shè)定ebp呢?
答案是為了方便程序員。
因?yàn)閑sp在函數(shù)運(yùn)行時(shí)會(huì)不斷的變化,所以保存一個(gè)一進(jìn)入某個(gè)函數(shù)的esp到ebp中會(huì)方便程序員訪問參數(shù)和局部變量,而且還方便調(diào)試器分析函數(shù)調(diào)用過程中的堆棧情況。前面說了,這個(gè)ebp不是必須要有的,你非要使用esp來訪問函數(shù)參數(shù)和局部變量也是可行的,只不過這樣會(huì)麻煩一些。
這里函數(shù)調(diào)用約定使用的是_stdcall
通過一段程序理解esp和ebp:
main() {//執(zhí)行test前
print(int p1,intp2);//執(zhí)行test后
}
分析下上面程序的調(diào)用原理,假設(shè)執(zhí)行print前esp=Q:
push p2;//函數(shù)參數(shù)p2入棧,esp=Q-4H
push p1;//函數(shù)參數(shù)p1入棧,esp=Q-8H
call print;//函數(shù)返回地址入棧,esp=Q-0CH
//現(xiàn)在進(jìn)入print內(nèi),做些準(zhǔn)備工作:push ebp;//保護(hù)先前ebp指針,ebp入棧,esp=Q-10H
mov ebp,esp;//設(shè)置ebp等于當(dāng)前的esp
// 此時(shí),ebp+0CH=Q-4H,即p2的位置
// 同樣,ebp+08H=Q-8H,即p1的位置
// 下面是print內(nèi)的一些操作:sub esp,20H;//設(shè)置長度為10H大小的局部變量空間,esp=Q-20H
// ... ...
// 一系列操作
// ... ...add esp,20H;//釋放局部變量空間,esp=Q-10H
pop ebp;//出棧,恢復(fù)原先的ebp的值,esp=Q-0CH
ret 8;//ret返回,彈出先前入棧的返回地址,esp=Q-08H,后面加操作數(shù)8H為平衡堆棧
// 之后,彈出函數(shù)參數(shù),esp=Q,恢復(fù)執(zhí)行print函數(shù)前的堆棧;
圖示,注意棧在內(nèi)存中的生長方向是逆向:
執(zhí)行push p2;前,esp=Q;
執(zhí)行push p2;過程中,esp-=4H,p2入棧;
執(zhí)行push p2;后,esp=Q-4H;
因?yàn)閰?shù)傳遞和匯編語言有很大聯(lián)系,之后會(huì)出現(xiàn)較多x86匯編代碼。
該文會(huì)先講一下x86的堆棧參數(shù)傳遞過程,然后再分析C/C++子函數(shù)是怎樣通過堆棧傳遞參數(shù)的。
注:匯編語言的過程和C/C++的子函數(shù)是一回事。
寄存器參數(shù),存儲(chǔ)器參數(shù)和堆棧參數(shù)都可以用于x86匯編乃至其他匯編語言傳遞參數(shù)的方式。但C/C++在編譯時(shí),編譯器會(huì)對(duì)子函數(shù)使用堆棧參數(shù)傳遞方式。
三種參數(shù)傳遞方式對(duì)比:
1、寄存器參數(shù)
mov eal,4
callProc_using_eal
...
2、存儲(chǔ)器參數(shù)
.data
temp DB ?
.code
...mov temp,4
call Proc_using_temp
3、堆棧參數(shù)
push 4
call Proc_using_stack
1、x86堆棧參數(shù)傳遞過程
考慮一個(gè)過程add_num,該過程有兩個(gè)輸入?yún)?shù),一個(gè)輸出參數(shù)。其功能是將兩個(gè)輸入?yún)?shù)求和并將其結(jié)果輸出。下面這個(gè)例子中使用堆棧將3, 4兩個(gè)參數(shù)輸入到add_num中。
push 3
push 4
call add_num
執(zhí)行call指令前,堆棧如下:
其中ESP為x86CPU使用的堆棧指針,每進(jìn)行一次入棧操作,ESP要減4(32位CPU)(圖上堆棧向上地址減小,向下地址增加)
明顯的是,add_num只需要把堆棧中相應(yīng)的變量取出來使用就可以了。堆棧參數(shù)傳遞的確也是這么做,但是卻要稍稍費(fèi)事一點(diǎn)。
首先給出add_num過程的程序
add_num procpushebpmovebp,espmov eax,[ebp+8]add eax,[ebp+12]popebpretadd_num endp
之前筆者給出的堆棧是CPU執(zhí)行call指令前的結(jié)果,接下來從開始執(zhí)行call指令一步一步分析堆棧的變化情況。
(1)call add_num
執(zhí)行call add_num時(shí),ESP減4后將add_num過程的返回地址壓入堆棧,即當(dāng)前指令指針EIP的值(該值為主程序中call指令的下一條指令(不是push ebp)的地址)
(2)
pushebpmovebp,espmov eax,[ebp+8]add eax,[ebp+12]
此時(shí)已經(jīng)進(jìn)入add_num過程內(nèi)部
這一步是為了將esp的值賦予ebp。而將ebp壓入堆棧是為了保護(hù)ebp,在add_num過程結(jié)束后還要恢復(fù)ebp的值。
此時(shí)esp指向堆棧中的ebp,而將esp賦予ebp后,ebp便指向了堆棧中自己被保護(hù)的值。此時(shí)ebp的主要作用是為參數(shù)讀取提供絕對(duì)地址。比如參數(shù)4比ebp所在地址高8Byte(堆棧一個(gè)單元是4Byte),則過程中要使用參數(shù)4時(shí),使用基址-偏移量尋址即可,即[ebp+8]。
當(dāng)然這里也可以使用esp達(dá)到相同的效果,但是這個(gè)例子沒有局部變量。若子過程中有局部變量(局部變量也存放在堆棧里),采用ebp要方便很多。
(3)pop ebp
此時(shí)ebp彈出,ebp恢復(fù)調(diào)用前的值
(4)ret
最后彈出返回地址,程序返回到主程序中,并執(zhí)行下一條指令
以上為整個(gè)堆棧參數(shù)傳遞過程。
這里有幾個(gè)需要注意的點(diǎn):
(1)、堆棧幀到底是什么
堆棧幀(stack frame)(或活動(dòng)記錄(activation record))是一塊堆棧保留區(qū)域,用于存放被傳遞的實(shí)際參數(shù)、子程序的返回值、局部變量以及被保存的寄存器。
實(shí)際上堆棧幀就相當(dāng)于子函數(shù)的緩存,當(dāng)子函數(shù)使用的堆棧個(gè)數(shù)最大時(shí),其所擁有的所有部分構(gòu)成了這個(gè)函數(shù)的堆棧幀。
以add_num過程為例,其堆棧幀如下圖灰色部分所示。
(2)、堆棧幀為什么叫做堆棧幀
“堆棧”很好理解,而“幀”的概念在上面那個(gè)例子中的確很難搞通。不久后筆者會(huì)分析遞歸函數(shù)中的堆棧幀增消的現(xiàn)象,那個(gè)時(shí)候“幀”這個(gè)概念體現(xiàn)得淋漓盡致。
(3)、輸入?yún)?shù)3和4留在堆棧里沒有釋放是可以的嗎
上面的例子并沒有釋放參數(shù)4和3,只是為了演示,實(shí)際上一定會(huì)有相應(yīng)的代碼去釋放它。子函數(shù)的堆棧幀是包含其輸入堆棧變量的,當(dāng)退出子函數(shù)時(shí),其所有的堆棧幀必須被完全釋放,否則堆棧就會(huì)變得混亂。
釋放參數(shù)涉及兩種子函數(shù)調(diào)用標(biāo)準(zhǔn),一種是STDCALL標(biāo)準(zhǔn),一種是C標(biāo)準(zhǔn)。兩種在參數(shù)的堆棧傳遞細(xì)節(jié)幾乎完全相同,不同的是釋放參數(shù)的方式。
根據(jù)兩個(gè)標(biāo)準(zhǔn)重新改寫add_num過程:
STDCALL調(diào)用規(guī)范
add_num procpushebpmovebp,espmov eax,[ebp+8]add eax,[ebp+12]popebpret 8add_num endp
11. C調(diào)用規(guī)范
...push 3
push 4
calladd_numadd esp,8
兩種方式的核心思想就是修改esp,使esp指向堆棧參數(shù)3和4所在位置的前一個(gè)堆棧。但是STDCALL調(diào)用規(guī)范是在過程內(nèi)部修改esp(ret 8為將堆棧中返回地址彈出到EIP后,再將ESP加8);C調(diào)用規(guī)范是在子過程外部,在主調(diào)過程修改esp。
另引用這兩種方式的優(yōu)缺點(diǎn):
STDCALL不僅減少了子程序調(diào)用產(chǎn)生的代碼量(減少了一條指令),還保證了調(diào)用程序永遠(yuǎn)不會(huì)忘記清除堆棧。另一方面,C調(diào)用規(guī)范允許子程序聲明不同數(shù)量的參數(shù),主調(diào)程序可以決定傳遞多少個(gè)參數(shù)。C語言的printf函數(shù)就是一個(gè)例子
總結(jié)
以上是生活随笔為你收集整理的ebp 函数堆栈esp_对于ESP、EBP寄存器的理解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: wpf中内容包含在border中_WPF
- 下一篇: 机器人焊枪动作与编程实验_机器人编程实验