windows上bug崩溃定位分析(Qt或者VS)

任何情况下,都不能保证自己写的代码不会发生崩溃,崩溃不可怕,可怕的是无法定位哪里崩溃,特别是客户那边崩溃,开发者这边不崩溃,问题陷入僵局。自从有了下面这个神奇的代码,再也不怕了。

以下代码亲自测试没问题。

1. 如果是在VC++中,那么只需要将下列2个文件直接添加到工程中.发布程序后,如果发生了崩溃,那么在崩溃的文件中就已经记录了“哪个文件的哪一行发生了崩溃”。

再也不用像以前那样要在项目属性中配置,要生成.map、.cod等等文件,然后计算偏移地址来分析了。

windows上bug崩溃定位分析(Qt或者VS)_第1张图片

2. 如果是在Qt+MinGW,那么只需要将下列2个文件直接添加到工程中,然后在.pro工程文件中添加以下几行配置:

LIBS += -lDbgHelp

#表示在release下可以生成调试信息
QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_CFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO

这样,编译release后的文件就带有调试信息,当运行程序后,发生崩溃就会产生崩溃日志,拿到崩溃日志后就可以分析了,只需要执行如下命令:

addr2line.exe -f -e 你的应用程序 崩溃地址 32、64位的分别使用对应的addr2line.exe工具即可,工具在:D:\Qt\Qt5.12.3\Tools\mingw730_32\bin、D:\Qt\Qt5.12.3\Tools\mingw730_64\bin目录下,例如你的应用  TestDmp.exe,崩溃地址是0000000000402E20,则

addr2line.exe -f -e TestDmp.exe 0000000000402E20

3. 补充一些:

3.1)在Qt+MinGW情况下,虽然能够生成崩溃文件,记录了崩溃地址,但是使用addr2line.exe工具时,发现竟然无法知道是哪行发生崩溃,甚至无法知道是哪个文件,此时仍然可以有挽救的方式:

1. 首先在崩溃的记录日志中知道是哪个库崩溃,例如本例子中是在TestDmp.exe中发生了崩溃。
2. 打开Qt的命令行工具,注意32和64位的区别,并cd到崩溃库所在目录。
3. 在命令行中输入以下命令,表示将引起崩溃的库导出为汇编代码

objdump -S TestDmp.exe > TestDmp.exe.asm

4. 最后就可以看到了汇编代码了

windows上bug崩溃定位分析(Qt或者VS)_第2张图片

 3.2)在崩溃记录的日志中,拿到崩溃的地址,然后在刚生成的那个asm文件中搜索,就可以看到了,就比如上面截图中的36337行(这个截图和2里面的不一致,是因为3是后来补充的)

3.3)要想能够在导出的asm文件中显示汇编代码和源代码,需要把.pro中的配置信息更换为如下:

如果不更改的话,也能显示很详细(只不过看到的区别就是:void TestForm::on_btnTest_clicked()显示的不会这么友好,而是显示为:

#表示在release下可以生成调试信息
#QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO
#QMAKE_CFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
#QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO

QMAKE_CXXFLAGS_RELEASE += -g
QMAKE_CFLAGS_RELEASE += -g
#禁止优化
QMAKE_CFLAGS_RELEASE -= -O2
QMAKE_CXXFLAGS_RELEASE -= -O2
#release在最后link时默认有"-s”参数,表示"忽略输出文件中的所有符号信息",因此要去掉该参数
QMAKE_LFLAGS_RELEASE = -mthreads -W

4. 补充二:

还有一个更加方便的方式,就是直接生成dump文件,然后利用WinDbg工具去分析即可;WinDbg常用的一个命令就是:这样就可以直接分析dump文件

!analyze -v

windows上bug崩溃定位分析(Qt或者VS)_第3张图片

生成dump的文件,只需要把 CMSExceptionHandler::HandleException改为如下,这样其他代码都可以删掉了:

LONG CMSExceptionHandler::HandleExceptionMinDump(LPEXCEPTION_POINTERS pExceptionInfo)
{
    HANDLE hDumpFile = CreateFile(m_szLogFileName,
                                  GENERIC_WRITE,
                                  0,
                                  0,
                                  OPEN_ALWAYS,
                                  FILE_FLAG_WRITE_THROUGH,
                                  0);
    if (hDumpFile != INVALID_HANDLE_VALUE)
    {
        MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
        dumpInfo.ThreadId = GetCurrentThreadId();
        dumpInfo.ExceptionPointers = pExceptionInfo;
        dumpInfo.ClientPointers = TRUE;

        // 创建Dump文件
        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, 
                          MiniDumpWithFullMemory, pExceptionInfo ? &dumpInfo : nullptr, nullptr, nullptr);
        CloseHandle(hDumpFile);
    }
    return EXCEPTION_EXECUTE_HANDLER;
}

