Qt Windows系统使用QBreakpad实战

网友投稿 1587 2022-09-16

Qt Windows系统使用QBreakpad实战

Qt Windows系统使用QBreakpad实战

文章目录

​​前言​​​​1、dump和pdb是什么​​​​2、Breakpad介绍​​​​3、源码准备​​

​​-Breakpad源码​​​​-LSS源码​​​​-qBreakpad源码​​

​​4、编译qBreakpad​​

​​将Breakpad、LSS源码放入third_party目录​​​​qBreakpad工程介绍​​​​参考网上报错,源码bug修正(本人未报错)​​​​编译生成qBreakpad.lib​​

​​debug 编译​​​​release 编译​​

​​5、在程序中调用qBreakpad​​​​6、生成dump文件​​​​7、生成Release pdb文件​​​​8、使用VS进行调试​​

​​打开dump文件​​​​设置pdb文件路径​​​​进行调试​​

​​9、使用Windbg进行调试​​

​​指定pdb文件路径​​​​指定代码路径(可选)​​​​打开dump文件​​​​分析dump文件​​

​​10、dump文件上报​​​​13、获取崩溃时的通知​​​​12、总结​​

前言

最近想研究下Qt下跨平台的崩溃捕获,经过一番调查,发现有一个来自谷歌的开源项目叫​​Breakpad​​​,统一了这三平台​​win、linux、mac​​​生成​​dump​​的方式,通过它就可以跨平台。

使用也是相对简单的,大概就是-源码,编译生成lib和dll,然后在你自己的程序中​​include​​​头文件,就可以在你的程序中集成,在崩溃时生成​​dump​​文件。

在我查找​​Breakpad​​​相关文章时,又发现了一个开源项目叫​​qBreakpad​​​,这玩意,腻害了,直接将懒癌进行到底,使用​​Qt​​​对​​Breakpad​​进一步封装,使用更简单了。

大致了解了下​​qBreakpad​​​,该源码简单到无以复加,虽​​github​​上文档有些年久失修,但是考虑到如此简单,也就无关痛痒了。

俗话说,站在巨人的肩上看得更远。接下来,我们就选择​​qBreakpad​​​来生成​​dump​​文件吧。

1、dump和pdb是什么

当我们写的程序跑在客户的机器上,因为一个bug,导致程序崩溃,你会有些什么办法来,定位并修复这个bug呢?

有人会说记录日志,即便有日志,也是不好定位的,因为你只能推测出大概的模块或者位置,无法定位到具体出错的代码行。

此时,我们可以让程序崩溃后,自动生成一个*.dmp文件,并配合在编译该程序时生成的pdb文件,来准确定位到调用堆栈、代码行上。这样很轻易就可以找到该bug。

dump文件,后缀 *.dmp,是程序崩溃时的内存转储文件;pdb文件,后缀 *.pdb,是程序的符号文件。

微软有成熟的工具可以分析,比如VS和windbg。所以我们当务之急,就是准备好这2个文件,后面再说如何分析,其实比较简单,耐心就好。

2、Breakpad介绍

当我们写的程序跑在客户的机器上,因为一个bug,导致程序崩溃,你会有些什么办法来,定位并修复这个bug呢?

我们大概先了解下Breakpad的一些常识。

​​Breakpad​​​是​​Google​​​公司开发的开源多平台C++崩溃检测库。​​Breakpad​​​可以捕获发布给用户的应用程序的崩溃,并记录软件崩溃的调试信息到​​minidump​​​文件中,即​​*.dmp​​。

minidump是由微软开发的崩溃记录文件格式。minidump为二进制文件,体积小。为了保持统一,Breakpad在其他系统下也选择生成minidump文件。

除此之外,Breakpad还可以调试信息包括错误行号,报错详情,堆栈错误(stack traces)。支持软件崩溃时候把生成的dump文件上传到自己的服务器上就可以方便的获取崩溃详情。

支持的平台:​​windows、linux、mac、ios、solaris、android ndk​​

在不同平台下的实现原理:

Windows:通过SetUnhandledExceptionFilter()设置崩溃回掉函数Max OS:监听 Mach Exception Port 获取崩溃事件Linux:监听 SIGILL SIGSEGV 等异常信号 获取崩溃事件

BreakPad工作原理示意图:

表达的意思就是:

