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

1
2
cd breakpad
./configure && make

这一步将会编译src为一个静态链接库,当然这一步给qmake做也可以

  • processor 目录包含minidump的处理代码,不需要包含在客户端中,在需要进行分析的工作机器上使用
  • client 目录包含所有平台的breakpad头文件
  • tools 包含各种处理工具及源码

    编译Breakpad tools

    Breakpad自带的工具区分各个平台有不同的实现,所以编译完Breakpad主体lib只后还需要手动编译tool.

    进入tool下手动编译tool

    1
    xcodebuild -project XXX.xcodeproj -configuration Release

    编译完可执行文件就会出现在build/Release目录下

    在QT项目中使用Breakpad

    如果直接使用Breakpad的静态库,那么只需要在pro文件里加入LIB和INCLUDEPATH就可以使用。

    但是我们也可以自定义Breakpad组件作为项目的一部分。

    创建breakpad.pro文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
     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

    Mac OSX 下需要额外引入两个系统库

    注册handler

    在main前写一个用来回调的函数,用于程序崩溃时breakpad执行自定义的内容,比如说写个log啥的,当然不用也可以

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #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;
    }

    注册handler

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     (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中写上

1
2
int* a = nullptr;
*a = 1;

程序成功的crash并且生成了minidump文件

1
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(文本格式符号文件)

1
breakpad/src/tools/mac/dump_syms/dump_syms ./test > test.sym`

储存符号文件

为了让stack trace能让正常人看得懂,还需要把文本格式的符号文件.sym放到指定的目录下面

1
2
3
# head -n1 test.sym

MODULE mac x86_64 5A8CD7BF25A537BB9AFF54AE8CD140200 test

所以需要新建一个目录为symbols/test/5A8CD7BF25A537BB9AFF54AE8CD140200,再把sym文件放进去

生成stack trace文件

现在就可以生成一个正常人能看得懂的Stack trace文件了

1
breakpad/src/processor/minidump_stackwalk 64975436-F039-4DB2-8A90-C7D66D7F4509.dump ./symbols > test.log

image.png

可以看到Crash在Thread 0,且是在main.cpp的43行,偏移0个字符长的代码发生了崩溃
image.png

跟实际的代码情况一致,这样就可以在以后程序出现崩溃的情况比较容易发现问题所在。如果程序内包含了几个库,看情况需要生成symbol文件。

文章作者: Image
文章链接: https://by.cx/2020/05/14/debug-crash-with-breakpad-and-qt/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 编译程序