Windows下Qt生成dump文件并定位bug(基于qBreakpad)

Windows下Qt生成dump文件并定位bug(基于qBreakpad)

  • 一、dump和pdb是什么
  • 二、搜集跨平台生成dump的方案
  • 三、基于qBreakpad生成dump文件
    • 1、Breakpad介绍
    • 2、源码准备
      • (1)下载Breakpad源码
      • (2)下载LSS源码
      • (3)下载qBreakpad源码
    • 3、编译qBreakpad
      • (1)将Breakpad、LSS源码放入third_party目录
      • (2)qBreakpad工程介绍
      • (3)源码bug修正
      • (4)编译生成qBreakpad.lib
    • 4、在程序中调用qBreakpad
    • 5、生成dump文件
  • 四、生成pdb文件
  • 五、使用VS进行调试
  • 六、使用Windbg进行调试
  • 七、dump文件上报
  • 八、总结

一、dump和pdb是什么

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

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

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

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

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

二、搜集跨平台生成dump的方案

在不同的平台下,生成dump文件的方式都不同。既然用上Qt了,自然是想要任何东西都最好跨平台。

经过一番调查,发现有一个来自谷歌的开源项目叫Breakpad,统一了这三平台win、linux、mac生成dump的方式,通过它就可以跨平台。

Github地址:https://github.com/google/breakpad

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

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

Github地址:https://github.com/buzzySmile/qBreakpad

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

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

三、基于qBreakpad生成dump文件

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

1、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。

2、源码准备

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

(1)下载Breakpad源码

下载地址:https://github.com/google/breakpad

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第1张图片

(2)下载LSS源码

下载地址:https://github.com/ithaibo/linux-syscall-support

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第2张图片

(3)下载qBreakpad源码

下载地址:https://github.com/buzzySmile/qBreakpad

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第3张图片

3、编译qBreakpad

以下开发环境:Win10下,Qt Creator + MSVC编译器。

(1)将Breakpad、LSS源码放入third_party目录

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

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第4张图片

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

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第5张图片

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第6张图片

(2)qBreakpad工程介绍

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

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第7张图片

  • demo工程下,有2个演示程序program和reporter,分别实现了演示生成dump文件,上报dump文件的功能。

  • handler为静态库工程,该工程封装了Breakpad,直接编译此工程,可生成qBreakpad.lib。

  • tests为一个简单的测试工程。

但是在源码中有3个bug,在编译前,我们需要先修正。

(3)源码bug修正

I、在Debug模式下编译demo工程时,报错

报错如下:检测到“RuntimeLibrary”的不匹配项:值“MD_DynamicRelease”不匹配值“MDd_DynamicDebug”(TestThread.obj 中)

解决办法: 在qBreakpad-master/config.pri文件中,删除CONFIG += release此行,重新编译handler工程,再编译demo工程,错误消失。

II、编译demo工程时,报错

报错如下:error: LNK1104: 无法打开文件“qBreakpad.lib”

解决办法:在qBreakpad-master\demo\reporter\reporter.pro文件中,添加如下一行,

QMAKE_LIBDIR += $$OUT_PWD/../../handler

再次编译demo工程,错误消失。

III、编译tests工程时,函数返回值报错

报错如下:
> 这里是引用

解决办法:在qBreakpad-master\tests\duplicates\main.cpp文件中,为各个函数添加返回值即可。

这3个报错,其实都比较简单,但是为了方便,我这里提供一个集成了Breakpad、LSS、qBreakpad源码,并修正了bug的压缩包,供大家下载,可以直接在win、linux下编译通过。

链接: https://pan.baidu.com/s/1UHgikyipUZyEygJ7AKTBzg
提取码: 6jki

(4)编译生成qBreakpad.lib

分别在Debug、Release模式下,编译handler工程,生成2个版本的qBreakpad.lib静态库

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

4、在程序中调用qBreakpad

我们建立qBreakpadTest控制台程序,如下:

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第8张图片

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

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

