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

问题描述

我尝试遵循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或汇编程序编程中,相对很少有这样的情况,可以保证违反这样的规则以段错误或其他任何可预测的方式失败;取而代之的是人们说“行为未定义”之类的事情,这意味着它可能以您可以想象的任何方式失败,或者再次失败。因此,您真的无法从非法代码会发生起作用的实验中得出任何结论。反复试验不是学习汇编编程的好方法。

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...