在x86程序集中,在通常的32位调用约定中,push
函数args是正常的,然后用add
清除堆栈。是否有任何有用的替代品,例如使用MOV
代替PUSH
?
例如,是否有其他有效的方法可以执行以下操作?
PUSH 10
PUSH 20
CALL plus ; int plus(int,int) adds two integer args, leaving EAX = 30
add esp, 8
答案 0 :(得分:3)
sub rsp, 8
mov dword ptr[rsp+4], 10
mov dword ptr[rsp], 20
call plus
这与您的代码完全相同,但不使用push
; “转换”是直截了当的,因为push被定义为“通过操作数大小递减堆栈指针,然后将操作数移动到堆栈指针所指向的位置”。
答案 1 :(得分:3)
在这个问题的原始版本中,OP提到了AMD64。 AMD64上通常的调用约定传递了寄存器中的前几个args(比如MS的32位__vectorcall
ABI),而不是堆栈,所以当然你在那里使用mov
。
mov edi, 10 ; x86-64 SysV calling convention.
mov esi, 20
call plus ; leaves eax = 30.
如果您正在编写仅从asm调用的函数,则可以基于每个函数构建自定义调用约定,因此即使对于32位也可以使用上述代码。 (但我建议选择在正常ABI中未调用保留的寄存器。)请参阅x86标记wiki以获取ABI文档的链接。
push
以将args放入堆栈如果ESP
以上的房间,你可以mov
到那个空间而不是推args。
e.g。对于两个背靠背call
,而不是add esp, 8
/ push / push来清理堆栈并推送新的args,你可以使用mov
来编写新的args。对于某些-maccumulate-outgoing-args
设置,gcc默认执行此操作(-mtune
),但不是全部。请参阅this mailing list message from 2014 where Honza describes the pros and cons,以及为-mtune=generic
禁用它的原因。
(实际上,gcc' s -maccumulate-outgoing-args
可能会略有不同。我认为它会在使用sub
的第一次通话之前为args保留空间,所以即使是第一次通话也没有使用{ {1}}。这增加了代码大小,但最小化了对push
的更改,因此它缩小了将指令地址映射到堆栈帧大小的CFI元数据,用于使用esp
进行堆栈展开。)
e.g。
-fomit-frame-pointer
这会;; Delayed arg popping. May not be a win since push is cheap on modern CPUs with a stack engine.
push 10
push 20
call foo
; add esp,8 ; don't do this yet
mov [esp+4], 15
mov [esp], eax
call bar
add esp,8 ; NOW pop the args.
,并使堆栈指向与开始之前相同的位置。弹出堆栈(使用bar( foo(20,10), 15)
)然后add
新的args可能会在现代CPU上做得更好。这可以保存说明,但不能保存代码大小。它可以节省1个uop。
在英特尔的堆栈引擎上,push
之前或mov [esp+4]
之前需要额外的堆栈同步uop,因此无论哪种方式,成本都是相同的。避免它的唯一方法是推动新的args,并在最后做一次大的清理(add esp, 4
)。但是,这可能会因触及新的堆栈空间而导致更多缓存丢失。 (有关更多优化文档,请参阅x86标记wiki)。
答案 2 :(得分:-5)
像这样:
mov eax [value]
祝你在装配程序上好运很难。