日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Win32 环境下的堆栈

發布時間:2023/11/27 生活经验 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Win32 环境下的堆栈 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原文已經找不到,作者應該是:http://blog.csdn.net/slimak?? 但是沒有找到此文,其中丟了2幅圖

簡介

Win32環境下利用調試器調試應用程序的時候經常要和堆棧(Stack)打交道,尤其是在需要手工遍歷堆棧(Manually Walking Stack)的時候我們需要對堆棧的工作過程有一個比較清晰的了解.接下來的這些文字將通過一個例子程序詳細的講解堆棧的工作過程.

關鍵字

調試堆棧 Stack Stack-Frame

目錄

1.堆棧是什么?

2.堆棧里面放的都是什么信息?

3.堆棧是在什么時候被建立起來的?它的默認大小是多少?

4.默認才1M??那要是我的程序使用超過了1M的堆棧怎么辦?

5.什么叫Stack Frame?

6.在一次函數調用中,堆棧是如何工作的?

7.老大,結合一個例子講講吧?

1.堆棧是什么?

從內存管理角度看,堆棧是就是一塊連續的內存空間,對它的操作采用先入后出的規則,他的生長方向與內存的生長方向正好相反,也就是說它是從高地址向低地址生長.

Win32程序內部的角度看,每一個線程有自己的堆棧,它主要用來給線程提供一個暫時存放數據的區域,程序使用POP/PUSH指令來對堆棧進行操作.

2.堆棧里面放的都是什么信息?

堆棧中存放的信息包括:

當前正在執行的函數的局部變量;

函數返回地址;

該函數的上層函數傳給該函數的參數;

EBP的值;

一些通用寄存器(EDI,ESI…)的值。

?

注意這里提到的正在執行的函數,比如有下面的一段C代碼:

void B()

{

printf(“B\n”);

}

void A()

{

B();

}

那么當程序執行到B函數的printf函數的時候我們說正在執行的函數包括A和B而不僅僅是B函數,這一點需要注意.

3.堆棧是在什么時候被建立起來了?它的默認大小是多少?

堆棧是在我們的main主函數被系統調用之前被建立起來的,對于非主線程它是在線程被建立之前創建的,

它的默認大小是1M,

如果需要修改堆棧的大小的話可以在VC6++中通過使用/STACK編譯項實現:

#pragmacomment(linker,“/STACK:2048,1024″)//預約(Reserve)2M,提交(Commit)1M

關于預約(Reserve)和提交(Commit)的概念請參看Programming Applications for Microsoft Windows“( Jeffrey Richter,Chapter 15Using Virtual Memory in Your Own Applications)

4.默認才1M??那要是我的程序使用超過了1M的堆棧怎么辦?

系統通過使用異常捕獲(Exception Handling)機制來捕獲應用程序企圖去訪問超過該程序提交(Commit)的堆棧范圍這種異常,假如你程序預約了2M并且提交了1M大小的堆棧,那么當你的程序企圖訪問超過1M的范圍的時候會產生一個異常并且被系統捕獲,系統會幫你繼續從另外1M預約的內存中提交內存來滿足你的需求,如果你要求提交的大小甚至超過了2M(你一開始預約的大小)在 NT系統下(98除外)系統也會嘗試去分配(allocate)內存來滿足你,但是系統并不保證分配會成功

5.什么叫Stack Frame?

Stack Frame這個詞你可以在各種各樣的匯編書籍中看到,到底它表示什么意思呢?也許你看完文章的后半部分就會明白,在此我們先給它一個定義,你看完整篇文章在回過頭來回味一下就會知道它的確切含義了,Stack Frame是堆棧中的一塊區域,它保存著一個函數的返回地址,和該函數內部使用的局部數據(Local Data),它是由函數入口處的SUB ESP,48h之類的語句來建立的.

6.在一次函數調用中,堆棧是如何工作的?

假設我們的主角叫A函數…

a.首先上級函數傳給A函數的參數被壓入堆棧中(至于是誰來做這個壓棧操作取決于A函數的調用方式:是__stdcall, __cdecl還是其他);