代码文件如下:

头文件 MSExceptionHandler.h

#pragma once

#include 
#include 

class CMSExceptionHandler
{
public:
    CMSExceptionHandler();
    ~CMSExceptionHandler();

private:
    static LONG ExceptionFilter(LPEXCEPTION_POINTERS e);

    // The main function to handle exception
    LONG HandleException(LPEXCEPTION_POINTERS pExceptionInfo);

    void GenerateExceptionReport(LPEXCEPTION_POINTERS pExceptionInfo);

    const char* GetExceptionString(DWORD dwCode);

    // Work through the stack upwards to get the entire call stack
    void TraceCallStack(CONTEXT* pContext);

    int PrintTraceLog(const char * format, ...);

private:
    static LPTOP_LEVEL_EXCEPTION_FILTER m_previousFilter;

    // Machine type matters when trace the call stack (StackWalk64)
    DWORD m_dwMachineType;
    HANDLE m_hReportFile;
    TCHAR m_szLogFileName[MAX_PATH];
};

cpp文件MSExceptionHandler.cpp

#include "MSExceptionHandler.h"

#include 
#include 
#include 
#include 

#pragma warning(push)
#pragma warning(disable : 4091)
#include 
#pragma warning(pop)

#pragma comment(lib, "Dbghelp.lib")

static CMSExceptionHandler g_MSExceptionHandler;

LPTOP_LEVEL_EXCEPTION_FILTER CMSExceptionHandler::m_previousFilter;

static void GetNowTime(struct tm& nowTime)
{
    memset(&nowTime, 0, sizeof(struct tm));
    time_t t = time(NULL);
    struct tm* pTime = localtime(&t);
    if (pTime)
    {
        nowTime.tm_sec = pTime->tm_sec;
        nowTime.tm_min = pTime->tm_min;
        nowTime.tm_hour = pTime->tm_hour;
        nowTime.tm_mday = pTime->tm_mday;
        nowTime.tm_mon = pTime->tm_mon;
        nowTime.tm_year = pTime->tm_year;
        nowTime.tm_wday = pTime->tm_wday;
        nowTime.tm_yday = pTime->tm_yday;
        nowTime.tm_isdst = pTime->tm_isdst;
    }
}

static char* TrimString(char* psz)
{
    char szTmp[512] = { 0 };
    char* pszDot = strrchr(psz, '\\');
    if (pszDot)
    {
        pszDot++;   // Advance past the '\\'
        strcpy(szTmp, pszDot);
        ZeroMemory(psz, strlen(psz));
        strcpy(psz, szTmp);
    }
    return psz;
}

