也谈栈和栈帧(二)
談x86的棧幀之前,補(bǔ)充一下堆和棧的認(rèn)識(shí)。
1. ?堆和棧的關(guān)系
? ? 我們平時(shí)說的堆棧其實(shí)是指棧,而實(shí)際上堆和棧是兩種不同的內(nèi)存分配。簡單羅列一下各方面的異同點(diǎn)。
? ??1).堆需要用戶在程序中顯式申請(qǐng),棧不用,由系統(tǒng)自動(dòng)完成。申請(qǐng)/釋放堆內(nèi)存的API,在C中是malloc/free,在C++中是new/delete。申請(qǐng)與釋放一定要配對(duì)使用,否則會(huì)造成內(nèi)存泄漏(memory leak),久而久之系統(tǒng)就無內(nèi)存可用了,出現(xiàn)OOM(Out Of Memory)錯(cuò)誤。一般在return/exit或break/continue等語句時(shí)容易忘記釋放內(nèi)存,所以檢查內(nèi)存泄漏的代碼時(shí)要關(guān)注這些語句,看它們前面是否有必要的釋放語句free/delete。
? ??2).堆的空間比較大,棧比較小。所以申請(qǐng)大的內(nèi)存一般在堆中申請(qǐng);棧上不要有較大的內(nèi)存使用,比如大的靜態(tài)數(shù)組;而且除非算法必要,否則一般不要使用較深的迭代函數(shù)調(diào)用,那樣棧消耗內(nèi)存會(huì)隨著迭代次數(shù)的增加飛漲。
? ? 3).關(guān)于生命周期。棧較短,隨著函數(shù)退出或返回,本函數(shù)的棧就完成了使用;堆就要看什么時(shí)候釋放,生命周期就什么時(shí)候結(jié)束。
? ? 說了這么多,我們發(fā)現(xiàn)解析Coredump還是跟棧的關(guān)系相對(duì)緊密,跟堆的關(guān)系是有一種產(chǎn)生Coredump的原因是訪問堆內(nèi)存出錯(cuò)。
2. ?x86的棧幀
? ? 繼續(xù)談棧幀布局,這次說說x86的棧幀布局和操作方法,見棧幀布局圖:
? ??
? ? 上圖描述的是一般x86的stack frame棧幀布局方式,當(dāng)前幀為當(dāng)前函數(shù)(被調(diào)用者)的stack frame,調(diào)用者的幀為調(diào)用函數(shù)(調(diào)用者)的stack frame。棧底在高地址,棧向下增長。圖中,ebp(base pointer)就是棧基址,它指向當(dāng)前函數(shù)的棧幀起始地址;esp(stack pointer)則是當(dāng)前函數(shù)的棧指針,它指向棧頂?shù)奈恢谩簵5捻樞蛞来螢闂;積bp、其它寄存器、本地變量和臨時(shí)變量。注意所傳遞的參數(shù)和返回地址lr一般放在調(diào)用者的棧幀中,當(dāng)然按照實(shí)際的壓棧過程,也有人認(rèn)為previous ebp也在調(diào)用者棧幀內(nèi),這個(gè)暫時(shí)影響不大。
? ? 相比于MIPS,x86比較可愛的地方是剛剛提到的兩點(diǎn),就是可以用棧基址和棧指針明確標(biāo)示棧幀的位置,棧指針esp一直移動(dòng),同時(shí)壓棧的順序有一定的規(guī)律,一個(gè)棧空間內(nèi)的地址前面,必然有一個(gè)代碼地址(lr)明確標(biāo)示著調(diào)用函數(shù)位置內(nèi)的某個(gè)地址。
3. ?x86的棧操作
? ? x86有一對(duì)表示棧底和棧頂?shù)募拇嫫?#xff0c;寄存器ebp是棧基址指針,指棧幀的底部(高地址),寄存器esp是棧指針,指棧幀的頂部(地址地)。
? ? 函數(shù)的返回結(jié)果是通過寄存器eax傳遞的,因此在函數(shù)退出前會(huì)將計(jì)算結(jié)果拷貝到eax中,然后再出棧返回調(diào)用者。
? ? 再來關(guān)注幾個(gè)常用的函數(shù)內(nèi)外跳轉(zhuǎn)指令:
? ??call: 調(diào)用一個(gè)函數(shù)。以寄存器和偏移量來調(diào)用函數(shù)和c++里的虛函數(shù)調(diào)用很類似
? ??ret: ?從一個(gè)函數(shù)返回。_stdcall調(diào)用規(guī)范要求如果有返回值,就要將返回值從堆棧的棧頂彈出
? ??leave:是move ESP EBP/pop EBP的簡寫,用來退出函數(shù),同時(shí)釋放了為局部變量分配的空間
? ? jmp: ?無條件跳轉(zhuǎn)
? ? je: ? 如果相等則跳轉(zhuǎn)
? ? jne: ?如果不等則跳轉(zhuǎn)
? ? 順便提一下數(shù)據(jù)的傳遞操作mov和movl,mov是將右操作數(shù)復(fù)制到左操作數(shù),而movl傳遞方向相反,是將左操作數(shù)復(fù)制到右操作數(shù)。裝入有效地址指令(即用來得到局部變量和函數(shù)參數(shù)的指針)lea和leal也一樣。
? ? 最后,x86的壓棧出棧指令眾人皆知,分別是push和pop,或者其各種變體,比如puchad/popad是對(duì)所有通用寄存器的棧操作。?
總結(jié)