【问题标题】:Why linux nasm working even WITHOUT 16 bytes stack alignment为什么即使没有 16 字节堆栈对齐,linux nasm 也能正常工作
【发布时间】:2020-12-05 23:21:39
【问题描述】:

我尝试遵循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

【问题讨论】:

  • 如果您调用的函数(如 C 库(puts 等))是使用不需要对齐的指令(如某些 SSE/AVX 指令)编译/组装的,那么它们可能会按预期工作。如果您开始使用浮点运算(例如使用printf 的浮点),您通常会看到更多对齐问题。您可能会发现在 MacOS 上它失败了,因为他们似乎在他们的 C 库中使用了相当多的对齐指令,即使对于整数类数据也是如此。如果您不对齐堆栈,它可能会或可能不会工作。
  • 您的问题类似于问:“我开车闯红灯,然后安全地开到了对面。我以为有人会撞到我。为什么要遵守交通规则不被尊重?”

标签: assembly x86-64 nasm glibc ubuntu-20.04


【解决方案1】:

只是运气。

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

编译器有权假设堆栈是对齐的,并且它可以在感觉喜欢的情况下使用这些指令。所以在任何时候,如果你的 libc 被升级或重新编译,新版本的puts 可能会使用这样的指令,你的代码会神秘地开始失败。

显然你不希望这样,所以像你应该的那样对齐该死的堆栈。

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

【讨论】:

  • TL:DR:“未定义”并不意味着“必须失败”。我喜欢你的闯红灯比喻,很好。无论如何,这种未来的事情已经在现实生活中发生了:glibc scanf 现在在堆栈中使用 movaps,即使在使用 AL=0 正确调用时也是如此。 glibc scanf Segmentation faults when called from a function that doesn't align RSP x86-64 glibc 的旧版本没有这样做,并且在 32 位代码中也不会发生,尽管 i386 System V ABI 具有相同的 16 字节堆栈对齐保证/要求。
猜你喜欢
  • 1970-01-01
  • 2014-12-25
  • 2017-09-07
  • 2021-12-30
  • 2022-08-11
  • 2021-01-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多