32位x86程序集中堆栈对齐的责任

时间:2016-10-28 14:22:40

标签: linux gcc assembly x86 memory-alignment

我试图清楚地了解谁(调用者或被调用者)对堆栈对齐的反应。 64位汇编的情况相当清楚,它是由调用者

参考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之前是否需要对齐堆栈?如果是这样,那么在将这两个参数推送到%esp之前,这需要将scanf减少四个字节:

4 bytes (return address)
4 bytes (%ebp of previous stack frame)
8 bytes (for two variables)
12 bytes (three arguments for scanf)
= 28

1 个答案:

答案 0 :(得分:8)

仅限GCC main中执行此额外堆栈对齐;该功能很特别。如果您查看任何其他功能的代码,您将看不到它,除非您有alignas(32)或其他地方的本地。

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

多年来,i386 System V ABI保证/要求ESP + 4在进入功能时进行16B对齐。 (即,在 CALL指令之前,ESP必须是16B对齐,因此堆栈上的args从16B边界开始。这与x86-64 System V相同。)

ABI还保证新的32位进程以ESP在16B边界上对齐开始(例如在_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链接。 http://x86-64.org/仍然死了,似乎没有回来,所以我更新了System V链接以指向HJ Lu的github repo中当前版本的PDF和his page with links

请注意,last version on SCO's site 当前版本,并且不包含16B-stack-alignment要求。

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