我们在编译的时候,需要在​​Release​​​版程序中​​生成调试信息​​。使用​​Breakpad​​​提供的​​dump_syms​​​工具,从​​release​​​版本程序​​导出符号文件​​。当程序崩溃时,​​breakpad​​​会捕捉崩溃,并生成​​dump​​文件。​​dump​​​文件可以直接发送到指定服务器,或者由用户手动​​发给开发者​​。收到​​dump​​​文件后,结合符号文件,可通过​​minidump_stackwalk​​​工具生成​​堆栈调用信息文件​​​,这个文件可以直接阅读,定位​​bug​​。

3、源码准备

我们知道qBreakpad是对Breakpad的封装,所以qBreakpad的编译,还依赖2套源码Breakpad、LSS。

因为github可能需要翻墙,所以我这里给出我收集好的​​所有源码码云连接​​,当然下面也会贴出github的源码原链接,有需要的可以自己去克隆或-也是一样

-Breakpad源码

-:​​Creator​​​(Qt5.12.12) + ​​MSVC​​ (Vs2019)编译器。

将Breakpad、LSS源码放入third_party目录

克隆或解压qBreakpad源码后,在​​qBreakpad-master\third_party​​目录下,有如下2个目录,如下:

此时这两个目录是空的,需要分别克隆或解压Breakpad、LSS源码至breakpad和lss目录,此2个目录下源码需要参与qBreakpad的编译。放置好后,如下所示:

​breakpad​

​LSS​

qBreakpad工程介绍

在​​qBreakpad​​源码目录下,使用​​QtCreator​​打开​​qBreakpad.pro​​工程,如下:

​​demo​​​工程下,有2个演示程序program和reporter,分别实现了演示​​生成dump文件​​,上报dump文件的功能。​​handler​​​为静态库工程,该工程封装了Breakpad,直接编译此工程,可​​生成qBreakpad.lib​​。​​tests​​​为一个简单的​​测试工程​​。

根据查阅网上参考说在源码中有3个bug,在编译前,我们需要先修正, ​​但是我直接一键编译构建没有任何错误,一步到位​​,不过也记录一下网上的错误吧!

所以,先自己构建一下,看有没有对应的错误,有的话,可以看下面的,没有直接跳过即可!

参考网上报错,源码bug修正(本人未报错)

编译生成qBreakpad.lib

进入主题,开始正式编译需要的库环境,首先分别在Debug、Release模式下,编译handler工程,生成2个版本的qBreakpad.lib静态库。

因为程序调用qBreakpad.lib时,只能debug版程序链接debug版库,release版程序链接release版库。debug版程序链接release版库会报错。

debug 编译

生产后的​​*.lib​​如下图:

这个时候需要拷贝到debug目录,然后重新清除或手动删除后,再编译release版本即可,不然可能就会覆盖

release 编译

生产的 ​​*.lib​​ 如下图:

对比​​debug​​生产的发现少了一个​​*.pdb​​文件, 说明​​release​​不会自动生产​​pdb​​文件,如何生产,我们后续测试程序会讲到,这里只需要拿到对应的​​*.lib​​文件即可!

5、在程序中调用qBreakpad

我们使用Qt新建一个名为​​qBreakpadTest​​ 的简单QWidget程序,如下:

在工程目录下建立​​qbreakpadlib​​​目录,用于存放​​lib​​​和​​头文件​​。

然后,分别将​​debug​​​版、​​release​​​版​​qBreakpad.lib​​​拷贝至,​​qbreakpadlib\lib\debug​​​和​​qbreakpadlib\lib\release​​目录下。

再将调用库所需的头文件​​QBreakpadHandler.h、QBreakpadHttpUploader.h、call_once.h、singleton.h​​​共4个文件拷贝至​​qbreakpadlib\include​​​下。​​call_conce.h​​​ 和 ​​singleton.h​​​ 在​​singletone​​文件夹目录下,一起​连同文件夹​目录拷贝到​​include​​下即可。

最后目录结构,如下:

在​​qBreakpadTest.pro​​文件中,添加如下内容:

############ for qBreakpad ############# qBreakpad中需要使用到network模块QT += network# 启用多线程、异常、RTTI、STL支持CONFIG += thread exceptions rtti stl# without c++11 & AppKit library compiler can't solve address for symbolsCONFIG += c++11macx: LIBS += -framework AppKit# 配置头文件搜索路径和链接库路径win32:CONFIG(release, debug|release): {LIBS += -L$$PWD/qbreakpadlib/lib/release/ -lqBreakpadDEPENDPATH += $$PWD/qbreakpadlib/lib/release} else:win32:CONFIG(debug, debug|release): {LIBS += -L$$PWD/qbreakpadlib/lib/debug/ -lqBreakpadDEPENDPATH += $$PWD/qbreakpadlib/lib/debug}INCLUDEPATH += $$PWD/qbreakpadlib/include############ for qBreakpad ############

