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