mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 10:56:58 +02:00
kernel: use per-thread signal stacks for guest stack faults
This commit is contained in:
@@ -9,7 +9,9 @@
|
||||
#include <csignal>
|
||||
#include <sys/ucontext.h>
|
||||
#else
|
||||
#include <algorithm>
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <initializer_list>
|
||||
#include <ucontext.h> // IWYU pragma: keep
|
||||
#include <unistd.h>
|
||||
@@ -164,6 +166,46 @@ static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
|
||||
|
||||
#else
|
||||
|
||||
class ThreadSignalStack {
|
||||
public:
|
||||
ThreadSignalStack() {
|
||||
const auto page_size = static_cast<size_t>(::getpagesize());
|
||||
const auto stack_size =
|
||||
(std::max<size_t>(64 * 1024, MINSIGSTKSZ) + page_size - 1) & ~(page_size - 1);
|
||||
if (::posix_memalign(&m_memory, page_size, stack_size) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack_t stack {};
|
||||
stack.ss_sp = m_memory;
|
||||
stack.ss_size = stack_size;
|
||||
if (::sigaltstack(&stack, &m_previous) != 0) {
|
||||
std::free(m_memory);
|
||||
m_memory = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
~ThreadSignalStack() {
|
||||
if (m_memory != nullptr && ::sigaltstack(&m_previous, nullptr) == 0) {
|
||||
std::free(m_memory);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsInitialized() const { return m_memory != nullptr; }
|
||||
|
||||
KYTY_CLASS_NO_COPY(ThreadSignalStack)
|
||||
|
||||
private:
|
||||
void* m_memory = nullptr;
|
||||
stack_t m_previous {};
|
||||
};
|
||||
|
||||
bool InitializeThreadSignalStack() {
|
||||
// Keep fault handling off guest stacks, which GPU tracking can make read-only.
|
||||
thread_local ThreadSignalStack signal_stack;
|
||||
return signal_stack.IsInitialized();
|
||||
}
|
||||
|
||||
// x86-64 page-fault error bits.
|
||||
constexpr uint64_t PAGE_FAULT_ERROR_WRITE = 0x02;
|
||||
constexpr uint64_t PAGE_FAULT_ERROR_INSTRUCTION = 0x10;
|
||||
@@ -274,8 +316,7 @@ bool InstallHandler(Handler handler) {
|
||||
struct sigaction action {};
|
||||
action.sa_sigaction = SignalHandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
// Fault resolution needs the normal thread stack.
|
||||
action.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
action.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;
|
||||
|
||||
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
|
||||
if (::sigaction(signal_number, &action, nullptr) != 0) {
|
||||
|
||||
@@ -39,6 +39,10 @@ using Handler = bool (*)(const ExceptionInfo&);
|
||||
|
||||
bool InstallHandler(Handler handler);
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
bool InitializeThreadSignalStack();
|
||||
#endif
|
||||
|
||||
} // namespace Common::HostException
|
||||
|
||||
#endif /* KYTY_COMMON_HOST_EXCEPTION_H_ */
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common/common.h"
|
||||
#include "common/dateTime.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/hostException.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/stringUtils.h"
|
||||
@@ -1061,6 +1062,12 @@ void PthreadDeleteStaticObjects(Loader::Program* program) {
|
||||
void PthreadInitSelfForMainThread() {
|
||||
EXIT_IF(g_pthread_self != nullptr);
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
if (!Common::HostException::InitializeThreadSignalStack()) {
|
||||
EXIT("Failed to initialize the thread signal stack\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
g_pthread_self = new PthreadPrivate {};
|
||||
PthreadAttrInit(&g_pthread_self->attr);
|
||||
UpdateCurrentThreadStackAttr(&g_pthread_self->attr);
|
||||
@@ -3382,6 +3389,13 @@ static void CleanupThread(void* arg) {
|
||||
}
|
||||
|
||||
static void* RunThread(void* arg) {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
if (!Common::HostException::InitializeThreadSignalStack()) {
|
||||
EXIT("Failed to initialize the thread signal stack\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
auto* thread = static_cast<Pthread>(arg);
|
||||
void* ret = nullptr;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "common/hostException.h"
|
||||
#include "common/virtualMemory.h"
|
||||
#include "graphics/host_gpu/memoryTracker.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
@@ -21,6 +22,7 @@
|
||||
#undef min
|
||||
#undef max
|
||||
#else
|
||||
#include <csignal>
|
||||
#include <map>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/wait.h>
|
||||
@@ -832,6 +834,68 @@ void TestFatalPaths() {
|
||||
}
|
||||
}
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
void *g_fault_stack = nullptr;
|
||||
constexpr size_t FAULT_STACK_SIZE = 64 * 1024;
|
||||
volatile sig_atomic_t g_stack_faults = 0;
|
||||
|
||||
bool HandleStackFault(const Common::HostException::ExceptionInfo &info) {
|
||||
using namespace Common::HostException;
|
||||
stack_t active_stack{};
|
||||
const auto fault_address = reinterpret_cast<uintptr_t>(g_fault_stack) +
|
||||
FAULT_STACK_SIZE - sizeof(uintptr_t);
|
||||
if (info.type != ExceptionType::AccessViolation ||
|
||||
info.access_violation_type != AccessViolationType::Write ||
|
||||
info.access_violation_vaddr != fault_address ||
|
||||
::sigaltstack(nullptr, &active_stack) != 0 ||
|
||||
(active_stack.ss_flags & SS_ONSTACK) == 0) {
|
||||
std::_Exit(1);
|
||||
}
|
||||
g_stack_faults = 1;
|
||||
return ::mprotect(g_fault_stack, FAULT_STACK_SIZE,
|
||||
PROT_READ | PROT_WRITE) == 0;
|
||||
}
|
||||
|
||||
// A stack write must fault before any signal frame can use the protected stack.
|
||||
__attribute__((naked)) void WriteProtectedStack(void *) {
|
||||
asm volatile("mov %rsp, %rax\n"
|
||||
"mov %rdi, %rsp\n"
|
||||
"push %rax\n"
|
||||
"pop %rsp\n"
|
||||
"ret\n");
|
||||
}
|
||||
|
||||
void TestFaultOnProtectedStack() {
|
||||
const pid_t pid = ::fork();
|
||||
Check(pid >= 0, "stack fault fork failed");
|
||||
if (pid == 0) {
|
||||
std::thread worker([] {
|
||||
Check(Common::HostException::InitializeThreadSignalStack(),
|
||||
"initialize thread signal stack failed");
|
||||
Check(Common::HostException::InstallHandler(HandleStackFault),
|
||||
"install stack fault handler failed");
|
||||
g_fault_stack = ::mmap(nullptr, FAULT_STACK_SIZE, PROT_READ,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
Check(g_fault_stack != MAP_FAILED, "allocate protected stack failed");
|
||||
WriteProtectedStack(static_cast<char *>(g_fault_stack) + FAULT_STACK_SIZE);
|
||||
Check(g_stack_faults == 1, "protected stack write did not resume");
|
||||
struct sigaction action{};
|
||||
Check(::sigaction(SIGSEGV, nullptr, &action) == 0 &&
|
||||
action.sa_handler != SIG_DFL,
|
||||
"stack fault reset the process handler");
|
||||
Check(::munmap(g_fault_stack, FAULT_STACK_SIZE) == 0,
|
||||
"release protected stack failed");
|
||||
});
|
||||
worker.join();
|
||||
std::_Exit(0);
|
||||
}
|
||||
int status = 0;
|
||||
Check(::waitpid(pid, &status, 0) == pid && WIFEXITED(status) &&
|
||||
WEXITSTATUS(status) == 0,
|
||||
"fault on a protected stack did not recover");
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Libs::LibKernel::Memory {
|
||||
@@ -863,6 +927,9 @@ int main(int argc, char **argv) {
|
||||
TestGpuUnmarkUsesRegionMask();
|
||||
TestFullRegionGpuUnmarkBatching();
|
||||
TestFatalPaths();
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
TestFaultOnProtectedStack();
|
||||
#endif
|
||||
std::puts("MemoryTrackerTests: all cases passed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user