CMSExceptionHandler::CMSExceptionHandler()
{
    m_hReportFile = NULL;

    m_previousFilter = SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ExceptionFilter);

    // Get machine type
    m_dwMachineType = 0;
    TCHAR wszProcessor[256] = { 0 };
    _tcscpy(wszProcessor, ::_tgetenv(_T("PROCESSOR_ARCHITECTURE")));
    if (_tcslen(wszProcessor)>0)
    {
        if ((!_tcsicmp(_T("EM64T"), wszProcessor)) || !_tcsicmp(_T("AMD64"), wszProcessor))
        {
            m_dwMachineType = IMAGE_FILE_MACHINE_AMD64;
        }
        else if (!_tcsicmp(_T("x86"), wszProcessor))
        {
            m_dwMachineType = IMAGE_FILE_MACHINE_I386;
        }
    }

    // Figure out what the report file will be named, and store it away
    GetModuleFileName(0, m_szLogFileName, MAX_PATH);
    TCHAR szLogFile[MAX_PATH] = _T("");
    // Look for the '.' before the "EXE" extension.  Replace the extension
    // with "RPT"
    PTSTR pszDot = _tcsrchr(m_szLogFileName, _T('\\'));
    if (pszDot)
    {
        pszDot++;   // Advance past the '\\'
        *pszDot = 0;
        _tcscpy(szLogFile, m_szLogFileName);
    }
    TCHAR szTime[125] = _T("debug.rpt");
    struct tm nowTime;
    GetNowTime(nowTime);
    _stprintf(szTime, _T("debug-%04d%02d%02d%02d%02d%02d.RPT"), nowTime.tm_year + 1900,
        nowTime.tm_mon + 1, nowTime.tm_mday, nowTime.tm_hour,
        nowTime.tm_min, nowTime.tm_sec);
    _tcscat(szLogFile, szTime);
    _tcscpy(m_szLogFileName, szLogFile);
}

CMSExceptionHandler::~CMSExceptionHandler()
{
    SetUnhandledExceptionFilter(m_previousFilter);
}

LONG CMSExceptionHandler::ExceptionFilter(LPEXCEPTION_POINTERS e)
{
    return g_MSExceptionHandler.HandleException(e);
}

LONG CMSExceptionHandler::HandleException(LPEXCEPTION_POINTERS pExceptionInfo)
{
    HANDLE hProcess = INVALID_HANDLE_VALUE;

    // Initializes the symbol handler
    if (!SymInitialize(GetCurrentProcess(), NULL, TRUE))
    {
        SymCleanup(hProcess);
        return EXCEPTION_EXECUTE_HANDLER;
    }

    m_hReportFile = CreateFile(m_szLogFileName,
        GENERIC_WRITE,
        0,
        0,
        OPEN_ALWAYS,
        FILE_FLAG_WRITE_THROUGH,
        0);

    if (m_hReportFile != INVALID_HANDLE_VALUE && NULL != m_hReportFile)
    {
        SetFilePointer(m_hReportFile, 0, 0, FILE_END);

        GenerateExceptionReport(pExceptionInfo);

        // Work through the call stack upwards.
        TraceCallStack(pExceptionInfo->ContextRecord);

        CloseHandle(m_hReportFile);
        m_hReportFile = 0;
    }

    SymCleanup(hProcess);

    return(EXCEPTION_EXECUTE_HANDLER);

    /*if (m_previousFilter)
    return m_previousFilter(pExceptionInfo);
    else
    return EXCEPTION_CONTINUE_SEARCH;*/
}

void CMSExceptionHandler::GenerateExceptionReport(LPEXCEPTION_POINTERS pExceptionInfo)
{
    // Start out with a banner
    PrintTraceLog("//=====================================================\n");

    struct tm nowTime;
    GetNowTime(nowTime);
    PrintTraceLog("Crash Last Time: %04d-%02d-%02d %02d:%02d:%02d\n", nowTime.tm_year + 1900,
        nowTime.tm_mon + 1, nowTime.tm_mday, nowTime.tm_hour,
        nowTime.tm_min, nowTime.tm_sec);

    PEXCEPTION_RECORD pExceptionRecord = pExceptionInfo->ExceptionRecord;

    // First print information about the type of fault
    PrintTraceLog("Exception code: %08X %s\n",
        pExceptionRecord->ExceptionCode,
        GetExceptionString(pExceptionRecord->ExceptionCode));
#if defined(_WIN64)
    PrintTraceLog("Fault address: %016llX\n",
        pExceptionRecord->ExceptionAddress);
#else
    PrintTraceLog("Fault address: %08X\n",
        pExceptionRecord->ExceptionAddress);
#endif
}

