这段反汇编代码中发生了什么,在 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 short
和 unsigned 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 位的错误依赖导致了问题,我们可以使用 movzx
或 movsx
加载到两个寄存器中,并像下面那样执行 or r32, r32
原始的,但仍然存储低 8。(使用 32 位或内存操作数将执行 4B 加载,而不是 1B 加载,这可能会跨越缓存行甚至页面和段错误。)
我已经反汇编了这个 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 short
和 unsigned 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 位的错误依赖导致了问题,我们可以使用 movzx
或 movsx
加载到两个寄存器中,并像下面那样执行 or r32, r32
原始的,但仍然存储低 8。(使用 32 位或内存操作数将执行 4B 加载,而不是 1B 加载,这可能会跨越缓存行甚至页面和段错误。)