對于程序傻谁,編譯器會為其分配一段內(nèi)存,在邏輯上可以分為代碼段列粪、數(shù)據(jù)段栅螟、堆、棧篱竭,而函數(shù)調(diào)用則是發(fā)生在棧上力图。
代碼段:保存程序文本,指令指針eip就是指向代碼段掺逼,可讀可執(zhí)行不可寫
數(shù)據(jù)段:保存初始化的全局變量和靜態(tài)變量吃媒,可讀可寫不可執(zhí)行
BSS:未初始化的全局變量和靜態(tài)變量
堆(Heap):動態(tài)分配內(nèi)存,向地址增大的方向增長,可讀可寫可執(zhí)行
棧(Stack):存放局部變量赘那,函數(shù)參數(shù)刑桑,當前狀態(tài),函數(shù)調(diào)用信息等募舟,向地址減小的方向增長祠斧,可讀可寫可執(zhí)行
相關(guān)寄存器
ebp:基址指針寄存器,存放當前函數(shù)棧幀的基地址
esp:堆棧(Stack)指針寄存器拱礁,存放當前函數(shù)棧幀的棧頂?shù)刂?eip: 指令寄存器琢锋,指向下一條指令的地址
exp:存放函數(shù)返回值
函數(shù)調(diào)用棧結(jié)構(gòu)圖
入棧過程
1、將調(diào)用者函數(shù)的ebp入棧
2呢灶、將調(diào)用者函數(shù)的棧頂指針esp賦值給被調(diào)用函數(shù)的ebp
3吴超、按從右到左的順序?qū)⒈徽{(diào)用函數(shù)的參數(shù)入棧
4、按聲明的順序?qū)⒈徽{(diào)用函數(shù)的局部變量入棧
5鸯乃、將調(diào)用函數(shù)的下一個指令地址作為返回地址入棧
6鲸阻、將被調(diào)用函數(shù)的第一條指令地址賦值給eip寄存器
7、開始執(zhí)行被調(diào)用函數(shù)指令
ebp寄存器處于一個非常重要的位置缨睡,該寄存器中存放的地址可以作為基準鸟悴,向棧底方向可以獲取返回地址,傳入?yún)?shù)值奖年,向棧頂方向可以獲取函數(shù)的局部變量细诸。而esp所指向的內(nèi)存中又存放著上一層函數(shù)調(diào)用的ebp值。
出棧過程
1拾并、將函數(shù)返回值存入eax寄存器中
2揍堰、執(zhí)行l(wèi)eave指令
3鹏浅、執(zhí)行ret指令
帶異承嵋澹回退的函數(shù)調(diào)用棧
棧展開
棧展開(unwinding)是指當前的try...catch...塊匹配成功或者匹配不成功異常對象后,從try塊內(nèi)異常對象的拋出位置隐砸,到try塊的開始處的所有已經(jīng)執(zhí)行了各自構(gòu)造函數(shù)的局部變量之碗,按照構(gòu)造生成順序的逆序,依次被析構(gòu)季希。如果當前函數(shù)內(nèi)對拋出的異常對象匹配不成功褪那,則從最外層的try語句到當前函數(shù)體的起始位置處的局部變量也依次被逆序析構(gòu),實現(xiàn)棧展開式塌,然后再回退到調(diào)用棧的上一層函數(shù)內(nèi)從函數(shù)調(diào)用點開始繼續(xù)處理該異常博敬。
catch語句如果匹配異常對象成功,在完成了對catch語句的參數(shù)的初始化(對傳值參數(shù)完成了參數(shù)對象的copy構(gòu)造)之后妆艘,對同層級的try塊執(zhí)行棧展開破加。
相關(guān)數(shù)據(jù)結(jié)構(gòu)
struct UNWINDTBL {
int nNextIdx;
void (*pfnDestroyer)(void *this);
void *pObj;
};
struct CATCHBLOCK {
//...
type_info *piType;
void *pCatchBlockEntry;
}
struct TRYBLOCK {
//...
int nBeginStep;
int nEndStep;
CATCHBLOCK tblCatchBlocks[];
};
struct EHDL {
//...
UNWINDTBL tblUnwind[];
TRYBLOCK tblTryBlocks[];
//...
};
struct EXP {
EXP *piPrev; //成員指向鏈表的上一個節(jié)點秸滴,它主要用于在函數(shù)調(diào)用棧中逐級向上尋找匹配的 catch 塊绳泉,并完成椉劳回退工作伦意。
EHDL *piHandler; //成員指向完成異常捕獲和棧回退所必須的數(shù)據(jù)結(jié)構(gòu)(主要是兩張記載著關(guān)鍵數(shù)據(jù)的表:“try”塊表:tblTryBlocks 及“椗鸩梗回退表”:tblUnwind)驮肉。
int nStep; //成員用來定位 try 塊,以及在椧押В回退表中尋找正確的入口离钝。
};
調(diào)用棧示意圖
棧展開過程
nStep 變量用于跟蹤函數(shù)內(nèi)局部對象的構(gòu)造、析構(gòu)階段疾捍。再配合編譯器為每個函數(shù)生成的 tblUnwind 表奈辰,就可以完成退棧機制。表中的 pfnDestroyer 字段記錄了對應(yīng)階段應(yīng)當執(zhí)行的析構(gòu)操作(析構(gòu)函數(shù)指針)乱豆;pObj 字段則記錄了與之相對應(yīng)的對象 this 指針偏移奖恰。將 pObj 所指的偏移值加上當前棧框架基址(EBP)宛裕,就是要代入 pfnDestroyer 所指析構(gòu)函數(shù)的 this 指針瑟啃,這樣即可完成對該對象的析構(gòu)工作。而 nNextIdx 字段則指向下一個需要析構(gòu)對象所在的行(下標)揩尸。
在發(fā)生異常時蛹屿,異常處理器首先檢查當前函數(shù)棧框架內(nèi)的 nStep 值岩榆,并通過 piHandler 取得 tblUnwind[] 表错负。然后將 nStep 作為下標帶入表中,執(zhí)行該行定義的析構(gòu)操作勇边,然后轉(zhuǎn)向由 nNextIdx 指向的下一行犹撒,直到 nNextIdx 為 -1 為止。在當前函數(shù)的椓0回退工作結(jié)束后识颊,異常處理器可沿當前函數(shù)棧框架內(nèi) piPrev 的值回溯到異常處理鏈中的上一節(jié)點重復上述操作奕坟,直到所有回退工作完成為止祥款。