[转] 函数调用栈
http://kingj.iteye.com/blog/1555017
http://www.cnblogs.com/rain-lei/p/3622057.html
?
函數(shù)調(diào)用大家都不陌生,調(diào)用者向被調(diào)用者傳遞一些參數(shù),然后執(zhí)行被調(diào)用者的代碼,最后被調(diào)用者向調(diào)用者返回結(jié)果,還有大家比較熟悉的一句話,就是函數(shù)調(diào)用是在棧上發(fā)生的,那么在計(jì)算機(jī)內(nèi)部到底是如何實(shí)現(xiàn)的呢? 對(duì)于程序,編譯器會(huì)對(duì)其分配一段內(nèi)存,在邏輯上可以分為代碼段,數(shù)據(jù)段,堆,棧 代碼段:保存程序文本,指令指針EIP就是指向代碼段,可讀可執(zhí)行不可寫(xiě) 數(shù)據(jù)段:保存初始化的全局變量和靜態(tài)變量,可讀可寫(xiě)不可執(zhí)行 BSS:未初始化的全局變量和靜態(tài)變量 堆(Heap):動(dòng)態(tài)分配內(nèi)存,向地址增大的方向增長(zhǎng),可讀可寫(xiě)可執(zhí)行 棧(Stack):存放局部變量,函數(shù)參數(shù),當(dāng)前狀態(tài),函數(shù)調(diào)用信息等,向地址減小的方向增長(zhǎng),非常非常重要,可讀可寫(xiě)可執(zhí)行 如圖所示 寄存器 EAX:累加(Accumulator)寄存器,常用于函數(shù)返回值 EBX:基址(Base)寄存器,以它為基址訪問(wèn)內(nèi)存 ECX:計(jì)數(shù)器(Counter)寄存器,常用作字符串和循環(huán)操作中的計(jì)數(shù)器 EDX:數(shù)據(jù)(Data)寄存器,常用于乘除法和I/O指針 ESI:源變址寄存器 EDI:目的變址寄存器 ESP:堆棧(Stack)指針寄存器,指向堆棧頂部 EBP:基址指針寄存器,指向當(dāng)前堆棧底部 EIP:指令寄存器,指向下一條指令的地址當(dāng)調(diào)用(call)一個(gè)函數(shù)時(shí),主調(diào)函數(shù)將聲明中的參數(shù)表以逆序壓棧,然后將當(dāng)前的代碼執(zhí)行指針(eip)壓棧,跳轉(zhuǎn)到被調(diào)函數(shù)的入口點(diǎn)。
??????? 進(jìn)入被調(diào)函數(shù)時(shí),函數(shù)將esp減去相應(yīng)字節(jié)數(shù)獲取局部變量存儲(chǔ)空間。被調(diào)函數(shù)返回(ret)時(shí),將esp加上相應(yīng)字節(jié)數(shù),歸還??臻g,彈出主調(diào)函數(shù)壓在棧中的代碼執(zhí)行指針(eip),跳回主調(diào)函數(shù)。再由主調(diào)函數(shù)恢復(fù)到調(diào)用前的棧。
??????? 為了訪問(wèn)函數(shù)局部變量,必須有方法定位每一個(gè)變量。變量相對(duì)于棧頂esp的位置在進(jìn)入函數(shù)體時(shí)就已確定,但是由于esp會(huì)在函數(shù)執(zhí)行期變動(dòng),所以將esp 的值保存在ebp中,并事先將原ebp的值壓棧保存,以聲明中的順序(即壓棧的相反順序)來(lái)確定偏移量。
訪問(wèn)函數(shù)的局部變量和訪問(wèn)函數(shù)參數(shù)的區(qū)別:
局部變量總是通過(guò)將ebp減去偏移量來(lái)訪問(wèn),函數(shù)參數(shù)總是通過(guò)將ebp加上偏移量來(lái)訪問(wèn)。對(duì)于32位變量而言,第一個(gè)局部變量位于ebp-4,第二個(gè)位于ebp-8,以此類推,32位局部變量在棧中形成一個(gè)逆序數(shù)組;第一個(gè)函數(shù)參數(shù)位于ebp+8,第二個(gè)位于ebp+12,以此類推,32位函數(shù)參數(shù)在棧中形成一個(gè)正序數(shù)組。?
??????? 函數(shù)的返回值不同于函數(shù)參數(shù),可以通過(guò)寄存器傳遞。如果返回值類型可以放入32位變量,比如int、short、char、指針等類型,將通過(guò)eax寄存 器傳遞。如果返回值類型是64位變量,如_int64,則通過(guò)edx+eax傳遞,edx存儲(chǔ)高32位,eax存儲(chǔ)低32位。如果返回值是浮點(diǎn)類型,如 float和double,通過(guò)專用的浮點(diǎn)數(shù)寄存器棧的棧頂返回。如果返回值類型是struct或class類型,編譯器將通過(guò)隱式修改函數(shù)的簽名,以引 用型參數(shù)的形式傳回。由于函數(shù)返回值通過(guò)寄存器返回,不需要空間分配等操作,所以返回值的代價(jià)很低?;谶@個(gè)原因,C89規(guī)范中約定,不寫(xiě)明返回值類型的 函數(shù),返回值類型默認(rèn)為int。這一規(guī)則與現(xiàn)行的C++語(yǔ)法相違背,因?yàn)镃++中,不寫(xiě)明返回值類型的函數(shù)返回值類型為void,表示不返回值。這種語(yǔ)法 不兼容性是為了加強(qiáng)C++的類型安全,但同時(shí)也帶來(lái)了一些代碼兼容性問(wèn)題。
代碼示例
VarType?Func (Arg1, Arg2, Arg3, ... ArgN)?
{?
????VarType?Var1, Var2, Var3, ...VarN;
????//...?
????return?VarN;?
}
假設(shè)sizeof(VarType) = 4(DWORD), 則一次函數(shù)調(diào)用匯編代碼示例為:
調(diào)用方代碼:?
push ArgN ; 依次逆序壓入調(diào)用參數(shù)
push?...?
push Arg1?
call Func_Address ; 壓入當(dāng)前EIP后跳轉(zhuǎn)
跳轉(zhuǎn)至被調(diào)方代碼:?
push ebp ; 備份調(diào)用方EBP指針
mov?ebp, esp?; 建立被調(diào)方棧底
sub esp, N *?4; 為局部變量分配空間
mov dword ptr[esp -?4?*?1?], 0 ; 初始化各個(gè)局部變量 = 0 這里假定VarType不是類?
mov dword ptr[esp -?4?*?...?], 0
mov dword ptr[esp -?4?*?N?], 0
. . . . . .?; 這里執(zhí)行一些函數(shù)功能語(yǔ)句(比如將第N個(gè)參數(shù)[ebp + N * 4]存入局部變量), 功能完成后將函數(shù)返回值存至eax
add esp, N *?4?; 銷毀局部變量
mov esp, ebp ; 恢復(fù)主調(diào)方棧頂
pop ebp ; 恢復(fù)主調(diào)方棧底
ret ; 彈出EIP 返回主調(diào)方代碼
接上面調(diào)用方代碼:?
add esp, N *?4?; 釋放參數(shù)空間, 恢復(fù)調(diào)用前的棧?
mov dword ptr[ebp - 4], eax ; 將返回值保存進(jìn)調(diào)用方的某個(gè)VarType型局部變量
?
下面用一系列圖說(shuō)明 1) 將實(shí)參,返回地址入棧2)把原ebp的地址壓棧保存,讓新的ebp等于esp,棧頂開(kāi)始變?yōu)闂5?/span> 2) 實(shí)參通過(guò)ebp+來(lái)訪問(wèn),局部變量通過(guò)ebp-來(lái)訪問(wèn)
?
? 接下來(lái)是返回過(guò)程 (correction: "pop ebp" in the 2nd picture)????
?所有局部變量都在棧中由函數(shù)統(tǒng)一分配,形成了類似逆序數(shù)組的結(jié)構(gòu),可以通過(guò)指針逐一訪問(wèn)。這一特點(diǎn)具有很多有趣性質(zhì),比如,考慮如下函數(shù),找出其中的錯(cuò)誤及其造成的結(jié)果:
void f()
{
int i,a[10];
for(i=0;i<=10;++i)a[i]=0;/An error occurs here!
}
????????這個(gè)函數(shù)中包含的錯(cuò)誤,即使是C++新手也很容易發(fā)現(xiàn),這是老生常 談的越界訪問(wèn)問(wèn)題。但是這個(gè)錯(cuò)誤造成的結(jié)果,是很多人沒(méi)有想到的。這次的越界訪問(wèn),并不會(huì)像很多新手預(yù)料的那樣造成一個(gè)“非法操作”消息,也不會(huì)像很多老 手估計(jì)的那樣會(huì)默不作聲,而是導(dǎo)致一個(gè)死循環(huán)。
????????錯(cuò)誤的本質(zhì)顯而易見(jiàn),我們?cè)L問(wèn)了a[10],但是a[10]并不存在。C++標(biāo)準(zhǔn)對(duì)于越界訪問(wèn)只是說(shuō)“未定義操作”。我們知道,a[10]是數(shù)組a所在位置之后的一個(gè)位置,但問(wèn)題是,是誰(shuí)在這個(gè)位置上。是i!?
??????? 根據(jù)前面的討論,i在數(shù)組a之前被聲明,所以在a之前分配在棧上。但是,I386上棧是向下增長(zhǎng)的,所以,a的地址低于i的地址。其結(jié)果是在循環(huán)的最 后,a[i]引用到了i自己!接下來(lái)的事情就不難預(yù)見(jiàn)了,a[i],也就是i,被重置為0,然后繼續(xù)循環(huán)的條件仍然成立……這個(gè)循環(huán)會(huì)一直繼續(xù)下去,直到 在你的帳單上產(chǎn)生高額電費(fèi),直到耗光地球電能,直到太陽(yáng)停止燃燒……呵呵,或者直到聰明的你把程序Kill了……
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/qiangxia/p/4263295.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
- 上一篇: Notice : Soft open f
- 下一篇: The maximum string c