汇编到 ECX 寄存器中的 OR 指令

X33*_*X33 3 x86 assembly

在我正在阅读的一本书中,我们给出了以下片段和问题:

此函数使用 SCAS 和 STOS 的组合来完成其工作。首先,分别解释第1行和第8行中的[EBP+8]和[EBP+C]的类型是什么。接下来,解释一下这个片段的作用:

01: 8B 7D 08    mov edi, [ebp+8]
02: 8B D7       mov edx, edi
03: 33 C0       xor eax, eax
04: 83 C9 FF    or ecx, 0FFFFFFFFh
05: F2 AE       repne scasb
06: 83 C1 02    add ecx, 2
07: F7 D9       neg ecx
08: 8A 45 0C    mov al, [ebp+0Ch]
09: 8B AA       mov edi, edx
10: F3 AA       rep stosb
11: 8B C2       mov eax, edx
Run Code Online (Sandbox Code Playgroud)

在使用在线解决方案(https://johannesbader.ch/2014/05/practical-reverse-engineering-exercises-page-11/)检查后,我几乎弄清楚了一切,但是,此代码段中的一个步骤仍然没有感觉我。

根据在线解决方案,当我们or ecx, 0FFFFFFFFh第 4 行运行命令时,它说

我们 [现在] 将 ECX 解释为有符号整数 -1

为了知道命令的结果是什么or,我们是否不需要事先知道值ECX是什么?为什么值是-1?

谢谢

Pet*_*des 5

32位二进制补码表示-10xFFFFFFFF(全1)。 1 OR xis always 1,因此无条件设置ecx为 -1。这个技巧只适用于 -1,因为 OR 只能设置位,不能将它们清零。


您引用的解决方案的一部分,关于将“解释ecx为有符号整数 -1”,仅在以下 gdb 命令的上下文中才有意义: (gdb) p/d $ecx -> $7 = -1

rep前缀将 ecx 视为无符号计数器。将 ecx 设置为 -1 / UINT_MAX 意味着repne scasb只有在内存中找到零时才会停止,而不是因为ecx一直倒计时。(理论上,如果没有零,它会倒计时并以这种方式结束,但实际上它会首先出现段错误。 -1不是 的特例rep)。


为什么使用or:代码大小

“正常”的方式寄存器设置为除零以外的任何是具有5个字节mov r32, imm32的insn,例如 B9 FF FF FF FF mov ecx,-1

如果您更关心代码大小而不是速度,或者您知道ecx这里的错误依赖不是问题,您可以通过使用符号扩展的 8 位立即数来节省两个字节:or r/m32, imm8

83 C9 FF    or ecx, 0FFFFFFFFh
Run Code Online (Sandbox Code Playgroud)

结果中的任何位实际上都不依赖于 ecx 的旧值,因为。但是,真正的 CPU 不会对此进行特殊处理,因此在ecx准备就绪之前无法开始乱序执行。这是对 ecx 旧值的错误依赖mov打破对先前值的依赖。(有关这方面的更多信息,请参阅标签 wiki,尤其是Agner Fog 的指南)。

or ecx, imm8需要一个 ModRM 字节来将目标编码为 ecx,这与mov每个目标寄存器都有一个单独的操作码的形式不同。不幸的是没有操作码mov r/m32, imm8,这会在许多指令中节省 2 个字节的代码。

如果英特尔愿意放弃与未记录指令的向后兼容性,他们本可以添加它。(8086 没有它,因为在将立即数移动到内存时它只会帮助 16 位代码。他们已经将 8 个操作码用于mov r16, imm16,这是 16 位模式下的 3 个字节,它不需要操作数大小前缀,就像不存在的mov r/m16, imm8一样。)


因此,在优化代码大小时这是一个有用的习惯用法,例如用于引导加载程序或https://codegolf.stackexchange.com/上的机器代码答案。(是的,这是一回事。)

另一个相关的技巧是使用 3 字节lea来创建一个常量,如果另一个寄存器中已经有另一个常量。 例如,对于x86-64 Adler32,我需要两个归零的寄存器和一个1,所以我使用了

401120:       31 c0          xor  eax,eax
401122:       99             cdq                 # zero rdx by sign-extending eax (0) into edx
401123:       8d 7a 01       lea  edi,[rdx+0x1]  # edi=0+1, using a reg + disp8 addressing mode
Run Code Online (Sandbox Code Playgroud)