在 x86 中使用 `and rsp,-16` 进行堆栈对齐

问题描述 投票:0回答:1

考虑这个“Hello world”x86 程序:

    bits 64
    global main
    extern puts

    section .text
main:
    push rbp
    mov rdi, msg
    call puts
    pop rbp
    ret

    section .data
msg:db "Hello world!", 10, 0

我理解

push rbp
指令的目的是将外部子程序
puts
的堆栈指针与16字节边界对齐。执行此指令后,
rsp
将错位 8 个字节,因为
rbp
的大小为 8 个字节。但
call
指令会将返回地址压入堆栈,因此
rsp
将再次减8。然后在执行
puts
子例程中的第一条指令之前,堆栈将被对齐。

还有其他方法可以对齐堆栈指针。例如,this相关问题的最佳答案表明,当我不知道堆栈指针是否对齐时,我可以这样做

and rsp,-16
。上面的程序可能会变成

    bits 64
    global main
    extern puts

    section .text
main:
    push rbp
    mov rbp, rsp
    and rsp,-16  ; after this instruction, expect rsp to be divisible by 16 (0b10000)
    mov rdi, msg
    call puts
    mov rsp, rbp ; restore the old stack pointer
    pop rbp
    ret

    section .data
msg:db "Hello world!", 10, 0

令我困惑的是,如果我在

and rsp,-16
指令之前执行
call
,然后在
call
之后隐式将返回地址压入堆栈,那么在执行
puts
中的第一条指令时,堆栈不会错位8个字节吗?子程序?也许我误解了如何正确使用
and rsp,-16

assembly x86 stack memory-alignment
1个回答
0
投票

好吧,我想我明白了我的误解。在 System V ABI 调用约定中,要求在执行

rsp % 16 == 0
指令之前堆栈指针必须是 16 字节对齐(即
call
)。

在我编写的第一个 hello world 示例中,在进入

main
函数时,堆栈指针未对齐 8。呃,为什么?那么,在 Linux 中,程序的真正入口点是
_start
而不是
main
。堆栈指针在
_start
的开头是 16 字节对齐的,但在程序控制从
_start
传递到
main
后,堆栈指针错位了 8 个字节。所以这就是为什么我们需要使用
push rbp
来对齐
puts
子例程的堆栈指针。

© www.soinside.com 2019 - 2024. All rights reserved.