mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 22:06:59 +01:00
kernel: use per-thread signal stacks for guest stack faults
This commit is contained in:
@@ -9,7 +9,9 @@
|
|||||||
#include <csignal>
|
#include <csignal>
|
||||||
#include <sys/ucontext.h>
|
#include <sys/ucontext.h>
|
||||||
#else
|
#else
|
||||||
|
#include <algorithm>
|
||||||
#include <csignal>
|
#include <csignal>
|
||||||
|
#include <cstdlib>
|
||||||
#include <initializer_list>
|
#include <initializer_list>
|
||||||
#include <ucontext.h> // IWYU pragma: keep
|
#include <ucontext.h> // IWYU pragma: keep
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -164,6 +166,46 @@ static void SignalHandler(int sig, siginfo_t* si, void* uctx) {
|
|||||||
|
|
||||||
#else
|
#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.
|
// x86-64 page-fault error bits.
|
||||||
constexpr uint64_t PAGE_FAULT_ERROR_WRITE = 0x02;
|
constexpr uint64_t PAGE_FAULT_ERROR_WRITE = 0x02;
|
||||||
constexpr uint64_t PAGE_FAULT_ERROR_INSTRUCTION = 0x10;
|
constexpr uint64_t PAGE_FAULT_ERROR_INSTRUCTION = 0x10;
|
||||||
@@ -274,8 +316,7 @@ bool InstallHandler(Handler handler) {
|
|||||||
struct sigaction action {};
|
struct sigaction action {};
|
||||||
action.sa_sigaction = SignalHandler;
|
action.sa_sigaction = SignalHandler;
|
||||||
sigemptyset(&action.sa_mask);
|
sigemptyset(&action.sa_mask);
|
||||||
// Fault resolution needs the normal thread stack.
|
action.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;
|
||||||
action.sa_flags = SA_SIGINFO | SA_RESTART;
|
|
||||||
|
|
||||||
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
|
for (const int signal_number: {SIGSEGV, SIGBUS, SIGILL}) {
|
||||||
if (::sigaction(signal_number, &action, nullptr) != 0) {
|
if (::sigaction(signal_number, &action, nullptr) != 0) {
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ using Handler = bool (*)(const ExceptionInfo&);
|
|||||||
|
|
||||||
bool InstallHandler(Handler handler);
|
bool InstallHandler(Handler handler);
|
||||||
|
|
||||||
|
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||||
|
bool InitializeThreadSignalStack();
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace Common::HostException
|
} // namespace Common::HostException
|
||||||
|
|
||||||
#endif /* KYTY_COMMON_HOST_EXCEPTION_H_ */
|
#endif /* KYTY_COMMON_HOST_EXCEPTION_H_ */
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "common/common.h"
|
#include "common/common.h"
|
||||||
#include "common/dateTime.h"
|
#include "common/dateTime.h"
|
||||||
#include "common/emulatorConfig.h"
|
#include "common/emulatorConfig.h"
|
||||||
|
#include "common/hostException.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
#include "common/stringUtils.h"
|
#include "common/stringUtils.h"
|
||||||
@@ -1061,6 +1062,12 @@ void PthreadDeleteStaticObjects(Loader::Program* program) {
|
|||||||
void PthreadInitSelfForMainThread() {
|
void PthreadInitSelfForMainThread() {
|
||||||
EXIT_IF(g_pthread_self != nullptr);
|
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 {};
|
g_pthread_self = new PthreadPrivate {};
|
||||||
PthreadAttrInit(&g_pthread_self->attr);
|
PthreadAttrInit(&g_pthread_self->attr);
|
||||||
UpdateCurrentThreadStackAttr(&g_pthread_self->attr);
|
UpdateCurrentThreadStackAttr(&g_pthread_self->attr);
|
||||||
@@ -3382,6 +3389,13 @@ static void CleanupThread(void* arg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void* RunThread(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);
|
auto* thread = static_cast<Pthread>(arg);
|
||||||
void* ret = nullptr;
|
void* ret = nullptr;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include "common/hostException.h"
|
||||||
#include "common/virtualMemory.h"
|
#include "common/virtualMemory.h"
|
||||||
#include "graphics/host_gpu/memoryTracker.h"
|
#include "graphics/host_gpu/memoryTracker.h"
|
||||||
#include "graphics/host_gpu/rangeSet.h"
|
#include "graphics/host_gpu/rangeSet.h"
|
||||||
@@ -21,6 +22,7 @@
|
|||||||
#undef min
|
#undef min
|
||||||
#undef max
|
#undef max
|
||||||
#else
|
#else
|
||||||
|
#include <csignal>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/wait.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
|
||||||
|
|
||||||
namespace Libs::LibKernel::Memory {
|
namespace Libs::LibKernel::Memory {
|
||||||
@@ -863,6 +927,9 @@ int main(int argc, char **argv) {
|
|||||||
TestGpuUnmarkUsesRegionMask();
|
TestGpuUnmarkUsesRegionMask();
|
||||||
TestFullRegionGpuUnmarkBatching();
|
TestFullRegionGpuUnmarkBatching();
|
||||||
TestFatalPaths();
|
TestFatalPaths();
|
||||||
|
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||||
|
TestFaultOnProtectedStack();
|
||||||
|
#endif
|
||||||
std::puts("MemoryTrackerTests: all cases passed");
|
std::puts("MemoryTrackerTests: all cases passed");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user