diff --git a/Source/Windows/Common/Exception.h b/Source/Windows/Common/Exception.h index 12162233cf..24b6f6b181 100644 --- a/Source/Windows/Common/Exception.h +++ b/Source/Windows/Common/Exception.h @@ -66,7 +66,14 @@ static inline EXCEPTION_RECORD HandleGuestException(FEXCore::Core::CpuStateFrame Dst.ExceptionInformation[0] = 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; + default: + // An inaccessible INT imm8 gate raises #GP, not #UD. Match the + // Windows access-violation record and leave RIP on the instruction. + Dst.ExceptionCode = EXCEPTION_ACCESS_VIOLATION; + Dst.NumberParameters = 2; + Dst.ExceptionInformation[0] = 0; + Dst.ExceptionInformation[1] = static_cast(-1); + return Dst; } } else { Dst.ExceptionCode = EXCEPTION_PRIV_INSTRUCTION; diff --git a/Source/Windows/WOW64/Module.cpp b/Source/Windows/WOW64/Module.cpp index 8c6b7f76e1..add1aba3a1 100644 --- a/Source/Windows/WOW64/Module.cpp +++ b/Source/Windows/WOW64/Module.cpp @@ -144,6 +144,9 @@ std::optional OvercommitTracker; std::optional ImageTracker; std::mutex ThreadCreationMutex; +// Targeted, bounded diagnostics without enabling the high-volume SMC log. +void (*ExceptionDiagnosticOutput)(const char*) {}; +std::atomic ExceptionDiagnosticCount {}; // Map of TIDs to their FEX thread state, `ThreadCreationMutex` must be locked when accessing std::unordered_map Threads; @@ -504,6 +507,11 @@ void BTCpuProcessInit() { FEXCore::Config::ReloadMetaLayer(); FEX::Windows::Logging::Init(); + if (ExecutableName == "DDO.exe" || ExecutableName == "ddo.exe") { + ExceptionDiagnosticOutput = reinterpret_cast( + GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "__wine_dbg_output")); + } + FEXCore::Config::Set(FEXCore::Config::CONFIG_INTERPRETER_INSTALLED, "0"); FEXCore::Config::Set(FEXCore::Config::CONFIG_IS64BIT_MODE, "0"); FEXCore::Config::Set(FEXCore::Config::CONFIG_APP_FILENAME, ExecutablePath); @@ -790,6 +798,9 @@ bool BTCpuResetToConsistentStateImpl(EXCEPTION_POINTERS* Ptrs) { LogMan::Msg::DFmt("pc: {:X} eip: {:X} code: {:X}", Context->Pc, WowContext->Eip, Exception->ExceptionCode); auto& Fault = Thread->CurrentFrame->SynchronousFaultData; + const auto OriginalFault = Fault; + const auto OriginalEip = WowContext->Eip; + const auto OriginalFlags = WowContext->EFlags; BOOL FirstChance = TRUE; EXCEPTION_RECORD GuestException = FEX::Windows::HandleGuestException(Fault, *Exception, WowContext->Eip, WowContext->Eax, WowContext->Ecx, FirstChance); @@ -798,6 +809,40 @@ bool BTCpuResetToConsistentStateImpl(EXCEPTION_POINTERS* Ptrs) { } // wow64.dll will handle adjusting PC in the dispatched context after a breakpoint + if (ExceptionDiagnosticOutput && ExceptionDiagnosticCount.load(std::memory_order_relaxed) < 65536) { + const auto Sample = ExceptionDiagnosticCount.fetch_add(1, std::memory_order_relaxed) + 1; + // First 32 exceptions, then powers of two: at most 43 samples per process. + if (Sample <= 32 || (Sample & (Sample - 1)) == 0) { + const auto Emit = [](const auto& Message) { ExceptionDiagnosticOutput(Message.c_str()); }; + const auto Tid = GetCurrentThreadId(); + Emit(fextl::fmt::format("FEX-WOW64-DIAG n={} tid={:X} hostpc={:X} hostcode={:X} eip={:X} generated={} signal={} trap={} err={:X}\n", + Sample, Tid, Context->Pc, Exception->ExceptionCode, OriginalEip, + OriginalFault.FaultToTopAndGeneratedException, OriginalFault.Signal, OriginalFault.TrapNo, OriginalFault.err_code)); + Emit(fextl::fmt::format("FEX-WOW64-DIAG n={} tid={:X} guestcode={:X} address={:X} context_eip={:X} flags={:X}->{:X} params={} p0={:X} p1={:X}\n", + Sample, Tid, GuestException.ExceptionCode, reinterpret_cast(GuestException.ExceptionAddress), + WowContext->Eip, OriginalFlags, WowContext->EFlags, GuestException.NumberParameters, + GuestException.NumberParameters > 0 ? GuestException.ExceptionInformation[0] : 0, + GuestException.NumberParameters > 1 ? GuestException.ExceptionInformation[1] : 0)); + Emit(fextl::fmt::format("FEX-WOW64-DIAG n={} tid={:X} eax={:X} ecx={:X} edx={:X} esp={:X} dr6={:X} dr7={:X}\n", + Sample, Tid, WowContext->Eax, WowContext->Ecx, WowContext->Edx, WowContext->Esp, WowContext->Dr6, WowContext->Dr7)); + + // Read through the OS so an unreadable guest address cannot fault here. + // Each word is little-endian; bytes cover EIP-4 through EIP+11. + const uintptr_t Start = OriginalEip >= 4 ? OriginalEip - 4 : OriginalEip; + uint64_t Words[2] {}; + SIZE_T BytesRead {}; + const auto ReadStatus = NtReadVirtualMemory(NtCurrentProcess(), reinterpret_cast(Start), Words, sizeof(Words), &BytesRead); + MEMORY_BASIC_INFORMATION Memory {}; + const auto QueryStatus = NtQueryVirtualMemory(NtCurrentProcess(), reinterpret_cast(static_cast(OriginalEip)), + MemoryBasicInformation, &Memory, sizeof(Memory), nullptr); + Emit(fextl::fmt::format("FEX-WOW64-DIAG n={} tid={:X} bytes_at={:X} read_status={:X} bytes={} le64={:016X},{:016X}\n", + Sample, Tid, Start, static_cast(ReadStatus), BytesRead, Words[0], Words[1])); + Emit(fextl::fmt::format("FEX-WOW64-DIAG n={} tid={:X} query_status={:X} base={:X} size={:X} state={:X} protect={:X} type={:X}\n", + Sample, Tid, static_cast(QueryStatus), reinterpret_cast(Memory.BaseAddress), + Memory.RegionSize, Memory.State, Memory.Protect, Memory.Type)); + } + } + Context::UnlockJITContext(); NtRaiseException(&GuestException, GetTLS().EntryContext(), FirstChance); return false; diff --git a/Source/Windows/include/winternl.h b/Source/Windows/include/winternl.h index 2534ee4811..be67b8e658 100644 --- a/Source/Windows/include/winternl.h +++ b/Source/Windows/include/winternl.h @@ -555,6 +555,7 @@ NTSTATUS WINAPI NtQueryValueKey(HANDLE, const UNICODE_STRING*, KEY_VALUE_INFORMA NTSTATUS WINAPI NtQueryVirtualMemory(HANDLE, LPCVOID, MEMORY_INFORMATION_CLASS, PVOID, SIZE_T, SIZE_T*); NTSTATUS WINAPI NtRaiseException(EXCEPTION_RECORD*, CONTEXT*, BOOL); NTSTATUS WINAPI NtReadFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, PIO_STATUS_BLOCK, PVOID, ULONG, PLARGE_INTEGER, PULONG); +NTSTATUS WINAPI NtReadVirtualMemory(HANDLE, LPCVOID, PVOID, SIZE_T, SIZE_T*); NTSTATUS WINAPI NtSetContextThread(HANDLE, const CONTEXT*); NTSTATUS WINAPI NtSuspendThread(HANDLE, PULONG); NTSTATUS WINAPI NtTerminateProcess(HANDLE, LONG);