主要和堆栈对齐

时间:2014-11-11 14:06:27

标签: linux gcc assembly nasm x86-64

我有一个打印文本和浮点数的功能。这是一个不使用main

的版本
extern printf
extern _exit

section .data
    hello:     db 'Hello world! %f',10,0
    pi:        dq  3.14159
section .text
    global _start
_start:
    xor eax, eax
    lea rdi, [rel hello]
    movsd xmm0, [rel pi]
    mov eax, 1
    call printf
    mov rax, 0
    jmp _exit

我像这样组装和链接

nasm -felf64 hello.asm
ld hello.o -dynamic-linker /lib64/ld-linux-x86-64.so.2 -lc -melf_x86_64

这很好。但是,现在我想使用main

执行此操作
global main
extern printf

section .data
    hello:     db 'Hello world! %f',10,0
    pi:        dq  3.14159
section .text
    main:
    sub rsp, 8
    xor eax, eax
    lea rdi, [rel hello]
    movsd xmm0, [rel pi]
    mov eax, 1
    call printf
    mov rax, 0
    add rsp, 8
    ret

我像这样组装和链接

nasm -felf64 hello_main.asm
gcc hello_main.o

这也运行良好。但是,我必须在调用printf之前从堆栈指针中减去8个字节,然后在堆栈指针之后添加8个字节,否则会出现分段错误。

查看堆栈指针,我发现没有使用main它的16字节对齐但是main它只有8字节对齐。必须减去并添加八个字节的事实表明它总是8字节对齐而且从不16字节对齐(除非我误解了某些东西)。为什么是这样?我认为使用x86_64代码我们可以假设堆栈是16字节对齐的(至少对于标准的库函数调用,我认为它包括main)。

1 个答案:

答案 0 :(得分:7)

根据ABI,在进入函数时,堆栈指针+ 8应保持16字节对齐。你必须减去8的原因是call本身在堆栈上放置了8个字节的返回地址,从而违反了这个约束。基本上你必须确保总堆栈指针移动是16的倍数,包括返回地址。因此,堆栈指针需要移动16 + 8的倍数才能为返回地址留出空间。

对于_start,我不认为你可以依赖它而不需要手动对齐。事情就是这样,在你的情况下,由于堆栈上已有的东西,它会起作用。