b.然后是返回地址(A函數執行完后接下來程序繼續執行的地址)入棧;

c.接下來是當前的EBP;

d.如果A函數有局部變量,就在堆棧中開辟相應的空間以構造那些變量變量(A函數執行結束,這些局部變量的內容將被忽略/遺棄,但是不被清除,比如A函數中有一個變量int m存在于地址0×0012FFCC處,函數結束時9依然存在于0×0012FFCC處沒有被清除,但是此時它已經沒有任何意義了,

e.在函數返回的時候,彈出EBP,恢復堆棧到函數調用前的地址,彈出返回地址到EIP以繼續執行程序。

7.結合一個例子

下面就是我們要拿來做模特的代碼,程序很簡單,wWinMain調用AFunc,AFunc再調用BFunc,下面的講解過程中我們要觀摩這個程序的匯編代碼形式,可以通過在VC6++該工程的Debug模式中按F5然后Ctrl+Tab做到,我想這對于Win32程序員應該不是難事.

int BFunc(int i,int j)
{
int m = 1;
int n = 2;

m = i;
n = j;

return m;
}

int AFunc(int i,int j)
{
int m = 3;
int n = 4;

m = i;
n = j;

BFunc(m,n);

return 8;
}

int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
AFunc(5,6);
return 0;
}

步驟1.我們從wWinMain調用AFunc函數開始

wWinMain調用AFunc的時候,先把參數壓棧(至于為什么壓棧順序是6,5而不是5,6請參看附錄.注解1)參數壓棧結束后此時ESP = 0×0012FEDC,EBP = 0×0012FF30,

這是進入AFunc函數之前的堆棧形勢圖:

1

步驟2.記住進入AFcun函數之前的ESP,EBP的值,然后我們進入AFunc…

為方便大家觀摩,先把AFunc函數的全貌貼出來

29: int AFunc(int i,int j)

30: {

004010D0push ebp ;先把EBP入棧保存

004010D1mov ebp,esp ;再把此時的ESP賦給EBP,這樣EBP就可以拿來訪問本函數的局部變量

004010D3sub esp,48h ;AFunc函數在堆棧重開辟一塊空間,一般來說開辟的空間大小是40+

;函數內所有局部變量的大小;

004010D6push ebx ;通用寄存器入棧,算保存現場吧

004010D7push esi

004010D8push edi

004010D9lea edi,[ebp-48h]

004010DCmov ecx,12h

004010E1mov eax,0CCCCCCCCh

004010E6rep stos dword ptr [edi]

31: int m = 3;

004010E8mov dword ptr [ebp-4],3 ;為什么局部變量m位于ebp-3?

32: int n = 4;

004010EFmov dword ptr [ebp-8],4;為什么局部變量n位于ebp-8?

33:

34: m = i;

004010F6mov eax,dword ptr [ebp+8] ;ebp+8處存的是什么?

004010F9mov dword ptr [ebp-4],eax

35: n = j;

004010FCmov ecx,dword ptr [ebp+0Ch] ;ebp+0ch處存的是什么?

004010FFmov dword ptr [ebp-8],ecx

36:

37: BFunc(m,n);

00401102mov edx,dword ptr [ebp-8] ;AFunc調用BFunc之前先把傳給BFunc的參數入棧

00401105push edx

00401106mov eax,dword ptr [ebp-4]

00401109push eax

0040110Acall @ILT+25(BFunc) (0040101e)

0040110Fadd esp,8 ;這個出棧操作為什么?

38:

39:return 8;

00401112mov eax,8

40: }

00401117pop edi ;恢復現場

00401118pop esi

00401119pop ebx

0040111Aadd esp,48h ;收回函數一開始在棧中開辟的空間

;對應于一開始的sub esp,48h

0040111Dcmp ebp,esp

0040111Fcall __chkesp (00401220)

00401124mov esp,ebp

00401126pop ebp;恢復調用前的EBP

00401127ret


下面我們要花些篇幅詳細的解釋AFunc函數執行過程堆棧(主要是ESP,EBP)的變化情況:

