文章目录
前言
前面写了基于win
系统的qBreakpad
的编译到项目实战,本章将继续以qBreakpad
为基础在linux
系统上面演示程序崩溃的如何生成dump
文件并定位到BUG
所在
关于一些dump
文件的知识和breakpad
相关的理念这里就不啰嗦了,因为上一篇在win
系统使用中已经全面讲解了,有兴趣的可以去上一篇看了再回来看本篇,此篇主要涉及如何在linux
系统中编译出qbreakpad
需要的*.so
库文件,然后使用qt如何调用,并生成dump
文件后,如何定位出bug
。
ps: 此篇也适用于Max系统上部署QBreakpad调式bug定位。
一、如何基于qBreakpad在Linux中生成dump文件
1、源码准备
我们知道qBreakpad
是对Breakpad
的封装,所以qBreakpad
的编译,还依赖2套源码Breakpad
、LSS
。
因为github可能需要翻墙,所以我这里给出我收集好的所有源码码云连接,当然下面也会贴出github的源码原链接,有需要的可以自己去克隆或下载也是一样
下载Breakpad
源码
下载地址:https://github.com/google/breakpad
下载LSS
源码
下载地址:https://github.com/ithaibo/linux-syscall-support
下载qBreakpad
源码
下载地址:https://github.com/buzzySmile/qBreakpad
2、编译qBreakpad
以下开发环境:Ubuntu18.04
下,Qt Creator(Qt5.12.12)
+ GCC(7.5.0)
编译器。
将Breakpad、LSS源码放入third_party目录
克隆或下载qBreakpad
源码后,如果是直接克隆我上面码云的链接,基本就只要拷贝就行了,下面是我自己码云链接克隆后的截图如下:
在qBreakpad\third_party
目录下,有如下2个空目录,如下:
分别将breakpad
、LSS(linux-syscall-support)
源码拷贝至breakpad
和lss
目录,因为这两个目录下源码需要参与qBreakpad
的编译
。放置好后,如下所示:
breakpad
目录下:
lss
目录下:
qBreakpad工程介绍
在qBreakpad源码目录下,使用QtCreator打开qBreakpad.pro工程,如下:
-
demo
工程下,有两个演示程序program
和reporter
,分别实现了演示生成dump文件
,上报dump文件
的功能。 -
handler
为静态库工程,该工程封装了Breakpad,直接编译此工程,可生成libqBreakpad.a
。 -
tests
为一个简单的测试工程
。
然后直接构建就ok了,构建成功后,会在qBreakpad/handler
目录下生成libqBreakpad.a
文件, 如下图
我们只需要拿到这个libqBreakpad.a
文件即可, 根据网上查阅资料参考的文章中,有说在编译时会报错,需要修正源码。但是我这边一键构建,没有出现任何错误
,不过我下面还是会记录贴出来,以免有人会遇到对应的错误!
源码bug修正(本人编译并未遇到任何错误,此错误参考于网上记录,可以跳过忽略)
以下内容均复制于原帖,无改动~
编译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
工程,错误消失。
编译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工程,错误本应该消失。但是又出现错误,见下一条。
编译demo工程,报错
报错如下:
error: cannot find -lqBreakpad
解决办法:在qBreakpad-master\demo\reporter\reporter.pro
文件中,添加如下一行
QMAKE_LIBDIR += $$OUT_PWD/../../handler
再次编译demo
工程,错误消失。
编译生成libqBreakpad.a
在Release
模式下,编译handler
工程,生成libqBreakpad.a
静态库, 上面已经贴出我编译成功的截图!
3、在程序中调用qBreakpad
我们建立基于QWidget
的一个工程,工程名为qBreakpadTest
,如下:
在工程目录下建立qBreakpad
目录,用于存放库和头文件。
然后,将libqBreakpad.a
拷贝至,qBreakpad\lib\
目录下。如图所示:
再将调用库所需的头文件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
# 配置头文件搜索路径和链接库路径
unix:!macx: LIBS += -L$$PWD/qBreakpad/lib/ -lqBreakpad
INCLUDEPATH += $$PWD/qBreakpad/include
DEPENDPATH += $$PWD/qBreakpad/include
unix:!macx: PRE_TARGETDEPS += $$PWD/qBreakpad/lib/libqBreakpad.a
############ for qBreakpad ############
然后在main.cpp
中添加调用代码,如下:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QBreakpadInstance.setDumpPath("crashes"); // 设置生成dump文件路径
qBreakpadTest w;
w.show();
return a.exec();
}
在qBreakpadTest
中添加按钮触发的崩溃代码如下:
void qBreakpadTest::on_pushButton_clicked()
{
QLabel * label = nullptr;
label->setText("crash");
}
4、生成dump文件
编译,运行程序,生成的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等系统下,程序的调试信息就包含在程序本体内部,所以带调试信息的程序一般比不带调试信息的大。
将原来的exe
更名为qBreakpadTest_no
, 再次编译,可以看到,已经生成了带调试信息的程序qBreakpadTest
。
qBreakpadTest
文件大小比不带调试信息
时,大得多。
特别注意:
如果是主程序+多个so的开发方式,需要使用上述方法,将每个so也附带调试信息,这样,在so中发生崩溃时,才能根据dump和调试信息定位到so的代码上。
目前我们已经生成了带调试信息
的程序,并且程序执行过程中发生崩溃,也可以自动记录dump
文件,这两个东西已经具备,接下来,我们看看如何利用他们定位到bug
所在位置。
三、生成dump_syms和minidump_stackwalk工具
Breakpad
为我们提供了两个工具dump_syms
和minidump_stackwalk
,我们将用他们来分析dump
,定位bug
。
dump_syms
,将程序导出符号文件;minidump_stackwalk
,生成堆栈调用信息,以便阅读。
所以我们需要准备一个干净的Breakpad
源码,用于编译生成这两个
工具。
1、将LSS源码放入src\third_party目录
首先准备好纯净的Breakpad
、LSS(linux-syscall-support)
源码,就是之前第一次拉取我给的连接里面的源码即可,然后将LSS(linux-syscall-support)
源码拷贝到breakpad\src\third_party\lss
目录,该lss
文件夹不存在,则新建之。如下:
可以看到是没有lss
文件夹的,现在我们新建一个lss
文件夹,然后将LSS
源码放置进去,如下图:
2、编译、安装Breakpad
命令行进入源码
目录
cd breakpad
如下图:
configure
配置。若未知configure
提供了哪些参数,可使用 ./configure -help
。
./configure
如果提示权限不够,则执行
chmod 755 configure
如下图
在configure
配置即可
执行完成以后,开始编译(编译需要一段时间)
sudo make
编译完毕后。
-
在
breakpad/src/tools/linux/dump_syms
目录下,生成了dump_syms
。如图所示:
-
在
breakpad/src/processor
目录下,生成了minidump_stackwalk
。
同时还在breakpad/src
目录下,生成了libbreakpad.a
;以及在breakpad/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、生成崩溃处调用堆栈信息
将crashes目录
拷贝到和symbols目录
一个级别目录下,如下图:
然后执行如下命令,生成调用堆栈信息:
minidump_stackwalk ./crashes/7211c8b8-126d-4de2-7f8f00a4-db86eecc.dmp ./symbols > error.log
- 第一个参数,是
dump
文件名; - 第二个参数,固定为
./symbols
,应该是指定符号文件位于当前symbols目录下默认路径位置; - 第三个参数,将命令执行结果,写入到
error.log
文件中。
执行报错,没有找到一些模块,不用理会。如下:
生成的堆栈调用信息文件error.log
,内容如下:
一般找到“crashed”
字样,与它最近的一行,就是发生崩溃时,程序的调用堆栈,可以很清楚的看到,崩溃发生在qBreakpadTestcpp文件,第20行。
与我们编写的测试程序,预期效果,完全符合,如下:
到此,我们顺利通过dump
文件、带调试信息
的程序文件,成功定位到了bug所在
。
五、dump文件上报
qBreakpad
还提供了上报dump
文件的方法。说白了就是,将生成的dump
文件上传到指定的服务器。
上报演示程序,位于qBreakpad\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
和带调试信息
的程序,这两个
文件。
关于其它,想自身程序在第一时间也拿到崩溃通知,可以参考上一篇Qt Windows系统使用QBreakpad实战 里面有详细阐述!