【QT 定位程序异常结束位置】windows环境实现程序异常结束定位

内容预览

1.在windows环境、MSVC编译器下,定位QT程序异常结束位置。
2.在windows环境、MINGW编译器下,定位QT程序异常结束位置。
3.适配MSVC、MINGW编译器的异常定位源码分析

QT程序异常结束位置定位

在windows下对QT程序异常结束位置定位的方法有很多如,Google的breakpad,或者是windows自带的api。

一、MSVC编译器

DbgHelp方法实现

#include "mainwindow.h"
#include <QApplication>
#include <QDebug>
#ifdef Q_CC_MSVC
#include <windows.h>
#include <Dbghelp.h>
#include <iostream>
#include <vector>
#include <tchar.h>
using namespace std;

#pragma comment(lib, "Dbghelp.lib")
namespace NSDumpFile
{
    void CreateDumpFile(LPCWSTR lpstrDumpFilePathName, EXCEPTION_POINTERS *pException)
    {
        // 创建Dump文件
        //
        HANDLE hDumpFile = CreateFile(lpstrDumpFilePathName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);


        // Dump信息
        //
        MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
        dumpInfo.ExceptionPointers = pException;
        dumpInfo.ThreadId = GetCurrentThreadId();
        dumpInfo.ClientPointers = TRUE;


        // 写入Dump文件内容
        MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);


        CloseHandle(hDumpFile);
    }


    LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter)
    {
        return NULL;
    }


    BOOL PreventSetUnhandledExceptionFilter()
    {
        HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll"));
        if (hKernel32 == NULL)
            return FALSE;


        void *pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter");
        if (pOrgEntry == NULL)
            return FALSE;


        unsigned char newJump[100];
        DWORD dwOrgEntryAddr = (DWORD)pOrgEntry;
        dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far


        void *pNewFunc = &MyDummySetUnhandledExceptionFilter;
        DWORD dwNewEntryAddr = (DWORD)pNewFunc;
        DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;


        newJump[0] = 0xE9;  // JMP absolute
        memcpy(&newJump[1], &dwRelativeAddr, sizeof(pNewFunc));
        SIZE_T bytesWritten;
        BOOL bRet = WriteProcessMemory(GetCurrentProcess(), pOrgEntry, newJump, sizeof(pNewFunc) + 1, &bytesWritten);
        return bRet;
    }

    LONG WINAPI UnhandledExceptionFilterEx(struct _EXCEPTION_POINTERS *pException)
    {
        TCHAR szMbsFile[MAX_PATH] = { 0 };
        ::GetModuleFileName(NULL, szMbsFile, MAX_PATH);
        TCHAR* pFind = _tcsrchr(szMbsFile, '\\');
        if (pFind)
        {
            *(pFind + 1) = 0;
            _tcscat(szMbsFile, _T("CrashDumpFile.dmp"));
            CreateDumpFile(szMbsFile, pException);
        }


        // TODO: MiniDumpWriteDump
        FatalAppExit(-1, _T("Fatal Error"));
        return EXCEPTION_CONTINUE_SEARCH;
    }


    void RunCrashHandler()
    {
        SetUnhandledExceptionFilter(UnhandledExceptionFilterEx);
        PreventSetUnhandledExceptionFilter();
    }
};
#define DeclareDumpFile() NSDumpFile::RunCrashHandler();
#endif
void crash()
{
    QString *p = NULL;
    p->clear();
}
int main(int argc, char *argv[])
{
    DeclareDumpFile()
    QApplication a(argc, argv);

    crash();

    return a.exec();
}

使用方法
1.1 运行程序后因跑到下图的异常代码中导致程序将异常崩溃
在这里插入图片描述
在这里插入图片描述
1.2 找到可执行文件目录,并双击CrashDumpFile.dmp
在这里插入图片描述
注意:

1.使用DMP文件和pdb文件调试时DMP、exe和pdb三个文件要保持版本一致。也就是说,如果你运行exe文件,程序崩溃,生成DMP文件后,此时你再重新编译程序,重新生成了exe文件和pdb文件。即使源码没有发生任何改动,你双击DMP文件也是没有办法再定位到源代码中的错误了。所以你得及时把exe文件和pdb文件做好备份。

