diff --git a/CMakeLists.txt b/CMakeLists.txt index c7f9b5f0..8a6fada2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,11 +140,20 @@ set(DREAVM_SOURCES src/renderer/gl_shader.cc src/sys/files.cc src/sys/keys.cc + src/sys/sigsegv_handler.cc src/sys/system.cc src/trace/trace.cc src/trace/trace_viewer.cc src/main.cc) +if(WIN32) + list(APPEND DREAVM_SOURCES src/sys/sigsegv_handler_win.cc) +elseif(APPLE) + list(APPEND DREAVM_SOURCES src/sys/sigsegv_handler_mac.cc) +else() + list(APPEND DREAVM_SOURCES src/sys/sigsegv_handler_linux.cc) +endif() + # assign source groups for visual studio projects source_group_by_dir(DREAVM_SOURCES) diff --git a/src/hw/dreamcast.cc b/src/hw/dreamcast.cc index 1bb4015c..d41befd8 100644 --- a/src/hw/dreamcast.cc +++ b/src/hw/dreamcast.cc @@ -103,6 +103,10 @@ Dreamcast::~Dreamcast() { } bool Dreamcast::Init() { + if (!(sigsegv_ = SIGSEGVHandler::Install())) { + return false; + } + MapMemory(); if (!aica_->Init()) { diff --git a/src/hw/dreamcast.h b/src/hw/dreamcast.h index 0c217558..e5ffe8b7 100644 --- a/src/hw/dreamcast.h +++ b/src/hw/dreamcast.h @@ -16,6 +16,7 @@ #include "jit/frontend/frontend.h" #include "jit/runtime.h" #include "renderer/backend.h" +#include "sys/sigsegv_handler.h" #include "trace/trace.h" namespace dreavm { @@ -120,6 +121,8 @@ class Dreamcast { hw::holly::TextureCache *texcache() { return texcache_; } hw::holly::TileRenderer *tile_renderer() { return tile_renderer_; } + sys::SIGSEGVHandler *sigsegv() { return sigsegv_; } + renderer::Backend *rb() { return rb_; } void set_rb(renderer::Backend *rb) { rb_ = rb; } @@ -179,6 +182,7 @@ class Dreamcast { hw::holly::TileRenderer *tile_renderer_; // not owned by us + sys::SIGSEGVHandler *sigsegv_; renderer::Backend *rb_; trace::TraceWriter *trace_writer_; }; diff --git a/src/sys/sigsegv_handler.cc b/src/sys/sigsegv_handler.cc new file mode 100644 index 00000000..3b4639cb --- /dev/null +++ b/src/sys/sigsegv_handler.cc @@ -0,0 +1,75 @@ +#include "core/core.h" +#include "core/interval_tree.h" +#include "emu/profiler.h" +#include "sys/sigsegv_handler.h" + +using namespace dreavm::sys; + +SIGSEGVHandler *dreavm::sys::SIGSEGVHandler::global_handler_ = nullptr; + +SIGSEGVHandler *SIGSEGVHandler::Install() { + if (global_handler_) { + return global_handler_; + } + + global_handler_ = CreateSIGSEGVHandler(); + + if (!global_handler_->Init()) { + LOG_WARNING("Failed to install SIGSEGV handler"); + + delete global_handler_; + global_handler_ = nullptr; + } + + return global_handler_; +} + +SIGSEGVHandler::~SIGSEGVHandler() { global_handler_ = nullptr; } + +void SIGSEGVHandler::AddWriteWatch(void *ptr, int size, + WriteWatchHandler handler, void *ctx, + void *data) { + int page_size = GetPageSize(); + uintptr_t physical_start = dreavm::align(reinterpret_cast(ptr), + static_cast(page_size)); + uintptr_t physical_end = + dreavm::align(reinterpret_cast(ptr) + size, + static_cast(page_size)); + + // write protect the pages + CHECK(Protect(reinterpret_cast(physical_start), + static_cast(physical_end - physical_start), ACC_READONLY)); + + write_watches_.Insert( + physical_start, physical_end - 1, + WriteWatch(handler, ctx, data, physical_start, physical_end)); + + // track number of watches + PROFILER_COUNT("WriteWatches", write_watches_.Size()); +} + +bool SIGSEGVHandler::HandleAccessFault(uintptr_t rip, uintptr_t fault_addr) { + WatchTree::node_type *node = write_watches_.Find(fault_addr, fault_addr); + + bool handled = node != nullptr; + + while (node) { + WriteWatch &watch = node->value; + + watch.handler(watch.ctx, watch.data); + + // remove write protection + CHECK(Protect(reinterpret_cast(watch.physical_start), + static_cast(watch.physical_end - watch.physical_start), + ACC_READWRITE)); + + write_watches_.Remove(node); + + node = write_watches_.Find(fault_addr, fault_addr); + } + + // track number of watches + PROFILER_COUNT("WriteWatches", write_watches_.Size()); + + return handled; +} diff --git a/src/sys/sigsegv_handler.h b/src/sys/sigsegv_handler.h new file mode 100644 index 00000000..ea8f83e3 --- /dev/null +++ b/src/sys/sigsegv_handler.h @@ -0,0 +1,61 @@ +#ifndef SIGSEGV_HANDLER_H +#define SIGSEGV_HANDLER_H + +#include +#include +#include "core/interval_tree.h" + +namespace dreavm { +namespace sys { + +// implemented in the platform specific souce file +class SIGSEGVHandler; +extern SIGSEGVHandler *CreateSIGSEGVHandler(); + +enum PageAccess { ACC_READONLY, ACC_READWRITE }; + +typedef std::function WriteWatchHandler; + +struct WriteWatch { + WriteWatch(WriteWatchHandler handler, void *ctx, void *data, + uintptr_t physical_start, uintptr_t physical_end) + : handler(handler), + ctx(ctx), + data(data), + physical_start(physical_start), + physical_end(physical_end) {} + + WriteWatchHandler handler; + void *ctx; + void *data; + uintptr_t physical_start; + uintptr_t physical_end; +}; + +typedef IntervalTree WatchTree; + +class SIGSEGVHandler { + public: + static SIGSEGVHandler *global_handler() { return global_handler_; } + + static SIGSEGVHandler *Install(); + + virtual ~SIGSEGVHandler(); + + void AddWriteWatch(void *ptr, int size, WriteWatchHandler handler, void *ctx, + void *data); + bool HandleAccessFault(uintptr_t rip, uintptr_t fault_addr); + + protected: + static SIGSEGVHandler *global_handler_; + + virtual bool Init() = 0; + virtual int GetPageSize() = 0; + virtual bool Protect(void *ptr, int size, PageAccess access) = 0; + + WatchTree write_watches_; +}; +} +} + +#endif diff --git a/src/sys/sigsegv_handler_linux.cc b/src/sys/sigsegv_handler_linux.cc new file mode 100644 index 00000000..a6167717 --- /dev/null +++ b/src/sys/sigsegv_handler_linux.cc @@ -0,0 +1,61 @@ +#include +#include +#include +#include "core/core.h" +#include "sys/sigsegv_handler_linux.h" + +using namespace dreavm::sys; + +SIGSEGVHandler *dreavm::sys::CreateSIGSEGVHandler() { + return new SIGSEGVHandlerLinux(); +} + +static struct sigaction old_sa; + +static void SignalHandler(int signo, siginfo_t *info, void *ctx) { + ucontext_t *uctx = reinterpret_cast(ctx); + + uintptr_t rip = uctx->uc_mcontext.gregs[REG_RIP]; + uintptr_t fault_addr = reinterpret_cast(info->si_addr); + bool handled = + SIGSEGVHandler::global_handler()->HandleAccessFault(rip, fault_addr); + + if (!handled) { + // call into the original handler if the installed handler fails to handle + // the signal + (*old_sa.sa_sigaction)(signo, info, ctx); + } +} + +SIGSEGVHandlerLinux::~SIGSEGVHandlerLinux() { + sigaction(SIGSEGV, &old_sa, nullptr); +} + +bool SIGSEGVHandlerLinux::Init() { + struct sigaction new_sa; + new_sa.sa_flags = SA_SIGINFO; + sigemptyset(&new_sa.sa_mask); + new_sa.sa_sigaction = &SignalHandler; + + if (sigaction(SIGSEGV, &new_sa, &old_sa) != 0) { + return false; + } + + return true; +} + +int SIGSEGVHandlerLinux::GetPageSize() { return getpagesize(); } + +bool SIGSEGVHandlerLinux::Protect(void *ptr, int size, PageAccess access) { + int prot = PROT_NONE; + switch (access) { + case ACC_READONLY: + prot = PROT_READ; + break; + case ACC_READWRITE: + prot = PROT_READ | PROT_WRITE; + break; + } + + return mprotect(ptr, size, prot) == 0; +} diff --git a/src/sys/sigsegv_handler_linux.h b/src/sys/sigsegv_handler_linux.h new file mode 100644 index 00000000..edd82a79 --- /dev/null +++ b/src/sys/sigsegv_handler_linux.h @@ -0,0 +1,24 @@ +#ifndef SIGSEGV_HANDLER_LINUX +#define SIGSEGV_HANDLER_LINUX + +#include +#include "sys/sigsegv_handler.h" + +namespace dreavm { +namespace sys { + +class SIGSEGVHandlerLinux : public SIGSEGVHandler { + public: + ~SIGSEGVHandlerLinux(); + + protected: + bool Init(); + int GetPageSize(); + bool Protect(void *ptr, int size, PageAccess access); + + private: +}; +} +} + +#endif diff --git a/src/sys/sigsegv_handler_mac.cc b/src/sys/sigsegv_handler_mac.cc new file mode 100644 index 00000000..79ef6d90 --- /dev/null +++ b/src/sys/sigsegv_handler_mac.cc @@ -0,0 +1,141 @@ +#include +#include +#include +#include "core/core.h" +#include "sys/sigsegv_handler_mac.h" + +using namespace dreavm::sys; + +SIGSEGVHandler *dreavm::sys::CreateSIGSEGVHandler() { + return new SIGSEGVHandlerMac(); +} + +// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/exc_server.html +extern "C" boolean_t exc_server(mach_msg_header_t *request_msg, + mach_msg_header_t *reply_msg); + +// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/catch_exception_raise.html +extern "C" kern_return_t catch_exception_raise( + mach_port_t exception_port, mach_port_t thread, mach_port_t task, + exception_type_t exception, exception_data_t code, + mach_msg_type_number_t code_count) { + // get exception state + mach_msg_type_number_t state_count = x86_EXCEPTION_STATE64_COUNT; + x86_exception_state64_t exc_state; + if (thread_get_state(thread, x86_EXCEPTION_STATE64, + reinterpret_cast(&exc_state), + &state_count) != KERN_SUCCESS) { + return KERN_FAILURE; + } + + // get thread state + state_count = x86_THREAD_STATE64_COUNT; + x86_thread_state64_t thread_state; + if (thread_get_state(thread, x86_THREAD_STATE64, + reinterpret_cast(&thread_state), + &state_count) != KERN_SUCCESS) { + return KERN_FAILURE; + } + + uintptr_t rip = thread_state.__rip; + uintptr_t fault_addr = exc_state.__faultvaddr; + bool handled = + SIGSEGVHandler::global_handler()->HandleAccessFault(rip, fault_addr); + if (!handled) { + return KERN_FAILURE; + } + + // reset thread state + if (thread_set_state(thread, x86_THREAD_STATE64, + reinterpret_cast(&thread_state), + state_count) != KERN_SUCCESS) { + return KERN_FAILURE; + } + + return KERN_SUCCESS; +} + +SIGSEGVHandlerMac::~SIGSEGVHandlerMac() { + task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, 0, + EXCEPTION_DEFAULT, 0); + mach_port_deallocate(mach_task_self(), listen_port_); +} + +bool SIGSEGVHandlerMac::Init() { + // allocate port to listen for exceptions + if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, + &listen_port_) != KERN_SUCCESS) { + return false; + } + + // http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_port_insert_right.html + if (mach_port_insert_right(mach_task_self(), listen_port_, listen_port_, + MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS) { + return false; + } + + // filter out any exception other than EXC_BAD_ACCESS + // http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/task_set_exception_ports.html + if (task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, + listen_port_, EXCEPTION_DEFAULT, + MACHINE_THREAD_STATE) != KERN_SUCCESS) { + return false; + } + + // launch thread to listen for exceptions + thread_ = std::thread([this] { ThreadEntry(); }); + thread_.detach(); + + return true; +} + +int SIGSEGVHandlerMac::GetPageSize() { return getpagesize(); } + +bool SIGSEGVHandlerMac::Protect(void *ptr, int size, PageAccess access) { + int prot = PROT_NONE; + switch (access) { + case ACC_READONLY: + prot = PROT_READ; + break; + case ACC_READWRITE: + prot = PROT_READ | PROT_WRITE; + break; + } + + return mprotect(ptr, size, prot) == 0; +} + +void SIGSEGVHandlerMac::ThreadEntry() { + while (true) { + struct { + mach_msg_header_t head; + mach_msg_body_t msgh_body; + char data[1024]; + } msg; + struct { + mach_msg_header_t head; + char data[1024]; + } reply; + + // wait for a message on the exception port + mach_msg_return_t ret = + mach_msg(&msg.head, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(msg), + listen_port_, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (ret != MACH_MSG_SUCCESS) { + LOG_INFO("mach_msg receive failed with %d %s", ret, + mach_error_string(ret)); + break; + } + + // call exc_server, which will call back into catch_exception_raise + exc_server(&msg.head, &reply.head); + + // send the reply + ret = mach_msg(&reply.head, MACH_SEND_MSG, reply.head.msgh_size, 0, + MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (ret != MACH_MSG_SUCCESS) { + LOG_INFO("mach_msg send failed with %d %s", ret, mach_error_string(ret)); + break; + } + } +} diff --git a/src/sys/sigsegv_handler_mac.h b/src/sys/sigsegv_handler_mac.h new file mode 100644 index 00000000..f4bb9573 --- /dev/null +++ b/src/sys/sigsegv_handler_mac.h @@ -0,0 +1,28 @@ +#ifndef SIGSEGV_HANDLER_MAC +#define SIGSEGV_HANDLER_MAC + +#include +#include "sys/sigsegv_handler.h" + +namespace dreavm { +namespace sys { + +class SIGSEGVHandlerMac : public SIGSEGVHandler { + public: + ~SIGSEGVHandlerMac(); + + protected: + bool Init(); + int GetPageSize(); + bool Protect(void *ptr, int size, PageAccess access); + + private: + void ThreadEntry(); + + mach_port_t listen_port_; + std::thread thread_; +}; +} +} + +#endif diff --git a/src/sys/sigsegv_handler_win.cc b/src/sys/sigsegv_handler_win.cc new file mode 100644 index 00000000..99b628ef --- /dev/null +++ b/src/sys/sigsegv_handler_win.cc @@ -0,0 +1,56 @@ +#include +#include "core/core.h" +#include "sys/sigsegv_handler_win.h" + +using namespace dreavm::sys; + +SIGSEGVHandler *dreavm::sys::CreateSIGSEGVHandler() { + return new SIGSEGVHandlerWin(); +} + +static LONG CALLBACK ExceptionHandler(PEXCEPTION_POINTERS ex_info) { + auto code = ex_info->ExceptionRecord->ExceptionCode; + if (code != STATUS_ACCESS_VIOLATION) { + return EXCEPTION_CONTINUE_SEARCH; + } + + uintptr_t rip = ex_info->ContextRecord->Rip; + uintptr_t fault_addr = ex_info->ExceptionRecord->ExceptionInformation[1]; + bool handled = + SIGSEGVHandler::global_handler()->HandleAccessFault(rip, fault_addr); + + if (!handled) { + return EXCEPTION_CONTINUE_SEARCH; + } + + return EXCEPTION_CONTINUE_EXECUTION; +} + +SIGSEGVHandlerWin::~SIGSEGVHandlerWin() { + RemoveVectoredExceptionHandler(ExceptionHandler); +} + +bool SIGSEGVHandlerWin::Init() { + return AddVectoredExceptionHandler(1, ExceptionHandler) != nullptr; +} + +int SIGSEGVHandlerWin::GetPageSize() { + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + return system_info.dwPageSize; +} + +bool SIGSEGVHandlerWin::Protect(void *ptr, int size, PageAccess access) { + DWORD new_protect = PAGE_NOACCESS; + DWORD old_protect; + switch (access) { + case ACC_READONLY: + new_protect = PAGE_READONLY; + break; + case ACC_READWRITE: + new_protect = PAGE_READWRITE; + break; + } + + return VirtualProtect(ptr, size, new_protect, &old_protect); +} diff --git a/src/sys/sigsegv_handler_win.h b/src/sys/sigsegv_handler_win.h new file mode 100644 index 00000000..65597300 --- /dev/null +++ b/src/sys/sigsegv_handler_win.h @@ -0,0 +1,24 @@ +#ifndef SIGSEGV_HANDLER_WIN +#define SIGSEGV_HANDLER_WIN + +#include +#include "sys/sigsegv_handler.h" + +namespace dreavm { +namespace sys { + +class SIGSEGVHandlerWin : public SIGSEGVHandler { + public: + ~SIGSEGVHandlerWin(); + + protected: + bool Init(); + int GetPageSize(); + bool Protect(void *ptr, int size, PageAccess access); + + private: +}; +} +} + +#endif