一直比較好奇,調(diào)試器是如何生成堆棧的調(diào)用過程的,比如如下代碼:
int add(int a, int b)
{
return a + b;
}
int main()
{
int c = add(1, 2);
system("pause");
return 0;
}
調(diào)用Add時(shí)的堆棧截圖如下:

調(diào)試器究竟是如何生成這個(gè)堆棧過程的呢?
我最初的理解調(diào)試器是根據(jù)EBP來生成該堆棧的,原理如下:

可以看到按照上面的原理, 每次EBP里存放的都是當(dāng)前函數(shù)的堆棧楨基址,所以我們只要一直遞推,就可以得到完整的Call Stack.
但是我們很快會(huì)發(fā)現(xiàn), 并不是每個(gè)函數(shù)都是以
push ebp
mov ebp, esp
開頭的,我們可以寫一些裸(naked)函數(shù),
比如
int __declspec(naked) add(int x,int y) 來手動(dòng)控制函數(shù)頭,而且很多編譯器優(yōu)化過的函數(shù)代碼也是沒有該標(biāo)準(zhǔn)函數(shù)頭的。
那么調(diào)試器在這種情況下又是如何生成完整的call stack的呢?
和群里的朋友討論的結(jié)果是調(diào)試器很可能是在調(diào)用call指令時(shí)保存了調(diào)用現(xiàn)場(chǎng),
這樣只要在調(diào)試器下運(yùn)行,它就一直可以知道正確而完整的call stack.
這也解釋了為什么我們?cè)诜治鯟rash的Dump文件時(shí)很多時(shí)候得不到正確的堆棧過程?
有可能是堆棧本身被我們的異常代碼破壞了;
更有可能是因?yàn)槲覀兊拇a在直接運(yùn)行時(shí)沒有調(diào)試器的參與, 所以堆棧過程沒有被保存,所以windbg分析dump時(shí)只能根據(jù)堆棧里內(nèi)容自己分析和推理堆棧調(diào)用過程,所以很多時(shí)候得不到正確的堆棧過程。
那么Windbg分析dump時(shí),會(huì)如何倒推堆棧過程呢?
如果每個(gè)函數(shù)都是有標(biāo)準(zhǔn)的push ebp, 那么按照ebp遞推就可以了;
否這就只能用其他方法分析,比如看看堆棧里某個(gè)地址是不是函數(shù)返回地址(該地址屬于某個(gè)模塊的代碼段),這樣就可以確定該地址是某個(gè)函數(shù)堆棧楨的起始地址。
上面關(guān)于生成call stack的原理只是一些非專業(yè)人士的個(gè)人看法,如果有不正確的地方,歡迎指正。
注: 和開發(fā)過調(diào)試器的朋友討論,上面 關(guān)于callstack產(chǎn)生原理的推論,實(shí)際上是不正確的, 調(diào)試器實(shí)際上是通過查詢PDB文件的方式獲取的callstack.
posted on 2012-07-20 14:00
Richard Wei 閱讀(5362)
評(píng)論(3) 編輯 收藏 引用 所屬分類:
匯編