#include "common/hostException.h" #include #include #include #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #include // IWYU pragma: keep #elif defined(__APPLE__) #include #include #endif // IWYU pragma: no_include // IWYU pragma: no_include // IWYU pragma: no_include // IWYU pragma: no_include // IWYU pragma: no_include namespace Common::HostException { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS static std::atomic g_handler {nullptr}; static std::atomic_uint32_t g_install_state {0}; static thread_local bool g_in_exception_filter = false; static_assert(decltype(g_handler)::is_always_lock_free); static_assert(decltype(g_install_state)::is_always_lock_free); [[noreturn]] static void FailFast(const char* reason) noexcept { std::fputs("HostException fail-fast: ", stderr); std::fputs(reason != nullptr ? reason : "unspecified", stderr); std::fputc('\n', stderr); std::fflush(stderr); TerminateProcess(GetCurrentProcess(), static_cast(EXCEPTION_NONCONTINUABLE_EXCEPTION)); std::_Exit(321); } class FilterScope final { public: FilterScope() noexcept { if (g_in_exception_filter) { FailFast("nested exception while resolving a host fault"); } g_in_exception_filter = true; } ~FilterScope() { g_in_exception_filter = false; } KYTY_CLASS_NO_COPY(FilterScope); }; static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) { FilterScope filter_scope; auto* exception_record = exception->ExceptionRecord; if (exception_record->ExceptionCode == DBG_PRINTEXCEPTION_C || exception_record->ExceptionCode == DBG_PRINTEXCEPTION_WIDE_C) { return EXCEPTION_CONTINUE_SEARCH; } if (exception_record->ExceptionCode == 0x406D1388) { // Set a thread name. return EXCEPTION_CONTINUE_EXECUTION; } ExceptionInfo info {}; info.exception_address = reinterpret_cast(exception_record->ExceptionAddress); info.native_code = exception_record->ExceptionCode; info.native_context = exception->ContextRecord; if (exception_record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { info.type = ExceptionType::AccessViolation; switch (exception_record->ExceptionInformation[0]) { case 0: info.access_violation_type = AccessViolationType::Read; break; case 1: info.access_violation_type = AccessViolationType::Write; break; case 8: info.access_violation_type = AccessViolationType::Execute; break; default: info.access_violation_type = AccessViolationType::Unknown; break; } info.access_violation_vaddr = exception_record->ExceptionInformation[1]; } else if (exception_record->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) { info.type = ExceptionType::IllegalInstruction; } else { printf("Unhandled win exception: code=0x%08" PRIx32 ", addr=0x%016" PRIx64 ", rip=0x%016" PRIx64 ", rsp=0x%016" PRIx64 ", rbp=0x%016" PRIx64 "\n", static_cast(exception_record->ExceptionCode), reinterpret_cast(exception_record->ExceptionAddress), exception->ContextRecord->Rip, exception->ContextRecord->Rsp, exception->ContextRecord->Rbp); return EXCEPTION_CONTINUE_SEARCH; } info.rax = exception->ContextRecord->Rax; info.rbx = exception->ContextRecord->Rbx; info.rcx = exception->ContextRecord->Rcx; info.rdx = exception->ContextRecord->Rdx; info.rsi = exception->ContextRecord->Rsi; info.rdi = exception->ContextRecord->Rdi; info.rbp = exception->ContextRecord->Rbp; info.rsp = exception->ContextRecord->Rsp; info.r8 = exception->ContextRecord->R8; info.r9 = exception->ContextRecord->R9; info.r10 = exception->ContextRecord->R10; info.r11 = exception->ContextRecord->R11; info.r12 = exception->ContextRecord->R12; info.r13 = exception->ContextRecord->R13; info.r14 = exception->ContextRecord->R14; info.r15 = exception->ContextRecord->R15; if (g_install_state.load(std::memory_order_acquire) == 0) { FailFast("host exception handler is not installed"); } const auto handler = g_handler.load(std::memory_order_acquire); if (handler == nullptr) { FailFast("host exception callback is null"); } return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH; } #elif defined(__APPLE__) static std::atomic g_handler {nullptr}; static std::atomic_uint32_t g_install_state {0}; static thread_local bool g_in_exception_filter = false; static_assert(decltype(g_handler)::is_always_lock_free); static_assert(decltype(g_install_state)::is_always_lock_free); [[noreturn]] static void FailFast(const char* reason) noexcept { std::fputs("HostException fail-fast: ", stderr); std::fputs(reason != nullptr ? reason : "unspecified", stderr); std::fputc('\n', stderr); std::fflush(stderr); std::_Exit(321); } // Translate the x86-64 page-fault error code (mcontext __es.__err) into an access type. // bit 1 (0x2) = write, bit 4 (0x10) = instruction fetch, otherwise a read. static AccessViolationType DecodeAccess(uint64_t err) { if ((err & 0x10u) != 0) { return AccessViolationType::Execute; } if ((err & 0x2u) != 0) { return AccessViolationType::Write; } return AccessViolationType::Read; } // POSIX signal handler that mirrors the Windows vectored handler: build an ExceptionInfo // from the mcontext and dispatch. A resolved fault (handler returns true) simply returns, // re-executing the faulting instruction against the now-fixed protection. An unresolved // fault restores the default disposition so the retry terminates the process. static void SignalHandler(int sig, siginfo_t* si, void* uctx) { if (g_in_exception_filter) { FailFast("nested exception while resolving a host fault"); } g_in_exception_filter = true; auto* uc = static_cast(uctx); const auto* mc = uc->uc_mcontext; const auto& ss = mc->__ss; ExceptionInfo info {}; info.exception_address = ss.__rip; info.native_code = static_cast(si->si_code); info.native_context = uctx; if (sig == SIGILL) { info.type = ExceptionType::IllegalInstruction; } else { info.type = ExceptionType::AccessViolation; info.access_violation_type = DecodeAccess(mc->__es.__err); info.access_violation_vaddr = reinterpret_cast(si->si_addr); } info.rax = ss.__rax; info.rbx = ss.__rbx; info.rcx = ss.__rcx; info.rdx = ss.__rdx; info.rsi = ss.__rsi; info.rdi = ss.__rdi; info.rbp = ss.__rbp; info.rsp = ss.__rsp; info.r8 = ss.__r8; info.r9 = ss.__r9; info.r10 = ss.__r10; info.r11 = ss.__r11; info.r12 = ss.__r12; info.r13 = ss.__r13; info.r14 = ss.__r14; info.r15 = ss.__r15; const auto handler = g_handler.load(std::memory_order_acquire); if (handler == nullptr) { FailFast("host exception callback is null"); } const bool resolved = handler(info); g_in_exception_filter = false; if (resolved) { return; // retry the faulting instruction against the fixed mapping } // Unresolved: restore the default action so the re-executed instruction terminates. struct sigaction dfl {}; dfl.sa_handler = SIG_DFL; sigemptyset(&dfl.sa_mask); sigaction(sig, &dfl, nullptr); } #endif bool InstallHandler(Handler handler) { #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (handler == nullptr) { return false; } uint32_t expected_state = 0; if (!g_install_state.compare_exchange_strong(expected_state, 1, std::memory_order_acq_rel)) { return expected_state == 2 && g_handler.load(std::memory_order_acquire) == handler; } g_handler.store(handler, std::memory_order_release); if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) { g_handler.store(nullptr, std::memory_order_release); g_install_state.store(0, std::memory_order_release); printf("AddVectoredExceptionHandler() failed\n"); return false; } g_install_state.store(2, std::memory_order_release); return true; #elif defined(__APPLE__) if (handler == nullptr) { return false; } uint32_t expected_state = 0; if (!g_install_state.compare_exchange_strong(expected_state, 1, std::memory_order_acq_rel)) { return expected_state == 2 && g_handler.load(std::memory_order_acquire) == handler; } g_handler.store(handler, std::memory_order_release); struct sigaction sa {}; sa.sa_sigaction = SignalHandler; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); // macOS raises SIGBUS for protection faults on some paths and SIGSEGV on others; // SIGILL covers instructions the host cannot execute (routed to the x64 emulator). bool ok = sigaction(SIGSEGV, &sa, nullptr) == 0 && sigaction(SIGBUS, &sa, nullptr) == 0 && sigaction(SIGILL, &sa, nullptr) == 0; if (!ok) { g_handler.store(nullptr, std::memory_order_release); g_install_state.store(0, std::memory_order_release); printf("sigaction() failed to install the host fault handler\n"); return false; } g_install_state.store(2, std::memory_order_release); return true; #else (void)handler; return false; #endif } } // namespace Common::HostException