QT使用Breakpad
在c开发中,很容易出现程序Crash而不出现任何提示的情况,尤其是在使用原生c开发的情况,如果用了QT之类的框架还可能会输出一点点线索(也可能没有),在引入dump之前,我们都是用断点的方式debug,但是一旦发布出去,那就只能打印整个流程的所有log,来“云”调试。
所以为了解决收集和调试发布之后的程序,需要收集程序崩溃之后的信息,微软在其Windows平台上提供了minidump,需要引用WinDbg
的库,注册handler也可以生成,但是这一套方案不是特别好用,而且也不能跨平台,所以Breakpad是比较好的第二方案。
Breakpad
Breakpad是谷歌开源的一个跨平台崩溃处理框架,内含崩溃转储、上报、分析一套工作流程框架。
主要的工作流程为:client以library的方式嵌入自己的程序,并设置handler,将会在程序崩溃时将会把一系列的线程列表、调用堆栈和一些系统信息写入minidump文件,再使用dump_syms将编译器生成的调试信息文件生成符号文件,最好在使用minidump_walker生成可以阅读的stack trace。
minidump
Minidump小内存转储文件是微软制定的一个应用程序崩溃后的dump文件标准,由于生成出来的文件抛弃了一些信息,所以生成的文件小,可以很轻松的通过互联网传输到服务器上,可以更有效的收集程序崩溃信息。同时使用minidump还需要配合程序的符号文件才能更好的分析进程。
Mac下使用
首先clone项目,可以从google源或者github源下载
编译Breakpad
cd breakpad
./configure && make
这一步将会编译src
为一个静态链接库,当然这一步给qmake做也可以
- processor 目录包含minidump的处理代码,不需要包含在客户端中,在需要进行分析的工作机器上使用
- client 目录包含所有平台的breakpad头文件
tools 包含各种处理工具及源码
编译Breakpad tools
Breakpad自带的工具区分各个平台有不同的实现,所以编译完Breakpad主体lib只后还需要手动编译tool.
进入tool下手动编译tool
xcodebuild -project XXX.xcodeproj -configuration Release
编译完可执行文件就会出现在build/Release目录下
在QT项目中使用Breakpad
如果直接使用Breakpad的静态库,那么只需要在pro文件里加入LIB和INCLUDEPATH就可以使用。 但是我们也可以自定义Breakpad组件作为项目的一部分。
创建breakpad.pro文件
BREAKPAD_PATH=$$PWD/breakpad
INCLUDEPATH += $$BREAKPAD_PATH/src
DESTDIR = ../bin
mac: {
HEADERS += $$BREAKPAD_PATH/src/client/mac/handler/exception_handler.h
HEADERS += $$BREAKPAD_PATH/src/client/mac/crash_generation/crash_generation_client.h
HEADERS += $$BREAKPAD_PATH/src/client/mac/crash_generation/crash_generation_server.h
HEADERS += $$BREAKPAD_PATH/src/client/mac/crash_generation/client_info.h
HEADERS += $$BREAKPAD_PATH/src/client/mac/handler/minidump_generator.h
HEADERS += $$BREAKPAD_PATH/src/client/mac/handler/dynamic_images.h
HEADERS += $$BREAKPAD_PATH/src/client/mac/handler/breakpad_nlist_64.h
HEADERS += $$BREAKPAD_PATH/src/client/mac/handler/mach_vm_compat.h
HEADERS += $$BREAKPAD_PATH/src/client/minidump_file_writer.h
HEADERS += $$BREAKPAD_PATH/src/client/minidump_file_writer-inl.h
HEADERS += $$BREAKPAD_PATH/src/common/mac/macho_utilities.h
HEADERS += $$BREAKPAD_PATH/src/common/mac/byteswap.h
HEADERS += $$BREAKPAD_PATH/src/common/mac/MachIPC.h
HEADERS += $$BREAKPAD_PATH/src/common/mac/scoped_task_suspend-inl.h
HEADERS += $$BREAKPAD_PATH/src/common/mac/file_id.h
HEADERS += $$BREAKPAD_PATH/src/common/mac/macho_id.h
HEADERS += $$BREAKPAD_PATH/src/common/mac/macho_walker.h
HEADERS += $$BREAKPAD_PATH/src/common/mac/macho_utilities.h
HEADERS += $$BREAKPAD_PATH/src/common/mac/bootstrap_compat.h
HEADERS += $$BREAKPAD_PATH/src/common/mac/string_utilities.h
HEADERS += $$BREAKPAD_PATH/src/common/linux/linux_libc_support.h
HEADERS += $$BREAKPAD_PATH/src/common/string_conversion.h
HEADERS += $$BREAKPAD_PATH/src/common/md5.h
HEADERS += $$BREAKPAD_PATH/src/common/using_std_string.h
HEADERS += $$BREAKPAD_PATH/src/common/convert_UTF.h
HEADERS += $$BREAKPAD_PATH/src/google_breakpad/common/minidump_exception_mac.h
HEADERS += $$BREAKPAD_PATH/src/google_breakpad/common/breakpad_types.h
HEADERS += $$BREAKPAD_PATH/src/google_breakpad/common/minidump_format.h
HEADERS += $$BREAKPAD_PATH/src/google_breakpad/common/minidump_size.h
SOURCES += $$BREAKPAD_PATH/src/client/mac/handler/exception_handler.cc
SOURCES += $$BREAKPAD_PATH/src/client/mac/crash_generation/crash_generation_client.cc
SOURCES += $$BREAKPAD_PATH/src/client/mac/crash_generation/crash_generation_server.cc
SOURCES += $$BREAKPAD_PATH/src/client/mac/handler/minidump_generator.cc
SOURCES += $$BREAKPAD_PATH/src/client/mac/handler/dynamic_images.cc
SOURCES += $$BREAKPAD_PATH/src/client/mac/handler/breakpad_nlist_64.cc
SOURCES += $$BREAKPAD_PATH/src/client/minidump_file_writer.cc
SOURCES += $$BREAKPAD_PATH/src/common/mac/macho_id.cc
SOURCES += $$BREAKPAD_PATH/src/common/mac/macho_walker.cc
SOURCES += $$BREAKPAD_PATH/src/common/mac/macho_utilities.cc
SOURCES += $$BREAKPAD_PATH/src/common/mac/string_utilities.cc
SOURCES += $$BREAKPAD_PATH/src/common/mac/file_id.cc
SOURCES += $$BREAKPAD_PATH/src/common/mac/MachIPC.mm
SOURCES += $$BREAKPAD_PATH/src/common/mac/bootstrap_compat.cc
SOURCES += $$BREAKPAD_PATH/src/common/md5.cc
SOURCES += $$BREAKPAD_PATH/src/common/string_conversion.cc
SOURCES += $$BREAKPAD_PATH/src/common/linux/linux_libc_support.cc
SOURCES += $$BREAKPAD_PATH/src/common/convert_UTF.cc
LIBS += /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
LIBS += /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices
QMAKE_CXXFLAGS+=-g
}
TARGET = breakpad
CONFIG += warn_on
TEMPLATE = lib
注册handler
在main前写一个用来回调的函数,用于程序崩溃时breakpad执行自定义的内容,比如说写个log啥的,当然不用也可以
#include <QtGlobal>
#ifdef Q_OS_WIN
#include <client/windows/handler/exception_handler.h>
#else
#include <client/mac/handler/exception_handler.h>
#endif
#ifdef Q_OS_WIN
bool minidumpCB(const wchar_t* dump_path, const wchar_t* id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool succeeded)
{
#else
bool minidumpCB(const char* dump_path, const char* id, void* context, bool succeeded)
{
#endif
if(succeeded)
{
std::wcout << "Mini Dump file: " << id << ".dump Path: " << dump_path << std::endl;
}
else
{
std::wcout << "failed to dump"<< std::endl;
}
return succeeded;
}
(Mac版注释)
// Creates a new ExceptionHandler instance to handle writing minidumps.
// 创建一个ExceptionHandler去处理写入minidump
// Minidump files will be written to dump_path, and the optional callback
// is called after writing the dump file, as described above.
// minidump文件将会写入指定的dump_path目录下,且会在写入完成之后调用回调函数
// If install_handler is true, then a minidump will be written whenever
// an unhandled exception occurs. If it is false, minidumps will only
// be written when WriteMinidump is called.
// 如果install_handler是true,minidump会在发生未处理的的exception(异常)时写入。否则只会在被调用的情况写入
// If port_name is non-NULL, attempt to perform out-of-process dump generation
// prot_name为非NULL,将会尝试执行进程外dump文件生成
// If port_name is NULL, in-process dump generation will be used.
// 为NULL,则使用进程内dump文件生成
#ifdef QT_NO_DEBUG
#ifdef Q_OS_WIN
google_breakpad::ExceptionHandler eh(dumpLocation.toStdWString(), NULL, minidumpCB, NULL, google_breakpad::ExceptionHandler::HANDLER_ALL);
#else
google_breakpad::ExceptionHandler eh(dumpLocation.toStdString(), NULL, minidumpCB, NULL, true, NULL);
#endif
#endif
到现在以及注册handler成功,可以尝试crash自己的应用程序。比如在main中写上
int* a = nullptr;
*a = 1;
程序成功的crash并且生成了minidump文件
Mini Dump file: 64975436-F039-4DB2-8A90-C7D66D7F4509.dump Path: /Users/Project/C/
- 不过要注意在调试minidump的时候需要用到程序的symbol(符号文件)所以qt中需要选择profile模式编译,qt会在build目录生成一个
.dSYM
文件
使用分析工具
提取符号文件
.dSYM
是一个二进制文件,所以还不能直接用需要使用dump_syms将二进制符号文件转换为text-format symbol files(文本格式符号文件)
breakpad/src/tools/mac/dump_syms/dump_syms ./test > test.sym`
储存符号文件
为了让stack trace能让正常人看得懂,还需要把文本格式的符号文件.sym
放到指定的目录下面
# head -n1 test.sym
MODULE mac x86_64 5A8CD7BF25A537BB9AFF54AE8CD140200 test
所以需要新建一个目录为symbols/test/5A8CD7BF25A537BB9AFF54AE8CD140200
,再把sym文件放进去
生成stack trace文件
现在就可以生成一个正常人能看得懂的Stack trace文件了
breakpad/src/processor/minidump_stackwalk 64975436-F039-4DB2-8A90-C7D66D7F4509.dump ./symbols > test.log
可以看到Crash在Thread 0,且是在main.cpp的43行,偏移0个字符长的代码发生了崩溃
跟实际的代码情况一致,这样就可以在以后程序出现崩溃的情况比较容易发现问题所在。如果程序内包含了几个库,看情况需要生成symbol文件。