【问题标题】:Should %rsp be aligned to 16-byte boundary before calling a function in NASM?在调用 NASM 中的函数之前,%rsp 是否应该与 16 字节边界对齐?
【发布时间】:2020-07-03 11:46:46
【问题描述】:

我从 NASM 的文档中看到了以下规则:

在进行调用之前,堆栈指针 %rsp 必须与 16 字节边界对齐。很好,但是进行调用的过程会将返回地址(8 个字节)压入堆栈,因此当函数获得控制权时,%rsp 未对齐。你必须自己腾出额外的空间,通过推动一些东西或从 %rsp 中减去 8。

我有一个 NASM 汇编代码的 sn-p 如下:

在我调用“_start”中的函数“inc”之前,%rsp 应该位于 8 字节的边界,这违反了 NASM 文档中描述的规则。但实际上,一切都进行得很顺利。那么,我该如何理解呢?

我在 Ubuntu 20.04 LTS (x86_64) 下构建了这个。

global _start

section .data
init:
    db 0x2

section .rodata
codes: 
    db '0123456789abcdef'

section .text
inc:
    mov rax, [rsp+8]  ; read param from the stack;
    add rax, 0x1
    ret

print:
    lea rsi, [codes + rax]
    mov rax, 1
    mov rdi, 1
    mov rdx, 1
    syscall
    ret

_start:
    ; enable AC check;
    pushf
    or dword [rsp], 1<<18
    popf

    mov rdi, [init]  ; move the first 8 bytes of init to %rdi;
    push rdi  ; %rsp -> 8 bytes;
    call inc
    pop r11  ; clean stack by the caller;
    call print

    mov rax, 60
    xor rdi, rdi
    syscall

【问题讨论】:

  • 对于您自己的函数,您可以使用任何您想要的约定。请注意,标准约定使用寄存器来传递参数(适用条款和条件,请阅读细则:))此外,即使使用 3rd 方函数,如果您未按要求对齐 rsp,天空也不一定会塌陷,它取决于被调用函数在做什么。将浮点数传递给诸如printf 之类的可变参数函数通常会因堆栈指针未对齐而崩溃。
  • 顺便说一句,inc 也是指令的名称;我建议不要将其用作标签名称。此外,NASM 文档几乎肯定没有说“%rsp”,除非它引用了另一个文档(例如 x86-64 System V ABI),因为 % 装饰是 AT&T 语法,而不是 NASM Intel 语法
  • 仅作记录,这看起来效率很低,并且只处理 4 位数字,即使您加载了 8 个 字节(16 个半字节)。请参阅How to convert a binary integer number to a hex string? 了解不会浪费时间进行函数调用的简单高效的循环。我猜你只是为了学习函数而使用函数。
  • 而不是pop r11 ; clean stack by the caller;,你可以更好地(不破坏任何寄存器)简单地添加你推送到rsp的大小
  • @Tommylee2k: pop 在其他两个堆栈操作(如retcall)之间实际上比add rsp,8 更有效。这就是为什么clang 使用它的原因,例如。 Why does this function push RAX to the stack as the first operation? / What is the stack engine in the Sandybridge microarchitecture? / NASM should I pop function argument after calling a function?

标签: linux assembly x86-64 memory-alignment calling-convention


【解决方案1】:

ABI 是一组关于函数应如何表现以实现互操作的规则。一侧的每个规则都与另一侧的允许假设配对。在这种情况下,关于调用者堆栈对齐的规则是关于被调用者堆栈对齐的允许假设。由于您的 inc 函数不依赖于 16 字节堆栈对齐,因此可以使用仅 8 字节对齐的堆栈调用该特定函数。

如果您想知道为什么在启用 AC 时它没有中断,那是因为您只从堆栈加载 8 字节值,而堆栈仍然是 8 字节对齐的。如果您也使用sub rsp, 4 或其他方式破坏了 8 字节对齐,那么您将收到总线错误。

ABI 变得重要的地方是,当情况不是您自己在汇编中编写的一个函数调用您自己在汇编中编写的另一个函数时。其他人的库(包括 C 标准库)中的函数,或者您从 C 编译而不是用汇编编写的函数,在其执行movaps [rsp - 24], xmm0 或其他事情的权利范围内,如果您没有正确对齐会中断在调用它之前堆栈。

旁注:ABI 还说明了您应该如何传递参数(调用约定),但您只是将它们传递到任何地方。同样,从您自己的程序集中很好,但如果您尝试从 C 调用它们,它们肯定会中断。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-18
    • 2012-04-30
    • 2017-12-21
    • 2016-12-19
    • 1970-01-01
    • 2020-03-26
    相关资源
    最近更新 更多