2010-05-21 203 views
0
int main(void) { 
    problem2(); 
} 

void doit2(void) { 
    int overflowme[16]; 
    //overflowme[37] =0; 
} 

void problem2(void) { 
    int x = 42; 
    doit2(); 
    printf("x is %d\n", x); 
    printf("the address of x is 0x%x\n", &x); 
} 

有人能幫我理解爲什麼overflowme [37] = 0;從doit2函數會覆蓋x的值? (請在你的解釋中包含函數doit2的程序計數器和幀指針)謝謝!堆棧溢出技術

它可以在x86 windows計算機上正常工作(ok!),並使用Project properties-> Configuration properties-> C/C++ - > Code Generation-> Basic Runtime Checks設置爲「Default」。所以它不是一個未定義的行爲。

+0

不,每次37個作品! – user133466 2010-05-21 22:26:56

+2

這是功課嗎? – 2010-05-21 22:27:31

+4

它不一定 - 這隻會發生在特定的編譯器,CPU,編譯器標誌等。 – 2010-05-21 22:27:43

回答

5

像其他人一樣說,這取決於目標和compliler的,但對你的那些保持不變,而且沒有在代碼的任何其他東西,看起來像他們引入隨機性堆棧(相對說話),所以它每次都會做同樣的事情。

系統堆棧通常從高地址向低地址增長。如果堆棧指針是0x1234並且你推入一個值(在32位{4字節}系統上),那麼堆棧指針將變爲0x1230。

數組從最低地址到最高地址進行尋址。如果您有

char a[2]; 

並且[0]位於0x0122,則[1]將位於0x0123。

doit2中的數組是一個自動變量,意味着它在創建函數時創建,並在函數退出時刪除。自動變量必須存在於堆棧或寄存器中。由於它是一個數組,因此編譯器將其放入RAM而不是寄存器會複雜得多(這使索引變得更容易,因爲它只是將索引*大小添加到數組第一個成員的地址)。由於堆棧在RAM中,因此編譯器會將數組放入堆棧。

爲該數組分配堆棧空間意味着堆棧指針爲sizeof(int)*16,比如果該數組不存在時小。堆棧指針最有可能指向overflowme[0]而在doit2

還有其他的東西可能在堆棧上,還有一些東西必須放在堆棧上。必須在堆棧上的東西是在函數被調用時被推到那裏的返回指針。在32位系統上,每個應占用4個字節。可能在堆棧上的東西(如果編譯器想要使用它的話)是前一幀指針。 (顯式*)x86-32上的堆棧幀僅僅是ESP和EBP之間的空間,但它們並不是必需的,因此它們通常不被使用,而EBP只是用作通用寄存器(可用的更多通用寄存器是一般很好)。 儘管使用堆棧幀很有用,因爲它們使調試變得更加容易,因爲ESP和EBP充當本地變量邊緣的標記。有時需要堆棧幀,例如當您使用alloca或C99的可變大小的自動數組時,因爲它們允許函數的局部變量空間被mov EPB, ESP或等效的instructiosn而不是sub size_of_local_variable, ESP丟棄,所以編譯器不必知道大小的框架。它們還允許局部變量相對於EBP被尋址,而不是在alloca的情況下變化的ESP。在這種情況下,EBP直到當前函數結束纔會更改,除非通過調用函數進行更改和恢復。

編譯時沒有優化打開編譯器通常總是使用堆棧幀,因爲它們使調試更容易。使用堆棧框架對代碼進行建模,然後在證明它們不是必需的之後,將代碼轉換爲不使用它們也更容易。

所以,EBP的前值可能會或可能不會駐留在返回地址(在problem2某處),並在doit2overflowme最後一個元素之間的堆棧。編譯器也可以自由地將任何其他東西放在堆棧上,所以誰知道還有什麼可以在那裏。

problem2的局部變量int x可以進入寄存器或棧中。在沒有優化的情況下編譯時,局部變量通常會進入堆棧,即使它們可以進入寄存器。

所以,讓我們假設有doit2overflowme陣列,舊的幀指針,返回地址,並problem2「堆疊在S x(並且在某些更多的東西{這是真正在較高地址}它)。

由於&(overflowme[i])相同的overflowme第一元素的地址的加法,以(I * {的int大小})和舊EBP位於的overflowme的最後一個元素和一個返回地址後的舊後位於EBP和int x位於返回地址之後,x肯定會被緩衝區溢出的方式阻止。

爲什麼發生這種情況對於37的指數還不清楚。指針數學(假設只有上面列出的數據位於數組和x之間的堆棧中)並不表明它應該基於4字節指針(32位機器),但如果這是8字節指針系統( 64位機器),那麼數學更接近我期望x的地址,如果是sizeof(int) == 8。編譯器也可以繼續前進,併爲printf(格式字符串必須放在堆棧中後的可變參數)分配堆棧空間,這會影響數學運算(也鼓勵編譯器將x放置在堆棧上,因爲它會無論如何都必須推它)。

如果您想要更詳細地回答您的問題,請查看此代碼的程序集並計算出確切的地址。

  • 即使沒有使用EBP作爲幀基本指針,也可以認爲堆棧幀存在,但是幀不會被分幀。
+0

ggrrrrr。很好的答案.. – 2011-05-04 13:38:14

3

它可能不是。變量在堆棧中的位置依賴於編譯器和平臺。

+0

每次使用項目屬性 - >配置屬性 - > C/C++ - >代碼生成 - >基本運行時檢查設置爲「默認」 – user133466 2010-05-21 22:27:36

+8

@metashockwave:僅僅因爲它適用於** 1 **非常特定的平臺/編譯器/ etc ,這並不意味着它通常是真的 – 2010-05-21 22:28:58

+0

不,這些設置是什麼,但聽起來像一些Windows的東西,最有可能的是x86的。 – johannes 2010-05-21 22:29:43

0

它並不:

x is 42 
the address of x is 0xbff9ea1c 

以上發生的每一次上一個真正的編譯器和平臺(地址變更),讓您明明白白是正確的,它不是不確定的行爲。

+1

我認爲你已經正確地擊中了那裏的頭部。 – 2010-05-22 03:11:20

2

你的籌碼將會是這個樣子:

char overflowme[16] 
return address to problem2() from calling doit2() 
parameters for doit2(), if it had any 
int x = 42 
return address to main() from calling problem2() 
parameters for problem2(), if it had any 
local variables for main(), if it had any 

當您向overflowme[37],你會走過去的overflowme末(因爲它只有16字節)和以往的返回地址從doit2()呼叫並覆蓋x

正如其他人所提到的,這高度依賴於平臺和編譯器,但應該給你一個很好的可視化問題。嘗試通過調試窗口打開代碼並顯示堆棧。