29: int AFunc(int i,int j)

30: {

004010D0 push ebp

004010D1 mov ebp,esp

004010D3 sub esp,48h

004010D6 push ebx

004010D7 push esi

004010D8 push edi

;

; 上面幾行代碼叫做prolog,可以理解成序曲,開始部分”,與之對應的叫epilog(結束曲,結束部分)對于

; prolog需要逐行解釋一下:

;

; 004010D0 PUSH EBP

; 將進入AFunc函數之前的EBP的值入棧保存,這時候的EBP相當于是AFunc上級函數

; 的一個現場信息,所以需要保存起來,以便于AFunc返回后上級函數可以恢復EBP使其指向其調用

; AFunc之前的堆棧位置(當然,這還需要靠恢復ESP來協助達到這一目的),該語句執行完之后堆棧將

; 變成下面這個樣子

;

圖 2

; 在這里要解釋一下什么時候”AFunc結束之后的返回地址入棧了?導致它入棧的語句就是

; CALL @ILT+20(AFunc) (00401019)

; 也就是說是CALL指令干的

;

; 004010D1 MOV EBP,ESP

; ESP賦給EBP,這樣EBP就可以拿來訪問本函數的局部變量

圖 3

; 004010D3 SUB ESP,48h AFunc函數中有兩個int型的變量所以開辟的空間大小是

; 40+2*sizeof(int),我暫時還沒有找到正式文檔中對于此大小

; 計算的公式.注意:ESP-48h后開辟的新的堆棧中的這塊空間就是

; 大名鼎鼎的Stack Frame.

; 004010D6 PUSH EBX 我們知道通用寄存器有時候在程序運算的時候可以用來存放

; 臨時結果,如果此結果有必要的話也是需要作為現場信息被保存在

; 堆棧中的.

; 004010D7 PUSH ESI

; 004010D8 PUSH EDI

圖 4

; 從上面的圖解我們很容易看出在進入AFunc函數執行完prolog之后ESPEBP指示出了堆棧中

; 存放的當前執行函數的信息(綠色部分,其上級函數的堆棧信息由亮綠色表示,呵呵,我可能有一點色

; 弱所以那到底是不是亮綠色我也不是很確定,夜深人靜也沒人可問…)

004010D9 lea edi,[ebp-48h]

004010DC mov ecx,12h

004010E1 mov eax,0CCCCCCCCh

004010E6 rep stos dword ptr [edi]

31: int m = 3;

004010E8 mov dword ptr [ebp-4],3;函數的局部變量放置在EBP的負偏移處(Negative

; Offset)也就是向低地址方向(當然,當然,這是針對該函數使用

; 了標準的Stack Frame,如果代碼被編譯器作了優化了那么你

; 很可能就要遇到FPO這個概念,這可能需要另外寫一篇文章

; 來解釋,所以這里假設我們的函數使用的是標準的Stack

; Frame)

32: int n = 4;

004010EF mov dword ptr [ebp-8],4;同上

圖 5

33:

34: m = i;

004010F6 mov eax,dword ptr [ebp+8];從上圖中很容易看出來dword ptr [ebp+8]里面放的是

; 上級函數傳給AFunc的第一個參數,這里用ebp+8來訪問

; 參數說明上級傳給下級函數的參數是放在下級函數

; EBP的正向偏移位置處(Positive Offset)

004010F9 mov dword ptr [ebp-4],eax;將參數的值賦給局部變量

35: n = j;

004010FC mov ecx,dword ptr [ebp+0Ch];同上

004010FF mov dword ptr [ebp-8],ecx;同上

圖 6

步驟3.現在AFcun函數要調用BFunc

這是調用前的準備工作:

a.參數被壓棧;

b.CALL指令導致返回地址0040110F入棧;

37: BFunc(m,n);

00401102 mov edx,dword ptr [ebp-8]

00401105 push edx

00401106 mov eax,dword ptr [ebp-4]

00401109 push eax

0040110A call @ILT+25(BFunc) (0040101e)

0040110F add esp,8

圖 7

; 這和一開始wWinMain調用AFunc是差不多的過程

38:

39: return 8;

00401112 mov eax,8

40: }

