假设我具有这样的nasm功能:
inc:
mov rax,[rsp + 8]
add [rax],BYTE 1
ret
我正在这样调用此函数:
push some_var
call inc
我想通过堆栈将参数传递给函数,所以我按下了some_var
,然后调用了我的函数。在功能上,我的项目在堆栈上排在第二位,因此我将其视为:mov rax,[rsp+8]
我的问题是:调用函数后,我应该以某种方式从堆栈中弹出参数吗?如果是这样,我可以以某种方式将其从堆栈中删除,我的意思是弹出它,但不进行注册? (因为我不再需要此arg了)
更新:我发现我可以简单地add rsp,8
,以及如何从堆栈中删除项目。但这是好习惯吗?要在调用函数后从堆栈中删除arg?
最佳实践是在寄存器中传递args,例如编译器使用的标准x86-64调用约定。例如x86-64系统V在寄存器中传递了前6个整数/指针args,因此您的功能应为add byte [rdi], 1
/ ret
,并且不需要任何清理。呼叫者只需要mov edi, some_var
或lea rdi, [rel some_var]
。
如果您do需要传递一个堆栈arg,则将其弹出到一个像pop rcx
这样的虚拟寄存器中实际上比add rsp, 8
效率更高[[more,原因与为何编译器有时会使用一个虚拟变量类似push
保留一个qword堆栈插槽/按16:重新对齐堆栈。Why does this function push RAX to the stack as the first operation?但是,如果您有多个堆栈arg供调用者清理,请使用add rsp, 8 * n
其中n
是堆栈插槽的数量。
ret 8
来清洁堆栈。但这使您失去了让调用方离开分配的堆栈空间并在其中进行mov
存储的机会,例如正在准备另一个call
。add rsp, x
lea rsp, [rsp + x]
mov rsp, rbp
(也是leave
的一部分)lea rsp, [rbp - x]
除此之外,是否
应该从调用者的堆栈中删除参数取决于您的调用约定是强制执行caller clean-up还是执行相反的被调用者清理。通过指定要从堆栈中删除的字节数作为retn
指令的立即操作数来完成被调用方清除。例如:
...
; caller code
push rax
push rdi
call function
...
; function code
push rbp
mov rbp, rsp
mov rcx, qword [rbp + 16]
...
mov rsp, rbp
pop rbp
retn 16