当前位置: 首页 > 知识库问答 >
问题:

为什么在外壳代码中使用Push/Pop而不是Mov将数字放入寄存器?

岑和风
2023-03-14

我有一些来自shell代码负载的示例代码,显示了for循环,并使用push/pop设置计数器:

push 9
pop ecx

为什么不能只使用mov?

mov ecx, 9

共有3个答案

逄嘉木
2023-03-14

本质上完全一样。将9推送到堆栈,然后将其弹出到ecx寄存器中,这与mov ecx,9基本相同。我个人认为,将9推送到ecx可能比将9推送到堆栈,然后将其弹出到ecx中更有效,但我认为流转时长不是问题,因此考虑到两种方式的代码都有多小,它们的速度都一样快。

龚镜
2023-03-14

这可能有不同的原因。

在这种情况下,这似乎是因为代码较小:

pop组合的变体长3字节,mov指令长5字节。

然而,我猜想mov的变体更快。。。

屠杰
2023-03-14

是的,出于性能原因,通常应始终使用mov ecx,9。作为可以在任何端口上运行的单个uop指令,它的运行效率高于推送。(Agner Fog测试的所有现有CPU都是如此:https://agner.org/optimize/)

推送imm8的正常原因是机器码没有零字节。对于必须通过strcpy溢出缓冲区的外壳代码,或将其视为以0字节终止的隐式长度C字符串的一部分的任何其他方法,这一点很重要。

mov ecx,immediate只能与32位immediate一起使用,因此机器代码看起来像B9 09 00。vs<代码>59。

(ECX是寄存器号1,其中B9和59来自:指令的低3位=001)

另一个用例纯粹是代码大小:mov r32,imm32是5个字节(使用无ModRM编码,将寄存器号放入操作码的低3位),因为不幸的是x86缺少用于mov的符号扩展imm8操作码(没有mov r/m32,imm8)。这几乎适用于所有可以追溯到8086年的ALU指令。

在16位8086中,这种编码不会节省任何空间:3字节的短格式mov r16,imm16几乎与假设的mov r/m16,imm8差不多,除了将立即数移动到内存中,在内存中需要mov r/m16,imm16(带有ModRM字节)。

由于386的32位模式没有添加特定于该模式的新操作码,只是更改了默认操作数大小和立即数宽度,因此32位模式下ISA中的这种“错过的优化”从386开始。由于全宽度立即数长2个字节,因此现在加法r32,imm32比加法r/m32,imm8长。请参阅x86汇编16位与8位立即数操作数编码。但是对于mov,我们没有这个选项,因为没有符号扩展(或零扩展)其立即数的mov操作码。

有趣的事实:clang-Oz(即使以牺牲速度为代价也优化大小)将编译int foo(){返回9;}推送9pop rax。GCC12也支持类似的-Oz

另请参阅Codegolf上x86/x64机器代码中的高尔夫技巧。SE(一个关于大小优化的网站,通常是为了好玩,而不是将代码放入一个小的ROM或引导扇区。但对于机器代码,大小优化有时确实有实际应用,甚至是以牺牲性能为代价。)

如果您已经有另一个包含已知内容的寄存器,可以使用3字节lea ecx[eax-0 9](如果eax保持0),在另一个寄存器中创建9。只需操作码ModRM disp8。因此,如果您已经将任何其他寄存器的异或置零,则可以避免推/弹出攻击<代码>lea的效率几乎低于mov,在优化速度时可以考虑它,因为较小的代码大小在大规模情况下对速度的好处很小:L1i缓存命中,如果uop缓存尚未热,有时会解码。

 类似资料:
  • 问题内容: 为什么这样 代替这个 问题答案: 因为如果抛出异常,则 除非 捕获到异常, 否则 在执行 该块之后没有任何代码。一个块总是执行,不管你里面发生了什么块。 __

  • 问题内容: Jenkins在调用命令时使用什么外壳?我在Linux机器上运行Jenkins。 问题答案: 从“执行外壳”部分的帮助/问号图标中: 运行用于构建项目的Shell脚本(默认为sh,但这是可配置的)。 如果转到Manage Jenkins-> Configure System,您将找到一个选项(称为“ Shell可执行文件”)来设置您希望您的Shell脚本使用的Shell的名称或绝对路径

  • 问题内容: 我知道python具有用于确定字符串大小的函数,但是我想知道为什么它不是字符串对象的方法。 更新资料 好吧,我意识到我是一个尴尬的错误。实际上是字符串对象的方法。在字符串对象上使用len函数在Python中看到面向对象的代码似乎很奇怪。此外,看到名字而不是len也很奇怪。 问题答案: 字符串确实有一个length方法: Python中的协议是在具有一定长度并使用内置函数的对象上实现此方

  • 具体是: 注意,我关心的是旧的x86 linux CPU,而不是现代的x86_64 CPU,那里的分段工作方式不同。

  • 问题内容: 我在这里找不到任何合理的答案,所以我希望它不会重复。那么为什么我应该更喜欢setter或构造函数注入而不是简单注入 如果您在类初始化期间需要对注入的bean进行某些操作,则可以使用构造函数注入的用法 但是仍然,它几乎和方法一样,并且我完全不会进行setter注入,这不只是Spring和其他DI框架之后的遗物吗? 问题答案: 构造函数和属性注入使您可以轻松地在非CDI环境中(例如,单元测

  • 我碰巧知道,在下面的表达式中,使用将导致无限流,将始终为0。我之所以困惑是因为我认为返回的值没有被使用,即便如此,它也不应该中断之后的增量。