|
| 1 | +#include "handler.hpp" |
| 2 | +#include <array> |
| 3 | +#include <cstdio> |
| 4 | +#include <cstring> |
| 5 | + |
| 6 | +#include <bits/types/sigset_t.h> |
| 7 | +#include <breakpad/client/linux/handler/exception_handler.h> |
| 8 | +#include <breakpad/client/linux/handler/minidump_descriptor.h> |
| 9 | +#include <breakpad/common/linux/linux_libc_support.h> |
| 10 | +#include <qdatastream.h> |
| 11 | +#include <qfile.h> |
| 12 | +#include <qlogging.h> |
| 13 | +#include <qloggingcategory.h> |
| 14 | +#include <sys/mman.h> |
| 15 | +#include <unistd.h> |
| 16 | + |
| 17 | +#include "../core/crashinfo.hpp" |
| 18 | + |
| 19 | +extern char** environ; // NOLINT |
| 20 | + |
| 21 | +using namespace google_breakpad; |
| 22 | + |
| 23 | +namespace qs::crash { |
| 24 | + |
| 25 | +Q_LOGGING_CATEGORY(logCrashHandler, "quickshell.crashhandler", QtWarningMsg); |
| 26 | + |
| 27 | +struct CrashHandlerPrivate { |
| 28 | + ExceptionHandler* exceptionHandler = nullptr; |
| 29 | + int minidumpFd = -1; |
| 30 | + int infoFd = -1; |
| 31 | + |
| 32 | + static bool minidumpCallback(const MinidumpDescriptor& descriptor, void* context, bool succeeded); |
| 33 | +}; |
| 34 | + |
| 35 | +CrashHandler::CrashHandler(): d(new CrashHandlerPrivate()) {} |
| 36 | + |
| 37 | +void CrashHandler::init() { |
| 38 | + // MinidumpDescriptor has no move constructor and the copy constructor breaks fds. |
| 39 | + auto createHandler = [this](const MinidumpDescriptor& desc) { |
| 40 | + this->d->exceptionHandler = new ExceptionHandler( |
| 41 | + desc, |
| 42 | + nullptr, |
| 43 | + &CrashHandlerPrivate::minidumpCallback, |
| 44 | + this->d, |
| 45 | + true, |
| 46 | + -1 |
| 47 | + ); |
| 48 | + }; |
| 49 | + |
| 50 | + qCDebug(logCrashHandler) << "Starting crash handler..."; |
| 51 | + |
| 52 | + this->d->minidumpFd = memfd_create("quickshell:minidump", MFD_CLOEXEC); |
| 53 | + |
| 54 | + if (this->d->minidumpFd == -1) { |
| 55 | + qCCritical(logCrashHandler |
| 56 | + ) << "Failed to allocate minidump memfd, minidumps will be saved in the working directory."; |
| 57 | + createHandler(MinidumpDescriptor(".")); |
| 58 | + } else { |
| 59 | + qCDebug(logCrashHandler) << "Created memfd" << this->d->minidumpFd |
| 60 | + << "for holding possible minidumps."; |
| 61 | + createHandler(MinidumpDescriptor(this->d->minidumpFd)); |
| 62 | + } |
| 63 | + |
| 64 | + qCInfo(logCrashHandler) << "Crash handler initialized."; |
| 65 | +} |
| 66 | + |
| 67 | +void CrashHandler::setInstanceInfo(const InstanceInfo& info) { |
| 68 | + this->d->infoFd = memfd_create("quickshell:instance_info", MFD_CLOEXEC); |
| 69 | + |
| 70 | + if (this->d->infoFd == -1) { |
| 71 | + qCCritical(logCrashHandler |
| 72 | + ) << "Failed to allocate instance info memfd, crash recovery will not work."; |
| 73 | + return; |
| 74 | + } |
| 75 | + |
| 76 | + QFile file; |
| 77 | + file.open(this->d->infoFd, QFile::ReadWrite); |
| 78 | + |
| 79 | + QDataStream ds(&file); |
| 80 | + ds << info; |
| 81 | + file.flush(); |
| 82 | + |
| 83 | + qCDebug(logCrashHandler) << "Stored instance info in memfd" << this->d->infoFd; |
| 84 | +} |
| 85 | + |
| 86 | +CrashHandler::~CrashHandler() { |
| 87 | + delete this->d->exceptionHandler; |
| 88 | + delete this->d; |
| 89 | +} |
| 90 | + |
| 91 | +bool CrashHandlerPrivate::minidumpCallback( |
| 92 | + const MinidumpDescriptor& /*descriptor*/, |
| 93 | + void* context, |
| 94 | + bool /*success*/ |
| 95 | +) { |
| 96 | + // A fork that just dies to ensure the coredump is caught by the system. |
| 97 | + auto coredumpPid = fork(); |
| 98 | + |
| 99 | + if (coredumpPid == 0) { |
| 100 | + return false; |
| 101 | + } |
| 102 | + |
| 103 | + auto* self = static_cast<CrashHandlerPrivate*>(context); |
| 104 | + |
| 105 | + auto exe = std::array<char, 4096>(); |
| 106 | + if (readlink("/proc/self/exe", exe.data(), exe.size() - 1) == -1) { |
| 107 | + perror("Failed to find crash reporter executable.\n"); |
| 108 | + _exit(-1); |
| 109 | + } |
| 110 | + |
| 111 | + auto arg = std::array<char*, 2> {exe.data(), nullptr}; |
| 112 | + |
| 113 | + auto env = std::array<char*, 4096>(); |
| 114 | + auto envi = 0; |
| 115 | + |
| 116 | + auto infoFd = dup(self->infoFd); |
| 117 | + auto infoFdStr = std::array<char, 38>(); |
| 118 | + memcpy(infoFdStr.data(), "__QUICKSHELL_CRASH_INFO_FD=-1" /*\0*/, 30); |
| 119 | + if (infoFd != -1) my_uitos(&infoFdStr[27], infoFd, 10); |
| 120 | + env[envi++] = infoFdStr.data(); |
| 121 | + |
| 122 | + auto corePidStr = std::array<char, 39>(); |
| 123 | + memcpy(corePidStr.data(), "__QUICKSHELL_CRASH_DUMP_PID=-1" /*\0*/, 31); |
| 124 | + if (coredumpPid != -1) my_uitos(&corePidStr[28], coredumpPid, 10); |
| 125 | + env[envi++] = corePidStr.data(); |
| 126 | + |
| 127 | + auto populateEnv = [&]() { |
| 128 | + auto senvi = 0; |
| 129 | + while (envi < 4095) { |
| 130 | + env[envi++] = environ[senvi++]; // NOLINT |
| 131 | + } |
| 132 | + |
| 133 | + env[envi] = nullptr; |
| 134 | + }; |
| 135 | + |
| 136 | + sigset_t sigset; |
| 137 | + sigemptyset(&sigset); // NOLINT (include) |
| 138 | + sigprocmask(SIG_SETMASK, &sigset, nullptr); // NOLINT |
| 139 | + |
| 140 | + auto pid = fork(); |
| 141 | + |
| 142 | + if (pid == -1) { |
| 143 | + perror("Failed to fork and launch crash reporter.\n"); |
| 144 | + return false; |
| 145 | + } else if (pid == 0) { |
| 146 | + // dup to remove CLOEXEC |
| 147 | + // if already -1 will return -1 |
| 148 | + auto dumpFd = dup(self->minidumpFd); |
| 149 | + auto logFd = dup(CrashInfo::INSTANCE.logFd); |
| 150 | + |
| 151 | + // allow up to 10 digits, which should never happen |
| 152 | + auto dumpFdStr = std::array<char, 38>(); |
| 153 | + auto logFdStr = std::array<char, 37>(); |
| 154 | + |
| 155 | + memcpy(dumpFdStr.data(), "__QUICKSHELL_CRASH_DUMP_FD=-1" /*\0*/, 30); |
| 156 | + memcpy(logFdStr.data(), "__QUICKSHELL_CRASH_LOG_FD=-1" /*\0*/, 29); |
| 157 | + |
| 158 | + if (dumpFd != -1) my_uitos(&dumpFdStr[27], dumpFd, 10); |
| 159 | + if (logFd != -1) my_uitos(&logFdStr[26], logFd, 10); |
| 160 | + |
| 161 | + env[envi++] = dumpFdStr.data(); |
| 162 | + env[envi++] = logFdStr.data(); |
| 163 | + |
| 164 | + populateEnv(); |
| 165 | + execve(exe.data(), arg.data(), env.data()); |
| 166 | + |
| 167 | + perror("Failed to launch crash reporter.\n"); |
| 168 | + _exit(-1); |
| 169 | + } else { |
| 170 | + populateEnv(); |
| 171 | + execve(exe.data(), arg.data(), env.data()); |
| 172 | + |
| 173 | + perror("Failed to relaunch quickshell.\n"); |
| 174 | + _exit(-1); |
| 175 | + } |
| 176 | + |
| 177 | + return false; // should make sure it hits the system coredump handler |
| 178 | +} |
| 179 | + |
| 180 | +} // namespace qs::crash |
0 commit comments