/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2022 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 #include #include #include "xenia/base/assert.h" #include "xenia/base/host_thread_context.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/base/platform.h" namespace xe { bool signal_handlers_installed_ = false; struct sigaction original_sigill_handler_; struct sigaction original_sigsegv_handler_; // 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]; static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info, void* signal_context) { mcontext_t& mcontext = reinterpret_cast(signal_context)->uc_mcontext; HostThreadContext thread_context; #if XE_ARCH_AMD64 thread_context.rip = uint64_t(mcontext.gregs[REG_RIP]); thread_context.eflags = uint32_t(mcontext.gregs[REG_EFL]); // The REG_ order may be different than the register indices in the // instruction encoding. thread_context.rax = uint64_t(mcontext.gregs[REG_RAX]); thread_context.rcx = uint64_t(mcontext.gregs[REG_RCX]); thread_context.rdx = uint64_t(mcontext.gregs[REG_RDX]); thread_context.rbx = uint64_t(mcontext.gregs[REG_RBX]); thread_context.rsp = uint64_t(mcontext.gregs[REG_RSP]); thread_context.rbp = uint64_t(mcontext.gregs[REG_RBP]); thread_context.rsi = uint64_t(mcontext.gregs[REG_RSI]); thread_context.rdi = uint64_t(mcontext.gregs[REG_RDI]); thread_context.r8 = uint64_t(mcontext.gregs[REG_R8]); thread_context.r9 = uint64_t(mcontext.gregs[REG_R9]); thread_context.r10 = uint64_t(mcontext.gregs[REG_R10]); thread_context.r11 = uint64_t(mcontext.gregs[REG_R11]); thread_context.r12 = uint64_t(mcontext.gregs[REG_R12]); thread_context.r13 = uint64_t(mcontext.gregs[REG_R13]); thread_context.r14 = uint64_t(mcontext.gregs[REG_R14]); thread_context.r15 = uint64_t(mcontext.gregs[REG_R15]); std::memcpy(thread_context.xmm_registers, mcontext.fpregs->_xmm, sizeof(thread_context.xmm_registers)); #elif XE_ARCH_ARM64 std::memcpy(thread_context.x, mcontext.regs, sizeof(thread_context.x)); thread_context.sp = mcontext.sp; thread_context.pc = mcontext.pc; thread_context.pstate = mcontext.pstate; struct fpsimd_context* mcontext_fpsimd = nullptr; struct esr_context* mcontext_esr = nullptr; for (struct _aarch64_ctx* mcontext_extension = reinterpret_cast(mcontext.__reserved); mcontext_extension->magic; mcontext_extension = reinterpret_cast( reinterpret_cast(mcontext_extension) + mcontext_extension->size)) { switch (mcontext_extension->magic) { case FPSIMD_MAGIC: mcontext_fpsimd = reinterpret_cast(mcontext_extension); break; case ESR_MAGIC: mcontext_esr = reinterpret_cast(mcontext_extension); break; default: break; } } assert_not_null(mcontext_fpsimd); if (mcontext_fpsimd) { thread_context.fpsr = mcontext_fpsimd->fpsr; thread_context.fpcr = mcontext_fpsimd->fpcr; std::memcpy(thread_context.v, mcontext_fpsimd->vregs, sizeof(thread_context.v)); } #endif // XE_ARCH Exception ex; switch (signal_number) { case SIGILL: ex.InitializeIllegalInstruction(&thread_context); break; case SIGSEGV: { Exception::AccessViolationOperation access_violation_operation; #if XE_ARCH_AMD64 // x86_pf_error_code::X86_PF_WRITE constexpr uint64_t kX86PageFaultErrorCodeWrite = UINT64_C(1) << 1; access_violation_operation = (uint64_t(mcontext.gregs[REG_ERR]) & kX86PageFaultErrorCodeWrite) ? Exception::AccessViolationOperation::kWrite : Exception::AccessViolationOperation::kRead; #elif XE_ARCH_ARM64 // For a Data Abort (EC - ESR_EL1 bits 31:26 - 0b100100 from a lower // Exception Level, 0b100101 without a change in the Exception Level), // bit 6 is 0 for reading from a memory location, 1 for writing to a // memory location. if (mcontext_esr && ((mcontext_esr->esr >> 26) & 0b111110) == 0b100100) { access_violation_operation = (mcontext_esr->esr & (UINT64_C(1) << 6)) ? Exception::AccessViolationOperation::kWrite : Exception::AccessViolationOperation::kRead; } else { // Determine the memory access direction based on which instruction has // requested it. // esr_context may be unavailable on certain hosts (for instance, on // Android, it was added only in NDK r16 - which is the first NDK // version to support the Android API level 27, while NDK r15 doesn't // have esr_context in its API 26 sigcontext.h). // On AArch64 (unlike on AArch32), the program counter is the address of // the currently executing instruction. bool instruction_is_store; if (IsArm64LoadPrefetchStore( *reinterpret_cast(mcontext.pc), instruction_is_store)) { access_violation_operation = instruction_is_store ? Exception::AccessViolationOperation::kWrite : Exception::AccessViolationOperation::kRead; } else { assert_always( "No ESR in the exception thread context, or it's not a Data " "Abort, and the faulting instruction is not a known load, " "prefetch or store instruction"); access_violation_operation = Exception::AccessViolationOperation::kUnknown; } } #else access_violation_operation = Exception::AccessViolationOperation::kUnknown; #endif // XE_ARCH ex.InitializeAccessViolation( &thread_context, reinterpret_cast(signal_info->si_addr), access_violation_operation); } break; default: assert_unhandled_case(signal_number); } for (size_t i = 0; i < xe::countof(handlers_) && handlers_[i].first; ++i) { if (handlers_[i].first(&ex, handlers_[i].second)) { // Exception handled. #if XE_ARCH_AMD64 mcontext.gregs[REG_RIP] = greg_t(thread_context.rip); mcontext.gregs[REG_EFL] = greg_t(thread_context.eflags); uint32_t modified_register_index; // The order must match the order in X64Register. static const size_t kIntRegisterMap[] = { REG_RAX, REG_RCX, REG_RDX, REG_RBX, REG_RSP, REG_RBP, REG_RSI, REG_RDI, REG_R8, REG_R9, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, }; uint16_t modified_int_registers_remaining = ex.modified_int_registers(); while (xe::bit_scan_forward(modified_int_registers_remaining, &modified_register_index)) { modified_int_registers_remaining &= ~(UINT16_C(1) << modified_register_index); mcontext.gregs[kIntRegisterMap[modified_register_index]] = thread_context.int_registers[modified_register_index]; } uint16_t modified_xmm_registers_remaining = ex.modified_xmm_registers(); while (xe::bit_scan_forward(modified_xmm_registers_remaining, &modified_register_index)) { modified_xmm_registers_remaining &= ~(UINT16_C(1) << modified_register_index); std::memcpy(&mcontext.fpregs->_xmm[modified_register_index], &thread_context.xmm_registers[modified_register_index], sizeof(vec128_t)); } #elif XE_ARCH_ARM64 uint32_t modified_register_index; uint32_t modified_x_registers_remaining = ex.modified_x_registers(); while (xe::bit_scan_forward(modified_x_registers_remaining, &modified_register_index)) { modified_x_registers_remaining &= ~(UINT32_C(1) << modified_register_index); mcontext.regs[modified_register_index] = thread_context.x[modified_register_index]; } mcontext.sp = thread_context.sp; mcontext.pc = thread_context.pc; mcontext.pstate = thread_context.pstate; if (mcontext_fpsimd) { mcontext_fpsimd->fpsr = thread_context.fpsr; mcontext_fpsimd->fpcr = thread_context.fpcr; uint32_t modified_v_registers_remaining = ex.modified_v_registers(); while (xe::bit_scan_forward(modified_v_registers_remaining, &modified_register_index)) { modified_v_registers_remaining &= ~(UINT32_C(1) << modified_register_index); std::memcpy(&mcontext_fpsimd->vregs[modified_register_index], &thread_context.v[modified_register_index], sizeof(vec128_t)); mcontext.regs[modified_register_index] = thread_context.x[modified_register_index]; } } #endif // XE_ARCH return; } } } 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; } } 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