这段反汇编代码中发生了什么,在 C 中会是什么样子?

What is happening in this disassembled code, and what would it look like in C?

我已经反汇编了这个 c 代码(使用 ida),并且 运行 跨越了这段代码。我相信第二行和第 5 行是一个数组,但我不确定为什么它使用符号扩展或零扩展。

我需要将代码转换为 C,我不确定为什么要使用 sign/zero 扩展,或者是什么 C 代码会导致这种情况。

mov     ecx, [ebp+var_58]
mov     dl, byte ptr [ebp+ecx*2+var_28]
mov     [ebp+var_59], dl
mov     eax, [ebp+var_58]
movsx   ecx, [ebp+eax*2+var_20]
movzx   edx, [ebp+var_59]
or      edx, ecx
mov     [ebp+var_59], dl

unsigned 整数类型将进行零扩展,而有符号类型将进行符号扩展。


我觉得这太微不足道了。说明参考手册没有涵盖任何内容。我想这与要求解释一个非常简单的 C 程序是不同的,因为这里的技巧是理解为什么一个人可能将这一系列指令串在一起,而不是仅仅理解每个指令单独做什么。熟悉非优化编译器使用的习惯用法(在每个语句后从 RAM 存储和重新加载)会有所帮助。


我猜这是一个来自函数内部的片段,它构成了一个堆栈框架,因此 ebp 的正偏移是局部变量不在寄存器中时溢出的地方。

mov     ecx, [ebp+var_58]     ; load var58 into ecx
mov     dl, byte ptr [ebp+ecx*2+var_28]   ; load a byte from var28[2*var58]
mov     [ebp+var_59], dl      ; store it to var59
mov     eax, [ebp+var_58]     ; load var58 again for some reason?  can var59 alias var58?
;  otherwise we still have the value in ecx, right?
;  Or is this non-optimizing compiler output that's really annoying to read?
movsx   ecx, [ebp+eax*2+var_20]   ; load var20[var58*2]
movzx   edx, [ebp+var_59]         ; load var59 again
or      edx, ecx                  ; edx = var59|var20[var58*2]
mov     [ebp+var_59], dl          ; spill var59 back to memory

我想 movsx/movzx 的默认操作数大小是字节到双字。 word-to-dword 也存在,我很惊讶你的反汇编程序没有用内存操作数上的 byte ptr 消除歧义。我推断这是一个字节加载,因为之前对该地址的存储是字节宽的。

movsx 用于加载小于 32b 的签名数据。 C 的整数提升规则规定,对小于 int 的整数类型的操作会自动提升为 int(或 unsigned int,如果 int 不能表示所有值。例如,如果 unsigned shortunsigned int 大小相同)。

8 位或 32 位操作数大小在没有操作数大小前缀字节的情况下可用。只有一些 Intel P6/SnB 系列 CPU 跟踪部分寄存器依赖性,在加载时将符号扩展到完整的寄存器宽度可以加快代码速度(避免对 AMD 和 Silvermont 上寄存器的先前内容的错误依赖)。因此,对加载进行符号扩展或零扩展(根据数据类型)通常是处理窄内存位置的最佳方式。


查看非优化编译器的输出通常不值得费心。

如果代码是由适当的优化编译器生成的,它可能更像

mov     ecx, [ebp+var_58]     ; var58 is live in ecx
mov     al, byte ptr [ebp+ecx*2+var_28]   ; var59 = var28[2*var58]
or      al, [ebp+ecx*2+var_20]    ; var59 |= var20[var58*2]
mov     [ebp+var_59], al          ; spill var59 to memory

更容易阅读,IMO,没有不断 storing/reloading 的噪音。您可以看到某个值何时被多次使用,而不必注意到加载来自刚刚存储到的地址。

如果对 eax 的高 24 位的错误依赖导致了问题,我们可以使用 movzxmovsx 加载到两个寄存器中,并像下面那样执行 or r32, r32原始的,但仍然存储低 8。(使用 32 位或内存操作数将执行 4B 加载,而不是 1B 加载,这可能会跨越缓存行甚至页面和段错误。)