[英]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.