-
Notifications
You must be signed in to change notification settings - Fork 23
Using Google Breakpad with Qt
Google Breakpad is a cross platform crash handler which generates minidumps when your application crash. Users can send these minidumps to you and it contains valuable information allowing you to figure out why it crashed on them. More information on the Google Breakpad project page.
This page contains notes I made while implementing a Google Breakpad based crash handler for a Qt 5.1.1 C++ application. The implementation was done and tested on both Windows 7 (MSVC Express 2010) and Linux (Ubuntu 12.0.4 and CentOs 5.6) using SVN revision 1281 of the Google Breakpad repository.
The implementation is roughly based on this blog post so its a good reference to fall back to if this page is unclear. All references to other operating systems were removed (Mac etc.).
If something is wrong in the article or if I am not understanding something correctly, please file a bug against this article or feel free to edit the wiki page.
- Svn checkout the Google Breakpad sources:
svn checkout http://google-breakpad.googlecode.com/svn/trunk/ google-breakpad-read-only
- Create a directory called
crashhandler
in your application's source directory. - Create a new class called
CrashHandler
in this directory. The header and source file contents looks like this:
crash_handler.h
#pragma once
#include <QtCore/QString>
namespace Breakpad {
class CrashHandlerPrivate;
class CrashHandler
{
public:
static CrashHandler* instance();
void Init(const QString& reportPath);
void setReportCrashesToSystem(bool report);
bool writeMinidump();
private:
CrashHandler();
~CrashHandler();
Q_DISABLE_COPY(CrashHandler)
CrashHandlerPrivate* d;
};
}
crash_handler.cpp
#include "crash_handler.h"
#include <QtCore/QDir>
#include <QtCore/QProcess>
#include <QtCore/QCoreApplication>
#include <QString>
#if defined(Q_OS_LINUX)
#include "client/linux/handler/exception_handler.h"
#elif defined(Q_OS_WIN32)
#include "client/windows/handler/exception_handler.h"
#endif
namespace Breakpad {
/************************************************************************/
/* CrashHandlerPrivate */
/************************************************************************/
class CrashHandlerPrivate
{
public:
CrashHandlerPrivate()
{
pHandler = NULL;
}
~CrashHandlerPrivate()
{
delete pHandler;
}
void InitCrashHandler(const QString& dumpPath);
static google_breakpad::ExceptionHandler* pHandler;
static bool bReportCrashesToSystem;
};
google_breakpad::ExceptionHandler* CrashHandlerPrivate::pHandler = NULL;
bool CrashHandlerPrivate::bReportCrashesToSystem = false;
/************************************************************************/
/* DumpCallback */
/************************************************************************/
#if defined(Q_OS_WIN32)
bool DumpCallback(const wchar_t* _dump_dir,const wchar_t* _minidump_id,void* context,EXCEPTION_POINTERS* exinfo,MDRawAssertionInfo* assertion,bool success)
#elif defined(Q_OS_LINUX)
bool DumpCallback(const google_breakpad::MinidumpDescriptor &md,void *context, bool success)
#endif
{
Q_UNUSED(context);
#if defined(Q_OS_WIN32)
Q_UNUSED(_dump_dir);
Q_UNUSED(_minidump_id);
Q_UNUSED(assertion);
Q_UNUSED(exinfo);
#endif
qDebug("BreakpadQt crash");
/*
NO STACK USE, NO HEAP USE THERE !!!
Creating QString's, using qDebug, etc. - everything is crash-unfriendly.
*/
return CrashHandlerPrivate::bReportCrashesToSystem ? success : true;
}
void CrashHandlerPrivate::InitCrashHandler(const QString& dumpPath)
{
if ( pHandler != NULL )
return;
#if defined(Q_OS_WIN32)
std::wstring pathAsStr = (const wchar_t*)dumpPath.utf16();
pHandler = new google_breakpad::ExceptionHandler(
pathAsStr,
/*FilterCallback*/ 0,
DumpCallback,
/*context*/
0,
true
);
#elif defined(Q_OS_LINUX)
std::string pathAsStr = dumpPath.toStdString();
google_breakpad::MinidumpDescriptor md(pathAsStr);
pHandler = new google_breakpad::ExceptionHandler(
md,
/*FilterCallback*/ 0,
DumpCallback,
/*context*/ 0,
true,
-1
);
#endif
}
/************************************************************************/
/* CrashHandler */
/************************************************************************/
CrashHandler* CrashHandler::instance()
{
static CrashHandler globalHandler;
return &globalHandler;
}
CrashHandler::CrashHandler()
{
d = new CrashHandlerPrivate();
}
CrashHandler::~CrashHandler()
{
delete d;
}
void CrashHandler::setReportCrashesToSystem(bool report)
{
d->bReportCrashesToSystem = report;
}
bool CrashHandler::writeMinidump()
{
bool res = d->pHandler->WriteMinidump();
if (res) {
qDebug("BreakpadQt: writeMinidump() success.");
} else {
qWarning("BreakpadQt: writeMinidump() failed.");
}
return res;
}
void CrashHandler::Init( const QString& reportPath )
{
d->InitCrashHandler(reportPath);
}
}
- Create a new .pri file for the required files in this directory. I did not explore the other ways of building breakpad on its own. Instead just adding the required files to a .pri and then adding this .pri to the Qt application proved very easy and clean. Note that more required files will probably be needed for other platform which I did not test, but any missing files can easily be added. The resulting pri looks like this:
# ***************************************************************************
# Implemented using http://blog.inventic.eu/2012/08/qt-and-google-breakpad/
# as a the reference.
#
# Get Google Breakpad here: https://code.google.com/p/google-breakpad/
#
# The required breakpad sources have been copied into /src in order to make
# integration with the application smooth and easy.
#
# To use source from Google Breakpad SVN checkout instead, change $$PWD/src
# to path where it was checked out.
#
# ***************************************************************************
HEADERS += $$PWD/crash_handler.h
SOURCES += $$PWD/crash_handler.cpp
INCLUDEPATH += $$PWD
INCLUDEPATH += $$PWD/src
# Windows
win32:HEADERS += $$PWD/src/common/windows/string_utils-inl.h
win32:HEADERS += $$PWD/src/common/windows/guid_string.h
win32:HEADERS += $$PWD/src/client/windows/handler/exception_handler.h
win32:HEADERS += $$PWD/src/client/windows/common/ipc_protocol.h
win32:HEADERS += $$PWD/src/google_breakpad/common/minidump_format.h
win32:HEADERS += $$PWD/src/google_breakpad/common/breakpad_types.h
win32:HEADERS += $$PWD/src/client/windows/crash_generation/crash_generation_client.h
win32:HEADERS += $$PWD/src/common/scoped_ptr.h
win32:SOURCES += $$PWD/src/client/windows/handler/exception_handler.cc
win32:SOURCES += $$PWD/src/common/windows/string_utils.cc
win32:SOURCES += $$PWD/src/common/windows/guid_string.cc
win32:SOURCES += $$PWD/src/client/windows/crash_generation/crash_generation_client.cc
# Linux
unix:HEADERS += $$PWD/src/client/linux/minidump_writer/cpu_set.h
unix:HEADERS += $$PWD/src/client/linux/minidump_writer/proc_cpuinfo_reader.h
unix:HEADERS += $$PWD/src/client/linux/handler/exception_handler.h
unix:HEADERS += $$PWD/src/client/linux/crash_generation/crash_generation_client.h
unix:HEADERS += $$PWD/src/client/linux/handler/minidump_descriptor.h
unix:HEADERS += $$PWD/src/client/linux/minidump_writer/minidump_writer.h
unix:HEADERS += $$PWD/src/client/linux/minidump_writer/line_reader.h
unix:HEADERS += $$PWD/src/client/linux/minidump_writer/linux_dumper.h
unix:HEADERS += $$PWD/src/client/linux/minidump_writer/linux_ptrace_dumper.h
unix:HEADERS += $$PWD/src/client/linux/minidump_writer/directory_reader.h
unix:HEADERS += $$PWD/src/client/linux/log/log.h
unix:HEADERS += $$PWD/src/client/minidump_file_writer-inl.h
unix:HEADERS += $$PWD/src/client/minidump_file_writer.h
unix:HEADERS += $$PWD/src/common/linux/linux_libc_support.h
unix:HEADERS += $$PWD/src/common/linux/eintr_wrapper.h
unix:HEADERS += $$PWD/src/common/linux/ignore_ret.h
unix:HEADERS += $$PWD/src/common/linux/file_id.h
unix:HEADERS += $$PWD/src/common/linux/memory_mapped_file.h
unix:HEADERS += $$PWD/src/common/linux/safe_readlink.h
unix:HEADERS += $$PWD/src/common/linux/guid_creator.h
unix:HEADERS += $$PWD/src/common/linux/elfutils.h
unix:HEADERS += $$PWD/src/common/linux/elfutils-inl.h
unix:HEADERS += $$PWD/src/common/linux/elf_gnu_compat.h
unix:HEADERS += $$PWD/src/common/using_std_string.h
unix:HEADERS += $$PWD/src/common/memory.h
unix:HEADERS += $$PWD/src/common/basictypes.h
unix:HEADERS += $$PWD/src/common/memory_range.h
unix:HEADERS += $$PWD/src/common/string_conversion.h
unix:HEADERS += $$PWD/src/common/convert_UTF.h
unix:HEADERS += $$PWD/src/google_breakpad/common/minidump_format.h
unix:HEADERS += $$PWD/src/google_breakpad/common/minidump_size.h
unix:HEADERS += $$PWD/src/google_breakpad/common/breakpad_types.h
unix:HEADERS += $$PWD/src/common/scoped_ptr.h
unix:HEADERS += $$PWD/src/third_party/lss/linux_syscall_support.h
unix:SOURCES += $$PWD/src/client/linux/crash_generation/crash_generation_client.cc
unix:SOURCES += $$PWD/src/client/linux/handler/exception_handler.cc
unix:SOURCES += $$PWD/src/client/linux/handler/minidump_descriptor.cc
unix:SOURCES += $$PWD/src/client/linux/minidump_writer/minidump_writer.cc
unix:SOURCES += $$PWD/src/client/linux/minidump_writer/linux_dumper.cc
unix:SOURCES += $$PWD/src/client/linux/minidump_writer/linux_ptrace_dumper.cc
unix:SOURCES += $$PWD/src/client/linux/log/log.cc
unix:SOURCES += $$PWD/src/client/minidump_file_writer.cc
unix:SOURCES += $$PWD/src/common/linux/linux_libc_support.cc
unix:SOURCES += $$PWD/src/common/linux/file_id.cc
unix:SOURCES += $$PWD/src/common/linux/memory_mapped_file.cc
unix:SOURCES += $$PWD/src/common/linux/safe_readlink.cc
unix:SOURCES += $$PWD/src/common/linux/guid_creator.cc
unix:SOURCES += $$PWD/src/common/linux/elfutils.cc
unix:SOURCES += $$PWD/src/common/string_conversion.cc
unix:SOURCES += $$PWD/src/common/convert_UTF.c
#breakpad app need debug info inside binaries
unix:QMAKE_CXXFLAGS+=-g
- (Optional) If you prefer to check the breakpad sources into your main code repository to make building on multiple machines easier, only the above files are required instead of checking in everything contained in the breakpad repository. The tree under our _crashhandler _directory should look like this:
.
├── crash_handler.cpp
├── crash_handler.h
├── crash_handler.pri
└── src
├── client
│ ├── linux
│ │ ├── crash_generation
│ │ │ ├── crash_generation_client.cc
│ │ │ └── crash_generation_client.h
│ │ ├── handler
│ │ │ ├── exception_handler.cc
│ │ │ ├── exception_handler.h
│ │ │ ├── minidump_descriptor.cc
│ │ │ └── minidump_descriptor.h
│ │ ├── log
│ │ │ ├── log.cc
│ │ │ └── log.h
│ │ └── minidump_writer
│ │ ├── cpu_set.h
│ │ ├── directory_reader.h
│ │ ├── line_reader.h
│ │ ├── linux_dumper.cc
│ │ ├── linux_dumper.h
│ │ ├── linux_ptrace_dumper.cc
│ │ ├── linux_ptrace_dumper.h
│ │ ├── minidump_writer.cc
│ │ ├── minidump_writer.h
│ │ └── proc_cpuinfo_reader.h
│ ├── minidump_file_writer.cc
│ ├── minidump_file_writer.h
│ ├── minidump_file_writer-inl.h
│ └── windows
│ ├── common
│ │ ├── auto_critical_section.h
│ │ └── ipc_protocol.h
│ ├── crash_generation
│ │ ├── crash_generation_client.cc
│ │ └── crash_generation_client.h
│ └── handler
│ ├── exception_handler.cc
│ └── exception_handler.h
├── common
│ ├── basictypes.h
│ ├── convert_UTF.c
│ ├── convert_UTF.h
│ ├── linux
│ │ ├── eintr_wrapper.h
│ │ ├── elf_gnu_compat.h
│ │ ├── elfutils.cc
│ │ ├── elfutils.h
│ │ ├── elfutils-inl.h
│ │ ├── file_id.cc
│ │ ├── file_id.h
│ │ ├── guid_creator.cc
│ │ ├── guid_creator.h
│ │ ├── ignore_ret.h
│ │ ├── linux_libc_support.cc
│ │ ├── linux_libc_support.h
│ │ ├── memory_mapped_file.cc
│ │ ├── memory_mapped_file.h
│ │ ├── safe_readlink.cc
│ │ └── safe_readlink.h
│ ├── memory.h
│ ├── memory_range.h
│ ├── memory_range_unittest.cc
│ ├── scoped_ptr.h
│ ├── string_conversion.cc
│ ├── string_conversion.h
│ ├── using_std_string.h
│ └── windows
│ ├── guid_string.cc
│ ├── guid_string.h
│ ├── string_utils.cc
│ └── string_utils-inl.h
├── google_breakpad
│ └── common
│ ├── breakpad_types.h
│ ├── minidump_cpu_amd64.h
│ ├── minidump_cpu_arm64.h
│ ├── minidump_cpu_arm.h
│ ├── minidump_cpu_mips.h
│ ├── minidump_cpu_ppc64.h
│ ├── minidump_cpu_ppc.h
│ ├── minidump_cpu_sparc.h
│ ├── minidump_cpu_x86.h
│ ├── minidump_exception_linux.h
│ ├── minidump_exception_mac.h
│ ├── minidump_exception_ps3.h
│ ├── minidump_exception_solaris.h
│ ├── minidump_exception_win32.h
│ ├── minidump_format.h
│ └── minidump_size.h
└── third_party
└── lss
└── linux_syscall_support.h
- Add
crash_handler.pri
to the Qt application's .pro file. - Add the required files to the Qt application's main file:
#include "crash_handler.h"
#include <QStandardPaths>
int buggyFunc() {
delete reinterpret_cast<QString*>(0xFEE1DEAD);
return 0;
}
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
// We put the dumps in the user's home directory for this example:
Breakpad::CrashHandler::instance()->Init(QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
buggyFunc();
return 0;
}
- Run the application. It will crash and produce crash dump file in the path specified during the Init call.
- The above dump file + the symbol file (see next section) is used to debug the crash (see last section).
Exporting of the application symbols is OS dependent as far as I can tell.
- In the breakpad checkout, go to
src\tools\windows\binaries
. Thedump_syms.exe
application does the exporting for us. I had numerous issues in order to getdump_syms
to work. Launching it greeted me with the following output:CoCreateInstance CLSID_DiaSource failed (msdia80.dll unregistered?)
To fix it the following worked for me (fix obtained from here under the "Decoding Windows crash dumps on Linux" section):
- Download a copy of msdia80.dll and put it in
c:\Program Files\Common Files\Microsoft Shared\VC
. - Download a version of dump_syms built against MSVC9 here into the
binaries
directory and rename the file todump_sums_vc9.exe
. It is also possible to build dump_syms manually, but I did not attempt it. - Also download the following files and place them into the
binaries
directory as well: cygstdc++-6.dll, cyggcc_s-1.dll and cygwin1.dll. - Next register all copies of msdia on the computer. I registered the following 3 files:
c:\Windows\system32\regsvr32 "c:\Program Files\Common Files\Microsoft Shared\VC\msdia80.dll"
c:\Windows\system32\regsvr32 "c:\Program Files\Common Files\Microsoft Shared\VC\msdia90.dll"
c:\Windows\system32\regsvr32 "c:\Program Files\Common Files\Microsoft Shared\VC\msdia100.dll"
- The symbol export process basically converts the .pdb file generated by MSVC into a .sym file used by Breakpad. Run the following command from the
binaries
directory:
dump_syms_vc9.exe <qt_app_build_path>\foo.pdb > foo.sym
- That's it. This .sym file will be used when analyzing the minidump.
For this I used the "Get the debugging symbols" section on http://www.chromium.org/developers/decoding-crash-dumps as a reference.
The following steps were required from the breakpad checkout directory:
./configure
make
cd src/tools/linux/dump_syms
./dump_syms <path_to_build_dir>/foo.so > foo.sym
This command produced some warning, although it did not seem to break anything downstream.
<path_to_build_dir>/foo.so, section '.eh_frame': the call frame entry at offset 0x18 uses a DWARF expression to describe how to recover register '.cfa', but this translator cannot yet translate DWARF expressions to Breakpad postfix expressions
- That's it. This .sym file will be used when analyzing the minidump.
Note that dump_syms uses a library, not the main application.
I'm not sure if it is possible to properly analyze the dumps using the build in dump analysis applications found in the Debugging Tools for Windows set of tools since as far as I could figure out the Windows dump and Breakpad formats does not match exactly.
Breakpad does however contains a small application which is intended to make sense out of the dump files.
- In the breakpad checkout, go to
src\tools\windows\binaries
. - Launch the
minidump_stackwalk.exe
with the dump file.
minidump_stackwalk.exe ./foo.dmp foo.sym
- The above command prints a bunch of very useful information to help debug crashes (loaded modules, details of OS on which crash happened, reason for the crash, stack traces for all active threads). I noticed that the stack trace did not contain line numbers which indicates that the debug symbols was not correctly loaded. Inspecting the top part of the output I found this line:
2014-02-26 11:49:53: simple_symbol_supplier.cc:171: INFO: No symbol file at foo.sym/foo.pdb/BD6D3E40BCF34C77861B0EADF13A901C69/foo.sym
- To fix it you need to place the symbols in a specific directory structure. For example:
- Create directory in the
binaries
directory calledfoo.pdb
. - Inside this new directory, create another directory called (notice that I tood the hex characters from the above message)
BD6D3E40BCF34C77861B0EADF13A901C69
. - Copy the symbol file into this new directory.
- Rerun the stack walk application like this:
minidump_stackwalk.exe ./foo.dmp .
See "Notes on the directory structure required in order to use minidump_stackwalk" at the end of this document for more information on the required symbols directory structure.
- Looking at the output of
minidump_stackwalk.exe
now shows correct file numbers for the code in the Qt application.
For this I used the "Get the debugging symbols" section on http://www.chromium.org/developers/decoding-crash-dumps as a reference.
The following steps were required from within the breakpad checkout directory:
./configure && make
-
minidump_stackwalk
can be found in /src/processor
After building, you can run minidump_stackwalk
providing it with the .dmp file as well as the path to the symbols. For example:
minidump_stackwalk foo.dmp ./symbols 2>&1 > foo_stackwalk.txt
See "Notes on the directory structure required in order to use minidump_stackwalk" at the end of this document for more information on the required symbols directory structure.
- When using this approach to get crash reports from end users which will use release builds, we need to make sure the .pdb files on Windows are generated in order to do symbol exporting on the release libraries etc. To ensure that this happens, add the following to the .pro files or all libraries and executables:
# The following makes sure .pdb files are generated in release mode in order to analyze crash reports:
win32-msvc* {
QMAKE_LFLAGS_RELEASE += /MAP
QMAKE_CFLAGS_RELEASE += /Zi
QMAKE_LFLAGS_RELEASE += /debug /opt:ref
}
- Notes on the directory structure required in order to use minidump_stackwalk:
In order to use these symbols with the minidump_stackwalk
tool, you will need to place them in a specific directory structure. To see where they should be placed, you can do the following (note that you need to run it from within the symbols directory otherwise it does not work):
$ cd <symbols_path>
$ <breakpad_path>/src/processor/minidump_stackwalk foo.dmp . 2>&1 | grep "No symbol"
This will produce output like the following:
2014-03-20 12:41:17: simple_symbol_supplier.cc:196: INFO: No symbol file at ./libxcb.so.1/33466400C1C9785E1FADDFEA24D0D5F60/libxcb.so.1.sym
2014-03-20 12:41:17: simple_symbol_supplier.cc:196: INFO: No symbol file at ./libqxcb.so/37BD6BAC43D805F32EB4F3537000E40E0/libqxcb.so.sym
2014-03-20 12:41:17: simple_symbol_supplier.cc:196: INFO: No symbol file at ./ld-2.5.so/2D6E32C01DB690E6E6113D736D9B45AC0/ld-2.5.so.sym
2014-03-20 12:41:17: simple_symbol_supplier.cc:196: INFO: No symbol file at ./libqca-ossl.so/0EA2F5969C8B7B163AA67668688AEED30/libqca-ossl.so.sym
2014-03-20 12:41:17: simple_symbol_supplier.cc:196: INFO: No symbol file at ./libtcl8.4.so/F3E473507067ACA9672BF6E5DAF41ADC0/libtcl8.4.so.sym
2014-03-20 12:41:17: simple_symbol_supplier.cc:196: INFO: No symbol file at ./libstdc++.so.6/9366DEB563B557872C7CDA1F54FD78A90/libstdc++.so.6.sym
2014-03-20 12:41:17: simple_symbol_supplier.cc:196: INFO: No symbol file at ./gconv-modules.cache/000000000000000000000000000000000/gconv-modules.cache.sym
The system libraries can be ignored, and only the libraries for your application are of interest. Instead creating the required symbol tree structure manually, we can automate the process.
The first line of the symbol file contains the information you need to produce this directory structure, for example (your output will vary):
$ head -n1 test.sym
MODULE Linux x86_64 6EDC6ACDB282125843FD59DA9C81BD830 test
$ mkdir -p ./symbols/test/6EDC6ACDB282125843FD59DA9C81BD830
$ mv test.sym ./symbols/test/6EDC6ACDB282125843FD59DA9C81BD830
Obviously it makes sense to automate the process for bigger applications where multiple symbol files are involved. The bash script below will move all .sym files found in the directory from which it is called to the correct places:
for symbol_file in *.sym
do
file_info=$(head -n1 $symbol_file)
IFS=' ' read -a splitlist <<< "${file_info}"
basefilename=${symbol_file:0:${#symbol_file} - 4}
dest_dir=$basefilename/${splitlist[3]}
mkdir -p $dest_dir
mv $symbol_file $dest_dir
echo "$symbol_file -> $dest_dir/$symbol_file"
done
Note that a couple of things can go wrong here:
- First of all, when exporting your symbols make sure to specify the actual library paths to
dump_syms
, not any symbolic links. - For me, the hex code in the first line of some libraries was different to the hex code which was actually listed in the
grep "No symbols"
output and I had to manually fix it. Not sure why this is the case.