/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2015 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/base/exception_handler.h" #include "xenia/base/assert.h" #include "xenia/base/math.h" #include "xenia/base/platform_win.h" namespace xe { // Handle of the added VectoredExceptionHandler. void* veh_handle_ = nullptr; // Handle of the added VectoredContinueHandler. void* vch_handle_ = nullptr; // 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]; LONG CALLBACK ExceptionHandlerCallback(PEXCEPTION_POINTERS ex_info) { // Visual Studio SetThreadName. if (ex_info->ExceptionRecord->ExceptionCode == 0x406D1388) { return EXCEPTION_CONTINUE_SEARCH; } // TODO(benvanik): avoid this by mapping X64Context virtual? X64Context thread_context; thread_context.rip = ex_info->ContextRecord->Rip; thread_context.eflags = ex_info->ContextRecord->EFlags; std::memcpy(thread_context.int_registers, &ex_info->ContextRecord->Rax, sizeof(thread_context.int_registers)); std::memcpy(thread_context.xmm_registers, &ex_info->ContextRecord->Xmm0, sizeof(thread_context.xmm_registers)); // https://msdn.microsoft.com/en-us/library/ms679331(v=vs.85).aspx // https://msdn.microsoft.com/en-us/library/aa363082(v=vs.85).aspx Exception ex; switch (ex_info->ExceptionRecord->ExceptionCode) { case STATUS_ILLEGAL_INSTRUCTION: ex.InitializeIllegalInstruction(&thread_context); break; case STATUS_ACCESS_VIOLATION: { Exception::AccessViolationOperation access_violation_operation; switch (ex_info->ExceptionRecord->ExceptionInformation[0]) { case 0: access_violation_operation = Exception::AccessViolationOperation::kRead; break; case 1: access_violation_operation = Exception::AccessViolationOperation::kWrite; break; default: access_violation_operation = Exception::AccessViolationOperation::kUnknown; break; } ex.InitializeAccessViolation( &thread_context, ex_info->ExceptionRecord->ExceptionInformation[1], access_violation_operation); } break; default: // Unknown/unhandled type. return EXCEPTION_CONTINUE_SEARCH; } for (size_t i = 0; i < xe::countof(handlers_) && handlers_[i].first; ++i) { if (handlers_[i].first(&ex, handlers_[i].second)) { // Exception handled. // TODO(benvanik): update all thread state? Dirty flags? ex_info->ContextRecord->Rip = thread_context.rip; return EXCEPTION_CONTINUE_EXECUTION; } } return EXCEPTION_CONTINUE_SEARCH; } void ExceptionHandler::Install(Handler fn, void* data) { if (!veh_handle_) { veh_handle_ = AddVectoredExceptionHandler(1, ExceptionHandlerCallback); if (IsDebuggerPresent()) { // TODO(benvanik): do we need a continue handler if a debugger is // attached? // vch_handle_ = AddVectoredContinueHandler(1, ExceptionHandlerCallback); } } for (size_t i = 0; i < xe::countof(handlers_); ++i) { if (!handlers_[i].first) { handlers_[i].first = fn; handlers_[i].second = data; return; } } 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 (veh_handle_) { RemoveVectoredExceptionHandler(veh_handle_); veh_handle_ = nullptr; } if (vch_handle_) { RemoveVectoredContinueHandler(vch_handle_); vch_handle_ = nullptr; } } } } // namespace xe