mirror of https://github.com/inolen/redream.git
added back sigsegv handler
This commit is contained in:
parent
99c19a05e9
commit
ad3435a720
|
@ -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)
|
||||
|
|
|
@ -103,6 +103,10 @@ Dreamcast::~Dreamcast() {
|
|||
}
|
||||
|
||||
bool Dreamcast::Init() {
|
||||
if (!(sigsegv_ = SIGSEGVHandler::Install())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MapMemory();
|
||||
|
||||
if (!aica_->Init()) {
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
|
|
@ -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<uintptr_t>(ptr),
|
||||
static_cast<uintptr_t>(page_size));
|
||||
uintptr_t physical_end =
|
||||
dreavm::align(reinterpret_cast<uintptr_t>(ptr) + size,
|
||||
static_cast<uintptr_t>(page_size));
|
||||
|
||||
// write protect the pages
|
||||
CHECK(Protect(reinterpret_cast<void *>(physical_start),
|
||||
static_cast<int>(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<void *>(watch.physical_start),
|
||||
static_cast<int>(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;
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
#ifndef SIGSEGV_HANDLER_H
|
||||
#define SIGSEGV_HANDLER_H
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#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<void(void *, void *)> 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<WriteWatch> 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
|
|
@ -0,0 +1,61 @@
|
|||
#include <sys/mman.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#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<ucontext_t *>(ctx);
|
||||
|
||||
uintptr_t rip = uctx->uc_mcontext.gregs[REG_RIP];
|
||||
uintptr_t fault_addr = reinterpret_cast<uintptr_t>(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;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef SIGSEGV_HANDLER_LINUX
|
||||
#define SIGSEGV_HANDLER_LINUX
|
||||
|
||||
#include <thread>
|
||||
#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
|
|
@ -0,0 +1,141 @@
|
|||
#include <mach/mach.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#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<thread_state_t>(&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_t>(&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_t>(&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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef SIGSEGV_HANDLER_MAC
|
||||
#define SIGSEGV_HANDLER_MAC
|
||||
|
||||
#include <thread>
|
||||
#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
|
|
@ -0,0 +1,56 @@
|
|||
#include <windows.h>
|
||||
#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);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef SIGSEGV_HANDLER_WIN
|
||||
#define SIGSEGV_HANDLER_WIN
|
||||
|
||||
#include <thread>
|
||||
#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
|
Loading…
Reference in New Issue