2.有些时候即使程序崩溃了也是没有办法生成DMP文件的,比如有时数组越界导致程序崩溃的时候。所以这个方法也不能保证一定能找到程序崩溃的原因,所以大家得掌握更多的调试手段。

1.3 点击“使用 仅限本机 进行调试” 按钮在这里插入图片描述
1.4 查看异常退出位置在这里插入图片描述

二、MINGW编译器

参考文章: qt mingw 创建dump 并查找crash 出错行
tlhelp32方法实现
ccrashstack.h

#ifndef CCRASHSTACK_H
#define CCRASHSTACK_H

#include <QApplication>
#ifdef Q_CC_MINGW

#include <windows.h>
#include <QString>
#include <QDir>

class CCrashStack
{

private:
    PEXCEPTION_POINTERS m_pException;

private:
    QString GetModuleByRetAddr(PBYTE Ret_Addr, PBYTE & Module_Addr);
    QString GetCallStack(PEXCEPTION_POINTERS pException);
    QString GetVersionStr();
    bool GetHardwareInaformation(QString &graphics_card, QString &sound_deivce);

public:
    CCrashStack(PEXCEPTION_POINTERS pException);

    QString GetExceptionInfo();
};
#endif
#endif //CCRASHSTACK_H

ccrashstack.cpp

#include <QApplication>
#ifdef Q_CC_MINGW

#include "ccrashstack.h"
#include <tlhelp32.h>
#include <stdio.h>

#define _WIN32_DCOM
#include <comdef.h>
#include <wbemidl.h>
#include <QFile>
#include <QDir>

//#include<base/constants.h>
#include "qdebug.h"


CCrashStack::CCrashStack(PEXCEPTION_POINTERS pException)
{
    m_pException = pException;
}


QString CCrashStack::GetModuleByRetAddr(PBYTE Ret_Addr, PBYTE & Module_Addr)
{
    MODULEENTRY32   M = {sizeof(M)};
    HANDLE  hSnapshot;

    wchar_t Module_Name[MAX_PATH] = {0};

    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);

    if ((hSnapshot != INVALID_HANDLE_VALUE) &&
            Module32First(hSnapshot, &M))
    {
            do
            {
                    if (DWORD(Ret_Addr - M.modBaseAddr) < M.modBaseSize)
                    {
                            lstrcpyn(Module_Name, M.szExePath, MAX_PATH);
                            Module_Addr = M.modBaseAddr;
                            break;
                    }
            } while (Module32Next(hSnapshot, &M));
    }

    CloseHandle(hSnapshot);

    QString sRet = QString::fromWCharArray(Module_Name);
    return sRet;
}

QString CCrashStack::GetCallStack(PEXCEPTION_POINTERS pException)
{
        PBYTE   Module_Addr_1;
        char bufer[256]={0};
        QString sRet;

        typedef struct STACK
        {
                STACK * Ebp;
                PBYTE   Ret_Addr;
                DWORD   Param[0];
        } STACK, * PSTACK;

        STACK   Stack = {0, 0};
        PSTACK  Ebp;

        if (pException)     //fake frame for exception address
        {
                Stack.Ebp = (PSTACK)pException->ContextRecord->Ebp;
                Stack.Ret_Addr = (PBYTE)pException->ExceptionRecord->ExceptionAddress;
                Ebp = &Stack;
        }
        else
        {
                Ebp = (PSTACK)&pException - 1;  //frame addr of Get_Call_Stack()

                // Skip frame of Get_Call_Stack().
                if (!IsBadReadPtr(Ebp, sizeof(PSTACK)))
                        Ebp = Ebp->Ebp;     //caller ebp
        }

        // Break trace on wrong stack frame.
        for (; !IsBadReadPtr(Ebp, sizeof(PSTACK)) && !IsBadCodePtr(FARPROC(Ebp->Ret_Addr));
                 Ebp = Ebp->Ebp)
        {
                // If module with Ebp->Ret_Addr found.
                memset(bufer,0, sizeof(0));
                sprintf(bufer, "\n%08X  ", (unsigned int)Ebp->Ret_Addr);
                sRet.append(bufer);

                QString moduleName = this->GetModuleByRetAddr(Ebp->Ret_Addr, Module_Addr_1) ;
                if (moduleName.length() > 0)
                {
                    sRet.append(moduleName);
                }
        }

        return sRet;
} //Get_Call_Stack

