diff --git a/src/xenia/base/exception_handler_posix.cc b/src/xenia/base/exception_handler_posix.cc index 5c3aa81f0..591928434 100644 --- a/src/xenia/base/exception_handler_posix.cc +++ b/src/xenia/base/exception_handler_posix.cc @@ -9,10 +9,139 @@ #include "xenia/base/exception_handler.h" +#include +#include + +#include "xenia/base/logging.h" + namespace xe { -// TODO(DrChat): Exception handling on linux. -void ExceptionHandler::Install(Handler fn, void* data) {} -void ExceptionHandler::Uninstall(Handler fn, void* data) {} +bool signal_handlers_installed_ = false; +struct sigaction original_sigill_handler_; +struct sigaction original_sigsegv_handler_; -} // namespace xe \ No newline at end of file +// This can be as large as needed, but isn't often needed. +// As we will be sometimes firing many exceptions we want to avoid having to +// scan the table too much or invoke many custom handlers. +constexpr size_t kMaxHandlerCount = 8; + +// All custom handlers, left-aligned and null terminated. +// Executed in order. +std::pair handlers_[kMaxHandlerCount]; + +void ExceptionHandlerCallback(int signum, siginfo_t* siginfo, void* sigctx) { + ucontext_t* ctx = static_cast(sigctx); + + X64Context thread_context; + + thread_context.rip = ctx->uc_mcontext.gregs[REG_RIP]; + thread_context.eflags = ctx->uc_mcontext.gregs[REG_EFL]; + thread_context.rax = ctx->uc_mcontext.gregs[REG_RAX]; + thread_context.rcx = ctx->uc_mcontext.gregs[REG_RCX]; + thread_context.rdx = ctx->uc_mcontext.gregs[REG_RDX]; + thread_context.rbx = ctx->uc_mcontext.gregs[REG_RBX]; + thread_context.rsp = ctx->uc_mcontext.gregs[REG_RSP]; + thread_context.rbp = ctx->uc_mcontext.gregs[REG_RBP]; + thread_context.rsi = ctx->uc_mcontext.gregs[REG_RSI]; + thread_context.rdi = ctx->uc_mcontext.gregs[REG_RDI]; + thread_context.r8 = ctx->uc_mcontext.gregs[REG_R8]; + thread_context.r9 = ctx->uc_mcontext.gregs[REG_R9]; + thread_context.r10 = ctx->uc_mcontext.gregs[REG_R10]; + thread_context.r11 = ctx->uc_mcontext.gregs[REG_R11]; + thread_context.r12 = ctx->uc_mcontext.gregs[REG_R12]; + thread_context.r13 = ctx->uc_mcontext.gregs[REG_R13]; + thread_context.r14 = ctx->uc_mcontext.gregs[REG_R14]; + thread_context.r15 = ctx->uc_mcontext.gregs[REG_R15]; + std::memcpy(thread_context.xmm_registers, ctx->uc_mcontext.fpregs->_xmm, + sizeof(thread_context.xmm_registers)); + + Exception ex; + switch (signum) { + case SIGILL: + ex.InitializeIllegalInstruction(&thread_context); + break; + case SIGSEGV: { + Exception::AccessViolationOperation access_violation_operation = + ((ucontext_t*)sigctx)->uc_mcontext.gregs[REG_ERR] & 0x2 + ? Exception::AccessViolationOperation::kRead + : Exception::AccessViolationOperation::kWrite; + ex.InitializeAccessViolation(&thread_context, + reinterpret_cast(siginfo->si_addr), + access_violation_operation); + } break; + default: + XELOGE("Unhandled signum: 0x{:08X}", signum); + assert_always("Unhandled signum"); + } + + for (size_t i = 0; i < xe::countof(handlers_) && handlers_[i].first; ++i) { + if (handlers_[i].first(&ex, handlers_[i].second)) { + // Exception handled. + return; + } + } + + assert_always("Unhandled exception"); +} + +void ExceptionHandler::Install(Handler fn, void* data) { + if (!signal_handlers_installed_) { + struct sigaction signal_handler; + + std::memset(&signal_handler, 0, sizeof(signal_handler)); + signal_handler.sa_sigaction = ExceptionHandlerCallback; + signal_handler.sa_flags = SA_SIGINFO; + + if (sigaction(SIGILL, &signal_handler, &original_sigill_handler_) != 0) { + assert_always("Failed to install new SIGILL handler"); + } + if (sigaction(SIGSEGV, &signal_handler, &original_sigsegv_handler_) != 0) { + assert_always("Failed to install new SIGSEGV handler"); + } + signal_handlers_installed_ = true; + } + + for (size_t i = 0; i < xe::countof(handlers_); ++i) { + if (!handlers_[i].first) { + handlers_[i].first = fn; + handlers_[i].second = data; + return; + } + } + XELOGW("Too many exception handlers installed"); + assert_always("Too many exception handlers installed"); +} + +void ExceptionHandler::Uninstall(Handler fn, void* data) { + for (size_t i = 0; i < xe::countof(handlers_); ++i) { + if (handlers_[i].first == fn && handlers_[i].second == data) { + for (; i < xe::countof(handlers_) - 1; ++i) { + handlers_[i] = handlers_[i + 1]; + } + handlers_[i].first = nullptr; + handlers_[i].second = nullptr; + break; + } + } + + bool has_any = false; + for (size_t i = 0; i < xe::countof(handlers_); ++i) { + if (handlers_[i].first) { + has_any = true; + break; + } + } + if (!has_any) { + if (signal_handlers_installed_) { + if (sigaction(SIGILL, &original_sigill_handler_, NULL) != 0) { + assert_always("Failed to restore original SIGILL handler"); + } + if (sigaction(SIGSEGV, &original_sigsegv_handler_, NULL) != 0) { + assert_always("Failed to restore original SIGSEGV handler"); + } + signal_handlers_installed_ = false; + } + } +} + +} // namespace xe