Linux下Qt生成dump文件并定位bug(基于qBreakpad)
- 一、dump文件和调试信息
- 二、搜集跨平台生成dump的方案
- 三、基于qBreakpad生成dump文件
- 1、Breakpad介绍
- 2、源码准备
- (1)下载Breakpad源码
- (2)下载LSS源码
- (3)下载qBreakpad源码
- 3、编译qBreakpad
- (1)将Breakpad、LSS源码放入third_party目录
- (2)qBreakpad工程介绍
- (3)源码bug修正
- (4)编译生成libqBreakpad.a
- 4、在程序中调用qBreakpad
- 5、生成dump文件
- 四、生成带调试信息的可执行程序
- 五、生成dump_syms和minidump_stackwalk工具
- 1、将LSS源码放入src\third_party目录
- 2、编译、安装Breakpad
- 六、使用dump_syms和minidump_stackwalk定位bug
- 七、dump文件上报
- 八、总结
一、dump文件和调试信息
当我们写的程序跑在客户的机器上,因为一个bug,导致程序崩溃,你会有些什么办法来,定位并修复这个bug呢?
有人会说记录日志,即便有日志,也是不好定位的,因为你只能推测出大概的模块或者位置,无法定位到具体出错的代码行。
此时,我们可以让程序崩溃后,自动生成一个*.dmp文件,并配合在编译该程序时生成的调试信息,来准确定位到调用堆栈、代码行上。这样很轻易就可以找到该bug。
- dump文件,后缀*.dmp,是程序崩溃时的内存转储文件;
- 调试信息,在linux下,该信息就位于程序本体内部,只是需要我们设置一些编译选项即可。
我们只要准备好了上述的2个东西,那么就可以借助工具,来分析定位bug。
所以我们当务之急,就是准备好这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
(2)下载LSS源码
下载地址:https://github.com/ithaibo/linux-syscall-support
(3)下载qBreakpad源码
下载地址:https://github.com/buzzySmile/qBreakpad
3、编译qBreakpad
以下开发环境:Ubuntu16.04下,Qt Creator + GCC编译器。
(1)将Breakpad、LSS源码放入third_party目录
解压qBreakpad源码后,在qBreakpad-master\third_party目录下,有如下2个目录,如下:
分别解压Breakpad、LSS源码至breakpad和lss目录,此2个目录下源码需要参与qBreakpad的编译。放置好后,如下所示:
(2)qBreakpad工程介绍
在qBreakpad源码目录下,使用QtCreator打开qBreakpad.pro工程,如下:
- demo工程下,有2个演示程序program和reporter,分别实现了演示生成dump文件,上报dump文件的功能。
- handler为静态库工程,该工程封装了Breakpad,直接编译此工程,可生成libqBreakpad.a。
- tests为一个简单的测试工程。
但是在源码中有3个bug,在编译前,我们需要先修正。
(3)源码bug修正
I、编译handler工程时,报错
报错如下:error: No rule to make target ‘…/…/qBreakpad-master/third_party/breakpad/src/common/convert_UTF.c’, needed by ‘_build/obj/convert_UTF.o’. Stop.
解决办法: 在qBreakpad-master/third_party/breakpad.pri中,
将
$$BREAKPAD_PATH/common/convert_UTF.c \
改为:
$$BREAKPAD_PATH/common/convert_UTF.cc \
重新编译handler工程,错误消失。
II、编译demo工程时,报错
报错如下:exception_handler.cc: error: undefined reference to `breakpad_getcontext’
解决办法:该错误是链接libqBreakpad.a时,就会报错。在qBreakpad-master/third_party/breakpad.pri中,unix下添加如下一行
$$BREAKPAD_PATH/common/linux/breakpad_getcontext.S \
效果如下:
重新编译handler工程,生成新的库后,再次编译demo工程,错误本应该消失。但是又出现错误,见下一条。
III、编译demo工程,报错
报错如下:error: cannot find -lqBreakpad
解决办法:在qBreakpad-master\demo\reporter\reporter.pro文件中,添加如下一行
QMAKE_LIBDIR += $$OUT_PWD/../../handler
再次编译demo工程,错误消失。
这3个报错,其实都比较简单,但是为了方便,我这里提供一个集成了Breakpad、LSS、qBreakpad源码,并修正了bug的压缩包,供大家下载,可以直接在win、linux下编译通过。
链接: https://pan.baidu.com/s/1UHgikyipUZyEygJ7AKTBzg 提取码: 6jki
(4)编译生成libqBreakpad.a
在Release模式下,编译handler工程,生成libqBreakpad.a静态库。
4、在程序中调用qBreakpad
我们建立qBreakpadTest控制台程序,如下:
在工程目录下建立qBreakpad目录,用于存放库和头文件。
然后,将libqBreakpad.a拷贝至,qBreakpad\lib\linux目录下。
再将调用库所需的头文件QBreakpadHandler.h、QBreakpadHttpUploader.h、call_once.h、singleton.h共4个文件拷贝至qBreakpad\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 symbols
CONFIG += c++11
macx: LIBS += -framework AppKit
# 配置头文件搜索路径和链接库路径
INCLUDEPATH += $$PWD/qBreakpad/include
unix {
LIBS += -L$$PWD/qBreakpad/lib/linux -lqBreakpad
}
############ for qBreakpad ############
然后在main.cpp中添加调用代码,如下:
#include <QCoreApplication>
#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文件,如下:
前面我们说过需要dump和调试信息,才能进行更细致的定位bug。
目前dump文件已经生成,接下来了解如何生成调试信息。
四、生成带调试信息的可执行程序
在debug模式下,生成的可执行程序,默认就会带有调试信息。
但是我们期望的是,在release下也能生成,带有调试信息的可执行程序。毕竟交给客户的是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。
qBreakpadTest文件大小比不带调试信息时,大得多。
特别注意:
如果是主程序+多个so的开发方式,需要使用上述方法,将每个so也附带调试信息,这样,在so中发生崩溃时,才能根据dump和调试信息定位到so的代码上。
目前我们已经生成了带调试信息的程序,并且程序执行过程中发生崩溃,也可以自动记录dump文件,这2个东西已经具备,接下来,我们看看如何利用他们定位到bug所在位置。
五、生成dump_syms和minidump_stackwalk工具
Breakpad为我们提供了2个工具dump_syms和minidump_stackwalk,我们将用他们来分析dump,定位bug。
- dump_syms,将程序导出符号文件;
- minidump_stackwalk,生成堆栈调用信息,以便阅读。
所以我们需要准备一个干净的Breakpad源码,用于编译生成这2个工具。
1、将LSS源码放入src\third_party目录
分别解压Breakpad、LSS源码,将LSS源码拷贝至breakpad-main\src\third_party\lss目录,该lss文件夹不存在,则新建之。如下:
2、编译、安装Breakpad
进入源码目录
cd breakpad-main
configure配置。若未知configure提供了哪些参数,可使用 ./configure -help
。
./configure
编译
sudo make
编译完毕后。
- 在breakpad-main/src/tools/linux/dump_syms目录下,生成了dump_syms。
- 在breakpad-main/src/processor目录下,生成了minidump_stackwalk。
同时还在breakpad-main/src目录下,生成了libbreakpad.a;以及在breakpad-main/src/client/linux目录下,生成了libbreakpad_client.a。但这里libbreakpad.a、libbreakpad_client.a并不是我们关心的对象。
最后,进行安装。若要卸载,使用sudo make uninstall
。
sudo make install
安装完毕后,上述生成的文件会被拷贝到系统默认路径,使得我们可以直接在命令行中,使用这些工具。
六、使用dump_syms和minidump_stackwalk定位bug
依照前面的过程,到此我们得到了dump文件、带调试信息的程序文件,以及dump_syms和minidump_stackwalk工具。
1、生成符号文件
我们使用dump_syms读取带调试信息的程序文件,并生成符号文件qBreakpadTest.sym。
dump_syms ./qBreakpadTest > qBreakpadTest.sym
如下:
特别注意:
需要将sym文件放置到特定路径下,方可在后续生成的堆栈信息中,查看到崩溃发生的文件名和行号。否则,堆栈信息中,只能看到内存地址。下一步,将介绍,如何放置到特定路径。
2、将符号文件移动到特定路径
我们在qBreakpadTest程序所在目录下,创建symbols目录,并在该目录下,继续创建如下的目录结构:
解释:
- 第一级目录,固定为symbols;
- 第二级目录,为即将放入的符号文件名称,如qBreakpadTest.sym,则目录名为qBreakpadTest;
- 第三级目录,在sym文件中第一行内容,有一串16进制编号,将其作为目录名。
建立好以上路径后,将qBreakpadTest.sym移动到此路径下。
3、生成崩溃处调用堆栈信息
执行如下命令,生成调用堆栈信息:
minidump_stackwalk ./crashes/0ba1a6f4-823b-4cf5-0ec7509e-8bb4b338.dmp ./symbols > error.log
- 第一个参数,是dump文件名;
- 第二个参数,固定为./symbols,应该是指定符号文件位于当前symbols目录下默认路径位置;
- 第三个参数,将命令执行结果,写入到error.log文件中。
执行报错,没有找到一些模块,不用理会。如下:
生成的堆栈调用信息文件error.log,内容如下:
一般找到“crashed”字样,与它最近的一行,就是发生崩溃时,程序的调用堆栈,可以很清楚的看到,崩溃发生在main.cpp文件,第11行。
与我们编写的测试程序,预期效果,完全符合,如下:
到此,我们顺利通过dump文件、带调试信息的程序文件,成功定位到了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文件和带调试信息的程序文件,借助dump_syms和minidump_stackwalk工具,就可以快速定位bug。
特别注意:
- 欲定位bug,至少需要dump和带调试信息的程序,这2个文件。
本文涉及工程代码:
https://gitee.com/bailiyang/cdemo/tree/master/Qt/57qBreakpadTest/qBreakpadTest
提示:
本文讲述的是单文件程序为例,当异常发生于单文件程序内部时,定位崩溃点过程进行了说明。
若需要查看App+多个so开发方式,异常发生于so中,怎么来定位异常位置