再将调用库所需的头文件QBreakpadHandler.h、QBreakpadHttpUploader.h、call_once.h、singleton.h共4个文件拷贝至qBreakpad\include下。

最后目录结构,如下:

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第9张图片

在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 symbols
CONFIG += c++11
macx: LIBS += -framework AppKit

# 配置头文件搜索路径和链接库路径
INCLUDEPATH += $$PWD/qBreakpad/include
CONFIG(debug, debug|release) {
LIBS += -L$$PWD/qBreakpad/lib/debug -lqBreakpad
} else {
LIBS += -L$$PWD/qBreakpad/lib/release -lqBreakpad
}
############ for qBreakpad ############

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

#include 
#include "QBreakpadHandler.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QBreakpadInstance.setDumpPath("crashes"); // 设置生成dump文件路径

    // 执行此句发生异常时,会自动生成dump文件
    *((int*)0) = 10;

    return a.exec();
}

5、生成dump文件

编译,运行程序,生成的dump文件,如下:

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第10张图片

前面我们说过需要dump和pdb文件才能进行更细致的定位bug。

目前dump文件已经生成,接下来了解如何生成pdb文件。

四、生成pdb文件

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

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

QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO
QMAKE_LFLAGS_RELEASE = $$QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO

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

再次编译,可以看到,已经生成了qBreakpadTest.pdb。

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第11张图片

特别注意:

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进行介绍了,主要下面对后2种进行说明。

五、使用VS进行调试

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

1、打开dump文件

文件菜单下,选择“打开”->“文件”,如下:

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第12张图片

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

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第13张图片

2、设置pdb文件路径

接下来,点击“设置符号路径”,点击“+”号,添加pdb文件路径,之后,“确定”。如下:

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第14张图片

提示:

一般来说,我们只需要填写pdb所在的目录,不需要具体到pdb名称,因为根据dump文件,可以自动搜索到pdb文件。

尤其是对于主程序+多个dll的方式,就会存在多个pdb文件的情况,此时我们只设置目录,就可以方便的,自动从多个pdb文件中,找到对应的pdb。

3、进行调试

最后,点击“使用 仅限本机 进行调试”,可以很清楚的,定位到崩溃发生的代码行上。如下:

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第15张图片

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

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

六、使用Windbg进行调试

下载windbg:https://dl.pconline.com.cn/download/770876-1.html

先解压,然后运行WinDbg(x64)\windbg.exe。

1、指定pdb文件路径

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

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第16张图片

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

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第17张图片

注意:

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

2、指定代码路径(可选)

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

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第18张图片

输入源文件路径。

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第19张图片

3、打开dump文件

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

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第20张图片

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

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第21张图片

4、分析dump文件

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

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第22张图片

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

Windows下Qt生成dump文件并定位bug(基于qBreakpad)_第23张图片

  • STACK_TEXT表示调用堆栈信息。
  • FAULTING_SOURCE_CODE表示发生错误的源码,可以看到在第11行,发生的异常。

我们把内容拉到最右边,查看完整的STACK_TEXT信息,如下:

在这里插入图片描述

堆栈指示异常发生于main.cpp的第11行,与FAULTING_SOURCE_CODE段内容不谋而合。

注意:

若前面的源文件路径不填写,则最后分析生成结果中,只会存在STACK_TEXT堆栈信息,不会存在FAULTING_SOURCE_CODE字段。但是即便如此,仅凭堆栈信息,也可以定位到bug位于main.cpp的第11行。

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

七、dump文件上报

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

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

使用也是十分简单。

class QBreakpadHandler: public QObject
{
    Q_OBJECT
public:
    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()方法,来实现http协议方式的,文件上传。

上报功能,根据自身的需求,来确定有没有必要。此处不再举例说明。

八、总结

我们可以在自己的程序中,借助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也不同。

本文涉及工程代码:

https://gitee.com/bailiyang/cdemo/tree/master/Qt/57qBreakpadTest/qBreakpadTest



若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!

同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。

在这里插入图片描述

你可能感兴趣的:(Dump,生成dump,定位bug,Breakpad,windbg)