我已经拆解了这个c代码(使用ida),然后遇到了这段代码。我相信第二行是一个数组,以及第五行,但我不确定为什么它使用符号扩展或零扩展。
我需要将代码转换为C,并且我不确定为什么使用符号/零扩展,或者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
答案 0 :(得分:2)
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
的歧义。我推断它是一个字节加载,因为该地址的前一个存储是字节宽的。
在加载小于32b的签名数据时使用movsx。 C的整数提升规则规定小于int
的整数类型的操作会自动提升为int
(如果unsigned int
不能代表所有值,则为int
。例如{ {1}}和unsigned short
大小相同。)
8位或32位操作数大小可用,没有操作数大小前缀字节。有些只有Intel P6 / SnB系列CPU跟踪部分寄存器的依赖关系,在加载时符号扩展到完整的寄存器宽度可以实现更快的代码(避免对AMD和Silvermont上寄存器的先前内容的错误依赖)。因此,对负载进行符号或零扩展(适用于数据类型)通常是处理窄内存位置的最佳方法。
查看非优化编译器的输出通常不值得打扰。
如果代码是由适当的优化编译器生成的,那么它可能更像是
unsigned int
更容易阅读,IMO,没有不断存储/重新加载的噪音。您可以查看何时多次使用某个值,而不必注意到加载来自刚刚存储到的地址。
如果对eax的高24位的错误依赖导致问题,我们可以将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
或movzx
加载到两个寄存器中,并像原始版本一样执行movsx
,但是仍然存储低8位。(使用32位或内存操作数将执行4B加载,而不是1B加载,这可能会跨越缓存行甚至页面和段错误。)