32 位 x86 汇编中堆栈对齐的职责

Responsibility of stack alignment in 32-bit x86 assembly

我想清楚地了解谁(调用者或被调用者)负责堆栈对齐。 64 位汇编的情况相当清楚,它是由 caller.

参考 System V AMD64 ABI,第 3.2.2 节堆栈框架

The end of the input argument area shall be aligned on a 16 (32, if __m256 is passed on stack) byte boundary.

换句话说,应该安全地假设,对于被调用函数的每个入口点:

16 | (%rsp + 8)

持有(额外八个是因为 call 隐含地将 return 地址压入堆栈)。


它在 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

GCC main 中执行此额外的堆栈对齐;该函数很特殊。 如果您查看任何其他函数的代码生成,您将看不到它,除非您有一个带有 alignas(32) 或其他东西的本地文件。

GCC 只是对 -m32 采取防御性方法,不假设 main 是使用正确的 16B 对齐堆栈调用的。或者这种特殊待遇是 -mpreferred-stack-boundary=4 只是一个好主意,而不是法律时遗留下来的。

多年来,i386 System V ABI guaranteed/required ESP+4 在函数入口处是 16B 对齐的。 (即 ESP 必须是 16B 对齐的 CALL 指令之前,因此堆栈上的参数从 16B 边界开始。这与 x86-64 系统 V 相同。)

ABI 还保证新的 32 位进程以在 16B 边界上对齐的 ESP 开始(例如在 _start,ELF 入口点,其中 ESP 指向 argc,而不是 return地址),并且 glibc CRT 代码保持该对齐。

就调用约定而言,EBP只是另一个调用保留寄存器。但是,是的,带有 -fno-omit-frame-pointer 的编译器输出确实会在其他调用保留寄存器(如 EBX)之前注意 push ebp,因此保存的 EBP 值形成一个链表。 (因为它还在推送之后设置帧指针的 mov ebp, esp 部分。)


也许 gcc 是防御性的,因为一个非常古老的 Linux 内核(在对 i386 ABI 的修订之前,当所需的对齐仅为 4B 时)可能违反该假设,并且它只是额外的几条指令运行 在进程的生命周期中一次(假设程序不递归调用 main)。


与 gcc 不同,clang 假定堆栈在进入 main 时正确对齐。 (clang 也 ,即使当前的 ABI 修订版还没有指定该行为。gcc 和 clang 都发出在调用方执行的代码,但只有 clang 在被调用方依赖它。这种情况发生在 64 位代码中,但我没有检查 32 位代码。)

如果您好奇,请查看 http://gcc.godbolt.org/ 上的编译器输出,了解 main 和 main 以外的函数。


我刚刚更新了 tag wiki the other day. http://x86-64.org/ is still dead and seems to be not coming back, so I updated the System V links to point to the PDFs of the current revision in HJ Lu's github repo, and his page with links 中的 ABI 链接。

请注意 last version on SCO's site 不是 当前修订版,并且不包括 16B 堆栈对齐要求。

我认为某些 BSD 版本仍然不需要/保持 16 字节堆栈对齐。