const char* CMSExceptionHandler::GetExceptionString(DWORD dwCode)
{
#define EXCEPTION( x ) case EXCEPTION_##x: return (#x);

    switch (dwCode)
    {
        EXCEPTION(ACCESS_VIOLATION)
            EXCEPTION(DATATYPE_MISALIGNMENT)
            EXCEPTION(BREAKPOINT)
            EXCEPTION(SINGLE_STEP)
            EXCEPTION(ARRAY_BOUNDS_EXCEEDED)
            EXCEPTION(FLT_DENORMAL_OPERAND)
            EXCEPTION(FLT_DIVIDE_BY_ZERO)
            EXCEPTION(FLT_INEXACT_RESULT)
            EXCEPTION(FLT_INVALID_OPERATION)
            EXCEPTION(FLT_OVERFLOW)
            EXCEPTION(FLT_STACK_CHECK)
            EXCEPTION(FLT_UNDERFLOW)
            EXCEPTION(INT_DIVIDE_BY_ZERO)
            EXCEPTION(INT_OVERFLOW)
            EXCEPTION(PRIV_INSTRUCTION)
            EXCEPTION(IN_PAGE_ERROR)
            EXCEPTION(ILLEGAL_INSTRUCTION)
            EXCEPTION(NONCONTINUABLE_EXCEPTION)
            EXCEPTION(STACK_OVERFLOW)
            EXCEPTION(INVALID_DISPOSITION)
            EXCEPTION(GUARD_PAGE)
            EXCEPTION(INVALID_HANDLE)
    }

    // If not one of the "known" exceptions, try to get the string
    // from NTDLL.DLL's message table.

    static char szBuffer[512] = { 0 };

    FormatMessageA(FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE,
        GetModuleHandle(_T("NTDLL.DLL")),
        dwCode, 0, szBuffer, sizeof(szBuffer), 0);

    return szBuffer;
}

