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