quickshell/src/crash/handler.cpp

180 lines
4.8 KiB
C++

#include "handler.hpp"
#include <array>
#include <cstdio>
#include <cstring>
#include <bits/types/sigset_t.h>
#include <breakpad/client/linux/handler/exception_handler.h>
#include <breakpad/client/linux/handler/minidump_descriptor.h>
#include <breakpad/common/linux/linux_libc_support.h>
#include <qdatastream.h>
#include <qfile.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <sys/mman.h>
#include <unistd.h>
#include "../core/crashinfo.hpp"
extern char** environ; // NOLINT
using namespace google_breakpad;
namespace qs::crash {
Q_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg);
struct CrashHandlerPrivate {
ExceptionHandler* exceptionHandler = nullptr;
int minidumpFd = -1;
int infoFd = -1;
static bool minidumpCallback(const MinidumpDescriptor& descriptor, void* context, bool succeeded);
};
CrashHandler::CrashHandler(): d(new CrashHandlerPrivate()) {}
void CrashHandler::init() {
// MinidumpDescriptor has no move constructor and the copy constructor breaks fds.
auto createHandler = [this](const MinidumpDescriptor& desc) {
this->d->exceptionHandler = new ExceptionHandler(
desc,
nullptr,
&CrashHandlerPrivate::minidumpCallback,
this->d,
true,
-1
);
};
qCDebug(logCrashHandler) << "Starting crash handler...";
this->d->minidumpFd = memfd_create("quickshell:minidump", MFD_CLOEXEC);
if (this->d->minidumpFd == -1) {
qCCritical(logCrashHandler
) << "Failed to allocate minidump memfd, minidumps will be saved in the working directory.";
createHandler(MinidumpDescriptor("."));
} else {
qCDebug(logCrashHandler) << "Created memfd" << this->d->minidumpFd
<< "for holding possible minidumps.";
createHandler(MinidumpDescriptor(this->d->minidumpFd));
}
qCInfo(logCrashHandler) << "Crash handler initialized.";
}
void CrashHandler::setInstanceInfo(const InstanceInfo& info) {
this->d->infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC);
if (this->d->infoFd == -1) {
qCCritical(logCrashHandler
) << "Failed to allocate instance info memfd, crash recovery will not work.";
return;
}
QFile file;
file.open(this->d->infoFd, QFile::ReadWrite);
QDataStream ds(&file);
ds << info;
file.flush();
qCDebug(logCrashHandler) << "Stored instance info in memfd" << this->d->infoFd;
}
CrashHandler::~CrashHandler() {
delete this->d->exceptionHandler;
delete this->d;
}
bool CrashHandlerPrivate::minidumpCallback(
const MinidumpDescriptor& /*descriptor*/,
void* context,
bool /*success*/
) {
// A fork that just dies to ensure the coredump is caught by the system.
auto coredumpPid = fork();
if (coredumpPid == 0) {
return false;
}
auto* self = static_cast<CrashHandlerPrivate*>(context);
auto exe = std::array<char, 4096>();
if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) {
perror("Failed to find crash reporter executable.\n");
_exit(-1);
}
auto arg = std::array<char*, 2> {exe.data(), nullptr};
auto env = std::array<char*, 4096>();
auto envi = 0;
auto infoFd = dup(self->infoFd);
auto infoFdStr = std::array<char, 38>();
memcpy(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD=-1" /*\0*/, 30);
if (infoFd != -1) my_uitos(&infoFdStr[27], infoFd, 10);
env[envi++] = infoFdStr.data();
auto corePidStr = std::array<char, 39>();
memcpy(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID=-1" /*\0*/, 31);
if (coredumpPid != -1) my_uitos(&corePidStr[28], coredumpPid, 10);
env[envi++] = corePidStr.data();
auto populateEnv = [&]() {
auto senvi = 0;
while (envi < 4095) {
env[envi++] = environ[senvi++]; // NOLINT
}
env[envi] = nullptr;
};
sigset_t sigset;
sigemptyset(&sigset); // NOLINT (include)
sigprocmask(SIG_SETMASK, &sigset, nullptr); // NOLINT
auto pid = fork();
if (pid == -1) {
perror("Failed to fork and launch crash reporter.\n");
return false;
} else if (pid == 0) {
// dup to remove CLOEXEC
// if already -1 will return -1
auto dumpFd = dup(self->minidumpFd);
auto logFd = dup(CrashInfo::INSTANCE.logFd);
// allow up to 10 digits, which should never happen
auto dumpFdStr = std::array<char, 38>();
auto logFdStr = std::array<char, 37>();
memcpy(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD=-1" /*\0*/, 30);
memcpy(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD=-1" /*\0*/, 29);
if (dumpFd != -1) my_uitos(&dumpFdStr[27], dumpFd, 10);
if (logFd != -1) my_uitos(&logFdStr[26], logFd, 10);
env[envi++] = dumpFdStr.data();
env[envi++] = logFdStr.data();
populateEnv();
execve(exe.data(), arg.data(), env.data());
perror("Failed to launch crash reporter.\n");
_exit(-1);
} else {
populateEnv();
execve(exe.data(), arg.data(), env.data());
perror("Failed to relaunch quickshell.\n");
_exit(-1);
}
return false; // should make sure it hits the system coredump handler
}
} // namespace qs::crash