QString CCrashStack::GetVersionStr()
{
        OSVERSIONINFOEX V = {sizeof(OSVERSIONINFOEX)};  //EX for NT 5.0 and later

        if (!GetVersionEx((POSVERSIONINFO)&V))
        {
                ZeroMemory(&V, sizeof(V));
                V.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
                GetVersionEx((POSVERSIONINFO)&V);
        }

        if (V.dwPlatformId != VER_PLATFORM_WIN32_NT)
                V.dwBuildNumber = LOWORD(V.dwBuildNumber);  //for 9x HIWORD(dwBuildNumber) = 0x04xx

        QString sRet;
        sRet.append(QString("Windows:  %1.%2.%3, SP %4.%5, Product Type %6\n")
                .arg(V.dwMajorVersion).arg(V.dwMinorVersion).arg(V.dwBuildNumber)
                .arg(V.wServicePackMajor).arg(V.wServicePackMinor).arg(V.wProductType));

//        QString graphics_module = "GraphicsCard: ";
//        QString sound_module = "SoundDevice: ";
//        GetHardwareInaformation(graphics_module, sound_module);
//        sRet.append(graphics_module);
//        sRet.append(sound_module);

        return sRet;
}

QString CCrashStack::GetExceptionInfo()
{
        WCHAR       Module_Name[MAX_PATH];
        PBYTE       Module_Addr;

        QString sRet;
        char buffer[512]={0};

        QString sTmp = GetVersionStr();
        sRet.append(sTmp);
        sRet.append("Process:  ");

        GetModuleFileName(NULL, Module_Name, MAX_PATH);
        sRet.append(QString::fromWCharArray(Module_Name));
        sRet.append("\n");

        // If exception occurred.
        if (m_pException)
        {
                EXCEPTION_RECORD &  E = *m_pException->ExceptionRecord;
                CONTEXT &           C = *m_pException->ContextRecord;

                memset(buffer, 0, sizeof(buffer));
                sprintf(buffer, "Exception Addr:  %08X  ", (int)E.ExceptionAddress);
                sRet.append(buffer);
                // If module with E.ExceptionAddress found - save its path and date.
                QString module = GetModuleByRetAddr((PBYTE)E.ExceptionAddress, Module_Addr);
                if (module.length() > 0)
                {
                    sRet.append(" Module: ");

                    sRet.append(module);
                }

                memset(buffer, 0, sizeof(buffer));
                sprintf(buffer, "\nException Code:  %08X\n", (int)E.ExceptionCode);
                sRet.append(buffer);

                if (E.ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
                {
                        // Access violation type - Write/Read.
                    memset(buffer, 0, sizeof(buffer));
                    sprintf(buffer,"%s Address:  %08X\n",
                            (E.ExceptionInformation[0]) ? "Write" : "Read", (int)E.ExceptionInformation[1]);
                    sRet.append(buffer);
                }


                sRet.append("Instruction: ");
                for (int i = 0; i < 16; i++)
                {
                    memset(buffer, 0, sizeof(buffer));
                    sprintf(buffer, " %02X",  PBYTE(E.ExceptionAddress)[i]);
                    sRet.append(buffer);
                }

                sRet.append("\nRegisters: ");

                memset(buffer, 0, sizeof(buffer));
                sprintf(buffer, "\nEAX: %08X  EBX: %08X  ECX: %08X  EDX: %08X",  (unsigned int)C.Eax,(unsigned int) C.Ebx, (unsigned int)C.Ecx, (unsigned int)C.Edx);
                sRet.append(buffer);

                memset(buffer, 0, sizeof(buffer));
                sprintf(buffer, "\nESI: %08X  EDI: %08X  ESP: %08X  EBP: %08X", (unsigned int)C.Esi, (unsigned int)C.Edi, (unsigned int)C.Esp, (unsigned int)C.Ebp);
                sRet.append(buffer);

                memset(buffer, 0, sizeof(buffer));
                sprintf(buffer, "\nEIP: %08X  EFlags: %08X", (unsigned int)C.Eip,(unsigned int) C.EFlags);
                sRet.append(buffer);

        } //if (pException)

        sRet.append("\nCall Stack:");
        QString sCallstack = this->GetCallStack(m_pException);
        sRet.append(sCallstack);

        return sRet;
}
#endif

main.cpp

#include "mainwindow.h"
#include <QApplication>
#include <QDebug>
#include "ccrashstack.h"
void crash()
{
    QString *p = NULL;
    p->clear();
}
int main(int argc, char *argv[])
{
    SetUnhandledExceptionFilter(callback);
    QApplication a(argc, argv);
    
    crash();
    
    return a.exec();
}

使用方法
2.1 运行程序后因跑到下图的异常代码中导致程序将异常崩溃*
在这里插入图片描述
2.2 找到可执行文件目录,并进入crashes文件夹并打开后缀为.log的日志文件,并记录下下图红框框起来的地方
在这里插入图片描述2.3用addr2line 来查找 崩溃点
以我的QT5.9.7版本为例,addr2line.exe文件在QT安装路径下的\Tools\mingw530_32\bin 文件夹中,如下图所示。
在这里插入图片描述2.3.1 打开MinGW的命令输入窗口即如下图所示窗口
在这里插入图片描述
2.3.2 CD到addr2line 的文件夹下
在这里插入图片描述
2.3.3 执行addr2line指令

addr2line.exe -f -e C:\Users\jbyyy\Desktop\debug\DumpTest.exe 00409F41   00409927  004019BD  00401A01  00403BC5  
addr2line.exe -f -e '你的可执行文件路径' '输入 所有2.2所示图例红框所标的地址'

下图红框为定位到的程序异常结束位置
在这里插入图片描述

三、异常定位源码分析

程序适配MSVC、MINGW编译器。
将提供源码的crashManager文件夹移植到你项目的根目录下,并在你程序项目的pro文件夹中加入 include(crashManager/DumpManager.pri)即可。
使用方法
在mian.cpp中添加头文件crashManager.h,并执行setCrashManager()函数即可
在这里插入图片描述
关键代码展示

#ifdef Q_OS_UNIX
#include <iostream>
#include "linuxCrash/handler/QBreakpadHandler.h"
#endif

void setCrashManager()
{
    && _MSC_VER > 1500
    #if defined(Q_OS_UNIX)
    QCoreApplication::setApplicationName("AppName");
    QCoreApplication::setApplicationVersion("1.0");
    QCoreApplication::setOrganizationName("OrgName");
    QCoreApplication::setOrganizationDomain("name.org");
    QBreakpadInstance.setDumpPath("crashes");
    #elif defined(Q_CC_MSVC)
         DeclareDumpFile()
    #elif defined(Q_CC_MINGW)
        SetUnhandledExceptionFilter(callback);
    #endif
}

源码下载

支持windows环境,MSVC、MINGW编译器源码
需要参考文献:【QT 定位程序异常结束位置】windows环境实现程序异常结束定位

支持ubuntu、arm环境源码
需要参考文献:【QT 定位程序异常结束位置】arm环境使用 breakpad 实现程序异常崩溃定位

支持windows、ubuntu环境源码

  • 7
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论
回答: 当在Qt编译好之后运行程序时提示程序异常结束和crash的问题可能有几种原因。一种可能性是在debug模式下使用了release版本的库或者在release模式下使用了debug版本的库。这种情况下,需要确保使用相同模式的库进行编译和运行。另一种可能性是在.pro文件中对lib库的路径指明错误,即使用到了除了Qt以外的其他库,但是编译程序时找不到该库。在这种情况下,需要检查.pro文件中库路径的正确性。还有一种可能性是在使用QStandardItemModel填写表格内容时,还没有使用model.setItem(...)函数创建单元格,就使用model.item(i,j)->setText(s)往单元格内填写内容。这样会导致访问空指针而引发crash。所以在填写表格内容之前,需要先使用setItem函数创建相应的单元格。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [在Qt编译好之后运行程序时提示:程序异常结束。The process was ended forcefully. ....exe crashed.](https://blog.csdn.net/weixin_45824067/article/details/130855665)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [QT 提示 程序异常结束 并且 crash的解决办法](https://blog.csdn.net/antony_z/article/details/120462824)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [QT 程序异常结束,crashed](https://blog.csdn.net/chenben/article/details/124190614)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jbyyy、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值