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

标签 linux gcc assembly x86 memory-alignment

我试图清楚地了解谁(调用者或被调用者)负责堆栈对齐。 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 隐含地将返回地址压入堆栈)。


它在 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 多年来一直保证/要求 ESP+4 在函数入口处是 16B 对齐的。 (即 ESP 在 CALL 指令之前必须是 16B 对齐的,因此堆栈上的参数从 16B 边界开始。这与 x86-64 System V 相同。)

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

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


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


与 gcc 不同,clang 假定堆栈在进入 main 时正确对齐。 (clang 也是 assumes that narrow args have been sign or zero-extended to 32 bits ,即使当前的 ABI 修订版还没有指定该行为。gcc 和 clang 都发出在调用方执行的代码,但只有 clang 在被调用方依赖它。这发生在 64位代码,但我没有检查 32 位。)

查看 http://gcc.godbolt.org/ 上的编译器输出对于 main 和除 main 之外的函数,如果你好奇的话。


我刚刚更新了 中的 ABI 链接前几天标记 wiki。 http://x86-64.org/仍然死了,似乎不会回来,所以我更新了 System V 链接以指向 HJ Lu 的 github 存储库中当前修订版的 PDF,和 his page with links .

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

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

关于linux - 32 位 x86 汇编中堆栈对齐的职责,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40307193/

相关文章:

r - 在 WSL 上安装 R devtools 包

c++ - 关于 Qt4 setValidator 用法的莫名其妙的 gcc 投诉

c - 如何将 Turbo-C 远指针分配和取消引用转换为 x86 程序集?

c++ - 内联汇编操作数约束

assembly - 你还有什么古老的旧学校代码?

linux - 在 Linux 上禁用端口阻塞

linux - 为 bash 中的参数设置特定选项集

python - 通过 jenkins-plugin 运行 pip?

如果使用 CLion 而不是直接使用 gcc 编译,C 程序的行为会有所不同。为什么?

gcc 内联 asm 模板,用于不带哈希的常量