繁体   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