假设我有一个像这样的nasm功能:
inc:
mov rax,[rsp + 8]
add [rax],BYTE 1
ret
并且我这样调用此函数:
push some_var
call inc
我想通过堆栈将参数传递给函数,所以我按下some_var
,然后调用我的函数。在该函数中,我的项目在堆栈中排在第二位,因此我将其视为:mov rax,[rsp+8]
我的问题是:调用函数后,我应该以某种方式从堆栈中弹出参数吗?如果是这样,我可以以某种方式将其从堆栈中删除,我的意思是弹出它,但不进行注册? (因为我不再需要该参数了。)
更新:我发现我可以简单地add rsp,8
,这就是我可以从堆栈中删除项目的方式。但这是好习惯吗?要在调用函数后从堆栈中删除参数?
最佳实践是在寄存器中传递args,例如编译器使用的标准x86-64调用约定。例如x86-64系统V在寄存器中传递了前6个整数/指针args,因此您的功能应为add byte [rdi], 1
/ ret
,并且不需要任何清理。呼叫者只需要mov edi, some_var
或lea rdi, [rel some_var]
。
(What are the calling conventions for UNIX & Linux system calls on i386 and x86-64中记录了用户空间函数调用的基础,尽管标题中提到了系统调用。https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI中的完整详细信息。实际上查看编译器对简单C函数的操作也很方便:请参阅How to remove "noise" from GCC/clang assembly output?)
如果您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还是执行相反的被调用者清理。通过将要从堆栈中删除的字节数指定为an immediate operand至retn
指令,可以完成被调用方清除。例如:
...
; caller code
push rax
push rdi
call testfunction
...
; function code
testfunction:
push rbp
mov rbp, rsp
mov rcx, qword [rbp + 16]
...
mov rsp, rbp
pop rbp
retn 16