#include "handler.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../core/instanceinfo.hpp" extern char** environ; // NOLINT using namespace google_breakpad; namespace qs::crash { namespace { 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::setRelaunchInfo(const RelaunchInfo& 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(context); auto exe = std::array(); 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 {exe.data(), nullptr}; auto env = std::array(); auto envi = 0; auto infoFd = dup(self->infoFd); auto infoFdStr = std::array(); 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(); 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) { auto var = environ[senvi++]; // NOLINT if (var == nullptr) break; env[envi++] = var; } 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(); auto logFdStr = std::array(); 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