無論是分析程序崩潰原因,還是解決程序hang問題,我們最常查看的就是程序調用堆棧。學會windbg調用堆棧命令,以及理解堆棧中的各個參數的意義就顯得至關重要。
上圖就是一個典型的Windbg堆棧,如果不理解ChildEBP、RetAddr、Args to Child等參數意義,以及它們之間的來龍去脈,調試工作將很難進行下去。
1. 函數參數
函數的參數傳遞有二種方式:堆棧方式、寄存器方式。如果是堆棧方式傳遞的,就需要定義參數在堆棧中的傳遞順序,並約定函數被調用之后,由誰來平衡堆棧;如果是寄存器方式傳遞的,就需要確定參數存放在哪個寄存器中。每一種方式都有其優缺點,而且與使用的編程語言有關系,不存在哪種方式好與壞。
如Visual Studio中的C++工程,可以C++ --> 高級 --> 調用約定中進行設置:
常用的調用約定類型有__cdecl、stdcall、PASCAL、fastcall。除了fastcall可以支持以寄存器的方式來傳遞函數參數外,其他的都是通過堆棧的方式來傳遞函數參數的。
利用堆棧傳遞參數
堆棧是一種“后進先出”的數據結構,ESP寄存器始終指向棧頂。棧中數據地址從底部到頂部依次減小,也就是說,棧底對應高地址,棧頂對應低地址。
調用函數時,調用者依次把參數壓棧,然后調用函數,函數被調用之后,在堆棧中取得參數數據。函數調用結束以后,堆棧需要恢復到函數調用之前的樣子,具體由調用者來恢復還是由函數自身來恢復,根據不同的調用約定類型采用不同的方式。
約定類型 | __cdecl | stdcall | PASCAL | fastcall |
---|---|---|---|---|
參數傳遞順序 | 從右到左 | 從右到左 | 從左到右 | 使用寄存器 |
平衡堆棧者 | 調用者 | 函數自身 | 函數自身 | 函數自身 |
__cdcel是C/C++/MFC程序默認的調用約定。
stdcall是Win32中絕大多數 API函數的約定方式,也有少部分使用__cdcel約定方式,如wsprintf等。
在windows C/C++開發中常用的就是__cdecl和stdcall這2種調用約定。
假設調用函數int add(int a, int b)
, 按照不同的調用約定來調用它。從調用者的視角來看,其匯編代碼分別表示如下:
__cdecl
push b ;參數按從右到左傳遞
push a
call add
add esp, 8 ;調用者在函數外部平衡堆棧
stdcall
push b ;參數按從右到左傳遞
push a
call add ;函數自己內部平衡堆棧
在函數調用過程中,參數入棧的過程:
上圖中,EBP和函數返回地址都是地址,在32位程序中地址占4個字節。在函數的一次調用過程中EBP是不會變化的,函數調用完之后會將EBP恢復為暫存在堆棧中的原EBP值。所以,通過EBP可以獲取函數各個參數的值:
參數a = EBP + 0x8
參數b = EBP + 0xC
2. Windbg堆棧命令
2.1 顯示堆棧信息k*
[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] [FrameCount]
[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] = BasePtr [FrameCount]
[~Thread] k[b|p|P|v] [c] [n] [f] [L] [M] = BasePtr StackPtr InstructionPtr
[~Thread] kd [WordCount]
參數:
Thread 指定顯示哪個線程的調用堆棧。如果省略該參數,則顯示當前線程的調用堆棧。*顯示所有線程的調用堆棧。
b 顯示每個函數的前3個參數。
p 顯示每個函數的所有參數。參數列表包括每個參數的類型、名稱、值。
如上圖,可以看到函數的每個參數的類型,名稱,值。但是這個需要有對應的符號文件(pdb),沒有應用程序的符號文件只能顯示系統API的參數信息。
P 類似p。不同之處在於,每個參數顯示在單獨的行上面。
n 顯示調用堆棧中每幀的序號(一般稱棧幀,如棧幀3)。
FrameCount 指定顯示調用堆棧的幀數,即調用堆棧的深度。默認為16進制格式。默認幀數為0x14=20
2.2 切換到指定幀信息.frame
調用堆棧顯示出來之后,如果想知道調用某幀時的相關信息,可以使用.frame
來切換指定幀,然后就可以使用如dv命令顯示局部變量等。
.frame [/c] [/r] [FrameNumber]
/c
/r 顯示執行該幀時寄存器的值。
FrameNumber 指定要切換到的幀號。
3. 實例分析
kbn
顯示堆棧信息:
棧幀12:
調用add函數,參數1=00000001,參數2=000000002,EBP=0015fc0c
根據圖1得知,函數返回地址=EBP+4,我們使用dw命令來驗證。
參考:
《軟件調試》張銀奎 著
《格蠹匯編》張銀奎 著
《加密與解密》第三版 段剛編著