00401117 pop edi

00401118 pop esi

00401119 pop ebx

0040111A add esp,48h

0040111D cmp ebp,esp

0040111F call __chkesp (00401220)

00401124 mov esp,ebp

00401126 pop ebp

00401127 ret

步驟4.進入BFcun函數之后堆棧的變化

老規矩,我們先通篇看看BFunc在VC6++中的匯編代碼:

18: int BFunc(int i,int j)

19: {

00401090 push ebp

00401091 mov ebp,esp

00401093 sub esp,48h

00401096 push ebx

00401097 push esi

00401098 push edi

00401099 lea edi,[ebp-48h]

0040109C mov ecx,12h

004010A1 mov eax,0CCCCCCCCh

004010A6 rep stos dword ptr [edi]

20: int m = 1;

004010A8 mov dword ptr [ebp-4],1

21: int n = 2;

004010AF mov dword ptr [ebp-8],2

22:

23: m = i;

004010B6 mov eax,dword ptr [ebp+8]

004010B9 mov dword ptr [ebp-4],eax

24: n = j;

004010BC mov ecx,dword ptr [ebp+0Ch]

004010BF mov dword ptr [ebp-8],ecx

25:

26: return m;

004010C2 mov eax,dword ptr [ebp-4]

27: }

004010C5 pop edi

004010C6 pop esi

004010C7 pop ebx

004010C8 mov esp,ebp

004010CA pop ebp

004010CB ret

; 先看看BFuncprolog:

18: int BFunc(int i,int j)

19: {

00401090 push ebp

00401091 mov ebp,esp

00401093 sub esp,48h

00401096 push ebx

00401097 push esi

00401098 push edi

圖 8

; 這個時候BFunc的堆棧信息也搭建好了(灰色部分)

20: int m = 1;

004010A8 mov dword ptr [ebp-4],1;沒什么新意的操作,AFunc中發生的一模一樣

21: int n = 2;

004010AF mov dword ptr [ebp-8],2;沒新意

圖 9

22:

23: m = i;

004010B6 mov eax,dword ptr [ebp+8];沒新意

004010B9 mov dword ptr [ebp-4],eax

24: n = j;

004010BC mov ecx,dword ptr [ebp+0Ch];沒新意

004010BF mov dword ptr [ebp-8],ecx

25:

26: return m;

004010C2 mov eax,dword ptr [ebp-4];函數的返回值是放在EAX里面返回的,如果說一個個函

; 數之間是行星的話EAX就是神5那載著楊天人的返回

; 艙了.

27: }

; 我們把重點放在BFunc函數返回時執行的這些指令上(epilog)

004010C5 pop edi

004010C6 pop esi

004010C7 pop ebx

004010C8 mov esp,ebp

004010CA pop ebp

004010CB ret

圖 10

圖 11

; 此時你會發現圖11與圖 7時的堆棧情況完全(ESP,EBP的值相同)一樣,也就是說調用完BFunc函數后

; 堆棧恢復到了調用前的狀態.

0040110F add esp,8;注意BFunc執行完返回AFuncAFunc將通過改變ESP將先前傳給BFunc

; 的參數出棧,但不清空.

圖12

就此AFunc調用BFunc函數結束了,接下來堆棧繼續重演著:父函數調用子函數,子函數執行結束后返回.然后父函數又作為別人的子函數,執行結束,返回…..

附錄

注解1

因為默認C/C++函數的調用約定是__cdecl,這種調用約定參數是從右到左壓棧的,Windows提供的函數大部分是__stdcall的調用約定,符合該約定的函數在傳參數的時候也是從右到左壓棧.

參考書目

[1] Jeffrey Richter,”Programming Applications for Microsoft Windows4rd”.( Microsoft Press,1999)

[2] Intel Architecture Software Developer Manual

[3] Randy Kath. “The Win32 Debugging API.” MSDN

總結

以上是生活随笔為你收集整理的Win32 环境下的堆栈的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。