一例“msvc编译器O2优化导致的崩溃”的分析

1. 初步分析

某进程崩溃必现。
打开崩溃dmp,结合c++源代码,崩溃大致发生在某dll代码里的这句:SAFE_DELETE(pContentData);

En_HP_HandleResult CTcpOperation::OnClintReceive(HP_Client pSender, HP_CONNID dwConnID, const BYTE * pdata, int iLength)
{
        LOG_INFO(_T("Client OnReceive iLength is %d"), iLength);
        if (iLength == sizeof(STcpTransferData))
        {
                LPVOID pVoidTemp = nullptr;
                STcpTransferData cTransferData;
                memmove(&cTransferData, pdata, sizeof(STcpTransferData));

                if (CTcpOperation::GetInstance()->CheckAndSetData(ETransferType::SenddataLength, cTransferData, dwConnID, pVoidTemp))//单条数据总大小
                {
                        HP_Client_SetExtra(pSender, pVoidTemp);
                        LOG_INFO(_T("OnClintReceive new message set length"));
                        return HR_OK;
                }
        }
        //获取服务端发送的数据,累加接收,直到得到完整数据,发送

        LPVOID pSendDataExtra = HP_Client_GetExtra(pSender);
        CVMsgSendData* pSendData = (CVMsgSendData*)pSendDataExtra;
        if (pSendData)
        {
                pSendData->SetOffset(iLength);
                pSendData->AddData(pdata);
                if (pSendData->GetLeftLength() <= 0)
        {
            const size_t nDataSize = pSendData->GetLength();
                        BYTE* pContentData = new BYTE[nDataSize];
            memmove_s(pContentData, nDataSize, pSendData->GetData(), nDataSize);
            //TODO 释放数据,后续用智能指针代替
#define SAFE_DELETE(p)          if ( p ) { delete p; p = nullptr; }
            SAFE_DELETE(pSendData);
            //防止OnClintReceive堵塞,提高发送效率,起线程调用回调函数。
            THREAD_CREATE(
                [pContentData, iLength]()mutable
            {
                                GetInstance()->GetTcpClient()->DoReceiveCallBack(true, pContentData, iLength);
                                SAFE_DELETE(pContentData);
            }
            );
                }
        }

        return En_HP_HandleResult::HR_OK;
}

若编译此dll时,选择不优化,而不是默认的O2 max speed优化,则必不崩溃。

2. 反汇编分析


查看反汇编,崩溃在这句:mov     eax, [edi]时候edi是0x10
看起来dword ptr[edi]里存的就是pContentData的值,因为之后代码判断是否为0然后delete()之。
一开始的mov edi, this实际上是mov edi,ecx。ecx应该是这个lambda函数用ecx传参传进来的pContentData(后来ecx才是采用做TcpOperation类的this指针。这里ida的笔记有点偏差)。
一例“msvc编译器O2优化导致的崩溃”的分析_第1张图片

3. 实时调试


在mov edi, ecx处和mov eax,[edi]处下断点并查看edi的值。第一次未见异常,第二次则发现mov edi, ecx时edi时0xXXXXXXXX,但是走到mov eax,[edi]就edi变成了0x10

4. 分析和解法


从汇编代码来看,edi在mov edi, ecx处和mov eax,[edi]之间是没有改动的。虽然这期间也有函数调用,但函数体内的头尾部一般会把edi存在栈上然后恢复回去。
由于没法给edi寄存器下硬件断点,所以也不知道edi究竟在哪里有发生变化。
解法就是使用c语言的关键字volatile,使之delete的时候要从栈上找处pContentData,而不是从dword ptr[edi]里找。经验证,不会崩溃。
如下:
THREAD_CREATE(
    [pContentData, iLength]()mutable {
        BYTE* volatile p2 = pContentData;
        GetInstance()->GetTcpClient()->DoReceiveCallBack(true, pContentData, iLength);
        SAFE_DELETE(p2);
}
查看此段落的汇编代码如下:它把临时变量p2存在栈上esp+2Ch+p2处。届时从此而不是dword ptr[edi]处取回p2的值。
一例“msvc编译器O2优化导致的崩溃”的分析_第2张图片

5.关于volatile和指针


若代码改为
volatile BYTE* p2 = pContentData;
也不会崩溃,但是看汇编,它依然没有把p2放在栈上,而是放在ebx里。具体原因参见https://blog.csdn.net/turkeyzhou/article/details/8953911
其实这样改也是没有解决隐患的。
一例“msvc编译器O2优化导致的崩溃”的分析_第3张图片

你可能感兴趣的:(逆向,visualstudio,汇编)