跨平台的Qt程序崩溃生成Dump文件Breakpad
简介:
1、Window平台:可以使用“SetUnhandledExceptionFilter”来设置应用程序奔溃捕捉,这在崩溃的时候,这个函数指定的回掉函数就会被调用并返回崩溃详情。返回的崩溃详情为MiniDump格式。这个是windows提供的函数,因此必须包含“#include <Windows.h>”,具体的使用方法参考 Window端Qt Create dmp的生成与解析
2、 如果需要同时满足 Windows、macOS、Linux三个平台呢?
查阅相关资料,发现Qt并没有提供类似的功能。但是Google breakpad 提供了一整套的崩溃解析方案,并且breadpad 是为了解决c\c++异常尔存在的支持跨平台。
Google breakpad简单介绍
1、Breakpad可以捕获发布给用户的应用程序的崩溃,并记录软件崩溃的调试信息到“minidump”文件中。调试信息包括错误行号,报错详情,堆栈错误(stack traces)。软件崩溃时候把生成的“minidump”上传到自己的服务器上就可已方便的获取足够细致崩溃详情。Breakpad提供了简单的上传“minidump”到服务器的代码实现
2、BreakPad是开源协议下发布的项目由C++开发,目的在于捕捉各个系统平台下的C\C++开发的程序的崩溃详情。从而辅助修改bug。BreakPad支持的系统平台有:windows(Qt MinGW64 无法使用)、linux、mac、ios、solaris、android ndk
Google breakpad实现原理简单了解
1、不通平台实现方式
Windows:通过SetUnhandledExceptionFilter()设置崩溃回掉函数
Max OS:监听 Mach Exception Port 获取崩溃事件
Linux:监听 SIGILL SIGSEGV 等异常信号 获取崩溃事件
2、Dump格式
minidump是由微软开发的崩溃记录文件格式。minidump为二进制文件,体积小。为了保持统一,breakpad在其他系统下也选择生成minidump文件。
Google breakpad 的具体使用方法
1、使用源码
breakpad提供的是源码,源码提供了configure和Makefile来编译。编译之后获取到一个静态库文件和对应的头文件。在平台的应用软件工程中添加静态库即可使用。在breakpad的源码中有各个平台的使用说明文档,其中Linux平台下的使用方法如下:
#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;
}
1.1、descriptor指定当捕获到崩溃的时候生成minidump文件的储存路径
1.2、dumpCallback这个回掉函数是当保存完minidump文件之后通过这个回掉函数返回minidump文件的保存路径与文件文件名字
跨平台具体使用细则
当使用qt开发跨平台软件使用breakpad的时候,可以对各个平台下使用breakpad方法做一个封装。从而给qt应用程序提供统一的调用接口。(注意:不同平台调用breakpad的函数和头文件是不同的)
由于不同平台下不仅调用函数不同,编译方法也不尽相同。所以这个封装不是特别简单。因此使用使用gitHub上封装好的开源代码了。
这个封装库支持在windows、linux、macos平台下使用breakpad。并且有demo帮助你快速使用qBreakpad。
1 、编译qBreakpad
1.1 下载qBreakpad 源码
git clone --recursive https://github.com/buzzySmile/qBreakpad.git
1.2 下载Google breakpad源码
在源码 third_party目录下 下载最新的breakpad
cd third_party
git clone https://github.com/google/breakpad
1.3、在third_party目录下 下载最新的linux-syscall-support,下载好后把文件夹名字改为 “lss”:
git clone https://github.com/ithaibo/linux-syscall-support
mv linux-syscall-support lss
1.4、编译:
Mac、Linux:
在命令行切换到qBreakpad目录下,执行一下命令:“qmake”就会生成”Makefile”,然后执行“make”,等待编译完成,即可编译出“libqBreakpad.a”库文件。或者用Qt Creater 开“third_party/qBreakpad/qBreakpad.pro”这个文件,并进行编译即可,只不过编译得到的“.a”文件在qBreakpad并行以“build开头”的目录里面。需要手工放置“libqBreakpad.a”到“third_party/qBreakpad/handler”,主程序就能找到这个库:
Windows–MinGW:
(只能编译32、64的可以编译通过但是 无法正常使用):使用mingw在windows下编译可以可以选择用命令行编译(需要配置qmake和mingw32-make的环境变量)。也可以使用QtCreater 按照编译MacOS的下使用QtCreater的步骤编译。注意用mingw在windows下生成的静态库也是以“.a”结尾。但是这个“.a”库和MacOs或Linux下的库是不同的,无法混用。
Windows–MSVC:
用Qt Creater 首先打开“third_party/qBreakpad/qBreakpad.pro” 工程,切换到release下编译,在qBreakpad目录并行以“build开头”的目录里面的Handler目录下可以找到编译好的“libqBreakpad.lib”文件。把这个文件复制到“third_party/qBreakpad/handler”即可。然后用Qt create 编译你调用这个库的工程。调用库的方法参考上面的“Qt多个平台下使用breakpad的方法”。注意需要把工程在Qt creater 的左下角设置为release。然后可以成功编译。这里多次提到用release,是因为在debug下,无论是“libqBreakpad.lib”还是调用他的工程都没法正常编译。报错“MD_DynamicRelease”不匹配值“MDd_DynamicDebug”,没有找到解决办法。还好release 下可用,不影响发布应用程序。
2、qBreakpad 的使用
2.1、pro中的设置
#config for qBreakpad
#CONFIG -= app_bundle #配置上这个参数以后 你的图形界面程序就会以命令行方式运行,
CONFIG += warn_on # warn_off 则不能再次打开使用 会将exe 移动位置实现不能再次运行
CONFIG += thread exceptions rtti stl
macx: LIBS += -framework AppKit
#加入调试信息
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 #此行经过测试可用可不用
#link qBreakpad library
include($$PWD/3rdparty/qBreakpad/qBreakpad.pri)
#end of config for qBreakpad
2.2 main.cpp中的设置
#include "mainwindow.h"
#include <QApplication>
#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();
}
3、解析生成的Dump
软件崩溃生成minidump文件之后,怎样从这个文件通过breakpad提供的工具生成Stack Trace的方法
要想获得stack track需要图示的几个步骤。其中用到的几个工具是:
symbol dumper: 对应到实际命令行工具是:dump_syms
minidump processor:对应到实际命令行工具是:minidump_stackwalk
这两个命令行工具是由breakpad以源码形式提供的,需要使用者在各个平台下编译生成可执行文件,然后才能使用
3.1、编译 dump_syms
Linux、macOS 进入到 breakpad目录,执行下名命令
./configure
make
即可在对平平台 生成dump_syms
breakpad/src/tools/linux/dump_syms/目录生成“dump_syms”
breakpad/src/tools/mac/dump_syms/目录生成“dump_syms“
Window:
breakpad/src/tools/mac/dump_syms/目录 直接使用VS打开工程编译即可获得dump_syms
3.2、编译minidump_stackwalk
Linux,MacOS: 切换到breakpad所在的目录,然后依次执行以下两个命令:
./configure
make
然后在breakpad/src/processor/目录找到“minidump_stackwalk”可执行文件.
Widow: 后续补充 未找到相关文档
3.3 使用dump_syms 生成 symbols文件
Linux,MacOS:
把测试程序test和生成的可执行文件“dump_syms”放到同一目录下,然后执行以下命令生成symbols文件,生成的文件名字指定为:test.sym
./dump_syms ./test > test.sym
3.4 使用“minidump_stackwalk”生成Stack Trace文件
nux,MacOS:
把程序test崩溃生成的xxx.dmp 和test程序对应的test.sym
放到工具“minidump_stackwalk”所在的目录,然后执行以下程序。
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”
3.5 命令脚本
为了简便上线繁琐的步骤,可以使用脚本来实现解析,脚本如下:
#!/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"增加可执行权限。
需要解析的时候执行如下命令
./dump_tool.sh test 3872B2CF-983B-4963-AFA9-C8534DFD4C44.dmp
之后 就会在 symbols文件夹下生成test>3872B2CF-983B-4963-AFA9-C8534DFD4C44>text.sys
当然文件下生成3872B2CF-983B-4963-AFA9-C8534DFD4C44.dmp.text 里面就是具体的崩溃信息