为什么 linux nasm 即使没有 16 字节堆栈对齐也能工作

Why linux nasm working even WITHOUT 16 bytes stack alignment

我尝试遵循 https://cs.lmu.edu/~ray/notes/nasmtutorial/ 提供的一个非常简单的示例。 我故意在下面注释行以确保堆栈未按照 x64 调用约定的要求与 16 字节边界对齐。但程序仍然继续工作。请有人回答为什么不遵守调用约定,我期待某种分段错误。

;       sub     rsp, 8                  ; must align stack before call

为了运行这个程序:(Ubuntu 20.04 lts,​​ gcc 9.3.0)

nasm -felf64 echo.asm && gcc echo.o && ./a.out 123ABC
; -----------------------------------------------------------------------------
; A 64-bit program that displays its command line arguments, one per line.
;
; On entry, rdi will contain argc and rsi will contain argv.
; -----------------------------------------------------------------------------

        global  main
        extern  puts
        section .text
main:
        push    rdi                     ; save registers that puts uses
        push    rsi
;       sub     rsp, 8                  ; must align stack before call

        mov     rdi, [rsi]              ; the argument string to display
        call    puts WRT ..plt          ; print it

;       add     rsp, 8                  ; restore %rsp to pre-aligned value
        pop     rsi                     ; restore registers puts used
        pop     rdi

        add     rsi, 8                  ; point to next argument
        dec     rdi                     ; count down
        jnz     main                    ; if not done counting keep going

        ret

运气好。

要求堆栈对齐的主要原因之一是函数可以安全地使用 SSE 对齐的数据指令,例如 movaps,如果与未对齐的数据一起使用会出错。但是,如果 puts 碰巧没有使用任何此类指令,或执行任何其他真正需要堆栈对齐的操作,则不会发生错误(尽管可能仍然会降低性能)。

编译器有权假设堆栈是对齐的,并且它可以使用这些指令,如果它觉得这样的话。所以在任何时候,如果你的 libc 被升级或重新编译,可能会发生 puts 的新版本使用这样的指令并且你的代码会莫名其妙地开始失败。

显然你不想这样,所以按照你应该的方式对齐该死的堆栈。

在 C 语言或汇编编程中,违反此类规则的情况相对较少保证 出现段错误或以任何其他可预测的方式失败;相反,人们会说“行为未定义”之类的话,这意味着它 可能 以您可以想象的任何方式失败,或者它可能不会。因此,您真的无法从非法代码 碰巧 似乎有效的实验中得出任何结论。反复试验并不是学习汇编编程的好方法。