Qt下生成pdb文件,并在exe崩溃时生成dmp文件,且由dmp查询崩溃原因

1.前言

对于大多数人来说,编写的程序在生产现场运行时崩溃是很正常,必定会发生的事情。(至少对于我来说是这样😂)。那么,一旦这样的事情发生了,我们该如何确定是程序的代码的哪个位置出了问题呢?
其实在Windows平台下,我们借助dump文件,就可以在程序崩溃时,记录好程序崩溃时的信息。事后,可能拿着这个dump文件来进行分析,确定程序是运行到哪一行代码崩溃的。
在Qt下,操作步骤如下。

2.前期准备

2.1.选择Profile编译模式,生成符号文件pdb

符号文件可以简单理解成一个字典(类似QMap)对象,里面存储了你的代码的符号信息、行号映射及其他信息。
在使用QtCreator编译Qt程序时,选择profile编译即可自动生成pdb文件。在此模式下,既可享受release的运行速度,又可以享受debug的符号查找。发布的程序可以正常运行在生产环境,尤其是适合在一些实时性要求高的项目中。
在这里插入图片描述
选择了Profile模式后,编译时,就会生成与程序同名的pdb文件。
在这里插入图片描述

2.2.在exe崩溃时生成dmp文件

有符号文件pdb还不够,我们还需要程序运行时的调用堆栈。这个堆栈信息可以通过一些系统函数,保存在dump文件中。
参考:
https://gongjianbo1992.blog.csdn.net/article/details/113791423
https://blog.csdn.net/a844651990/article/details/85225273
https://www.lmlphp.com/user/62390/article/item/902039/

2.2.1.声明函数

DumpHelper.h

//#ifndef DUMPHELPER_H
//#define DUMPHELPER_H

#pragma once

#include <QSystemSemaphore>
#include <QDir>
#include <QDateTime>
#include <QDebug>

#include <tchar.h>
#include <Windows.h>
#include <DbgHelp.h>

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "DbgHelp.Lib")

