【发布时间】:2016-10-28 14:22:39
【问题描述】:
我试图清楚地了解谁(调用者或被调用者)负责堆栈对齐。 64 位汇编的情况相当清楚,它是由 caller 完成的。
参考 System V AMD64 ABI,第 3.2.2 节 堆栈框架:
输入参数区域的结尾应对齐在 16(32,如果 __m256 在堆栈上传递)字节边界。
换句话说,应该安全地假设,对于被调用函数的每个入口点:
16 | (%rsp + 8)
持有(额外的八个是因为call 隐式地将返回地址压入堆栈)。
它在 32 位世界中的外观(假设为 cdecl)?我注意到gcc 将对齐放置在内部具有以下构造的调用函数中:
and esp, -16
这似乎表明,这是被调用者的责任。
为了更清楚,请考虑以下 NASM 代码:
global main
extern printf
extern scanf
section .rodata
s_fmt db "%d %d", 0
s_res db `%d with remainder %d\n`, 0
section .text
main:
start 0, 0
sub esp, 8
mov DWORD [ebp-4], 0 ; dividend
mov DWORD [ebp-8], 0 ; divisor
lea eax, [ebp-8]
push eax
lea eax, [ebp-4]
push eax
push s_fmt
call scanf
add esp, 12
mov eax, [ebp-4]
cdq
idiv DWORD [ebp-8]
push edx
push eax
push s_res
call printf
xor eax, eax
leave
ret
在调用scanf 之前是否需要对齐堆栈?如果是这样,那么在将这两个参数推送到scanf 之前,这需要将%esp 减少四个字节:
4 bytes (return address)
4 bytes (%ebp of previous stack frame)
8 bytes (for two variables)
12 bytes (three arguments for scanf)
= 28
【问题讨论】:
-
看起来像
and esp, 0xfffffff0的 16 字节对齐代码通常会添加到main的模板代码中。但是对于每个其他函数,对齐都是由调用函数维护的。我应该指出,对于 32 位代码,您应该遵循 System V i386 ABI。是的,您需要在调用scanf等函数之前保持堆栈 16 字节对齐 -
x86 堆栈必须仅对齐 4 字节(通用寄存器大小)。所以通常不需要为堆栈对齐执行特殊任务。在 x64 中 - 这是调用者在调用前对 16*x 上的堆栈对齐负责
-
@RbMm:据我了解,Linux 上 x86 堆栈的对齐要求有所提高。当前要求为 16 个字节(如果一个通过
_m256参数,则为 32 个字节)。 ABI 的第 2.2.2 节现在包含这个短语:The end of the input argument area shall be aligned on a 16 (32, if __m256 is passed on stack) byte boundary. -
@GrzegorzSzpetkowski - 我对 Linux 一无所知,但对于处理器视图和 Windows 操作系统 - 32 位堆栈 enouhg 的 4 字节对齐
-
@RbMm:您对 Windows 的看法是正确的。我应该指定有问题的操作系统。
标签: linux gcc assembly x86 memory-alignment