我正在尝试理解此汇编代码,有人可以用C / C ++语言编写该代码吗?
这是代码:
loc_1C1D40: ; unsigned int
push 5
call ??_U@YAPAXI@Z ; operator new[](uint)
mov [ebp+esi*4+var_14], eax
add esp, 4
inc esi
mov byte ptr [eax+4], 0
cmp esi, 4
jl short loc_1C1D40
据我了解,前两行只是调用“ operator new”,它以eax返回一个地址。 此后,“ mov [ebp + esi * 4 + var_14],eax”表示该地址可能保存在某种数组中。 esi增加的原因非常明显。 但是为什么我们要给esp加4?
答案 0 :(得分:2)
首先进行逐行分析,找出代码的作用。
push 5
该指令将常量值“ 5”压入堆栈。为什么?好吧,因为...
call ??_U@YAPAXI@Z ; operator new[](uint)
此指令调用operator new[]
,该参数采用单个uint
参数。该参数显然以该代码使用的任何调用约定在堆栈上传递。因此,显然,到目前为止,我们已经调用operator new[]
来分配一个大小为5个字节的数组。
在C ++中,它将写为:
BYTE* eax = new BYTE[5];
对operator new[]
的调用在EAX
寄存器中返回其值(指向已分配存储块开头的指针)。这是所有x86调用约定的通用规则-函数始终在EAX
寄存器中返回结果。
mov [ebp+esi*4+var_14], eax
以上代码将结果指针(在mov
中返回的指针)存储(EAX
)到EBP + (ESI * 4) + var_14
寻址的存储位置中。换句话说,它会将ESI
寄存器中的值缩放4(大概是uint
的大小),加上EBP
寄存器的偏移量,然后加上常数var_14
。
这大致等效于以下伪C ++代码:
void* address = (EBP + (ESI * 4) + var_14);
*address = eax;
add esp, 4
这将清理堆栈,从而有效地撤消初始的push 5
指令。
push
将一个32位(4字节)的值压入堆栈,该值减堆栈指针,该指针保存在ESP
寄存器中(请注意,堆栈在x86上向下增长)。该add
指令将堆栈指针(同样是ESP
寄存器)增加 4个字节。
以这种方式平衡堆栈是一种优化。您可能等效地编写了pop eax
,但是这会产生破坏EAX
寄存器中值的附加副作用。
此指令没有直接的C ++等效项,因为它只是在做簿记工作,通常会被高级语言隐藏。
inc esi
这会将ESI
寄存器的值加1。等效于:
esi += 1;
mov byte ptr [eax+4], 0
这将常数0存储在EAX + 4
的BYTE大小的存储块中。它对应于以下伪C ++:
BYTE* ptr = (eax + 4);
*ptr = 0;
cmp esi, 4
这会将ESI
寄存器的值与常数4进行比较。CMP
指令实际上将标志设置为好像已进行了减法。
因此,后续说明:
如果jl short loc_1C1D40
ESI
寄存器的值小于 4,则有条件地跳转。
比较跳转是高级语言(例如for
或while
循环)中循环构造的标志。
将所有内容放在一起,您会得到类似的东西:
void Foo(char** var_14)
{
for (int esi = 0; esi < 4; ++esi)
{
var_14[esi] = new char[5];
var_14[esi][4] = 0;
}
}
那是不完全正确的。从编译的程序集中重构原始的C或C ++代码,就像从地面牛肉饼重构原始的母牛一样。
void Foo(char**) PROC
push esi
push edi
mov edi, DWORD PTR _var_14$[esp+4]
xor esi, esi
$LL4@Foo:
push 5
call void * operator new[](unsigned int) ; operator new[]
mov DWORD PTR [edi+esi*4], eax
add esp, 4
inc esi
mov BYTE PTR [eax+4], 0
cmp esi, 4
jl SHORT $LL4@Foo
pop edi
pop esi
ret 0
void Foo(char**) ENDP
与问题中的内容几乎完全相同,假设您忽略了序言和结语(无论如何您都没有在问题中显示)。
主要区别在于,编译器正在对MOV
指令应用相当明显的循环提升优化。而不是原始代码:
mov [ebp + esi * 4 + var_14], eax
相反,它会在序言中预先计算esp + var_14
,并将结果缓存到空闲的EDI
寄存器中:
mov edi, DWORD PTR _var_14$[esp + 4]
允许循环内的加载指令很简单:
mov DWORD PTR [edi + esi * 4], eax
我不知道您的代码为什么不执行此操作,或者为什么它使用EBP
来保存偏移量。