int GenerateMiniDump(PEXCEPTION_POINTERS pExceptionPointers)
{
    // 定义函数指针
    typedef BOOL(WINAPI * MiniDumpWriteDumpT)(
        HANDLE,
        DWORD,
        HANDLE,
        MINIDUMP_TYPE,
        PMINIDUMP_EXCEPTION_INFORMATION,
        PMINIDUMP_USER_STREAM_INFORMATION,
        PMINIDUMP_CALLBACK_INFORMATION
        );
    // 从 "DbgHelp.dll" 库中获取 "MiniDumpWriteDump" 函数
    MiniDumpWriteDumpT pfnMiniDumpWriteDump = NULL;
    HMODULE hDbgHelp = LoadLibrary(_T("DbgHelp.dll"));
    if (NULL == hDbgHelp)
    {
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    pfnMiniDumpWriteDump = (MiniDumpWriteDumpT)GetProcAddress(hDbgHelp, "MiniDumpWriteDump");

    if (NULL == pfnMiniDumpWriteDump)
    {
        FreeLibrary(hDbgHelp);
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    // 创建 dmp 文件件
    TCHAR szFileName[MAX_PATH] = { 0 };
    TCHAR szVersion[] = L"DumpFile";
    SYSTEMTIME stLocalTime;
    GetLocalTime(&stLocalTime);
    wsprintf(szFileName, L"%s-%04d%02d%02d-%02d%02d%02d.dmp",
        szVersion, stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay,
        stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond);
    HANDLE hDumpFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_WRITE | FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
    if (INVALID_HANDLE_VALUE == hDumpFile)
    {
        FreeLibrary(hDbgHelp);
        return EXCEPTION_CONTINUE_EXECUTION;
    }
    // 写入 dmp 文件
    MINIDUMP_EXCEPTION_INFORMATION expParam;
    expParam.ThreadId = GetCurrentThreadId();
    expParam.ExceptionPointers = pExceptionPointers;
    expParam.ClientPointers = FALSE;
    pfnMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
        hDumpFile, MiniDumpWithDataSegs, (pExceptionPointers ? &expParam : NULL), NULL, NULL);
    // 释放文件
    CloseHandle(hDumpFile);
    FreeLibrary(hDbgHelp);
    return EXCEPTION_EXECUTE_HANDLER;
}

LONG WINAPI ExceptionFilter(LPEXCEPTION_POINTERS lpExceptionInfo)
{
    // 这里做一些异常的过滤或提示
    if (IsDebuggerPresent()) {
        return EXCEPTION_CONTINUE_SEARCH;
    }
    return GenerateMiniDump(lpExceptionInfo);
}

long __stdcall errCallback(_EXCEPTION_POINTERS*  pException)
{
 
//     //用于崩溃重启
//    // 信号量的意义,把操作共享内存的代码锁住。因为有可能同时启动, 防止并发
//    QSystemSemaphore sema("DyError", 1, QSystemSemaphore::Open);
//    sema.acquire();


    QDir dir;
    dir.mkdir("./dumps");
    dir.cd("./dumps");

    /*
      ***保存数据代码***
    */

    QString fileName = dir.path() + "/" +
                       QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss.zzz") + ".dmp";

    LPCWSTR pFileName = (LPCWSTR)fileName.unicode();

    //创建 Dump 文件
    HANDLE hDumpFile = CreateFile(pFileName,
                                  GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    qDebug() << "create dumpFile:" << hDumpFile << INVALID_HANDLE_VALUE;
    if(hDumpFile != INVALID_HANDLE_VALUE)
    {
        //Dump信息
        MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
        dumpInfo.ExceptionPointers = pException;
        dumpInfo.ThreadId = GetCurrentThreadId();
        dumpInfo.ClientPointers = TRUE;
        
        // ::全局作用域符号
        // 写入Dump文件内容
        // DumpType这里仅仅保存普通信息。假如需要保存变量值,可以加上【MiniDumpWithFullMemory】
        // 参考https://learn.microsoft.com/en-us/windows/win32/api/minidumpapiset/ne-minidumpapiset-minidump_type
        ::MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal, &dumpInfo, NULL, NULL);
//        ::MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpNormal | MiniDumpWithFullMemory, &dumpInfo, NULL, NULL);
    }

//    delete  unimem;

//    qDebug() << "start application:" << QProcess::startDetached(qApp->applicationFilePath(), QStringList());//重启

//    qApp->quit();
    qApp->exit(-1);

    return EXCEPTION_EXECUTE_HANDLER;
}


//#endif // DUMPHELPER_H

2.2.2.调用函数

在main函数中注册

main.cpp

#include "mainwindow.h"

#include <QApplication>

#include "DumpHelper.h"

int main(int argc, char *argv[])
{
//    SetUnhandledExceptionFilter(ExceptionFilter); // dmp文件比较大
    SetUnhandledExceptionFilter(errCallback); // dmp文件比较小

    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

3.经由dmp文件查询崩溃原因

在我们写好程序,编译好,且发布了给客户时,一定要注意保存几个东西:此时的源码、此时的pdb文件。
在给到客户的程序发生了崩溃,产生了dmp文件后,我们需要把我们当时发布的pdb、客户发给我们的dmp文件、源码准备好,然后用vs打开,进行调试操作。

3.1.基本前提

能够查询溯源的前提是对软件发布时的东西做好了备份:源码、pdb。
下面以实例说明。
目前,有一个 versionTest 项目,经过编译后,发布了v1版本。内容如下:
在这里插入图片描述

并且,在发布时,我也备份了对应的源码、pdb文件
在这里插入图片描述

3.2.利用dump文件定位崩溃位置

假设此时,用户发现软件崩溃了,且在对应目录生成了dmp文件,他将文件发给了我。我把这个dump文件可以放到任意一个目录下,比如这里我直接将它放到一个bug文件夹中:
在这里插入图片描述

然后用VS将其打开
在这里插入图片描述
然后点击设置符号路径,将前面保存的pdb文件路径加到列表中,然后点击确定。
在这里插入图片描述

然后点击【使用 仅限本机 进行调试】
在这里插入图片描述

VS就会开始自动初始化调试环境,它会弹出下面的窗口,最好不要点取消,让它慢慢加载完。
在这里插入图片描述
这个最好是等他下载完(这次下载完了会缓存起来,下次就可以直接打开了),否则有些跨线程的bug无法被精确定位。
关于qt自己的pdb文件,可以参考这篇文章。同时,最好在安装Qt时就选择安装qt的源码,因为Qt的pdb文件会指向qt的源文件。
当其定位好之后,双击一下调用堆栈中的最上面的一行
在这里插入图片描述

此时它可能会定位到你最新(也就是你发布了程序后,又重新修改过的)的代码那里去的,需要自己手动定位到咱们之前保存的代码目录中。
在这里插入图片描述在这里插入图片描述

选择你之前备份的文件夹
在这里插入图片描述
自然就对上了。
在这里插入图片描述

4.qBreakPad

有个开源库可以做到跨平台生成dump,我还没实际测试过。有兴趣的可以试试。
https://blog.csdn.net/CLinuxF/article/details/122944959

5.其他

5.1.用dmp文件查找错误时卡住

有时后可能是因为所使用的第三方库的问题,导致一直卡在第三方库。此时就需要自己把第三方库的名字改一下,让在索引时跳过第三方库。
这时有可能第三方库用了防调试功能(具体如何实现的,我也不太清楚,可能是Virbox自带的功能?),比如我在使用思谋的sdk后,假如想根据程序崩溃时生成的dmp文件找到出错的原因,那就在前面的设置的基础上,将exe目录下的 vimo_inference.dll 文件 改成 vimo_inference-.dll
在这里插入图片描述

5.2.opencv的pdb文件

假如你的项目中使用了opencv,最好也把opencv的pdb文件路径也加到VS中的pdb符号文件路径中去。否则有时候调用堆栈会显示得不完整。
在这里插入图片描述未加载opencv符号文件的调用堆栈:
在这里插入图片描述同一个dump文件,但是加载了opencv符号文件的调用堆栈:
在这里插入图片描述

  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值