// Work through the stack to get the entire call stack
void CMSExceptionHandler::TraceCallStack(CONTEXT* pContext)
{
    // Initialize stack frame
    STACKFRAME64 sf;
    memset(&sf, 0, sizeof(STACKFRAME));

#if defined(_WIN64)
    sf.AddrPC.Offset = pContext->Rip;
    sf.AddrStack.Offset = pContext->Rsp;
    sf.AddrFrame.Offset = pContext->Rbp;
#elif defined(WIN32)
    sf.AddrPC.Offset = pContext->Eip;
    sf.AddrStack.Offset = pContext->Esp;
    sf.AddrFrame.Offset = pContext->Ebp;
#endif
    sf.AddrPC.Mode = AddrModeFlat;
    sf.AddrStack.Mode = AddrModeFlat;
    sf.AddrFrame.Mode = AddrModeFlat;


    PrintTraceLog("\nRegisters:\n");

#if defined(_WIN64)
    PrintTraceLog("EAX:%016llX\nEBX:%016llX\nECX:%016llX\nEDX:%016llX\nESI:%016llX\nEDI:%016llX\n",
        pContext->Rax, pContext->Rbx, pContext->Rcx, pContext->Rdx, pContext->Rsi, pContext->Rdi);
    PrintTraceLog("CS:EIP:%04X:%016llX\n", pContext->SegCs, sf.AddrPC.Offset);
    PrintTraceLog("SS:ESP:%04X:%016llX  EBP:%016llX\n",
        pContext->SegSs, sf.AddrStack.Offset, sf.AddrFrame.Offset);
#else
    PrintTraceLog("EAX:%08X\nEBX:%08X\nECX:%08X\nEDX:%08X\nESI:%08X\nEDI:%08X\n",
        pContext->Eax, pContext->Ebx, pContext->Ecx, pContext->Edx, pContext->Esi, pContext->Edi);
    PrintTraceLog("CS:EIP:%04X:%08llX\n", pContext->SegCs, sf.AddrPC.Offset);
    PrintTraceLog("SS:ESP:%04X:%08llX  EBP:%08llX\n",
        pContext->SegSs, sf.AddrStack.Offset, sf.AddrFrame.Offset);
#endif
    PrintTraceLog("DS:%04X  ES:%04X  FS:%04X  GS:%04X\n",
        pContext->SegDs, pContext->SegEs, pContext->SegFs, pContext->SegGs);
    PrintTraceLog("Flags:%08X\n", pContext->EFlags);

    if (0 == m_dwMachineType)
        return;

    PrintTraceLog("\nCall stack:\n");

#if defined(_WIN64)
    PrintTraceLog("Address           Line    Function    File    Module\n");
#else
    PrintTraceLog("Address   Line    Function    File    Module\n");
#endif

    // Walk through the stack frames.
    int CALLSTACK_DEPTH = 0;
    HANDLE hProcess = GetCurrentProcess();
    HANDLE hThread = GetCurrentThread();
    while (StackWalk64(m_dwMachineType, hProcess, hThread, &sf, pContext, 0, SymFunctionTableAccess64, SymGetModuleBase64, 0))
    {
        if (sf.AddrFrame.Offset == 0 || CALLSTACK_DEPTH >= 10)
            break;
        CALLSTACK_DEPTH++;

        // 1. Get function name at the address
        const int nBuffSize = (sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR) + sizeof(ULONG64) - 1) / sizeof(ULONG64);
        ULONG64 symbolBuffer[nBuffSize];
        PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)symbolBuffer;

        pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
        pSymbol->MaxNameLen = MAX_SYM_NAME;

        DWORD64 dwAddress = sf.AddrPC.Offset;
        DWORD dwLineNumber = 0;
        char szFuncName[260] = { 0 };
        char szFileName[260] = { 0 };
        char szModuleName[260] = { 0 };

        DWORD64 moduleBase = SymGetModuleBase64(hProcess, sf.AddrPC.Offset);
        if (moduleBase && GetModuleFileNameA((HINSTANCE)moduleBase, szModuleName, 260))
        {
            TrimString(szModuleName);
        }
        if (strlen(szModuleName) <= 0) strcpy(szModuleName, "Unknow");

        DWORD64 dwSymDisplacement = 0;
        if (SymFromAddr(hProcess, sf.AddrPC.Offset, &dwSymDisplacement, pSymbol))
        {
            std::string str(pSymbol->Name);
            strcpy(szFuncName, str.c_str());
        }
        if (strlen(szFuncName) <= 0) strcpy(szFuncName, "Unknow");

        //2. get line and file name at the address
        IMAGEHLP_LINE64 lineInfo = { sizeof(IMAGEHLP_LINE64) };
        DWORD dwLineDisplacement = 0;

        if (SymGetLineFromAddr64(hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo))
        {
            std::string str(lineInfo.FileName);
            strcpy(szFileName, str.c_str());
            TrimString(szFileName);
            dwLineNumber = lineInfo.LineNumber;
        }
        if (strlen(szFileName) <= 0) strcpy(szFileName, "Unknow");

        // Call stack stored
#if defined(_WIN64)
        PrintTraceLog("%016llX  %-8ld%s    %s    %s\n",
            dwAddress, dwLineNumber, szFuncName, szFileName, szModuleName);
#else
        PrintTraceLog("%08llX  %-8d%s    %s    %s\n",
            dwAddress, dwLineNumber, szFuncName, szFileName, szModuleName);
#endif
    }
}

int CMSExceptionHandler::PrintTraceLog(const char * format, ...)
{
    char szBuff[1024] = "";
    int retValue;
    DWORD cbWritten;
    va_list argptr;

    va_start(argptr, format);
    retValue = vsprintf(szBuff, format, argptr);
    va_end(argptr);

    WriteFile(m_hReportFile, szBuff, retValue * sizeof(char), &cbWritten, 0);

    return retValue;
}

你可能感兴趣的:(Qt,VC++,bug,c++,qt,windows)