我不知道它是整数数组还是什么?或某些未在Hex-Rays中正确呈现的内联函数
我理解else语句会发送一个4字节的数据包,其中包含GetTickCount API的时间戳。
如果语句应发送
a2
中的数据包是指向字符的指针,其中a3是所有字符的大小。用法类似于此
char buffer[448];
memset(buffer, 0, sizeof(buffer));
//blah blah packet stuff here
strncpy(&buffer[90], "blah blah blah", 250u);
buffer[339] = 0;
//then the call below.
// 91+250+91 = 432, yet memset is 448, 16 extra probably stack padding.
test(*v28, buffer, strlen(&buffer[90]) + 91);
这里是从Hex-Rays反编译的原始代码。
void __thiscall test(void *this, const void *a2, unsigned int a3)
{
void *v3; // ebx@1
char *v4; // eax@3
int v5; // [sp-8h] [bp-418h]@3
int v6; // [sp-4h] [bp-414h]@3
char v7[4]; // [sp+Ch] [bp-404h]@4
char buf[1024]; // [sp+10h] [bp-400h]@3
v3 = this;
if ( a2 && (signed int)a3 > 0 )
{
*(_DWORD *)buf = 0;
memcpy(&buf[4], a2, 4 * (a3 >> 2));
v6 = 0;
v5 = a3 + 4;
v4 = buf;
memcpy(&buf[4 * (a3 >> 2) + 4], (char *)a2 + 4 * (a3 >> 2), a3 & 3);// Looks like Copy by DWORDs, not by Bytes.
}
else
{
v6 = 0;
*(_DWORD *)v7 = GetTickCount() / 0xA;
v5 = 4;
v4 = v7;
}
send(*(_DWORD *)v3, v4, v5, v6);
}
这里我修复了它一点点手工,仍然不理解。
void __thiscall test(void *this, const void *a2, unsigned int a3)
{
void *v3; // ebx@1
char *v4; // eax@3
int v5; // [sp-8h] [bp-418h]@3
int v6; // [sp-4h] [bp-414h]@3
char v7[4]; // [sp+Ch] [bp-404h]@4
char buf[1024]; // [sp+10h] [bp-400h]@3
v3 = this;
if ( a2 && (signed int)a3 > 0 )
{
*(_DWORD *)buf = 0;
//Might be a swap of the 5th offset DWORD to end of the packet?
//Or maybe it fills in the packet offsetted by the first 4 bytes?
memcpy(&buf[4], a2, 4 * (a3 / 4)); // Looks like Copy by DWORDs, not by Bytes.
v6 = 0;
v5 = a3 + 4;
v4 = buf;
//Might be a swap of the end of the packet to the 5th offset DWORD?
//Looks like some kind of footer to above memcpy function like to finish what the first function couldn't do?
memcpy(&buf[4 * (a3 / 4) + 4], (char *)a2 + 4 * (a3 / 4), a3 & 3);// Looks like Copy by DWORDs, not by Bytes.
}
else
{
v6 = 0;
*(_DWORD *)v7 = GetTickCount() / 0xA;
v5 = 4;
v4 = v7;
}
send(*(_DWORD *)v3, v4, v5, v6);
}
好吧,我给了它更多的时间,这可能是正确的吗?
void __thiscall test(void *this, const void *a2, unsigned int a3)
{
void *v3; // ebx@1
char *v4; // eax@3
int v5; // [sp-8h] [bp-418h]@3
int v6; // [sp-4h] [bp-414h]@3
char v7[4]; // [sp+Ch] [bp-404h]@4
char buf[1024]; // [sp+10h] [bp-400h]@3
v3 = this;
if ( a2 && (signed int)a3 > 0 )
{
*(_DWORD *)buf = 0;
memmove(&buf[4],a2,a3 - 4);
v6 = 0;
v5 = a3 + 4;
v4 = buf;
}
else
{
v6 = 0;
*(_DWORD *)v7 = GetTickCount() / 0xA;
v5 = 4;
v4 = v7;
}
send(*(_DWORD *)v3, v4, v5, v6);
}
下面的组件
.text:00408750 ; =============== S U B R O U T I N E =======================================
.text:00408750
.text:00408750
.text:00408750 ; void __thiscall test(void *this, const void *a2, unsigned int a3)
.text:00408750 test proc near
.text:00408750 ; CODE XREF: ServerMainLoop+5DDp
.text:00408750 ; ServerMainLoop+64Dp
.text:00408750
.text:00408750 var_404 = byte ptr -404h
.text:00408750 buf = byte ptr -400h
.text:00408750 a2 = dword ptr 4
.text:00408750 a3 = dword ptr 8
.text:00408750
.text:00408750 sub esp, 404h
.text:00408756 push ebx
.text:00408757 push esi
.text:00408758 mov esi, [esp+40Ch+a2]
.text:0040875F push edi
.text:00408760 test esi, esi
.text:00408762 mov ebx, ecx
.text:00408764 jz short loc_408799
.text:00408766 mov eax, [esp+410h+a3]
.text:0040876D test eax, eax
.text:0040876F jle short loc_408799
.text:00408771 mov ecx, eax
.text:00408773 lea edi, [esp+410h+buf+4]
.text:00408777 mov edx, ecx
.text:00408779 mov dword ptr [esp+410h+buf], 0
.text:00408781 shr ecx, 2
.text:00408784 rep movsd
.text:00408786 mov ecx, edx
.text:00408788 push 0
.text:0040878A and ecx, 3
.text:0040878D add eax, 4
.text:00408790 push eax
.text:00408791 lea eax, [esp+418h+buf]
.text:00408795 rep movsb
.text:00408797 jmp short loc_4087B7
.text:00408799 ; ---------------------------------------------------------------------------
.text:00408799
.text:00408799 loc_408799: ; CODE XREF: test+14j
.text:00408799 ; test+1Fj
.text:00408799 call ds:GetTickCount
.text:0040879F mov edx, eax
.text:004087A1 mov eax, 0CCCCCCCDh
.text:004087A6 mul edx
.text:004087A8 shr edx, 3
.text:004087AB push 0 ; flags
.text:004087AD mov dword ptr [esp+414h+var_404], edx
.text:004087B1 push 4 ; len
.text:004087B3 lea eax, [esp+418h+var_404]
.text:004087B7
.text:004087B7 loc_4087B7: ; CODE XREF: test+47j
.text:004087B7 mov ecx, [ebx]
.text:004087B9 push eax ; buf
.text:004087BA push ecx ; s
.text:004087BB call send
.text:004087C0 pop edi
.text:004087C1 pop esi
.text:004087C2 pop ebx
.text:004087C3 add esp, 404h
.text:004087C9 retn 8
.text:004087C9 test endp
.text:004087C9
.text:004087C9 ; ---------------------------------------------------------------------------
#1 楼
这段代码很简单,只是有点混乱,因为它是由decompiler
生成的。这是一个更简单的注释版本:void __thiscall test(void *this, const void *a2, unsigned int a3)
{
char v7[4];
int v5 = 4;
void *v3 = this;
char buf[1024], *v4;
if (a2 != NULL && (signed int)a3 > 0)
{
//Setting the 4 first bytes to 0. Certainly the message header !
*(_DWORD *)buf = 0;
/*
Same as :
memset(buff, 0, 4);
buf[0] = buf[1] = buf[2] = buf[3] = 0;
*/
/*
Copying the first a3 bytes of a2 into buff + 4.
The + 4 is to jump the 4 bytes header set to 0 previously.
*/
memcpy(buf + 4, a2, a3); //There's no point in 4 * a3 / 4;
//Size has changed to a3 + 4 (4 bytes for the header)
v5 += a3;
//
v4 = buf;
}
else
{
/* _DWORD is 4 bytes. This line converts v7 into an integer to
copy the value of GetTickCount() / 10 byte by byte into it.
*/
*(_DWORD *)v7 = GetTickCount() / 10;
//
v4 = v7;
}
send(*(_DWORD *)v3, v4, v5, 0);
}
从这里我要说的是,如果没有消息,此例程将发送一个包,该包的标头设置为GetTickCount()/ 10( a2 == NULL或a3 <= 0),否则它将消息头设置为0,将消息本身设置为a2并发送数据包。
关于您提供的原始版本的问题是执行4乘4的复制...这就是为什么
memcpy
被分成两部分的原因。第一个复制元素4 *(a3 / 4)(如果a3 = 19,那么它将复制16个元素),第二个复制剩余的a3%4(a3%4 <==> a3&(4- 1)<==> a3&3)元素(如果a3 = 19,则它将复制3)。 评论
消息头是数据包中前4个字节的一部分吗?因此,它的使用方式可能是GetTickCount,否则是填充零来表示消息?
–user3435580
2014-4-28 13:09
感谢您花时间进行重构。数据包的前4个字节会丢失吗?
–user3435580
2014年4月28日在13:15
别客气。
– Yaspr
14年4月28日在13:18
哦,我发现没有任何字节丢失,只是在前面填充了4个空字节。
–user3435580
2014年4月28日在13:21
正是:D那只是标题。我确定应该接收消息的例程会检查标头,以了解它是否应该同步或读取消息正文。
– Yaspr
2014年4月28日在13:23
#2 楼
它根本不是记忆载体,只是记忆。逐字复制dword比逐字节复制更有效率,因为每个副本可以使用32位数据总线(至少在数据按字对齐的情况下)。因此,编译器要做的是:将字节数除以4,通过将cx右移2位
,复制适当的4字节双字数,使用movsd
计算剩余的字节数(通过将cx与3进行“与”运算)
使用movsb
逐个复制这些字节。
是编译器已经在准备执行下一个memcpy的同时为下一个函数调用准备了参数-
push 0
,add eax, 4
和push eax
属于jmp
之后的send调用。评论
好的信息会派上用场,抱歉,我不能将两个答案都当作最佳答案。
–user3435580
2014年4月28日13:20在
好吧,我确实同意以下事实:用DWORD复制DWORD比通过BYTE复制BYTE更快。但是,有时,如果编译器正确完成其工作以及架构可以处理,则将使用向量化版本(memcpy_SSE42,...)。这就是使用GCC(用于静态和动态链接的二进制文件)时通常发生的情况。
– Yaspr
2014年4月28日在13:32
同意,但是我想解释为什么OP发布的特定代码看起来像它;)
–贡特拉姆·布洛姆(Guntram Blohm)
2014年4月28日在13:36
评论
常见的优化技术是将单个缓冲区副本分为两个步骤,一个由DWORD组成,一个由BYTE组成。 Hex-Rays并非万无一失,它可能被循环中间的一些指令所欺骗。由于您没有提供反汇编的代码,因此很难确定,但是memcpy比memset更有可能。大会现在也张贴在主题上。我遇到过类似的问题,其中memcpy原来是memset的卧底,而>> 2总是显示出它,因此我认为它可能是memset的变体。 rep movsd和rep movsb必须是它们。
通过反汇编,这很容易-rep movs *将数据从一个缓冲区复制到另一个缓冲区,例如memcpy。 rep stos *将常量数据写入缓冲区,就像memset一样。
那么两者都可能只是一个功能吗?我真的不明白为什么我需要做2个操作,就像将所有内容向右移动4个字节一样简单。 (我认为是这样)我有一个具有1024个字节的缓冲区,没有其他变量。老实说,除非我有一个数学大怪兽对我来说太难了,否则我不会幸运的。
可能是记忆记忆,然后记忆记忆。