467 lines
12 KiB
C++
467 lines
12 KiB
C++
// This file is part of Desktop App Toolkit,
|
|
// a set of libraries for developing nice desktop applications.
|
|
//
|
|
// For license and copyright information please follow this link:
|
|
// https://github.com/desktop-app/legal/blob/master/LEGAL
|
|
//
|
|
#include "base/crash_report_writer.h"
|
|
|
|
#ifndef DESKTOP_APP_DISABLE_CRASH_REPORTS
|
|
|
|
#include "base/platform/base_platform_info.h"
|
|
#include "base/integration.h"
|
|
#include "base/crash_report_header.h"
|
|
|
|
#include <QtCore/QDir>
|
|
#include <QtCore/QDateTime>
|
|
#include <QtCore/QMutex>
|
|
#include <QtCore/QMutexLocker>
|
|
#include <signal.h>
|
|
#include <new>
|
|
#include <mutex>
|
|
|
|
#if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
|
|
#define USE_BREAKPAD
|
|
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
|
|
|
|
// see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/
|
|
#ifdef Q_OS_WIN
|
|
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4091)
|
|
#include "client/windows/handler/exception_handler.h"
|
|
#pragma warning(pop)
|
|
|
|
#elif defined Q_OS_MAC // Q_OS_WIN
|
|
|
|
#include <execinfo.h>
|
|
#include <signal.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <dlfcn.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef USE_BREAKPAD
|
|
#include "client/mac/handler/exception_handler.h"
|
|
#else // USE_BREAKPAD
|
|
#include "client/crashpad_client.h"
|
|
#endif // USE_BREAKPAD
|
|
|
|
#elif defined Q_OS_UNIX // Q_OS_MAC
|
|
|
|
#include <execinfo.h>
|
|
#include <signal.h>
|
|
#include <sys/syscall.h>
|
|
#include <unistd.h>
|
|
|
|
#include "client/linux/handler/exception_handler.h"
|
|
|
|
#endif // Q_OS_UNIX
|
|
|
|
namespace base {
|
|
namespace {
|
|
|
|
using namespace details;
|
|
|
|
CrashReportWriter *Instance = nullptr;
|
|
|
|
QMutex AnnotationsMutex;
|
|
std::map<std::string, std::string> Annotations;
|
|
|
|
int ReportFileNo = -1;
|
|
|
|
std::atomic<Qt::HANDLE> ReportingThreadId = nullptr;
|
|
bool SkipWriteReportHeader = false;
|
|
bool ReportingHeaderWritten = false;
|
|
std::array<char, 65536> ReportingHeaderData;
|
|
int ReportingHeaderLength = 0;
|
|
QMutex ReportingMutex;
|
|
|
|
#ifdef Q_OS_WIN
|
|
const wchar_t *BreakpadDumpId = nullptr;
|
|
std::wstring FinalReportPath;
|
|
#else // Q_OS_WIN
|
|
const char *BreakpadDumpId = nullptr;
|
|
std::string FinalReportPath;
|
|
#endif // Q_OS_WIN
|
|
|
|
using ReservedMemoryChunk = std::array<gsl::byte, 1024 * 1024>;
|
|
std::unique_ptr<ReservedMemoryChunk> ReservedMemory;
|
|
|
|
const char *PlatformString() {
|
|
if (Platform::IsWindowsStoreBuild()) {
|
|
return Platform::IsWindows64Bit()
|
|
? "WinStore64Bit"
|
|
: "WinStore32Bit";
|
|
} else if (Platform::IsWindows32Bit()) {
|
|
return "Windows32Bit";
|
|
} else if (Platform::IsWindows64Bit()) {
|
|
return "Windows64Bit";
|
|
} else if (Platform::IsMacStoreBuild()) {
|
|
return "MacAppStore";
|
|
} else if (Platform::IsOSXBuild()) {
|
|
return "OSX";
|
|
} else if (Platform::IsMac()) {
|
|
return "MacOS";
|
|
} else if (Platform::IsLinux32Bit()) {
|
|
return "Linux32Bit";
|
|
} else if (Platform::IsLinux64Bit()) {
|
|
return "Linux64bit";
|
|
}
|
|
Unexpected("Platform in CrashReports::PlatformString.");
|
|
}
|
|
|
|
void AddAnnotation(std::string key, std::string value) {
|
|
QMutexLocker lock(&AnnotationsMutex);
|
|
Annotations.emplace(std::move(key), std::move(value));
|
|
}
|
|
|
|
void InstallOperatorNewHandler() {
|
|
ReservedMemory = std::make_unique<ReservedMemoryChunk>();
|
|
std::set_new_handler([] {
|
|
std::set_new_handler(nullptr);
|
|
ReservedMemory.reset();
|
|
Unexpected("Could not allocate!");
|
|
});
|
|
}
|
|
|
|
void InstallQtMessageHandler() {
|
|
static QtMessageHandler original = nullptr;
|
|
original = qInstallMessageHandler([](
|
|
QtMsgType type,
|
|
const QMessageLogContext &context,
|
|
const QString &message) {
|
|
if (original) {
|
|
original(type, context, message);
|
|
}
|
|
if (type == QtFatalMsg && Instance) {
|
|
AddAnnotation("QtFatal", message.toStdString());
|
|
Unexpected("Qt FATAL message was generated!");
|
|
}
|
|
});
|
|
}
|
|
|
|
#ifdef Q_OS_UNIX
|
|
struct sigaction SIG_def[32];
|
|
|
|
void SignalHandler(int signum, siginfo_t *info, void *ucontext) {
|
|
if (signum > 0) {
|
|
sigaction(signum, &SIG_def[signum], 0);
|
|
}
|
|
|
|
#else // Q_OS_UNIX
|
|
void SignalHandler(int signum) {
|
|
#endif // else for Q_OS_UNIX
|
|
|
|
const char* name = 0;
|
|
switch (signum) {
|
|
case SIGABRT: name = "SIGABRT"; break;
|
|
case SIGSEGV: name = "SIGSEGV"; break;
|
|
case SIGILL: name = "SIGILL"; break;
|
|
case SIGFPE: name = "SIGFPE"; break;
|
|
#ifndef Q_OS_WIN
|
|
case SIGBUS: name = "SIGBUS"; break;
|
|
case SIGSYS: name = "SIGSYS"; break;
|
|
#endif // !Q_OS_WIN
|
|
}
|
|
|
|
const auto thread = QThread::currentThreadId();
|
|
if (thread == ReportingThreadId) {
|
|
return;
|
|
}
|
|
|
|
QMutexLocker lock(&ReportingMutex);
|
|
ReportingThreadId = thread;
|
|
|
|
if (SkipWriteReportHeader || ReportFileNo < 0) {
|
|
return;
|
|
}
|
|
if (!ReportingHeaderWritten) {
|
|
ReportingHeaderWritten = true;
|
|
|
|
QMutexLocker lock(&AnnotationsMutex);
|
|
for (const auto &i : Annotations) {
|
|
ReportHeaderWriter() << i.first.c_str() << ": " << i.second.c_str() << "\n";
|
|
}
|
|
ReportHeaderWriter() << "\n";
|
|
}
|
|
if (name) {
|
|
ReportHeaderWriter() << "Caught signal " << signum << " (" << name << ") in thread " << uint64(thread) << "\n";
|
|
} else if (signum == -1) {
|
|
ReportHeaderWriter() << "Google Breakpad caught a crash, minidump written in thread " << uint64(thread) << "\n";
|
|
if (BreakpadDumpId) {
|
|
ReportHeaderWriter() << "Minidump: " << BreakpadDumpId << "\n";
|
|
}
|
|
} else {
|
|
ReportHeaderWriter() << "Caught signal " << signum << " in thread " << uint64(thread) << "\n";
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
_write(ReportFileNo, ReportHeaderBytes(), ReportHeaderLength());
|
|
_close(ReportFileNo);
|
|
#else // Q_OS_WIN
|
|
[[maybe_unused]] auto result_ = write(ReportFileNo, ReportHeaderBytes(), ReportHeaderLength());
|
|
close(ReportFileNo);
|
|
#endif // Q_OS_WIN
|
|
ReportFileNo = -1;
|
|
|
|
#ifdef Q_OS_WIN
|
|
if (BreakpadDumpId) {
|
|
FinalReportPath.append(BreakpadDumpId);
|
|
FinalReportPath.append(L".txt");
|
|
auto handle = int();
|
|
const auto errcode = _wsopen_s(
|
|
&handle,
|
|
FinalReportPath.c_str(),
|
|
_O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY,
|
|
_SH_DENYWR,
|
|
_S_IWRITE);
|
|
if (!errcode) {
|
|
_write(handle, ReportHeaderBytes(), ReportHeaderLength());
|
|
_close(handle);
|
|
}
|
|
}
|
|
#else // Q_OS_WIN
|
|
if (BreakpadDumpId) {
|
|
FinalReportPath.append(BreakpadDumpId);
|
|
const auto good = int(FinalReportPath.size()) - 4;
|
|
if (good > 0 && !strcmp(FinalReportPath.c_str() + good, ".dmp")) {
|
|
FinalReportPath.erase(FinalReportPath.begin() + good, FinalReportPath.end());
|
|
}
|
|
FinalReportPath.append(".txt");
|
|
const auto handle = open(
|
|
FinalReportPath.c_str(),
|
|
O_WRONLY | O_CREAT | O_TRUNC,
|
|
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
|
|
if (handle >= 0) {
|
|
[[maybe_unused]] auto result_ = write(handle, ReportHeaderBytes(), ReportHeaderLength());
|
|
close(handle);
|
|
}
|
|
}
|
|
#endif // Q_OS_WIN
|
|
|
|
ReportingThreadId = nullptr;
|
|
}
|
|
|
|
bool SetSignalHandlers = Platform::IsLinux() || Platform::IsMac();
|
|
bool CrashLogged = false;
|
|
|
|
#ifdef USE_BREAKPAD
|
|
google_breakpad::ExceptionHandler* BreakpadExceptionHandler = 0;
|
|
|
|
#ifdef Q_OS_WIN
|
|
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_MAC // Q_OS_WIN
|
|
bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success)
|
|
#elif defined Q_OS_UNIX // Q_OS_MAC
|
|
bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success)
|
|
#endif // Q_OS_UNIX
|
|
{
|
|
if (CrashLogged) return success;
|
|
CrashLogged = true;
|
|
|
|
#ifdef Q_OS_WIN
|
|
BreakpadDumpId = _minidump_id;
|
|
SignalHandler(-1);
|
|
#else // Q_OS_WIN
|
|
|
|
#ifdef Q_OS_MAC
|
|
BreakpadDumpId = _minidump_id;
|
|
#else // Q_OS_MAC
|
|
BreakpadDumpId = md.path();
|
|
auto afterLastSlash = BreakpadDumpId;
|
|
for (auto ch = afterLastSlash; *ch != 0; ++ch) {
|
|
if (*ch == '/') {
|
|
afterLastSlash = (ch + 1);
|
|
}
|
|
}
|
|
if (*afterLastSlash) {
|
|
BreakpadDumpId = afterLastSlash;
|
|
}
|
|
#endif // else for Q_OS_MAC
|
|
SignalHandler(-1, 0, 0);
|
|
|
|
#endif // else for Q_OS_WIN
|
|
|
|
return success;
|
|
}
|
|
#endif // USE_BREAKPAD
|
|
|
|
} // namespace
|
|
|
|
CrashReportWriter::CrashReportWriter(const QString &path) : _path(path) {
|
|
Expects(Instance == nullptr);
|
|
Expects(_path.endsWith('/'));
|
|
|
|
Instance = this;
|
|
_previousReport = readPreviousReport();
|
|
}
|
|
|
|
CrashReportWriter::~CrashReportWriter() {
|
|
Expects(Instance == this);
|
|
|
|
finishCatching();
|
|
closeReport();
|
|
|
|
Instance = nullptr;
|
|
}
|
|
|
|
void CrashReportWriter::start() {
|
|
AddAnnotation(
|
|
"Launched",
|
|
QDateTime::currentDateTime().toString(
|
|
"dd.MM.yyyy hh:mm:ss"
|
|
).toStdString());
|
|
AddAnnotation("Platform", PlatformString());
|
|
|
|
QDir().mkpath(_path);
|
|
|
|
openReport();
|
|
startCatching();
|
|
}
|
|
|
|
bool CrashReportWriter::openReport() {
|
|
if (ReportFileNo >= 0) {
|
|
return true;
|
|
}
|
|
|
|
// Try to lock the report file to kill
|
|
// all remaining processes that opened it.
|
|
_reportFile.setFileName(reportPath());
|
|
if (!_reportLock.lock(_reportFile, QIODevice::WriteOnly)) {
|
|
return false;
|
|
}
|
|
ReportFileNo = _reportFile.handle();
|
|
if (ReportFileNo < 0) {
|
|
return false;
|
|
}
|
|
#ifdef Q_OS_WIN
|
|
FinalReportPath = _path.toStdWString();
|
|
#else // Q_OS_WIN
|
|
FinalReportPath = QFile::encodeName(_path).toStdString();
|
|
#endif // Q_OS_WIN
|
|
FinalReportPath.reserve(FinalReportPath.size() + 1024);
|
|
|
|
if (SetSignalHandlers) {
|
|
#ifndef Q_OS_WIN
|
|
struct sigaction sigact;
|
|
|
|
sigact.sa_sigaction = SignalHandler;
|
|
sigemptyset(&sigact.sa_mask);
|
|
sigact.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO;
|
|
|
|
sigaction(SIGABRT, &sigact, &SIG_def[SIGABRT]);
|
|
sigaction(SIGSEGV, &sigact, &SIG_def[SIGSEGV]);
|
|
sigaction(SIGILL, &sigact, &SIG_def[SIGILL]);
|
|
sigaction(SIGFPE, &sigact, &SIG_def[SIGFPE]);
|
|
sigaction(SIGBUS, &sigact, &SIG_def[SIGBUS]);
|
|
sigaction(SIGSYS, &sigact, &SIG_def[SIGSYS]);
|
|
#else // !Q_OS_WIN
|
|
signal(SIGABRT, SignalHandler);
|
|
signal(SIGSEGV, SignalHandler);
|
|
signal(SIGILL, SignalHandler);
|
|
signal(SIGFPE, SignalHandler);
|
|
#endif // else for !Q_OS_WIN
|
|
}
|
|
|
|
InstallOperatorNewHandler();
|
|
InstallQtMessageHandler();
|
|
|
|
return true;
|
|
}
|
|
|
|
void CrashReportWriter::closeReport() {
|
|
QMutexLocker lock(&ReportingMutex);
|
|
if (SkipWriteReportHeader) {
|
|
return;
|
|
}
|
|
SkipWriteReportHeader = true;
|
|
lock.unlock();
|
|
|
|
_reportLock.unlock();
|
|
_reportFile.close();
|
|
_reportFile.remove();
|
|
ReportFileNo = -1;
|
|
}
|
|
|
|
void CrashReportWriter::startCatching() {
|
|
#ifdef Q_OS_WIN
|
|
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
|
|
_path.toStdWString(),
|
|
google_breakpad::ExceptionHandler::FilterCallback(nullptr),
|
|
DumpCallback,
|
|
(void*)nullptr, // callback_context
|
|
google_breakpad::ExceptionHandler::HANDLER_ALL,
|
|
MINIDUMP_TYPE(MiniDumpNormal),
|
|
// MINIDUMP_TYPE(MiniDumpWithFullMemory | MiniDumpWithHandleData | MiniDumpWithThreadInfo | MiniDumpWithProcessThreadData | MiniDumpWithFullMemoryInfo | MiniDumpWithUnloadedModules | MiniDumpWithFullAuxiliaryState | MiniDumpIgnoreInaccessibleMemory | MiniDumpWithTokenInformation),
|
|
(const wchar_t*)nullptr, // pipe_name
|
|
(const google_breakpad::CustomClientInfo*)nullptr
|
|
);
|
|
#elif defined Q_OS_MAC // Q_OS_WIN
|
|
|
|
#ifdef USE_BREAKPAD
|
|
#ifndef _DEBUG
|
|
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
|
|
QFile::encodeName(_path).toStdString(),
|
|
/*FilterCallback*/ 0,
|
|
DumpCallback,
|
|
/*context*/ 0,
|
|
true,
|
|
0
|
|
);
|
|
#endif // !_DEBUG
|
|
#else // USE_BREAKPAD
|
|
crashpad::CrashpadClient crashpad_client;
|
|
const auto handler = (Integration::Instance().executablePath() + "/Contents/Helpers/crashpad_handler").toStdString();
|
|
const auto database = QFile::encodeName(_path).constData();
|
|
if (crashpad_client.StartHandler(base::FilePath(handler),
|
|
base::FilePath(database),
|
|
std::string(),
|
|
Annotations,
|
|
std::vector<std::string>(),
|
|
false)) {
|
|
crashpad_client.UseHandler();
|
|
}
|
|
#endif // USE_BREAKPAD
|
|
#elif defined Q_OS_UNIX
|
|
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
|
|
google_breakpad::MinidumpDescriptor(QFile::encodeName(_path).toStdString()),
|
|
/*FilterCallback*/ 0,
|
|
DumpCallback,
|
|
/*context*/ 0,
|
|
true,
|
|
-1
|
|
);
|
|
#endif // Q_OS_UNIX
|
|
}
|
|
|
|
void CrashReportWriter::finishCatching() {
|
|
#ifdef USE_BREAKPAD
|
|
delete base::take(BreakpadExceptionHandler);
|
|
#endif // USE_BREAKPAD
|
|
}
|
|
|
|
void CrashReportWriter::addAnnotation(std::string key, std::string value) {
|
|
AddAnnotation(std::move(key), std::move(value));
|
|
}
|
|
|
|
QString CrashReportWriter::reportPath() const {
|
|
return _path + "report";
|
|
}
|
|
|
|
std::optional<QByteArray> CrashReportWriter::readPreviousReport() {
|
|
auto file = QFile(reportPath());
|
|
if (file.open(QIODevice::ReadOnly)) {
|
|
return file.readAll();
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
} // namespace CrashReports
|
|
|
|
#endif // DESKTOP_APP_DISABLE_CRASH_REPORTS
|