细节决定一切——由一个函数声明引发的诡异崩溃

今天LibCHD成功登陆游戏,遂使用Release编译并在VB中调用,结果立崩,开始还以为是VB的问题,但是用C++调用居然也崩,而Debug编译没有出现任何问题。VC挂载后发现崩溃出在recv的thread中,但调用堆栈看不出哪里崩掉的。然后用OD挂载,崩溃现场显示堆栈已溢出,但调用框架还能看出,从堆栈最下面往上翻,总算找到一个返回地址在我的DLL里,看出来是在DynCode解密的调用中崩掉的。语句为

pFuncDecrypt(Pac.Data, Pac.Length - 4);

但是解密为什么会崩掉。我所了解的SDDynDll只有可能在线程不同步,同时进入加解密才会出问题,而这段代码已经是在EnterCriticalSection了,不可能因为线程问题。仔细观察周边情况,封包数据——普通的102号包,12字节,只是12字节后不知为什么全是乱码,突然发现堆栈中返回地址往下两格的位置写着00010008,囧,8字节怎么成了10008了,显然是直接把一个32位的值放进去了。一看汇编,果真如此:

6AB34AC6   8B5424 14        MOV EDX,DWORD PTR SS:[ESP+14]6AB34ACA   8B8E D0104000    MOV ECX,DWORD PTR DS:[ESI+4010D0]6AB34AD0   83C2 FC          ADD EDX,-46AB34AD3   52               PUSH EDX6AB34AD4   8D4424 1C        LEA EAX,DWORD PTR SS:[ESP+1C]6AB34AD8   50               PUSH EAX6AB34AD9   FFD1             CALL ECX

但是上面的语句,Pac.Length是个16位整数,那么第一句的汇编应该是mov edx,word [esp+14]才对啊,难道编译器出错了把一个16位整数弄成了32位?长这么大还没听说萎软的编译器出错的,这可是特大新闻了,不过本着对萎软的崇敬之情,我还是觉得我应该瞅瞅我自己的代码。。结果就还真瞅出了问题:

void (STDCALL* pFuncEncrypt)(uint8* Data, uint16 Length);void (STDCALL* pFuncDecrypt)(uint8* Data, uint16 Length);pFuncEncrypt = ((void(STDCALL*(STDCALL*)(uint32))(uint8*, uint16))m_DynCode)(1);pFuncDecrypt = ((void(STDCALL*(STDCALL*)(uint32))(uint8*, uint16))m_DynCode)(2);

问题出在函数声明上。尽管代码的使用,传入的参数都是int16,但是函数本身却不是为了int16而写的。如果函数本身参数是int16,则函数内部会只使用低16位,就算传入32位的数字,高16位也会被舍弃。但是现在函数设计的是32位的,但声明写的是16位,VC会认为里面不会处理高16位而放心大胆地为了节省时间而不把参数的高16位清空,于是立即出问题,在SDDynDll中表现为堆栈溢出,但即使不溢出,Packet后面的数据也会遭到毁坏(而这种没有立即崩溃的情况会更难以察觉)可以说,玩C++几年了,第一次遇到这种诡异问题,C++真的是无奇不有,幸好咱懂汇编。

One Reply to “细节决定一切——由一个函数声明引发的诡异崩溃”

Leave a Reply

Your email address will not be published. Required fields are marked *