// Copyright (C) 2026 half144 and PS5 React contributors // SPDX-License-Identifier: GPL-3.0-or-later // Additional attribution term: see LICENSE-ATTRIBUTION. #include "crash_log.hpp" #include #include #include #include #include #include #include #include #include #include extern "C" int __real_sceKernelDebugOutText(int channel, const char* text); extern "C" { struct ModuleSegment { void* address; std::uint32_t size; std::int32_t prot; }; struct ModuleInfo { std::size_t size; char name[256]; ModuleSegment segments[4]; std::uint32_t segment_count; std::uint8_t fingerprint[20]; }; int sceKernelGetModuleList(int* handles, std::size_t count, std::size_t* actual); int sceKernelGetModuleInfo(int handle, ModuleInfo* info); } namespace { std::atomic log_fd{-1}; // A session that logs in a loop stops here rather than filling the drive. constexpr long kMaxBytes = 16L * 1024 * 1024; std::atomic written{0}; // Lines from before start(), such as how filesystem access chose the folder app.log lives in, wait // here until start() opens it. char early[16 * 1024]; std::atomic early_size{0}; std::atomic started{false}; void append(const char* text, std::size_t size) { if (!started.load(std::memory_order_acquire)) { std::size_t at = early_size.load(); do { if (at + size > sizeof early) return; } while (!early_size.compare_exchange_weak(at, at + size)); std::memcpy(early + at, text, size); return; } const int fd = log_fd.load(std::memory_order_relaxed); if (fd < 0 || written.fetch_add(static_cast(size)) > kMaxBytes) return; (void)!::write(fd, text, size); } // Async-signal-safe: no stdio, no allocation. char* hex(char* out, std::uint64_t value) { *out++ = '0'; *out++ = 'x'; for (int shift = 60; shift >= 0; shift -= 4) *out++ = "0123456789abcdef"[(value >> shift) & 0xf]; return out; } void fatal(int signal, siginfo_t* info, void* context) { char line[160]; char* at = line; const char prefix[] = "[PS5-REACT] fatal signal "; std::memcpy(at, prefix, sizeof prefix - 1); at += sizeof prefix - 1; if (signal >= 10) *at++ = static_cast('0' + signal / 10); *at++ = static_cast('0' + signal % 10); const char address[] = " address "; std::memcpy(at, address, sizeof address - 1); at += sizeof address - 1; at = hex(at, reinterpret_cast(info ? info->si_addr : nullptr)); const auto* uc = static_cast(context); const char rip[] = " rip "; std::memcpy(at, rip, sizeof rip - 1); at += sizeof rip - 1; at = hex(at, uc ? static_cast(uc->uc_mcontext.mc_rip) : 0); const char rsp[] = " rsp "; std::memcpy(at, rsp, sizeof rsp - 1); at += sizeof rsp - 1; at = hex(at, uc ? static_cast(uc->uc_mcontext.mc_rsp) : 0); *at++ = '\n'; append(line, static_cast(at - line)); __real_sceKernelDebugOutText(0, line); ::signal(signal, SIG_DFL); ::raise(signal); } } // Linked with --wrap=sceKernelDebugOutText: every klog line of the title, from any library. extern "C" int __wrap_sceKernelDebugOutText(int channel, const char* text) { if (text) append(text, std::strlen(text)); return __real_sceKernelDebugOutText(channel, text); } namespace { // Where every loaded module sits, so a fault's rip maps to a module and an offset in it. void log_modules() { int handles[256]; std::size_t count = 0; if (sceKernelGetModuleList(handles, 256, &count) != 0) return; for (std::size_t i = 0; i < count && i < 256; ++i) { ModuleInfo info {}; info.size = sizeof info; if (sceKernelGetModuleInfo(handles[i], &info) != 0) continue; char line[400]; const int n = std::snprintf(line, sizeof line, "[PS5-REACT] module %s text=%p+0x%x\n", info.name, info.segment_count ? info.segments[0].address : nullptr, info.segment_count ? info.segments[0].size : 0u); if (n > 0) { append(line, static_cast(n)); __real_sceKernelDebugOutText(0, line); } } } } namespace crash_log { void start(const char* directory) { char current[320], previous[320]; std::snprintf(current, sizeof current, "%s/app.log", directory); std::snprintf(previous, sizeof previous, "%s/app.prev.log", directory); ::rename(current, previous); log_fd = ::open(current, O_WRONLY | O_CREAT | O_TRUNC | O_APPEND, 0644); started.store(true, std::memory_order_release); if (log_fd >= 0) append(early, early_size.load()); struct sigaction action {}; action.sa_sigaction = fatal; action.sa_flags = SA_SIGINFO | SA_RESETHAND; sigemptyset(&action.sa_mask); for (int signal : {SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGABRT, SIGTRAP}) sigaction(signal, &action, nullptr); log_modules(); } }