然后在​​main.cpp​​中添加调用代码,如下:

#include "qBreakpadTest.h"#include #include "QBreakpadHandler.h"int main(int argc, char *argv[]){ QApplication a(argc, argv); QBreakpadInstance.setDumpPath("crashes"); // 设置生成dump文件路径 qBreakpadTest w; w.show(); return a.exec();}

void qBreakpadTest::on_pushButton_crash_clicked(){ QLabel * a = nullptr; // 执行此句发生异常时,会自动生成dump文件 a->setText("会触发崩溃");}

6、生成dump文件

编译,运行程序,生成的​​dump​​文件,调试程序打印如下:

下面来看看具体生成的文件如下:

可以看到确实生成了dump文件,那么我们再来看看debug版本的pdb文件生成, 截图如下:

可以看到debug版本的dump和pdb都文件已经生成, 下面我们再生成release版本的dump和pdb, 首先Qt 切换到release版本模式,然后重新构建,如下图:

步骤和debug版本一致,先看程序调试信息:

说明已经成功捕获并生成了dump文件,下面我们在看看实际文件

可以发现,是没有pdb文件的生成的

前面我们说过需要dump和pdb文件才能进行更细致的定位bug。目前dump文件已经生成,release版本的pdb文件却没有生产,所以接下来了解如何生成release版本的pdb文件。

7、生成Release pdb文件

在debug模式下,默认就会生成pdb,但是我们期望的是,在release下也能生成pdb。毕竟交给客户的是release版,我们大多时候,也只是需要对release版程序进行bug定位。

其实关于这一点,在我前面有一篇专门的博客也讲解过​​QT如何在Release编译下生成pdb文件​​, 当然可以直接看下面的内容,都是一致的!

所以,需要在​​qBreakpadTest.pro​​文件中,添加如下内容,让release版程序带上调试信息:

QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFOQMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO

win系统下,程序的调试信息,是在单独的pdb文件中;在其他linux、mac等系统下,程序的调试信息就包含在程序本体内部,所以带调试信息的程序一般比不带调试信息的大。

清除以后qmake,再次编译,可以看到,已经生成了​​qBreakpadTest.pdb​​, 如下图:

​特别注意​

MinGW是gcc在windows下的编译环境,GCC/MinGW以GNU GDB Debugger使用的格式生成调试信息,不支持Microsoft PDB格式。也就是说如果使用MinGW编译程序,无法生成pdb文件,这点需要注意一下。必须使用MSVC编译,方可生成pdb文件。如果是主程序+多个dll的开发方式,需要使用上述方法,将每个dll也生成pdb文件,这样,在dll中发生崩溃时,才能根据dump和pdb定位到dll的代码上。

目前我们已经生成了程序的pdb调试信息文件,并且程序执行过程中发生崩溃,也可以自动记录dump文件,这2个文件已经具备,接下来,我们看看如何利用他们定位到bug所在位置。

这里,其实有3种方式来,分析调试程序:

​​minidump_stackwalk​​​,这是​​Breakpad​​提供的工具,可以用来生成,可读的堆栈调用信息。​​VS​​​,​​微软​​提供的开发工具,使用最简单,缺点是安装过程较长。​​Windbg​​​,​​微软​​提供的调试工具,使用稍麻烦,无需安装。

在win系统下,还是使用微软的工具,来的最方便,所以这里就不对​​minidump_stackwalk​​进行介绍了(​后续我会单独开一篇关于如何使用​​minidump_stackwalk​​工具的针对性文章​),下面主要对后两种进行说明。

8、使用VS进行调试

我这里使用​​VS 2019​​,来进行如下的操作。

打开dump文件

Vs文件菜单下,选择​​“打开”​​->​​“文件”​​,如下:(也可以直接选择拖拽dump文件到Vs也是一样)

找到​​dump​​文件,并打开,可以看到​​转储摘要​​和​​模块​​等,但是​​并不能​​发现问题何在。如下:

设置pdb文件路径

​提示:​

一般来说,我们只需要填写pdb所在的目录,不需要具体到pdb名称,因为根据dump文件,可以自动搜索到pdb文件。 尤其是对于主程序+多个dll的方式,就会存在多个pdb文件的情况,此时我们只设置目录,就可以方便的,自动从多个pdb文件中,找到对应的pdb。

进行调试

到此,我们顺利通过​​dump、pdb​​文件,成功定位到了​​bug​​所在。

注意,源码一定不能变化,哪怕只是更改加入了一行或者什么空格,都会导致定位错误或者只能定位大概位置,所以源码一定要和编译出exe时一致,下面看我的示例,我更改源码,然后只保存,不编译出exe,记住exe必须要和pdb生成时一致,不然定位就会报错,所以我更改源码,并没有重新生成exe,还是可以定位的,只不过会定位不准而已,如下图,我更改的源码位置:

此时可以发现,只能定位到是哪里调用的崩溃上一层了,如果源码更改过多,那么就绝对会定位BUG失败了!所以注意,​​源码、exe、pdb,三者要保持一样​​

下面介绍另外一种调试方式。

9、使用Windbg进行调试

第三方-windbg:​​环境搭建,主要针对MSVC/Android 平台)​​ 建议使用微软

