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;
}
好的,当用-mreferred堆栈边界=4编译时,我希望看到一个对齐到16字节的堆栈(从技术上讲,它对齐到16个字节,但有额外的16个字节的未使用堆栈空间(。gcc生成的main的序言是:
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行将esp向下舍入到最近的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的倍数呢?
如果我将编译器标志更改为-mreferred 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行将esp向下舍入到最近的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个字节来自(调用__Z14testStackStuff
(堆栈上的4个字节来自(推送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,它正在构建返回地址/保存的ebp对的完整副本。
但是gcc似乎再次产生与64字节对齐的堆栈帧
不,它使用了and esp, -32
。堆栈帧大小看起来像64个字节,但其对齐方式只有32B。
我不知道为什么它会在堆栈框架中留下这么多额外的空间。猜测gcc -O0
为什么会这样做并不是很有趣,因为它甚至没有试图达到最优。
你显然是在没有优化的情况下编译的,这让整个事情变得不那么有趣。这篇文章告诉您更多关于gcc内部的信息,以及什么对gcc方便,而不是它发出的代码是必要的或做任何有用的事情。此外,使用http://gcc.godbolt.org/以在没有CFI指令和其他噪声的情况下获得良好的asm输出。(请整理问题中的asm代码块并输出。所有的噪音都会使它们更难阅读。(
- 如何理解将半精度指针转换为无符号长指针和相关的内存对齐
- 如何创建一个QTableWidgetItem,用长文本右对齐,左边有省略号
- 我可以检测和更改 gcc/g++ 中结构的当前数据对齐设置吗?
- 64位机器上的C++内存对齐
- 为什么我可以将变量存储在不是其最小对齐方式的倍数的地址?
- 使 std::vector 分配对齐内存的现代方法
- C++ cout 将双精度对齐到精度 2 并正确对齐
- 在 64 位边界上对齐C++结构数组?
- 使用 g++7 构建的代码在访问未对齐的内存时崩溃
- 在 capnp FlatArrayMessageReader 的对齐内存缓冲区中接收 zmq 消息
- 是否值得对齐变量?
- 初始化派生结构的基部分/意外打包派生结构字段以对齐基结构的间隙
- 警告#13212:引用需要堆栈对齐功能的EBX
- 在堆栈上分配对齐的内存,如_alloca
- 如何在堆栈上对齐缓冲区
- MSVS 2010 C++编译器和堆栈对齐问题
- 我将如何在C++03中便携地实现对齐堆栈存储
- 展开操作期间遇到无效或未对齐的堆栈
- 为什么堆栈上对齐的整数之间有 8 个字节的"0xcc"填充?C++ 32位视窗7
- gcc x86 Windows堆栈对齐