我有一些来自shell代码有效载荷的示例代码,显示了一个for循环并使用push / pop设置了计数器:
push 9
pop ecx
为什么不能只使用mov?
mov ecx, 9
答案 0 :(得分:6)
是的,出于性能方面的考虑,通常应该始终使用mov ecx, 9
。作为{u1} / pop`的运行效率更高,它可以在任意位置运行港口。 (在Agner Fog测试过的所有现有CPU上都是这样:https://agner.org/optimize/)
push
/ push imm8
的正常原因是机器代码中没有零字节。这对于必须通过pop r32
或任何其他将缓冲区视为以strcpy
字节终止的隐式长度C字符串的一部分的缓冲区溢出的 shellcode 非常重要。
0
仅适用于32位立即数,因此机器代码看起来像mov ecx, immediate
。与B9 09 00 00 00
按9; 6a 09
pop ecx。
(ECX是寄存器号59
,1
和B9
的来源:指令的低3位= 59
)
另一个用例是纯粹的代码大小:001
是5个字节(使用无ModRM编码,将寄存器号放在操作码的低3位),因为遗憾的是x86缺少针对mov r32, imm32
(没有mov
)的带符号扩展的imm8操作码。几乎所有可追溯到8086的ALU指令都存在这种情况。
在16位8086中,该编码将不会节省任何空间:3字节短格式mov r/m32, imm8
几乎与假设的mov r16, imm16
一样好,除了移动到需要mov r/m16, imm8
格式(带有ModRM字节)的内存的直接位置。
由于386的32位模式没有添加新的操作码,只是更改了默认的操作数大小和立即宽度,因此32位模式下ISA中的这种“遗漏的优化”始于386。现在,mov r/m16, imm16
比add r32,imm32
长2个字节。参见x86 assembly 16 bit vs 8 bit immediate operand encoding。但是add r/m32, imm8
没有该选项,因为没有MOV操作码对其立即数进行符号扩展(或零扩展)。
有趣的事实:mov
(即使以速度为代价也进行了大小优化)will compile clang -Oz
至int foo(){return 9;}
; push 9
。
另请参见Codegolf.SE上的Tips for golfing in x86/x64 machine code(该网站通常是出于娱乐目的而优化大小,而不是将代码放入小型ROM或引导扇区中。但是对于机器代码,进行大小优化确实具有实际应用有时,甚至以牺牲性能为代价。)
如果您已经拥有另一个具有已知内容的寄存器,则可以使用3字节的pop rax
在另一个寄存器中创建9(如果EAX持有lea ecx, [eax-0 + 9]
)。只需操作码+ ModRM + disp8。因此,如果您已经将其他任何寄存器的异或为零,则可以避免push / pop hack。 0
的效率几乎不及lea
,并且在优化速度时可以考虑使用它,因为较小的代码大小在大规模上具有较小的速度优势:L1i高速缓存命中,有时在uop高速缓存中解码还不是很热。
答案 1 :(得分:2)
这可能有不同的原因。
在这种情况下,似乎这样做是因为代码较小:
具有push
和pop
组合的变量的长度为3个字节,mov
指令的长度为5个字节。
但是,我猜想mov
变体会更快...
答案 2 :(得分:0)
基本上是一样的东西。将9推入堆栈,然后将其弹出到ecx寄存器中,这与mov ecx,9基本上相同。就我个人而言,我认为9到ecx可能比将9推入堆栈然后将其弹出到ecx中更有效,但是我认为处理时间是没问题,因此考虑到两种方式的代码多么短,他们俩都同样快。