指定pdb文件路径

选择​​“File”​​->​​“Symbol File Path…”​​,如下:

直接输入​​pdb文件​​所在目录即可,它会自动找到适合的pdb文件。也可以输入pdb文件路径,若多个路径,则用​​分号分隔​​。

​注意:​

如果程序涉及到DLL,需要将EXE、所有涉及DLL的PDB路径都包括。

指定代码路径(可选)

选择​​“File”​​->​​“Source File Path…”​​,如下:

输入源文件路径。

这一步跳过是可以的,我是跳过了测试和不跳过测试结果一致, 不过设置一下也没什么问题

打开dump文件

选择​​“File”​​->​​“Open Crash Dump…”​​,如下:

选择dump文件,并打开,如下:

分析dump文件

输入`“!analyze -v”`,回车,开始进行分析。如下:

​busy​状态表示正在生成结果。最后生成的结果,如下:

​​STACK_TEXT​​表示调用堆栈信息。

网上有的可以显示 ​​FAULTING_SOURCE_CODE​​ 字段,其表示发生错误的源码,但是我这边并未显示

通过查看​​STACK_TEXT​​​字段,根据​​堆栈​​​也能判断出错误的​​地方​​

到此,我们使用​​windbg​​​,顺利通过​​dump、pdb​​​文件,成功定位到了​​bug​​所在。

10、dump文件上报

qBreakpad还提供了上报dump文件的方法。说白了就是,将生成的dump文件上传到指定的服务器。

上报演示程序,位于qBreakpad-master\demo\reporter下,感兴趣可以去看看。

使用也是十分简单。

class QBreakpadHandler: public QObject{ Q_OBJECTpublic: static QString version(); QBreakpadHandler(); ~QBreakpadHandler(); QString uploadUrl() const; QString dumpPath() const; QStringList dumpFileList() const; void setDumpPath(const QString& path); void setUploadUrl(const QUrl& url);public slots: void sendDumps();private: QBreakpadHandlerPrivate* d;};

​基本流程:​

先通过​​setDumpPath​​​设置​​dump​​​文件生成目录;以便在发生崩溃时,自动在该目录下生成​​dump​​文件。再通过​​setUploadUrl​​​设置上报地址,以便后续将​​dump​​文件,上传到该地址。最后,通过​​sendDumps​​​将​​dump​​​文件发送至服务器。该函数会自动遍历,前面设置的​​dump​​​生成目录,将每一个​​dump​​文件进行发送。

