Files
2026-10-14 04:20:24 +02:00

120 lines
4.7 KiB
C++

// 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 <atomic>
#include <csignal>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <initializer_list>
#include <sys/stat.h>
#include <sys/ucontext.h>
#include <unistd.h>
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<int> 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<long> 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<std::size_t> early_size{0};
std::atomic<bool> 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<long>(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<char>('0' + signal / 10);
*at++ = static_cast<char>('0' + signal % 10);
const char address[] = " address "; std::memcpy(at, address, sizeof address - 1); at += sizeof address - 1;
at = hex(at, reinterpret_cast<std::uintptr_t>(info ? info->si_addr : nullptr));
const auto* uc = static_cast<const ucontext_t*>(context);
const char rip[] = " rip "; std::memcpy(at, rip, sizeof rip - 1); at += sizeof rip - 1;
at = hex(at, uc ? static_cast<std::uint64_t>(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<std::uint64_t>(uc->uc_mcontext.mc_rsp) : 0);
*at++ = '\n';
append(line, static_cast<std::size_t>(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<std::size_t>(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();
}
}