summaryrefslogtreecommitdiff
path: root/src/debug/crash
diff options
context:
space:
mode:
authorVaxry <43317083+vaxerski@users.noreply.github.com>2025-12-03 16:01:45 +0000
committerGitHub <noreply@github.com>2025-12-03 16:01:45 +0000
commit93e5e92b0ae7809ee0f64d94d51e210c476ee823 (patch)
tree096ff92094b899ee6ed138951caf9e735a148378 /src/debug/crash
parent3cf0280b11f370c11e6839275e547779a33f4a19 (diff)
crashReporter: cleanup code (#12534)
various code cleanups, reorders, move off of global NS
Diffstat (limited to 'src/debug/crash')
-rw-r--r--src/debug/crash/CrashReporter.cpp252
-rw-r--r--src/debug/crash/CrashReporter.hpp5
-rw-r--r--src/debug/crash/SignalSafe.cpp34
-rw-r--r--src/debug/crash/SignalSafe.hpp203
4 files changed, 494 insertions, 0 deletions
diff --git a/src/debug/crash/CrashReporter.cpp b/src/debug/crash/CrashReporter.cpp
new file mode 100644
index 00000000..1b18fce4
--- /dev/null
+++ b/src/debug/crash/CrashReporter.cpp
@@ -0,0 +1,252 @@
+#include "CrashReporter.hpp"
+#include <fcntl.h>
+#include <sys/utsname.h>
+#include <link.h>
+#include <ctime>
+#include <cerrno>
+#include <sys/stat.h>
+#include <filesystem>
+#include "../../helpers/MiscFunctions.hpp"
+
+#include "../../plugins/PluginSystem.hpp"
+#include "SignalSafe.hpp"
+
+#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__)
+#include <sys/sysctl.h>
+#endif
+
+static char const* const MESSAGES[] = {
+ "Sorry, didn't mean to...",
+ "This was an accident, I swear!",
+ "Calm down, it was a misinput! MISINPUT!",
+ "Oops",
+ "Vaxry is going to be upset.",
+ "Who tried dividing by zero?!",
+ "Maybe you should try dusting your PC in the meantime?",
+ "I tried so hard, and got so far...",
+ "I don't feel so good...",
+ "*thud*",
+ "Well this is awkward.",
+ "\"stable\"",
+ "I hope you didn't have any unsaved progress.",
+ "All these computers...",
+ "The math isn't mathing...",
+ "We've got an imposter in the code!",
+ "Well, at least the crash reporter didn't crash!",
+ "Everything's just fi-",
+ "Have you tried asking Hyprland politely not to crash?",
+};
+
+// <random> is not async-signal-safe, fake it with time(NULL) instead
+static char const* getRandomMessage() {
+ return MESSAGES[time(nullptr) % (sizeof(MESSAGES) / sizeof(MESSAGES[0]))];
+}
+
+[[noreturn]] static inline void exitWithError(char const* err) {
+ write(STDERR_FILENO, err, strlen(err));
+ // perror() is not signal-safe, but we use it here
+ // because if the crash-handler already crashed, it can't get any worse.
+ perror("");
+ abort();
+}
+
+void CrashReporter::createAndSaveCrash(int sig) {
+ int reportFd = -1;
+
+ // We're in the signal handler, so we *only* have stack memory.
+ // To save as much stack memory as possible,
+ // destroy things as soon as possible.
+ {
+ SignalSafe::CMaxLengthCString<255> reportPath;
+
+ const auto HOME = SignalSafe::getenv("HOME");
+ const auto CACHE_HOME = SignalSafe::getenv("XDG_CACHE_HOME");
+
+ if (CACHE_HOME && CACHE_HOME[0] != '\0') {
+ reportPath += CACHE_HOME;
+ reportPath += "/hyprland";
+ } else if (HOME && HOME[0] != '\0') {
+ reportPath += HOME;
+ reportPath += "/.cache/hyprland";
+ } else {
+ exitWithError("$CACHE_HOME and $HOME not set, nowhere to report crash\n");
+ return;
+ }
+
+ int ret = mkdir(reportPath.getStr(), S_IRWXU);
+ if (ret < 0 && errno != EEXIST)
+ exitWithError("failed to mkdir() crash report directory\n");
+
+ reportPath += "/hyprlandCrashReport";
+ reportPath.writeNum(getpid());
+ reportPath += ".txt";
+
+ {
+ SignalSafe::CBufFileWriter<64> stderrOut(STDERR_FILENO);
+ stderrOut += "Hyprland has crashed :( Consult the crash report at ";
+ if (!reportPath.boundsExceeded())
+ stderrOut += reportPath.getStr();
+ else
+ stderrOut += "[ERROR: Crash report path does not fit into memory! Check if your $CACHE_HOME/$HOME is too deeply nested. Max 255 characters.]";
+
+ stderrOut += " for more information.\n";
+ stderrOut.flush();
+ }
+
+ reportFd = open(reportPath.getStr(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
+ if (reportFd < 0)
+ exitWithError("Failed to open crash report path for writing");
+ }
+ SignalSafe::CBufFileWriter<512> finalCrashReport(reportFd);
+
+ finalCrashReport += "--------------------------------------------\n Hyprland Crash Report\n--------------------------------------------\n";
+ finalCrashReport += getRandomMessage();
+ finalCrashReport += "\n\n";
+
+ finalCrashReport += "Hyprland received signal ";
+ finalCrashReport.writeNum(sig);
+ finalCrashReport += '(';
+ finalCrashReport += SignalSafe::strsignal(sig);
+ finalCrashReport += ")\nVersion: ";
+ finalCrashReport += GIT_COMMIT_HASH;
+ finalCrashReport += "\nTag: ";
+ finalCrashReport += GIT_TAG;
+ finalCrashReport += "\nDate: ";
+ finalCrashReport += GIT_COMMIT_DATE;
+ finalCrashReport += "\nFlags:\n";
+#if ISDEBUG
+ finalCrashReport += "debug\n";
+#endif
+#ifdef NO_XWAYLAND
+ finalCrashReport += "no xwayland\n";
+#endif
+ finalCrashReport += "\n";
+
+ if (g_pPluginSystem && g_pPluginSystem->pluginCount() > 0) {
+ finalCrashReport += "Hyprland seems to be running with plugins. This crash might not be Hyprland's fault.\nPlugins:\n";
+
+ const size_t count = g_pPluginSystem->pluginCount();
+ std::vector<CPlugin*> plugins(count);
+ g_pPluginSystem->sigGetPlugins(plugins.data(), count);
+
+ for (size_t i = 0; i < count; i++) {
+ auto p = plugins[i];
+ finalCrashReport += '\t';
+ finalCrashReport += p->m_name;
+ finalCrashReport += " (";
+ finalCrashReport += p->m_author;
+ finalCrashReport += ") ";
+ finalCrashReport += p->m_version;
+ finalCrashReport += '\n';
+ }
+
+ finalCrashReport += "\n\n";
+ }
+
+ finalCrashReport += "System info:\n";
+
+ {
+ struct utsname unameInfo;
+ uname(&unameInfo);
+
+ finalCrashReport += "\tSystem name: ";
+ finalCrashReport += unameInfo.sysname;
+ finalCrashReport += "\n\tNode name: ";
+ finalCrashReport += unameInfo.nodename;
+ finalCrashReport += "\n\tRelease: ";
+ finalCrashReport += unameInfo.release;
+ finalCrashReport += "\n\tVersion: ";
+ finalCrashReport += unameInfo.version;
+ finalCrashReport += "\n\n";
+ }
+
+ finalCrashReport += "GPU:\n\t";
+#if defined(__DragonFly__) || defined(__FreeBSD__)
+ finalCrashReport.writeCmdOutput("pciconf -lv | grep -F -A4 vga");
+#else
+ finalCrashReport.writeCmdOutput("lspci -vnn | grep -E '(VGA|Display|3D)'");
+#endif
+
+ finalCrashReport += "\n\nos-release:\n";
+ finalCrashReport.writeCmdOutput("cat /etc/os-release | sed 's/^/\t/'");
+
+ finalCrashReport += '\n';
+ finalCrashReport += getBuiltSystemLibraryNames();
+ finalCrashReport += '\n';
+
+ // dladdr1()/backtrace_symbols()/this entire section allocates, and hence is NOT async-signal-safe.
+ // Make sure that we save the current known crash report information,
+ // so that if we are caught in a deadlock during a call to malloc(),
+ // there is still something to debug from.
+ finalCrashReport.flush();
+
+ finalCrashReport += "Backtrace:\n";
+
+ const auto CALLSTACK = getBacktrace();
+
+#if defined(KERN_PROC_PATHNAME)
+ int mib[] = {
+ CTL_KERN,
+#if defined(__NetBSD__)
+ KERN_PROC_ARGS,
+ -1,
+ KERN_PROC_PATHNAME,
+#else
+ KERN_PROC,
+ KERN_PROC_PATHNAME,
+ -1,
+#endif
+ };
+ u_int miblen = sizeof(mib) / sizeof(mib[0]);
+ char exe[PATH_MAX] = "/nonexistent";
+ size_t sz = sizeof(exe);
+ sysctl(mib, miblen, &exe, &sz, NULL, 0);
+ const auto FPATH = std::filesystem::canonical(exe);
+#elif defined(__OpenBSD__)
+ // Neither KERN_PROC_PATHNAME nor /proc are supported
+ const auto FPATH = std::filesystem::canonical("/usr/local/bin/Hyprland");
+#else
+ const auto FPATH = std::filesystem::canonical("/proc/self/exe");
+#endif
+
+ std::string addrs = "";
+ for (size_t i = 0; i < CALLSTACK.size(); ++i) {
+#ifdef __GLIBC__
+ // convert in memory address to VMA address
+ Dl_info info;
+ struct link_map* linkMap;
+ dladdr1(CALLSTACK[i].adr, &info, rc<void**>(&linkMap), RTLD_DL_LINKMAP);
+ size_t vmaAddr = rc<size_t>(CALLSTACK[i].adr) - linkMap->l_addr;
+#else
+ // musl doesn't define dladdr1
+ size_t vmaAddr = (size_t)CALLSTACK[i].adr;
+#endif
+
+ addrs += std::format("0x{:x} ", vmaAddr);
+ }
+#ifdef __clang__
+ const auto CMD = std::format("llvm-addr2line -e {} -Cf {}", FPATH.c_str(), addrs);
+#else
+ const auto CMD = std::format("addr2line -e {} -Cf {}", FPATH.c_str(), addrs);
+#endif
+
+ const auto ADDR2LINE = execAndGet(CMD.c_str());
+
+ std::stringstream ssin(ADDR2LINE);
+
+ for (size_t i = 0; i < CALLSTACK.size(); ++i) {
+ finalCrashReport += "\t#";
+ finalCrashReport.writeNum(i);
+ finalCrashReport += " | ";
+ finalCrashReport += CALLSTACK[i].desc;
+ std::string functionInfo;
+ std::string fileLineInfo;
+ std::getline(ssin, functionInfo);
+ std::getline(ssin, fileLineInfo);
+ finalCrashReport += std::format("\n\t\t{}\n\t\t{}\n", functionInfo, fileLineInfo);
+ }
+
+ finalCrashReport += "\n\nLog tail:\n";
+
+ finalCrashReport += std::string_view(Debug::m_rollingLog).substr(Debug::m_rollingLog.find('\n') + 1);
+}
diff --git a/src/debug/crash/CrashReporter.hpp b/src/debug/crash/CrashReporter.hpp
new file mode 100644
index 00000000..661f702f
--- /dev/null
+++ b/src/debug/crash/CrashReporter.hpp
@@ -0,0 +1,5 @@
+#pragma once
+
+namespace CrashReporter {
+ void createAndSaveCrash(int sig);
+}; \ No newline at end of file
diff --git a/src/debug/crash/SignalSafe.cpp b/src/debug/crash/SignalSafe.cpp
new file mode 100644
index 00000000..22717f1b
--- /dev/null
+++ b/src/debug/crash/SignalSafe.cpp
@@ -0,0 +1,34 @@
+#include "SignalSafe.hpp"
+
+#ifndef __GLIBC__
+#include <signal.h>
+#endif
+#include <fcntl.h>
+#include <unistd.h>
+#include <cstring>
+
+using namespace SignalSafe;
+
+// NOLINTNEXTLINE
+extern "C" char** environ;
+
+//
+char const* SignalSafe::getenv(char const* name) {
+ const size_t len = strlen(name);
+ for (char** var = environ; *var != nullptr; var++) {
+ if (strncmp(*var, name, len) == 0 && (*var)[len] == '=') {
+ return (*var) + len + 1;
+ }
+ }
+ return nullptr;
+}
+
+char const* SignalSafe::strsignal(int sig) {
+#ifdef __GLIBC__
+ return sigabbrev_np(sig);
+#elif defined(__DragonFly__) || defined(__FreeBSD__)
+ return sys_signame[sig];
+#else
+ return "unknown";
+#endif
+}
diff --git a/src/debug/crash/SignalSafe.hpp b/src/debug/crash/SignalSafe.hpp
new file mode 100644
index 00000000..8ec967fe
--- /dev/null
+++ b/src/debug/crash/SignalSafe.hpp
@@ -0,0 +1,203 @@
+#pragma once
+
+#include "defines.hpp"
+#include <cstring>
+
+namespace SignalSafe {
+ template <uint16_t N>
+ class CMaxLengthCString {
+ public:
+ CMaxLengthCString() {
+ m_str[0] = '\0';
+ }
+
+ void operator+=(char const* rhs) {
+ write(rhs, strlen(rhs));
+ }
+
+ void write(char const* data, size_t len) {
+ if (m_boundsExceeded || m_strPos + len >= N) {
+ m_boundsExceeded = true;
+ return;
+ }
+ memcpy(m_str + m_strPos, data, len);
+ m_strPos += len;
+ m_str[m_strPos] = '\0';
+ }
+
+ void write(char c) {
+ if (m_boundsExceeded || m_strPos + 1 >= N) {
+ m_boundsExceeded = true;
+ return;
+ }
+ m_str[m_strPos] = c;
+ m_strPos++;
+ }
+
+ void writeNum(size_t num) {
+ size_t d = 1;
+
+ while (num / 10 >= d) {
+ d *= 10;
+ }
+
+ while (num > 0) {
+ char c = '0' + (num / d);
+ write(c);
+ num %= d;
+ d /= 10;
+ }
+ }
+
+ char const* getStr() {
+ return m_str;
+ }
+
+ bool boundsExceeded() {
+ return m_boundsExceeded;
+ }
+
+ private:
+ char m_str[N];
+ size_t m_strPos = 0;
+ bool m_boundsExceeded = false;
+ };
+
+ template <uint16_t BUFSIZE>
+ class CBufFileWriter {
+ public:
+ CBufFileWriter(int fd_) : m_fd(fd_) {
+ ;
+ }
+
+ ~CBufFileWriter() {
+ flush();
+ }
+
+ void write(char const* data, size_t len) {
+ while (len > 0) {
+ size_t to_add = std::min(len, sc<size_t>(BUFSIZE) - m_writeBufPos);
+ memcpy(m_writeBuf + m_writeBufPos, data, to_add);
+ data += to_add;
+ len -= to_add;
+ m_writeBufPos += to_add;
+ if (m_writeBufPos == BUFSIZE)
+ flush();
+ }
+ }
+
+ void write(char c) {
+ if (m_writeBufPos == BUFSIZE)
+ flush();
+ m_writeBuf[m_writeBufPos] = c;
+ m_writeBufPos++;
+ }
+
+ void operator+=(char const* str) {
+ write(str, strlen(str));
+ }
+
+ void operator+=(std::string_view str) {
+ write(str.data(), str.size());
+ }
+
+ void operator+=(char c) {
+ write(c);
+ }
+
+ void writeNum(size_t num) {
+ size_t d = 1;
+
+ while (num / 10 >= d) {
+ d *= 10;
+ }
+
+ while (num > 0) {
+ char c = '0' + (num / d);
+ write(c);
+ num %= d;
+ d /= 10;
+ }
+ }
+
+ void writeCmdOutput(const char* cmd) {
+ int pipefd[2];
+ if (pipe(pipefd) < 0) {
+ *this += "<pipe(pipefd) failed with";
+ writeNum(errno);
+ *this += ">\n";
+ return;
+ }
+
+ // terminate child instead of waiting
+ {
+ struct sigaction act;
+ act.sa_handler = SIG_DFL;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = SA_NOCLDWAIT;
+#ifdef SA_RESTORER
+ act.sa_restorer = NULL;
+#endif
+ sigaction(SIGCHLD, &act, nullptr);
+ }
+
+ const pid_t pid = fork();
+
+ if (pid < 0) {
+ *this += "<fork() failed with ";
+ writeNum(errno);
+ *this += ">\n";
+ return;
+ }
+
+ if (pid == 0) {
+ close(pipefd[0]);
+ dup2(pipefd[1], STDOUT_FILENO);
+ char const* const argv[] = {"/bin/sh", "-c", cmd, nullptr};
+ execv("/bin/sh", cc<char* const*>(argv));
+
+ CBufFileWriter<64> failmsg(pipefd[1]);
+ failmsg += "<execv(";
+ failmsg += cmd;
+ failmsg += ") resulted in errno ";
+ failmsg.write(errno);
+ failmsg += ">\n";
+ close(pipefd[1]);
+ abort();
+ } else {
+ close(pipefd[1]);
+ int64_t len = 0;
+ char readbuf[256];
+ while ((len = read(pipefd[0], readbuf, 256)) > 0) {
+ write(readbuf, len);
+ }
+ if (len < 0) {
+ *this += "<interrupted, read() resulted in errno ";
+ writeNum(errno);
+ *this += ">\n";
+ }
+ close(pipefd[0]);
+ }
+ }
+
+ void flush() {
+ size_t i = 0;
+ while (i < m_writeBufPos) {
+ auto written = ::write(m_fd, m_writeBuf + i, m_writeBufPos - i);
+ if (written <= 0) {
+ return;
+ }
+ i += written;
+ }
+ m_writeBufPos = 0;
+ }
+
+ private:
+ char m_writeBuf[BUFSIZE] = {0};
+ size_t m_writeBufPos = 0;
+ int m_fd = 0;
+ };
+
+ char const* getenv(const char* name);
+ char const* strsignal(int sig);
+}