文件上传原理:​​QBreakpadHandler​​​的​​sendDumps​​​函数,使用​​QNetworkAccessManager​​​的​​post()​​​方法,来实现​​void (*p_callbackFun)(QString);class QBreakpadHandler: public QObject{ Q_OBJECTpublic: static QString version(); QBreakpadHandler(); ~QBreakpadHandler(); QString uploadUrl() const; QString dumpPath() const; QStringList dumpFileList() const; void setDumpPath(const QString& path); void setUploadUrl(const QUrl& url); //! 新增回调指针方法 void setCallbackMethod(p_callbackFun func){ m_callfunc = func; }; p_callbackFun m_callfunc; //!public slots: void sendDumps();private: QBreakpadHandlerPrivate* d;};

.cpp 文件修改如下:

#if defined(Q_OS_WIN32)bool DumpCallback(const wchar_t* dump_dir, const wchar_t* minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool succeeded)#elif defined(Q_OS_MAC)bool DumpCallback(const char *dump_dir, const char *minidump_id, void *context, bool succeeded)#elsebool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)#endif{#ifdef Q_OS_LINUX Q_UNUSED(descriptor);#endif Q_UNUSED(context);#if defined(Q_OS_WIN32) Q_UNUSED(assertion); Q_UNUSED(exinfo);#endif /* NO STACK USE, NO HEAP USE THERE !!! Creating QString's, using qDebug, etc. - everything is crash-unfriendly. */#if defined(Q_OS_WIN32) QString path = QString::fromWCharArray(dump_dir) + QLatin1String("/") + QString::fromWCharArray(minidump_id); qDebug("%s, dump path: %s\n", succeeded ? "Succeed to write minidump" : "Failed to write minidump", qPrintable(path));#elif defined(Q_OS_MAC) QString path = QString::fromUtf8(dump_dir) + QLatin1String("/") + QString::fromUtf8(minidump_id); qDebug("%s, dump path: %s\n", succeeded ? "Succeed to write minidump" : "Failed to write minidump", qPrintable(path));#else qDebug("%s, dump path: %s\n", succeeded ? "Succeed to write minidump" : "Failed to write minidump", descriptor.path());#endif //! 调用外部的回调函数 QBreakpadInstance.m_callfunc(path); return succeeded;}

下面看实战,重新编译出​​*.lib​​​文件, 拷贝新的​​QBreakpadHandler.h​​​头文件到我们上个​​qBreakpadTest​​​ 工程中,然后代码在​​main.cpp​​​中调用我们新增的​​回调接口​​,看在崩溃时,能否调用到我们自己写的回调方法中去,并做一些事情。新增工程代码如下:

int main(int argc, char *argv[]){ QApplication a(argc, argv); QBreakpadInstance.setDumpPath("crashes"); // 设置生成dump文件路径 qBreakpadTest w; //! 调用我们新增的回调方法,让崩溃时qbreakpad能调用我们自己实现的onBreakpadCrash方法逻辑 QBreakpadInstance.setCallbackMethod(&qBreakpadTest::onBreakpadCrash); w.show(); return a.exec();}# onBreakpadCrash实现如下void qBreakpadTest::onBreakpadCrash(QString dumpPath){ qInfo()<<"捕获到崩溃,现在准备调用发送dump文件到服务端程序~, dump文件路径是:"<

运行程序后打印如下:

可以看到,在程序崩溃之前,是可以触发到我们程序需要解决的逻辑应用环节中去的,此时,我们可以调用另外的崩溃上传文件程序,告知用户崩溃了,是否发送崩溃信息日志等文件功能!

12、总结

我们可以在自己的程序中,借助qBreakpad,很容易实现跨平台的,dump文件生成。

对于在程序中集成qBreakpad,实际就是,在程序中调用qBreakpad的静态库而已,非常的简单。

对于程序生成的dump文件,可以由用户直接发给我们,也可以由程序自动上报到我们的服务器上。

然后,我们拿到dump和pdb文件,借助VS或者Windbg,就可以快速定位bug。

​特别注意:​

欲定位​​bug​​,至少需要​​dump​​和​​pdb​​,这2个文件。

​​pdb​​​文件与生成​​dump​​​的程序必须配套,即同一次编译生成的。即使代码没有变化,重新编译生成的​​pdb​​​都是不行的。所以请妥善保管好发布程序的​​pdb​​文件。

​原因如下:​

调试器是如何来判别EXE、DLL等是否和一个pdb文件匹配呢? 每次我们链接EXE或者DLL的时候,链接器都将产生一个唯一的GUID,然后将其写入到PDB和可执行文件。调试器加载的时候将检查两者的GUID,如果一致就表示他们匹配。 注:如果我们需要调试,我们需要查dump文件,那么请妥善保管好自己的代码和pdb。每次重新编译,即使所有代码均没有变化,他们的GUID也不同。

文章参考于​​Windows下Qt生成dump文件并定位bug(基于qBreakpad)​​, 谢谢观看!

下一篇

​​Qt Linux系统使用QBreakpad实战​​ 待写

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:JSON Serialization/Deserialization in C#(json数据格式)
下一篇:解决所有人的痛点,禁止Win10强制更新,一键彻底关闭更新,Windows Update Blocker
相关文章

 发表评论

暂时没有评论,来抢沙发吧~