From 4d26178f25f700cadb7d7e6152d54535365aea18 Mon Sep 17 00:00:00 2001 From: Billy Laws Date: Mon, 29 Jul 2024 13:22:43 +0000 Subject: [PATCH] Windows: Commonise guest exception handling logic and use for WOW64 --- Source/Windows/ARM64EC/Module.cpp | 88 ++++++------------------------- Source/Windows/Common/Exception.h | 72 +++++++++++++++++++++++++ Source/Windows/WOW64/Module.cpp | 12 ++++- 3 files changed, 99 insertions(+), 73 deletions(-) create mode 100644 Source/Windows/Common/Exception.h diff --git a/Source/Windows/ARM64EC/Module.cpp b/Source/Windows/ARM64EC/Module.cpp index 4db8b13eb..2105eb14b 100644 --- a/Source/Windows/ARM64EC/Module.cpp +++ b/Source/Windows/ARM64EC/Module.cpp @@ -25,6 +25,7 @@ $end_info$ #include #include "Common/Config.h" +#include "Common/Exception.h" #include "Common/InvalidationTracker.h" #include "Common/TSOHandlerConfig.h" #include "Common/CPUFeatures.h" @@ -217,77 +218,6 @@ namespace Exception { static std::optional HandlerConfig; static uintptr_t KiUserExceptionDispatcher; -static EXCEPTION_RECORD HandleGuestException(const EXCEPTION_RECORD& Src, ARM64_NT_CONTEXT& Context) { - auto* Thread = GetCPUArea().ThreadState(); - auto& Fault = Thread->CurrentFrame->SynchronousFaultData; - EXCEPTION_RECORD Dst = Src; - Dst.ExceptionAddress = reinterpret_cast(Context.Pc); - - // X64 Windows always clears TF, DF and AF when handling an exception, restoring after. - // Current ARM64EC windows can only restore NZCV+SS when returning from an exception and other flags are left untouched from the handler context. - // TODO: Can extend wine to support this by mapping the remaining EFlags into reserved cpsr members. - uint32_t EFlags = CTX->ReconstructCompactedEFLAGS(Thread, true, Context.X, Context.Cpsr); - EFlags &= (1 << FEXCore::X86State::RFLAG_TF_LOC); - CTX->SetFlagsFromCompactedEFLAGS(Thread, EFlags); - - if (!Fault.FaultToTopAndGeneratedException) { - return Dst; - } - Fault.FaultToTopAndGeneratedException = false; - - Dst.ExceptionFlags = 0; - Dst.NumberParameters = 0; - - switch (Fault.Signal) { - case FEXCore::Core::FAULT_SIGILL: Dst.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; return Dst; - case FEXCore::Core::FAULT_SIGTRAP: - switch (Fault.TrapNo) { - case FEXCore::X86State::X86_TRAPNO_DB: - Context.Cpsr &= ~(1 << 21); // PSTATE.SS - Dst.ExceptionCode = EXCEPTION_SINGLE_STEP; - return Dst; - case FEXCore::X86State::X86_TRAPNO_BP: - Context.Pc -= 1; - Dst.ExceptionAddress = reinterpret_cast(Context.Pc); - Dst.ExceptionCode = EXCEPTION_BREAKPOINT; - Dst.NumberParameters = 1; - Dst.ExceptionInformation[0] = 0; - return Dst; - default: LogMan::Msg::EFmt("Unknown SIGTRAP trap: {}", Fault.TrapNo); break; - } - break; - case FEXCore::Core::FAULT_SIGSEGV: - switch (Fault.TrapNo) { - case FEXCore::X86State::X86_TRAPNO_GP: - if ((Fault.err_code & 0b111) == 0b010) { - switch (Fault.err_code >> 3) { - case 0x2d: - Context.Pc += 2; - Dst.ExceptionCode = EXCEPTION_BREAKPOINT; - Dst.ExceptionAddress = reinterpret_cast(Context.Pc + 1); - Dst.NumberParameters = 1; - Dst.ExceptionInformation[0] = Context.X8; // RAX - // Note that ExceptionAddress doesn't equal the reported context RIP here, this discrepancy expected and not having it can trigger anti-debug logic. - return Dst; - default: LogMan::Msg::EFmt("Unknown interrupt: 0x{:X}", Fault.err_code >> 3); break; - } - } else { - Dst.ExceptionCode = EXCEPTION_PRIV_INSTRUCTION; - return Dst; - } - break; - case FEXCore::X86State::X86_TRAPNO_OF: Dst.ExceptionCode = EXCEPTION_INT_OVERFLOW; return Dst; - default: LogMan::Msg::EFmt("Unknown SIGSEGV trap: {}", Fault.TrapNo); break; - } - break; - default: LogMan::Msg::EFmt("Unknown signal type: {}", Fault.Signal); break; - } - - // Default to SIGILL - Dst.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; - return Dst; -} - static bool HandleUnalignedAccess(ARM64_NT_CONTEXT& Context) { if (!CTX->IsAddressInCodeBuffer(GetCPUArea().ThreadState(), Context.Pc)) { return false; @@ -454,6 +384,8 @@ static ARM64_NT_CONTEXT ReconstructPackedECContext(ARM64_NT_CONTEXT& Context) { static void RethrowGuestException(const EXCEPTION_RECORD& Rec, ARM64_NT_CONTEXT& Context) { const auto& Config = SignalDelegator->GetConfig(); + auto* Thread = GetCPUArea().ThreadState(); + auto& Fault = Thread->CurrentFrame->SynchronousFaultData; uint64_t GuestSp = Context.X[Config.SRAGPRMapping[static_cast(FEXCore::X86State::REG_RSP)]]; struct DispatchArgs { ARM64_NT_CONTEXT Context; @@ -466,7 +398,19 @@ static void RethrowGuestException(const EXCEPTION_RECORD& Rec, ARM64_NT_CONTEXT& LogMan::Msg::DFmt("Reconstructing context"); Args->Context = ReconstructPackedECContext(Context); LogMan::Msg::DFmt("pc: {:X} rip: {:X}", Context.Pc, Args->Context.Pc); - Args->Rec = HandleGuestException(Rec, Args->Context); + + // X64 Windows always clears TF, DF and AF when handling an exception, restoring after. + // Current ARM64EC windows can only restore NZCV+SS when returning from an exception and other flags are left untouched from the handler context. + // TODO: Can extend wine to support this by mapping the remaining EFlags into reserved cpsr members. + uint32_t EFlags = CTX->ReconstructCompactedEFLAGS(Thread, false, nullptr, 0); + EFlags &= (1 << FEXCore::X86State::RFLAG_TF_LOC); + CTX->SetFlagsFromCompactedEFLAGS(Thread, EFlags); + + Args->Rec = FEX::Windows::HandleGuestException(Fault, Rec, Args->Context.Pc, Args->Context.X8); + if (Args->Rec.ExceptionCode == EXCEPTION_SINGLE_STEP) { + Args->Context.Cpsr &= ~(1 << 21); // PSTATE.SS + } + Context.Sp = reinterpret_cast(Args); Context.Pc = KiUserExceptionDispatcher; } diff --git a/Source/Windows/Common/Exception.h b/Source/Windows/Common/Exception.h new file mode 100644 index 000000000..b2671bcb6 --- /dev/null +++ b/Source/Windows/Common/Exception.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include +#include + +#include +#include + +namespace FEX::Windows { +template +static inline EXCEPTION_RECORD +HandleGuestException(FEXCore::Core::CpuStateFrame::SynchronousFaultDataStruct& Fault, const EXCEPTION_RECORD& Src, TReg& Rip, TReg Rax) { + EXCEPTION_RECORD Dst = Src; + Dst.ExceptionAddress = reinterpret_cast(Rip); + + if (!Fault.FaultToTopAndGeneratedException) { + return Dst; + } + Fault.FaultToTopAndGeneratedException = false; + + Dst.ExceptionFlags = 0; + Dst.NumberParameters = 0; + + switch (Fault.Signal) { + case FEXCore::Core::FAULT_SIGILL: Dst.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; return Dst; + case FEXCore::Core::FAULT_SIGTRAP: + switch (Fault.TrapNo) { + case FEXCore::X86State::X86_TRAPNO_DB: Dst.ExceptionCode = EXCEPTION_SINGLE_STEP; return Dst; + case FEXCore::X86State::X86_TRAPNO_BP: + Rip -= 1; + Dst.ExceptionAddress = reinterpret_cast(Rip); + Dst.ExceptionCode = EXCEPTION_BREAKPOINT; + Dst.NumberParameters = 1; + Dst.ExceptionInformation[0] = 0; + return Dst; + default: LogMan::Msg::EFmt("Unknown SIGTRAP trap: {}", Fault.TrapNo); break; + } + break; + case FEXCore::Core::FAULT_SIGSEGV: + switch (Fault.TrapNo) { + case FEXCore::X86State::X86_TRAPNO_GP: + if ((Fault.err_code & 0b111) == 0b010) { + switch (Fault.err_code >> 3) { + case 0x2d: + Rip += 2; + Dst.ExceptionCode = EXCEPTION_BREAKPOINT; + Dst.ExceptionAddress = reinterpret_cast(Rip + 1); + Dst.NumberParameters = 1; + Dst.ExceptionInformation[0] = Rax; // RAX + // Note that ExceptionAddress doesn't equal the reported context RIP here, this discrepancy expected and not having it can trigger anti-debug logic. + return Dst; + default: LogMan::Msg::EFmt("Unknown interrupt: 0x{:X}", Fault.err_code >> 3); break; + } + } else { + Dst.ExceptionCode = EXCEPTION_PRIV_INSTRUCTION; + return Dst; + } + break; + case FEXCore::X86State::X86_TRAPNO_OF: Dst.ExceptionCode = EXCEPTION_INT_OVERFLOW; return Dst; + default: LogMan::Msg::EFmt("Unknown SIGSEGV trap: {}", Fault.TrapNo); break; + } + break; + default: LogMan::Msg::EFmt("Unknown signal type: {}", Fault.Signal); break; + } + + // Default to SIGILL + Dst.ExceptionCode = EXCEPTION_ILLEGAL_INSTRUCTION; + return Dst; +} +} // namespace FEX::Windows diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index 5fd6f045c..89c350e00 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -26,6 +26,7 @@ $end_info$ #include #include "Common/Config.h" +#include "Common/Exception.h" #include "Common/TSOHandlerConfig.h" #include "Common/InvalidationTracker.h" #include "Common/CPUFeatures.h" @@ -123,6 +124,9 @@ uint64_t GetWowTEB(void* TEB) { } bool IsAddressInJit(uint64_t Address) { + const auto& Config = SignalDelegator->GetConfig(); + if (Address >= Config.DispatcherBegin && Address < Config.DispatcherEnd) return true; + auto Thread = GetTLS().ThreadState(); return Thread->CTX->IsAddressInCodeBuffer(Thread, Address); } @@ -641,7 +645,7 @@ NTSTATUS BTCpuSuspendLocalThread(HANDLE Thread, ULONG* Count) { NTSTATUS BTCpuResetToConsistentState(EXCEPTION_POINTERS* Ptrs) { auto* Context = Ptrs->ContextRecord; - const auto* Exception = Ptrs->ExceptionRecord; + auto* Exception = Ptrs->ExceptionRecord; if (Exception->ExceptionCode == EXCEPTION_DATATYPE_MISALIGNMENT && Context::HandleUnalignedAccess(Context)) { LogMan::Msg::DFmt("Handled unaligned atomic: new pc: {:X}", Context->Pc); NtContinue(Context, FALSE); @@ -677,6 +681,12 @@ NTSTATUS BTCpuResetToConsistentState(EXCEPTION_POINTERS* Ptrs) { WOW64_CONTEXT WowContext = Context::ReconstructWowContext(Context); LogMan::Msg::DFmt("pc: {:X} eip: {:X}", Context->Pc, WowContext.Eip); + auto& Fault = Thread->CurrentFrame->SynchronousFaultData; + *Exception = FEX::Windows::HandleGuestException(Fault, *Exception, WowContext.Eip, WowContext.Eax); + if (Exception->ExceptionCode == EXCEPTION_SINGLE_STEP) { + WowContext.EFlags &= ~(1 << FEXCore::X86State::RFLAG_TF_LOC); + } + BTCpuSetContext(GetCurrentThread(), GetCurrentProcess(), nullptr, &WowContext); Context::UnlockJITContext();