我已经拆解了一个程序。我在开头看到AND
和ESP
的{{1}}指令。
这个面具是什么意思?这是对齐问题吗?
它是32位x86的ELF可执行文件。
答案 0 :(得分:6)
i386的gcc Linux默认为-mpreferred-stack-boundary=4
(meaning 24 = 16 byte alignment)。 (其他非Linux系统也使用ELF可执行文件,但它们也具有相同的堆栈对齐默认值和SysV ABI。)
与clang不同, gcc并不假设堆栈在进入main
时会对齐,因此它使用AND
指令来屏蔽低位堆栈指针的位。这是在堆栈上保留足够的填充以到达下一个对齐边界的最便宜的方式。
堆栈对齐与您查看调用另一个函数的函数保留堆栈上的某些空间的原因相同,它不会用于任何事情:
extern int bar(void);
int foo(int x) { return x+bar(); }
gcc 5.3 -O3
sub esp, 12 # align the stack for another call
call bar
add eax, DWORD PTR [esp+16] # add our arg (from the stack) to bar()'s return value (in eax)
add esp, 12
ret
See this on the Godbolt compiler explorer,您可以尝试不同的编译器和选项。
-mincoming-stack-boundary=3
(或更少)会导致堆栈对齐样板添加到每个函数(不仅仅是main)。 -mstackrealign
和-mno-stackrealign
对foo()
或main()
无效,无论是否有-mincoming-stack-boundary
。基于the documentation in the gcc manual,我认为它将启用或禁用除main之外的函数或main的函数。
32bit x86 SysV ABI过去只保证4字节堆栈对齐,但调用约定现在保证在%esp
指令之前call
的16字节对齐。
第2.2.2节“堆栈帧”
...换句话说,值(
%esp + 4
)总是如此 当控制转移到功能入口点时,为16的倍数。 (32通过值传递32字节ymm向量时)
所以gcc的-mpreferred-stack-boundary=4
不仅仅是一个好主意,而是法律(在像Linux这样的系统上,包含这种更强保证的更新ABI版本是官方标准)。这使得除main
之外的函数在使用对齐的SSE存储/加载到堆栈之前省略该对齐步骤是安全的。这些指令(如movaps
)将在未对齐的地址上出错。因此,正确性需要对齐,而不仅仅是性能。
AND
指令当前版本的32位ABI 确保保证新的execve
32位进程将以%esp
16字节对齐开始。 (第2.3.1节“初始堆栈和寄存器状态”,请参阅有关%esp
本身初始状态的要点,而不是堆栈内容。)
这意味着gcc在main开头对齐堆栈的行为现在已经过时,假设调用main
的CRT启动代码没有错位堆栈。
clang确实假设堆栈在main的开头对齐,就像64bit gcc那样。
16B-alignment从一开始就是x86-64 SysV ABI的一部分,后来没有添加,因此它始终是一个安全的假设,并且没有旧的内核在进程启动时不能提供。
x86代码wiki包含指向其他ABI的链接等等。