crashReporter: cleanup code (#12534)

various code cleanups, reorders, move off of global NS
This commit is contained in:
Vaxry 2025-12-03 16:01:45 +00:00 committed by GitHub
parent 3cf0280b11
commit 93e5e92b0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 255 additions and 222 deletions

View file

@ -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);
}

View file

@ -0,0 +1,5 @@
#pragma once
namespace CrashReporter {
void createAndSaveCrash(int sig);
};

View file

@ -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
}

View file

@ -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);
}