From 93e5e92b0ae7809ee0f64d94d51e210c476ee823 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:01:45 +0000 Subject: crashReporter: cleanup code (#12534) various code cleanups, reorders, move off of global NS --- src/debug/CrashReporter.cpp | 247 ------------------------------------- src/debug/CrashReporter.hpp | 7 -- src/debug/crash/CrashReporter.cpp | 252 ++++++++++++++++++++++++++++++++++++++ src/debug/crash/CrashReporter.hpp | 5 + src/debug/crash/SignalSafe.cpp | 34 +++++ src/debug/crash/SignalSafe.hpp | 203 ++++++++++++++++++++++++++++++ 6 files changed, 494 insertions(+), 254 deletions(-) delete mode 100644 src/debug/CrashReporter.cpp delete mode 100644 src/debug/CrashReporter.hpp create mode 100644 src/debug/crash/CrashReporter.cpp create mode 100644 src/debug/crash/CrashReporter.hpp create mode 100644 src/debug/crash/SignalSafe.cpp create mode 100644 src/debug/crash/SignalSafe.hpp (limited to 'src/debug') diff --git a/src/debug/CrashReporter.cpp b/src/debug/CrashReporter.cpp deleted file mode 100644 index 9e871903..00000000 --- a/src/debug/CrashReporter.cpp +++ /dev/null @@ -1,247 +0,0 @@ -#include "CrashReporter.hpp" -#include -#include -#include -#include -#include -#include -#include -#include "../helpers/MiscFunctions.hpp" - -#include "../plugins/PluginSystem.hpp" -#include "../signal-safe.hpp" - -#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) -#include -#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..."}; - -// is not async-signal-safe, fake it with time(NULL) instead -char const* getRandomMessage() { - return MESSAGES[time(nullptr) % (sizeof(MESSAGES) / sizeof(MESSAGES[0]))]; -} - -[[noreturn]] 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 NCrashReporter::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. - { - CMaxLengthCString<255> reportPath; - - const auto HOME = sigGetenv("HOME"); - const auto CACHE_HOME = sigGetenv("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); - //__asm__("int $3"); - if (ret < 0 && errno != EEXIST) { - exitWithError("failed to mkdir() crash report directory\n"); - } - reportPath += "/hyprlandCrashReport"; - reportPath.writeNum(getpid()); - reportPath += ".txt"; - - { - CBufFileWriter<64> stderr_out(STDERR_FILENO); - stderr_out += "Hyprland has crashed :( Consult the crash report at "; - if (!reportPath.boundsExceeded()) { - stderr_out += reportPath.getStr(); - } else { - stderr_out += "[ERROR: Crash report path does not fit into memory! Check if your $CACHE_HOME/$HOME is too deeply nested. Max 255 characters.]"; - } - stderr_out += " for more information.\n"; - stderr_out.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"); - } - } - 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 += sigStrsignal(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 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(&linkMap), RTLD_DL_LINKMAP); - size_t vmaAddr = rc(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/CrashReporter.hpp b/src/debug/CrashReporter.hpp deleted file mode 100644 index 0ba48e7c..00000000 --- a/src/debug/CrashReporter.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "../defines.hpp" - -namespace NCrashReporter { - void createAndSaveCrash(int sig); -}; \ No newline at end of file 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 +#include +#include +#include +#include +#include +#include +#include "../../helpers/MiscFunctions.hpp" + +#include "../../plugins/PluginSystem.hpp" +#include "SignalSafe.hpp" + +#if defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) +#include +#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?", +}; + +// 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 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(&linkMap), RTLD_DL_LINKMAP); + size_t vmaAddr = rc(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 +#endif +#include +#include +#include + +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 + +namespace SignalSafe { + template + 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 + 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(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 += "(argv)); + + CBufFileWriter<64> failmsg(pipefd[1]); + failmsg += " 0) { + write(readbuf, len); + } + if (len < 0) { + *this += "