2019-06-14 09:16:45 robert_cysy 阅读数 201更多
分类专栏: Qt
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/robert_cysy/article/details/91948025
应用程序发布出去之后用户使用的过程中出现崩溃的情况下,软件需要记录崩溃详情并给用户弹出崩溃提示对话框,提示用户重启软件以及上报(上传)崩溃。
这样做能保证软件在使用过程中发现的bug都能很好的收集起来,并根据崩溃详情修改bug,然后发布修复bug的版本。
如果软件在使用过程中直接闪退没有任何提示,不仅用户体验不好,还无法获取崩溃的原因。之后更具用户反馈的口头描述来找bug就会非常头疼了。
开发c#应用程序的时候用Log4Net库就可以轻松的获取崩溃后的软件崩溃详情。如果还附带应用程序对应的pdb调试文件Exception报错记录里会有出现崩溃代码的具体行号,非常方便。但是现在开发Qt开发的时候怎样实现呢?
测试和举例所使用的Qt开发环境在win,macos,都是使用Qt Creater以及Qt creater 的工程文件。
#include "client/linux/handler/exception_handler.h"
int main(int argc, char* argv[]) {
google_breakpad::MinidumpDescriptor descriptor("/tmp");
google_breakpad::ExceptionHandler eh(descriptor, NULL, dumpCallback, NULL, true, -1);
crash();
return 0;
}
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) {
printf("Dump path: %s\n", descriptor.path());
return succeeded;
}
void crash() {
volatile int* a = (int*)(NULL);
*a = 1;
}
Qt多个平台下使用breakpad的方法
当使用qt开发跨平台软件使用breakpad的时候,可以对各个平台下使用breakpad方法做一个封装。从而给qt应用程序提供统一的调用接口。(注意:不同平台调用breakpad的函数和头文件是不同的)
由于不同平台下不仅调用函数不同,编译方法也不尽相同。所以这个封装不是特别简单。因此使用使用gitHub上封装好的开源代码了。[buzzySmile/qBreakpad] 这个封装库支持在windows、linux、macos平台下使用breakpad。并且有demo帮助你快速使用qBreakpad。在qt工程中使用qBreakpad的方法如下:
QT += core gui network
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = testCrash
TEMPLATE = app
#config for qBreakpad
#CONFIG -= app_bundle #配置上这个参数以后 你的图形界面程序就会以命令行方式运行,
CONFIG += warn_on
CONFIG += thread exceptions rtti stl
macx: LIBS += -framework AppKit
#without c++11 & AppKit library compiler can't solve address for symbols
CONFIG += c++11
#link qBreakpad library
include($$PWD/3rdparty/qBreakpad/qBreakpad.pri)
#end of config for qBreakpad
#include "mainwindow.h"
#include
#include "QBreakpadHandler.h"
void crash() { volatile int* a = (int*)(NULL); *a = 1; }
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QBreakpadInstance.setDumpPath(QLatin1String("crashes"));
MainWindow w;
w.show();
crash();
return a.exec();
}
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;
};
#include "reporter.h"
#include "ui_reporter.h"
#include
#include
#include "QBreakpadHandler.h"
#include "../program/TestThread.h"
int main (int argc, char *argv[])
{
QApplication app (argc, argv);
QCoreApplication::setApplicationName("ReporterExample");
QCoreApplication::setApplicationVersion("0.0.1");
QCoreApplication::setOrganizationName("OrgName");
QCoreApplication::setOrganizationDomain("name.org");
// Set directory to store dumps and url to upload
QBreakpadInstance.setDumpPath("crashes");
// Set server type for uploading
#if defined(SOCORRO)
QBreakpadInstance.setUploadUrl(QUrl("http://[your.site.com]/submit"));
#elif defined(CALIPER)
QBreakpadInstance.setUploadUrl(QUrl("http://[your.site.com]/crash_upload"));
#endif
// Create the dialog and show it
ReporterExample example;
example.show();
// Run the app
return app.exec();
}
ReporterExample::ReporterExample (QWidget *parent) :
QDialog (parent),
ui (new Ui::ReporterExample)
{
// Create and configure the user interface
ui->setupUi (this);
this->setWindowTitle("ReporterExample (qBreakpad v."+QBreakpadHandler::version()+")");
ui->urlLineEdit->setText(QBreakpadInstance.uploadUrl());
ui->dumpFilesTextEdit->appendPlainText(QBreakpadInstance.dumpFileList().join("\n"));
// Force crash app when the close button is clicked
connect (ui->crashButton, SIGNAL (clicked()),
this, SLOT (crash()));
// upload dumps when the updates button is clicked
connect (ui->uploadButton, SIGNAL (clicked()),
this, SLOT (uploadDumps()));
}
ReporterExample::~ReporterExample()
{
delete ui;
}
void ReporterExample::crash()
{
qsrand(QDateTime::currentDateTime().toTime_t());
TestThread t1(false, qrand());
TestThread t2(true, qrand());
t1.start();
t2.start();
QTimer::singleShot(3000, qApp, SLOT(quit()));
}
void ReporterExample::uploadDumps()
{
QBreakpadInstance.sendDumps();
}
之所以把qBreakpad的编译单独拿出来讲,是因为编译要有好多注意事项,编译比较复杂。单独讲比较清晰明了。qBreakpad的源码中需要包含Breakpad的源码。
git clone --recursive https://github.com/buzzySmile/qBreakpad.git
Clone之后,在源代码的“third_party”目录下需要下载“breakpad”的源代码和lss的源代码。其这样设计保证了封装的“breakpad”是最新版本的,不便之处就是还需要自己去下载“breakpad”的源代码。
git clone https://github.com/google/breakpad
git clone https://github.com/ithaibo/linux-syscall-support
为了方便测试使用,我把我编译好的并测试可以使用的各个平台都打包好了,下 载下来可以直接使用。包括编译生成的各个编译过程文件,因此包比较大。工程文件都是Qt Creater的工程文件。为了这个包能长久稳定的提供下载,我把他上传到csdn的“资源下载”中了,系统自己生成了要5C币才能下载,造成的不方便还请谅解,或者使用这个网盘链接:
链接:https://pan.baidu.com/s/1V_8ReIHNXqQUhG2RfH79hg 密码:ylsq
Csdn下载链接:https://download.csdn.net/download/robert_cysy/11240125
上面讲的是怎样在自己软件里加入qBreakpad的生成dump文件的客户端。
这里讲一下软件崩溃生成minidump文件之后,怎样从这个文件通过breakpad提供的工具生成Stack Trace的方法。
从上方的BreakPad工作原理示意图可以看出,要想获得stack track需要图示的几个步骤。其中用到的几个工具是:
symbol dumper: 对应到实际命令行工具是:dump_syms
minidump processor:对应到实际命令行工具是:minidump_stackwalk
这两个命令行工具是由breakpad以源码形式提供的,需要使用者在各个平台下编译生成可执行文件,然后才能使用。
在下载好第七节提到的工程文件的以下目录可以找breakpad的源码:
“crashtest/third_party/qBreakpad/third_party/breakpad“
./configure
Make
即可在breakpad/src/tools/linux/dump_syms/目录生成“dump_syms”可执行文件。./configure
Make
然后在breakpad/src/processor/目录找到“minidump_stackwalk”可执行文件../dump\_syms ./test > test.sym
head -n1 test.sym
得到test.sym这个文本文件的第一行,如下
MODULE mac x86_64 887D1A2C356F3401ABCCA76B666B3A810 test
然后执行命令:
mkdir -p ./symbols/test/887D1A2C356F3401ABCCA76B666B3A810
mv test.sym ./symbols/LedStripEditor/887D1A2C356F3401ABCCA76B666B3A810
./minidump_stackwalk xxx.dmp ./symbols > result.txt 2> process.txt
必须严格按照上方命令执行,比如你的应用名称叫做“test”则命令中用到“test”地方名字必须都是这个名字。否则也会生成Stack Trace但是里面只是内存地址,直接看不出出错的地方。只有按照上面的步骤才能正确加载test.sym文件,从而直观的显示出崩溃详情,包括出错的函数或变量名字以及行号。以上命令把“Stack Trace”记录在result.txt文件中。把处理过程记录在文件process.txt中。
如果不设置分成">"和“2>”分别输入到两个文件,则输入部分都会输出到一个文件中,即标准输出。而实际需要看的只有result.txt的部分。为了stack trace文件清晰明了。建议这两个文件单独输出。或标准错误输出到null:“2> /dev/null”。
以上命令会经常执行,做成脚本不仅也可以防止忘记命令操作的每一个步骤,而且极大的加快了多个命令依次执行的操作速度,脚本文件如下:
#!/bin/bash
if [ $# != 2 ] ; then
echo "USAGE: $0 EXE_NAME DMP_NAME"
echo " e.g.: $0 test 3872B2CF-983B-4963-AFA9-C8534DFD4C44.dmp"
exit 1;
fi
#get input param
exe_file_name=$1
dmp_file_name=$2
getSymbol() {
echo "@getSymbol: start get symbol"
./dump\_syms ./$exe_file_name > $exe_file_name'.sym'
}
getStackTrace() {
echo "@getStackTrace: start get StackTrace"
sym_file_name=$exe_file_name'.sym'
#get first line of $sym_file_name
line1=`head -n1 $sym_file_name`
#echo $line1
#get version number from string of first line
OIFS=$IFS; IFS=" "; set -- $line1; aa=$1;bb=$2;cc=$3;dd=$4; IFS=$OIFS
#echo $dd
version_number=$dd
#make standard dir and move *.sym in it
mkdir -p ./symbols/$exe_file_name/$version_number
mv $sym_file_name ./symbols/$exe_file_name/$version_number
#print stack trace at std output
./minidump_stackwalk $dmp_file_name ./symbols 2> /dev/null
#print stack trace at a file
#./minidump_stackwalk $dmp_file_name ./symbols 2>/dev/null >result.txt
}
main() {
getSymbol
if [ $? == 0 ]
then
getStackTrace
fi
}
# run main
main
把以上脚本保存成文件“dump_tool.sh” 并通过命令"chmod +x ./dump_tool.sh"增加可执行权限。这个脚本的执行环境如下图所示:
在使用breakpad的时候,需要目标应用程序内包含调试信息,这样“dump_syms”工具才能从你的应用程序中解析出调试符号。因此当你编译release版本的时候需要在项目的工程文件".pro" 文件末尾配置如下qmake参数:
#加入调试信息
QMAKE_CFLAGS_RELEASE += -g
QMAKE_CXXFLAGS_RELEASE += -g
#禁止优化
QMAKE_CFLAGS_RELEASE -= -O2
QMAKE_CXXFLAGS_RELEASE -= -O2
#release在最后link时默认有"-s”参数,表示"Omit all symbol information from the output file",因此要去掉该参数
QMAKE_LFLAGS_RELEASE = -mthreads -Wl #此行经过测试可用可不用
#参考自:https://blog.csdn.net/dgj8300/article/details/78450638
注意:release版最终发布给用户的时候,release版的可执行软件仍然需要在以上编译条件下编译。即最终发布给用户的可执行文件要包含调试符号。否则生成的dmp文件不能正确用“minidump_stackwalk”解析转换。