系统相关
首页 > 系统相关> > linux – 32位x86程序集中堆栈对齐的责任

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

作者:互联网

我试图清楚地了解谁(调用者或被调用者)负责堆栈对齐. 64位汇编的情况相当清楚,它来自调用者.

参考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)

保持(额外八个是因为调用隐含地在栈上推送返回地址).

它在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个字节:

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中进行额外的堆栈对齐;这个功能很特别.如果你查看任何其他函数的code-gen,你将看不到它,除非你有本地的alignas(32)或其他东西.

GCC只是采用-m32的防御方法,不假设使用正确的16B对齐堆栈调用main.或者,这个特殊的处理方法是从-mpreferred-stack-boundary = 4只是一个好主意,而不是法律.

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

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

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

也许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之外的编译器输出.

我刚刚更新了标签wiki中的ABI链接. http://x86-64.org/仍然死了,似乎没有回来,所以我更新了System V链接以指向HJ Lu的github repo和his page with links中当前版本的PDF.

请注意,last version on SCO’s site不是当前版本,并且不包括16B堆栈对齐要求.

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

标签:x86,linux,assembly,gcc,memory-alignment,x86
来源: https://codeday.me/bug/20190929/1830800.html