如何正确操作装配中的堆栈?
Posted
技术标签:
【中文标题】如何正确操作装配中的堆栈?【英文标题】:How do I correctly manipulate the stack in assembly? 【发布时间】:2021-11-08 22:29:29 【问题描述】:Q1) 我见过汇编代码使用[rsp+4]
来访问堆栈上的变量,而其他人使用[rbp-4]
。我假设它们都是正确的,唯一的区别是使用堆栈帧的哪一端。
Q2) 进入函数时,我们应该是push [rsp]
,离开时应该是pop rsp
。但是,当我离开这些指令时,代码运行得很好。为什么需要它们?下面在 test.asm 中给出了示例代码。
Q3) 当离开程序在 main 中时,我们将返回退出代码,例如0xor rdi rdi
。但是,当我离开此命令时,它仍然有效。与 test.asm 中的示例相同。
Q4) push 5
和 mov [rsp], 5
一样吗?
; test.asm
; Compiled as such (Linking with MSVC):
; nasm -f win64 -o test.obj test.asm
; /LINK /DEFAULTLIB:msvcrt.lib /DEFAULTLIB:legacy_stdio_definitions.lib /DEFAULTLIB:Kernel32.lib /SUBSYSTEM:console test.obj /OUT:test.exe
; Gives output:
; 1
; 2
bits 64
default rel
segment .data
ifmt: db "%d, 0xd, 0xa, 0x0
segment .text
global main
extern printf
PrintInt:
sub rsp, 40
mov rdx, rcx
lea rcx, [ifmt]
call printf
add rsp, 40
ret
main:
sub rsp, 24
mov rcx, 1
call PrintInt
mov rcx, 2
call PrintInt
add rsp, 24
ret
【问题讨论】:
答案取决于您使用的 ABI,因为不同的 ABI 在堆栈和寄存器使用方面有不同的约定。从工具链命令行中,您似乎正在使用Windows x64 ABI。有很多事情你没有正确地做。没有声明堆栈展开代码(因此任何异常都会导致应用程序立即终止),main
中未正确建立主空间。 Windows ABI 不需要push [rsp]
进入;不知道你从哪里得到这个想法。
在像汇编甚至 C 这样的低级语言中,很容易编写不正确的代码,幸运的是,在特定尝试中工作。例如,您可能将重要数据留在代码的其他部分允许覆盖的内存区域中,但在您测试过的情况下碰巧实际上并没有这样做。将来它可能会随机失败,使用不同的输入、不同的执行环境,或者当您进行明显不相关的更改时。在汇编中,“它有效,因此它是正确的”不是一个有效的论点。
"我可能会在错误的道路上行驶,但我没有收到任何错误和警告,而且我仍然像我预期的那样到达我的目的地。我不明白为什么在我到达时需要这样做无论哪种方式,我的目的地都一样。”您必须遵循 ABI,因为其他人会假设您正在这样做。你可能会因为在一条很少有人使用的街道上开车而侥幸逃脱很长时间,但你仍然做错了,最终会付出代价。
你肯定不想push [rsp];你可能想push rsp;但即使这很奇怪——在某些时候你会pop rsp,但rsp 必须在正确的位置,所以这几乎没有用。 推送 rbp; mov rbp,rsp 是一个相当常规的入口序列,pop rbp 在 return (ret) 之前。
@PeterCordes 我同意,如果你只是湿了脚,你可能不需要深入研究细节。然而,经常发生的情况是,这些开发人员中的一部分毕业后编写生产代码时仍不了解细节,最终给自己或他人造成问题。
【参考方案1】:
第一季度。没错。
第二季度。 push rsp
、push [rsp]
和 pop rsp
几乎从不正确。可能有一些专门的用途,但不适合初学者。您可能会想到push rbp
和pop rbp
,仅当您在函数中使用rbp 时才需要它们。
第三季度。从 main 返回时,将eax
设置为退出状态,而不是edi
。如果调用exit
函数,则将状态作为参数传递给ecx
中的退出函数。如果调用者不使用退出状态,那么如果你不设置它,你就不会注意到差异。
第四季度。 push 5
与 lea rsp, [rsp-8]; mov qword [rsp], 5
相同。
【讨论】:
以上是关于如何正确操作装配中的堆栈?的主要内容,如果未能解决你的问题,请参考以下文章
Spring TestRestTemplate 未正确自动装配