簡體   English   中英

gcc x86 Windows堆棧對齊

[英]gcc x86 Windows stack alignment

我正在編寫純粹作為學習經驗的編譯器。 我目前正在通過編譯簡單的c ++代碼然后研究gcc 4.9.2為Windows x86生成的輸出asm來學習堆棧幀。

我簡單的c ++代碼是

#include <iostream>

using namespace std;

int globalVar;

void testStackStuff(void);
void testPassingOneInt32(int v);
void forceStackFrameCreation(int v);

int main()
{
  globalVar = 0;

  testStackStuff();

  std::cout << globalVar << std::endl;
}

void testStackStuff(void)
{
  testPassingOneInt32(666);
}

void testPassingOneInt32(int v)
{
  globalVar = globalVar + v;

  forceStackFrameCreation(v);
}

void forceStackFrameCreation(int v)
{
  globalVar = globalVar + v;
}

好吧,當使用-mpreferred-stack-boundary = 4編譯它時,我希望看到一個堆棧對齊到16個字節(技術上它與16個字節對齊,但是有一個額外的16個字節的未使用堆棧空間)。 由gcc制作的主要序言是:

22                      .loc 1 12 0
23                      .cfi_startproc
24 0000 8D4C2404        lea ecx, [esp+4]
25                      .cfi_def_cfa 1, 0
26 0004 83E4F0          and esp, -16
27 0007 FF71FC          push    DWORD PTR [ecx-4]
28 000a 55              push    ebp
29                      .cfi_escape 0x10,0x5,0x2,0x75,0
30 000b 89E5            mov ebp, esp
31 000d 51              push    ecx
32                      .cfi_escape 0xf,0x3,0x75,0x7c,0x6
33 000e 83EC14          sub esp, 20
34                      .loc 1 12 0
35 0011 E8000000        call    ___main
35      00
36                      .loc 1 13 0
37 0016 C7050000        mov DWORD PTR _globalVar, 0
38                      .loc 1 15 0
39 0020 E8330000        call    __Z14testStackStuffv

第26行向下舍入到最近的16字節邊界。

第27,28和31行將總共12個字節推入堆棧,然后

第33行從esp中減去另外20個字節,總共32個字節!

為什么?

第39行然后調用testStackStuff。

- 此調用將推送返回地址(4個字節)。

現在,讓我們看看testStackStuff的序言,記住堆棧現在距離下一個16字節邊界更近4個字節。

67 0058 55              push    ebp
68                      .cfi_def_cfa_offset 8
69                      .cfi_offset 5, -8
70 0059 89E5            mov ebp, esp
71                      .cfi_def_cfa_register 5
72 005b 83EC18          sub esp, 24
73                      .loc 1 22 0
74 005e C704249A        mov DWORD PTR [esp], 666

第67行推動另外4個字節(現在朝向邊界8個字節)。

第72行減去另外24個字節(總共32個字節)。

此時,堆棧現在在16字節邊界上正確對齊。 但為什么2的倍數?

如果我將編譯器標志更改為-mpreferred-stack-boundary = 5,我希望堆棧對齊到32個字節,但gcc似乎再次產生與64個字節對齊的堆棧幀,是我期望的兩倍。

主要的序幕

23                      .cfi_startproc
24 0000 8D4C2404        lea ecx, [esp+4]
25                      .cfi_def_cfa 1, 0
26 0004 83E4E0          and esp, -32
27 0007 FF71FC          push    DWORD PTR [ecx-4]
28 000a 55              push    ebp
29                      .cfi_escape 0x10,0x5,0x2,0x75,0
30 000b 89E5            mov ebp, esp
31 000d 51              push    ecx
32                      .cfi_escape 0xf,0x3,0x75,0x7c,0x6
33 000e 83EC34          sub esp, 52
34                      .loc 1 12 0
35 0011 E8000000        call    ___main
35      00
36                      .loc 1 13 0
37 0016 C7050000        mov DWORD PTR _globalVar, 0
37      00000000 
37      0000
38                      .loc 1 15 0
39 0020 E8330000        call    __Z14testStackStuffv

第26行向下舍入到最近的32字節邊界

第27,28和31行將總共12個字節推入堆棧,然后

第33行從esp中減去另外52個字節,總共64個字節!

和testStackStuff的序言是

66                      .cfi_startproc
67 0058 55              push    ebp
68                      .cfi_def_cfa_offset 8
69                      .cfi_offset 5, -8
70 0059 89E5            mov ebp, esp
71                      .cfi_def_cfa_register 5
72 005b 83EC38          sub esp, 56
73                      .loc 1 22 0

(來自堆棧的4個字節)調用__Z14testStackStuffv

(來自堆棧的4個字節)push ebp

(來自堆棧的56個字節)sub esp,56

總共64個字節。

有誰知道為什么gcc會創建這個額外的堆棧空間或者我忽略了一些明顯的東西?

謝謝你盡你所能的幫助。

為了解決這個謎團,你需要查看gcc的文檔,找出它使用的應用程序二進制接口 (ABI)的確切風格,然后找到該ABI的規范並閱讀它。 如果您“正在編寫純粹作為學習經歷的編譯器”,那么您肯定需要它。

簡而言之,從廣義上講,正在發生的是ABI要求當前函數保留這個額外空間,以便將參數傳遞給當前函數調用的函數。 保留多少空間的決定主要取決於函數打算做的參數傳遞量,但它比那更細微,ABI是詳細解釋它的文檔。

在舊式的堆棧幀中,我們將PUSH參數添加到堆棧中,然后調用一個函數。

在新的堆棧幀樣式中,不再使用EBP(不確定為什么它被保留並從ESP復制),參數被放置在堆棧中相對於ESP的特定偏移處,然后調用該函數。 事實證明,使用mov DWORD PTR [esp], 666將666參數傳遞給調用testPassingOneInt32(666);

為什么它正在執行push DWORD PTR [ecx-4]來復制返回地址,請參閱此部分重復 IIRC,它正在構建一個return-address / saved-ebp對的完整副本。


但gcc似乎再次產生了與64字節對齊的堆棧幀

不,它使用and esp, -32 堆棧幀大小看起來像64字節,但它的對齊只有32B。

我不確定為什么它會在堆棧框架中留下如此多的空間。 猜測為什么gcc -O0能做它的功能並不是很有趣,因為它甚至都沒有嘗試做到最佳。

你顯然沒有優化編譯,這使整個事情變得不那么有趣。 這告訴你更多關於gcc內部和gcc的方便,而不是它發出的代碼是必要的或做了什么有用的。 此外,使用http://gcc.godbolt.org/獲得不錯的asm輸出,而不使用CFI指令和其他噪聲。 (請用你的輸出整理你問題中的asm代碼塊。所有的噪音使得它們更難閱讀。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM