有没有办法使用MOV而不是PUSH来设置函数调用的args?

时间:2016-07-24 11:23:42

标签: assembly x86

在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

3 个答案:

答案 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中未调用保留的寄存器。)请参阅标记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)。但是,这可能会因触及新的堆栈空间而导致更多缓存丢失。 (有关更多优化文档,请参阅标记wiki)。

答案 2 :(得分:-5)

像这样: mov eax [value] 祝你在装配程序上好运很难。