// Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later using System; using System.Buffers.Binary; using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using SharpEmu.Core.Cpu.Disasm; using SharpEmu.Core.Cpu.Native.Windows; using SharpEmu.HLE; using SharpEmu.HLE.Host; namespace SharpEmu.Core.Cpu.Native; public sealed partial class DirectExecutionBackend { private const ulong LazyCommitWindowBytes = 0x0200_0000UL; private static int _lazyCommitTraceCount; private unsafe void SetupExceptionHandler() { if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_RAW_HANDLER"), "1", StringComparison.Ordinal)) { _rawExceptionHandlerStub = _faultHandling.CreateHandlerThunk(RawVectoredHandlerPtrManaged, _hostRspSlotTlsIndex, _tlsGetValueAddress); if (_rawExceptionHandlerStub == 0) { throw new InvalidOperationException("Failed to create raw exception handler trampoline"); } _rawExceptionHandler = _faultHandling.AddFirstChanceHandler(_rawExceptionHandlerStub); Console.Error.WriteLine($"[LOADER][INFO] Raw exception handler installed: 0x{_rawExceptionHandler:X16}"); } else { Console.Error.WriteLine("[LOADER][INFO] Raw exception handler disabled by SHARPEMU_DISABLE_RAW_HANDLER=1"); } _handlerDelegate = VectoredHandler; _handlerHandle = GCHandle.Alloc(_handlerDelegate); _exceptionHandlerStub = _faultHandling.CreateHandlerThunk(Marshal.GetFunctionPointerForDelegate(_handlerDelegate), _hostRspSlotTlsIndex, _tlsGetValueAddress); if (_exceptionHandlerStub == 0) { throw new InvalidOperationException("Failed to create exception handler trampoline"); } _exceptionHandler = _faultHandling.AddFirstChanceHandler(_exceptionHandlerStub); Console.Error.WriteLine($"[LOADER][INFO] Exception handler installed: 0x{_exceptionHandler:X16}"); _unhandledFilterDelegate = UnhandledExceptionFilter; _unhandledFilterHandle = GCHandle.Alloc(_unhandledFilterDelegate); _unhandledFilterStub = _faultHandling.CreateHandlerThunk(Marshal.GetFunctionPointerForDelegate(_unhandledFilterDelegate), _hostRspSlotTlsIndex, _tlsGetValueAddress); if (_unhandledFilterStub == 0) { throw new InvalidOperationException("Failed to create unhandled exception filter trampoline"); } _faultHandling.SetUnhandledFilter(_unhandledFilterStub); } private unsafe int UnhandledExceptionFilter(void* exceptionInfo) { try { EXCEPTION_RECORD* exceptionRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ExceptionRecord; ulong rip = ReadCtxU64(((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord, CTX_RIP); ulong rsp = ReadCtxU64(((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord, CTX_RSP); Console.Error.WriteLine("[LOADER][FATAL] Unhandled exception filter fired."); Console.Error.WriteLine($"[LOADER][FATAL] Code: 0x{exceptionRecord->ExceptionCode:X8}"); Console.Error.WriteLine($"[LOADER][FATAL] Exception Address: 0x{(ulong)(nint)exceptionRecord->ExceptionAddress:X16}"); Console.Error.WriteLine($"[LOADER][FATAL] RIP: 0x{rip:X16}"); Console.Error.WriteLine($"[LOADER][FATAL] RSP: 0x{rsp:X16}"); Console.Error.Flush(); } catch { } return 0; } private unsafe int VectoredHandler(void* exceptionInfo) { if (_vectoredHandlerDepth > 0) { LogNestedVectoredException(exceptionInfo); Console.Error.Flush(); return 0; } _vectoredHandlerDepth++; try { EXCEPTION_RECORD* exceptionRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ExceptionRecord; uint exceptionCode = exceptionRecord->ExceptionCode; uint exceptionFlags = exceptionRecord->ExceptionFlags; ulong exceptionAddress = (ulong)exceptionRecord->ExceptionAddress; void* contextRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord; if (contextRecord == null) { Console.Error.WriteLine("[LOADER][FATAL] ContextRecord is null!"); Console.Error.Flush(); return 0; } ulong rip = ReadCtxU64(contextRecord, CTX_RIP); ulong rsp = ReadCtxU64(contextRecord, CTX_RSP); // Thread-mode probe: a hardware exception raised while this thread is inside // the managed import gateway means the VEH->managed reentry happened from // cooperative GC mode — a ReversePInvokeBadTransition candidate. if (LogThreadMode && _threadModeGatewayDepth > 0) { TraceThreadMode( $"veh_in_gateway code=0x{exceptionCode:X8} rip=0x{rip:X16} gateway_depth={_threadModeGatewayDepth}"); } if (exceptionCode == WindowsFaultCodes.AccessViolation && TryHandleLazyCommittedPage(exceptionRecord, rip, rsp)) { return -1; } if (IsBenignHostDebugException(exceptionCode)) { return -1; } if (exceptionCode == MSVC_CPP_EXCEPTION) { return 0; } switch (exceptionCode) { case WindowsFaultCodes.AccessViolation: LogAccessViolationTrace(exceptionAddress, exceptionRecord); break; case WindowsFaultCodes.FastFail: { ulong p0 = exceptionRecord->NumberParameters >= 1 ? (*exceptionRecord->ExceptionInformation) : 0; ulong p1 = exceptionRecord->NumberParameters >= 2 ? exceptionRecord->ExceptionInformation[1] : 0; Console.Error.WriteLine($"[LOADER][TRACE] VEH_FASTFAIL code=0x{exceptionCode:X8} ex=0x{exceptionAddress:X16} rip=0x{rip:X16} rsp=0x{rsp:X16} p0=0x{p0:X16} p1=0x{p1:X16}"); Console.Error.Flush(); break; } } ulong rax = ReadCtxU64(contextRecord, CTX_RAX); ulong rbx = ReadCtxU64(contextRecord, CTX_RBX); ulong rcx = ReadCtxU64(contextRecord, CTX_RCX); ulong rdx = ReadCtxU64(contextRecord, CTX_RDX); ulong rsi = ReadCtxU64(contextRecord, CTX_RSI); ulong rdi = ReadCtxU64(contextRecord, CTX_RDI); ulong rbp = ReadCtxU64(contextRecord, CTX_RBP); ulong r8 = ReadCtxU64(contextRecord, CTX_R8); ulong r9 = ReadCtxU64(contextRecord, CTX_R9); ulong r10 = ReadCtxU64(contextRecord, CTX_R10); ulong r11 = ReadCtxU64(contextRecord, CTX_R11); ulong r12 = ReadCtxU64(contextRecord, CTX_R12); ulong r13 = ReadCtxU64(contextRecord, CTX_R13); ulong r14 = ReadCtxU64(contextRecord, CTX_R14); ulong r15 = ReadCtxU64(contextRecord, CTX_R15); Console.Error.WriteLine("[LOADER][INFO] ========================================="); Console.Error.WriteLine("[LOADER][INFO] NATIVE EXCEPTION CAUGHT!"); Console.Error.WriteLine($"[LOADER][INFO] Code: 0x{exceptionCode:X8}"); Console.Error.WriteLine($"[LOADER][INFO] Exception Address: 0x{exceptionAddress:X16}"); Console.Error.WriteLine($"[LOADER][INFO] RIP: 0x{rip:X16}"); if (TryFormatNearestRuntimeSymbol(rip, out string symbol)) { Console.Error.WriteLine("[LOADER][INFO] RIP symbol: " + symbol); } Console.Error.WriteLine($"[LOADER][INFO] RAX: 0x{rax:X16}"); Console.Error.WriteLine($"[LOADER][INFO] RBX: 0x{rbx:X16}"); Console.Error.WriteLine($"[LOADER][INFO] RCX: 0x{rcx:X16}"); Console.Error.WriteLine($"[LOADER][INFO] RDX: 0x{rdx:X16}"); Console.Error.WriteLine($"[LOADER][INFO] RSI: 0x{rsi:X16}"); Console.Error.WriteLine($"[LOADER][INFO] RDI: 0x{rdi:X16}"); Console.Error.WriteLine($"[LOADER][INFO] RBP: 0x{rbp:X16}"); Console.Error.WriteLine($"[LOADER][INFO] RSP: 0x{rsp:X16}"); Console.Error.WriteLine($"[LOADER][INFO] R8 : 0x{r8:X16}"); Console.Error.WriteLine($"[LOADER][INFO] R9 : 0x{r9:X16}"); Console.Error.WriteLine($"[LOADER][INFO] R10: 0x{r10:X16}"); Console.Error.WriteLine($"[LOADER][INFO] R11: 0x{r11:X16}"); Console.Error.WriteLine($"[LOADER][INFO] R12: 0x{r12:X16}"); Console.Error.WriteLine($"[LOADER][INFO] R13: 0x{r13:X16}"); Console.Error.WriteLine($"[LOADER][INFO] R14: 0x{r14:X16}"); Console.Error.WriteLine($"[LOADER][INFO] R15: 0x{r15:X16}"); Console.Error.WriteLine($"[LOADER][INFO] Flags: 0x{exceptionFlags:X8}"); ulong accessType = 0; ulong target = 0; if (exceptionCode == WindowsFaultCodes.AccessViolation && exceptionRecord->NumberParameters >= 2) { accessType = *exceptionRecord->ExceptionInformation; target = exceptionRecord->ExceptionInformation[1]; string accessText = accessType switch { 0uL => "read", 1uL => "write", 8uL => "execute", _ => $"unknown({accessType})" }; Console.Error.WriteLine("[LOADER][INFO] AV access: " + accessText); Console.Error.WriteLine($"[LOADER][INFO] AV target: 0x{target:X16}"); if (_hostMemory.Query(target, out var mbi)) { Console.Error.WriteLine($"[LOADER][INFO] AV target region: base=0x{mbi.BaseAddress:X16} size=0x{mbi.RegionSize:X16} state=0x{mbi.RawState:X08} protect=0x{mbi.RawProtection:X08}"); } } try { Console.Error.WriteLine("[LOADER][INFO] Stack qwords (RSP..):"); for (int i = 0; i < 16; i++) { ulong stackAddr = rsp + (ulong)(i * 8); ulong value = (ulong)Marshal.ReadInt64((nint)stackAddr); Console.Error.WriteLine($"[LOADER][INFO] [rsp+0x{i * 8:X2}] @0x{stackAddr:X16} = 0x{value:X16}"); } } catch { Console.Error.WriteLine("[LOADER][WARNING] Could not read stack qwords."); } try { Console.Error.WriteLine("[LOADER][INFO] Frame chain (RBP walk):"); ulong frame = rbp; for (int i = 0; i < 12; i++) { if (frame < 140733193388032L || frame > 140737488355327L) { break; } ulong next = (ulong)Marshal.ReadInt64((nint)frame); ulong ret = (ulong)Marshal.ReadInt64((nint)(frame + 8)); string extra = TryFormatNearestRuntimeSymbol(ret, out string retSym) ? $" [{retSym}]" : string.Empty; Console.Error.WriteLine($"[LOADER][INFO] frame#{i}: rbp=0x{frame:X16} ret=0x{ret:X16}{extra} next=0x{next:X16}"); if (next <= frame) { break; } frame = next; } } catch { Console.Error.WriteLine("[LOADER][WARNING] Could not walk RBP frame chain."); } switch (exceptionCode) { case WindowsFaultCodes.AccessViolation: Console.Error.WriteLine("[LOADER][ERROR] Type: Access Violation"); Console.Error.WriteLine("[LOADER][ERROR] This usually means:"); Console.Error.WriteLine("[LOADER][ERROR] - Guest code called an unmapped import"); Console.Error.WriteLine("[LOADER][ERROR] - Guest code accessed unmapped memory"); Console.Error.WriteLine("[LOADER][ERROR] - Need to implement HLE for this NID"); try { byte[] code = new byte[16]; Marshal.Copy((nint)rip, code, 0, code.Length); Console.Error.WriteLine("[LOADER][INFO] Code at RIP: " + BitConverter.ToString(code).Replace("-", " ")); if (code[0] == 100) { Console.Error.WriteLine("[LOADER][ERROR] Detected FS segment prefix - TLS access not patched!"); } else if (code[0] == 101) { Console.Error.WriteLine("[LOADER][ERROR] Detected GS segment prefix - TLS access not patched!"); } else if (code[0] == 197 || code[0] == 196) { Console.Error.WriteLine("[LOADER][INFO] Detected AVX instruction - check CPU support!"); Console.Error.WriteLine($"[LOADER][INFO] RBP: 0x{rbp:X16} (mod 16 = {rbp % 16})"); Console.Error.WriteLine($"[LOADER][INFO] RSP: 0x{rsp:X16} (mod 16 = {rsp % 16})"); } if (rip > 16) { byte[] before = new byte[16]; Marshal.Copy((nint)(rip - 16), before, 0, before.Length); Console.Error.WriteLine("[LOADER][INFO] Code before RIP: " + BitConverter.ToString(before).Replace("-", " ")); } if (rip > 32) { byte[] window = new byte[64]; Marshal.Copy((nint)(rip - 32), window, 0, window.Length); Console.Error.WriteLine("[LOADER][INFO] Code window [RIP-0x20..]: " + BitConverter.ToString(window).Replace("-", " ")); } } catch { Console.Error.WriteLine("[LOADER][ERROR] Could not read code at RIP"); } DumpRecentImportTrace(); DumpGuestDisasmDiagnostics(rip, rbp); DumpGuestReferenceDiagnostics(); DumpGuestPointerWindowDiagnostics(); break; case WindowsFaultCodes.Breakpoint: Console.Error.WriteLine("[LOADER][WARNING] Type: Breakpoint (int3)"); Console.Error.WriteLine("[LOADER][WARNING] Unexpected breakpoint in direct-bridge mode"); break; case WindowsFaultCodes.IllegalInstruction: Console.Error.WriteLine("[LOADER][INFO] Type: Illegal Instruction"); break; } Console.Error.WriteLine("[LOADER][INFO] ========================================="); Console.Error.Flush(); return 0; } finally { _vectoredHandlerDepth--; } } private static bool IsBenignHostDebugException(uint exceptionCode) { return exceptionCode is DBG_PRINTEXCEPTION_C or DBG_PRINTEXCEPTION_WIDE_C or MS_VC_THREADNAME_EXCEPTION; } private unsafe static void LogNestedVectoredException(void* exceptionInfo) { int count = Interlocked.Increment(ref _nestedVehTraceCount); if (count > 16 && count % 128 != 0) { return; } try { EXCEPTION_POINTERS* pointers = (EXCEPTION_POINTERS*)exceptionInfo; EXCEPTION_RECORD* record = pointers->ExceptionRecord; void* contextRecord = pointers->ContextRecord; ulong rip = contextRecord != null ? ReadCtxU64(contextRecord, CTX_RIP) : 0; ulong rsp = contextRecord != null ? ReadCtxU64(contextRecord, CTX_RSP) : 0; ulong accessType = record->NumberParameters >= 1 ? *record->ExceptionInformation : 0; ulong target = record->NumberParameters >= 2 ? record->ExceptionInformation[1] : 0; Console.Error.WriteLine( $"[LOADER][TRACE] Nested VEH exception#{count}: code=0x{record->ExceptionCode:X8} ex=0x{(ulong)record->ExceptionAddress:X16} rip=0x{rip:X16} rsp=0x{rsp:X16} type={accessType} target=0x{target:X16}; passing through."); } catch { Console.Error.WriteLine($"[LOADER][TRACE] Nested VEH exception#{count}; passing through."); } } private unsafe void LogAccessViolationTrace(ulong exceptionAddress, EXCEPTION_RECORD* exceptionRecord) { ulong accessType = exceptionRecord->NumberParameters >= 1 ? (*exceptionRecord->ExceptionInformation) : 0; ulong target = exceptionRecord->NumberParameters >= 2 ? exceptionRecord->ExceptionInformation[1] : 0; if (_lastAvTraceRip == exceptionAddress && _lastAvTraceType == accessType && _lastAvTraceTarget == target) { _lastAvTraceRepeatCount++; if (_lastAvTraceRepeatCount > 4 && _lastAvTraceRepeatCount % 128 != 0) { return; } Console.Error.WriteLine($"[LOADER][TRACE] VEH_AV repeat#{_lastAvTraceRepeatCount} at 0x{exceptionAddress:X16} type={accessType} target=0x{target:X16}"); Console.Error.Flush(); return; } _lastAvTraceRip = exceptionAddress; _lastAvTraceType = accessType; _lastAvTraceTarget = target; _lastAvTraceRepeatCount = 1; Console.Error.WriteLine($"[LOADER][TRACE] VEH_AV first-chance at 0x{exceptionAddress:X16} type={accessType} target=0x{target:X16}"); Console.Error.Flush(); } private void DumpGuestInstructionStream(string name, ulong startRip, int maxInstructions) { if (_cpuContext == null || startRip < 0x10000 || maxInstructions <= 0) { return; } Console.Error.WriteLine($"[LOADER][INFO] {name} disasm @0x{startRip:X16}:"); ulong rip = startRip; for (int i = 0; i < maxInstructions; i++) { if (!IcedDecoder.TryReadGuestBytes(_cpuContext.Memory, rip, maxLen: 15, out var bytes) || !IcedDecoder.TryDecode(rip, bytes, out var instruction)) { Console.Error.WriteLine($"[LOADER][INFO] 0x{rip:X16}: "); break; } Console.Error.WriteLine( $"[LOADER][INFO] 0x{instruction.Rip:X16}: {instruction.Text} bytes={IcedDecoder.FormatBytes(instruction.Bytes)}"); rip += (ulong)instruction.Length; } } private void DumpGuestDisasmDiagnostics(ulong rip, ulong rbp) { if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DISASM"), "1", StringComparison.Ordinal)) { return; } if (rip >= 0x20) { DumpGuestInstructionStream("fault-prelude", rip - 0x20, 24); } try { ulong frame = rbp; for (int i = 0; i < 3; i++) { if (frame < 140733193388032L || frame > 140737488355327L) { break; } ulong ret = (ulong)Marshal.ReadInt64((nint)(frame + 8)); if (ret >= 0x40) { DumpGuestInstructionStream($"frame#{i}-ret-prelude", ret - 0x40, 24); } ulong next = (ulong)Marshal.ReadInt64((nint)frame); if (next <= frame) { break; } frame = next; } } catch { Console.Error.WriteLine("[LOADER][WARNING] Could not dump disasm diagnostics."); } var extraAddresses = Environment.GetEnvironmentVariable("SHARPEMU_LOG_DISASM_ADDRS"); if (string.IsNullOrWhiteSpace(extraAddresses)) { return; } foreach (var token in extraAddresses.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? token[2..] : token; if (!ulong.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address) || address < 0x20) { continue; } DumpGuestInstructionStream($"extra-0x{address:X16}", address, 48); } } private unsafe void DumpGuestReferenceDiagnostics() { var targetList = ParseDiagnosticAddresses(Environment.GetEnvironmentVariable("SHARPEMU_LOG_REFSCAN_ADDRS")); if (targetList.Count == 0 || _cpuContext == null) { return; } const ulong scanBase = 0x0000000800000000UL; const ulong scanEnd = 0x0000000810000000UL; const int maxHitsPerTarget = 24; Console.Error.WriteLine( $"[LOADER][INFO] Ref scan targets: {string.Join(", ", targetList.ConvertAll(static addr => $"0x{addr:X16}"))}"); var hitCounts = new Dictionary(targetList.Count); for (var i = 0; i < targetList.Count; i++) { hitCounts[targetList[i]] = 0; } ulong address = scanBase; while (address < scanEnd) { if (!_hostMemory.Query(address, out var mbi)) { break; } ulong regionBase = mbi.BaseAddress; ulong regionEnd = regionBase + mbi.RegionSize; if (regionEnd <= address) { break; } if (mbi.State == HostRegionState.Committed && IsReadableProtection(mbi.RawProtection) && IsExecutableProtection(mbi.RawProtection)) { ScanExecutableRegionForTargetReferences(regionBase, regionEnd, targetList, hitCounts, maxHitsPerTarget); } var allTargetsSatisfied = true; for (var i = 0; i < targetList.Count; i++) { if (hitCounts[targetList[i]] < maxHitsPerTarget) { allTargetsSatisfied = false; break; } } if (allTargetsSatisfied) { break; } address = regionEnd; } for (var i = 0; i < targetList.Count; i++) { var target = targetList[i]; if (!hitCounts.TryGetValue(target, out var count) || count == 0) { Console.Error.WriteLine($"[LOADER][INFO] Ref scan 0x{target:X16}: none"); } } } private void DumpGuestPointerWindowDiagnostics() { var targetList = ParseDiagnosticAddresses(Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOWS")); if (targetList.Count == 0) { return; } var windowSize = 0x80; var rawWindowSize = Environment.GetEnvironmentVariable("SHARPEMU_LOG_POINTER_WINDOW_SIZE"); if (!string.IsNullOrWhiteSpace(rawWindowSize)) { var normalized = rawWindowSize.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? rawWindowSize[2..] : rawWindowSize; if (int.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsedWindowSize) && parsedWindowSize > 0) { windowSize = parsedWindowSize; } } foreach (var target in targetList) { DumpPointerWindow($"ptrwin-0x{target:X16}", target, windowSize); } } private void ScanExecutableRegionForTargetReferences( ulong regionBase, ulong regionEnd, IReadOnlyList targets, IDictionary hitCounts, int maxHitsPerTarget) { if (_cpuContext == null || regionEnd <= regionBase) { return; } ulong rip = regionBase; while (rip < regionEnd) { if (!IcedDecoder.TryReadGuestBytes(_cpuContext.Memory, rip, maxLen: 15, out var bytes) || !IcedDecoder.TryDecode(rip, bytes, out var instruction) || instruction.Length <= 0) { rip++; continue; } if (instruction.MemoryAddress is { } memoryAddress) { for (var i = 0; i < targets.Count; i++) { var target = targets[i]; if (memoryAddress != target || !hitCounts.TryGetValue(target, out var count) || count >= maxHitsPerTarget) { continue; } hitCounts[target] = count + 1; Console.Error.WriteLine( $"[LOADER][INFO] Ref scan hit target=0x{target:X16} rip=0x{instruction.Rip:X16} text={instruction.Text} bytes={IcedDecoder.FormatBytes(instruction.Bytes)}"); } } rip += (ulong)instruction.Length; } } private static List ParseDiagnosticAddresses(string? rawValue) { var result = new List(); if (string.IsNullOrWhiteSpace(rawValue)) { return result; } foreach (var token in rawValue.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) { var normalized = token.StartsWith("0x", StringComparison.OrdinalIgnoreCase) ? token[2..] : token; if (!ulong.TryParse(normalized, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address)) { continue; } if (!result.Contains(address)) { result.Add(address); } } return result; } private void DumpUnresolvedSentinelWindow(string name, ulong baseAddress, int size) { if (baseAddress < 0x10000 || size <= 0) { return; } ulong scanStart = baseAddress; ulong scanEnd = baseAddress + (ulong)size; List hits = ScanSuspiciousResolverPointers(scanStart, scanEnd); if (hits.Count == 0) { Console.Error.WriteLine($"[LOADER][INFO] {name} unresolved scan: none"); return; } Console.Error.WriteLine($"[LOADER][INFO] {name} unresolved scan hits: {hits.Count}"); for (int i = 0; i < hits.Count && i < 8; i++) { ulong slotAddress = hits[i]; if (TryReadQword(slotAddress, out var value)) { Console.Error.WriteLine($"[LOADER][INFO] hit#{i}: slot=0x{slotAddress:X16} value=0x{value:X16}"); } } } private void DumpSentinelPatternWindow(string name, ulong baseAddress, int size) { if (_cpuContext == null || baseAddress < 0x10000 || size <= 0) { return; } byte[] buffer = new byte[size]; if (!_cpuContext.Memory.TryRead(baseAddress, buffer)) { Console.Error.WriteLine($"[LOADER][INFO] {name} sentinel-pattern scan: unreadable"); return; } List hits = new(); for (int offset = 0; offset + 2 <= buffer.Length; offset++) { if (BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset, 2)) == 0xFFFE) { hits.Add($"+0x{offset:X}:u16"); } if (offset + 4 <= buffer.Length && BinaryPrimitives.ReadUInt32LittleEndian(buffer.AsSpan(offset, 4)) == 0xFFFFFFFEu) { hits.Add($"+0x{offset:X}:u32"); } if (offset + 8 <= buffer.Length && BinaryPrimitives.ReadUInt64LittleEndian(buffer.AsSpan(offset, 8)) == 0xFFFFFFFFFFFFFFFEuL) { hits.Add($"+0x{offset:X}:u64"); } } if (hits.Count == 0) { Console.Error.WriteLine($"[LOADER][INFO] {name} sentinel-pattern scan: none"); return; } Console.Error.WriteLine($"[LOADER][INFO] {name} sentinel-pattern hits: {string.Join(", ", hits.GetRange(0, Math.Min(hits.Count, 12)))}"); } private void DumpReturnTargetCandidates(ulong rsp) { if (rsp < 0x10000) { return; } ulong start = rsp >= 0x10 ? rsp - 0x10 : rsp; Console.Error.WriteLine($"[LOADER][INFO] Return-target candidates near RSP=0x{rsp:X16}:"); for (int offset = 0; offset <= 0x20; offset++) { ulong address = start + (ulong)offset; try { ulong value = (ulong)Marshal.ReadInt64((nint)address); Console.Error.WriteLine($"[LOADER][INFO] [0x{address:X16}] -> 0x{value:X16}"); } catch { Console.Error.WriteLine($"[LOADER][INFO] [0x{address:X16}] -> "); break; } } } private void DumpObjectFieldTargets(string name, ulong objectAddress, int[] offsets, int windowSize) { if (objectAddress < 0x10000 || offsets.Length == 0) { return; } foreach (int offset in offsets) { ulong slotAddress = objectAddress + (ulong)offset; if (!TryReadQword(slotAddress, out var target) || target < 0x10000) { continue; } Console.Error.WriteLine($"[LOADER][INFO] {name}+0x{offset:X2} target = {FormatPointerWithNearestSymbol(target)}"); DumpPointerWindow($"{name}+0x{offset:X2}", target, windowSize); DumpUnresolvedSentinelWindow($"{name}+0x{offset:X2}", target, 0x80); } } private void DumpSuspiciousGlobalSlots() { DumpAbsoluteSlot("callback_slot[0x80293BD08]", 0x000000080293BD08uL); DumpAbsoluteSlot("callback_arg[0x8030FBBE8]", 0x00000008030FBBE8uL); DumpAbsoluteSlot("tsc_freq_global[0x8030FD590]", 0x00000008030FD590uL); DumpAbsoluteSlot("tsc_base_global[0x8030FD598]", 0x00000008030FD598uL); DumpAbsoluteSlot("plt_got[0x8028F6100]", 0x00000008028F6100uL); DumpAbsoluteSlot("plt_got[0x8028F6158]", 0x00000008028F6158uL); DumpAbsoluteSlot("plt_got[0x8028F6160]", 0x00000008028F6160uL); DumpAbsoluteSlot("plt_got[0x8028F64C0]", 0x00000008028F64C0uL); DumpAbsoluteSlot("plt_got[0x8028F64C8]", 0x00000008028F64C8uL); DumpAbsoluteSlot("plt_got[0x8028F6590]", 0x00000008028F6590uL); DumpAbsoluteSlot("plt_got[0x8028F6708]", 0x00000008028F6708uL); DumpUnresolvedSentinelWindow("PLT-GOT", 0x00000008028F6100uL, 0x700); } private void DumpAbsoluteSlot(string name, ulong slotAddress) { if (!TryReadQword(slotAddress, out var value)) { Console.Error.WriteLine($"[LOADER][INFO] {name} @0x{slotAddress:X16} = "); return; } Console.Error.WriteLine($"[LOADER][INFO] {name} @0x{slotAddress:X16} = {FormatPointerWithNearestSymbol(value)}"); } private void DumpPointerWindow(string name, ulong baseAddress, int size) { if (baseAddress < 0x10000 || size <= 0) { return; } Console.Error.WriteLine($"[LOADER][INFO] {name} window @0x{baseAddress:X16}:"); for (int offset = 0; offset < size; offset += 8) { ulong slotAddress = baseAddress + (ulong)offset; if (!TryReadQword(slotAddress, out var value)) { Console.Error.WriteLine($"[LOADER][INFO] +0x{offset:X2}: "); break; } Console.Error.WriteLine($"[LOADER][INFO] +0x{offset:X2}: {FormatPointerWithNearestSymbol(value)}"); } } private unsafe bool TryReadQword(ulong address, out ulong value) { value = 0; if (address < 0x10000) { return false; } if (!_hostMemory.Query(address, out var mbi)) { return false; } ulong regionEnd = mbi.BaseAddress + mbi.RegionSize; if (mbi.State != HostRegionState.Committed || !IsReadableProtection(mbi.RawProtection) || regionEnd <= address || address > regionEnd - 8) { return false; } try { value = (ulong)Marshal.ReadInt64((nint)address); return true; } catch { value = 0; return false; } } private string FormatPointerWithNearestSymbol(ulong value) { string text = $"0x{value:X16}"; if (TryFormatNearestRuntimeSymbol(value, out string symbol)) { text += $" [{symbol}]"; } return text; } private void InitializeRuntimeSymbolIndex(IReadOnlyDictionary runtimeSymbols) { _runtimeSymbolsByName.Clear(); if (runtimeSymbols.Count == 0) { _runtimeSymbolsByAddress = Array.Empty>(); return; } List> list = new(runtimeSymbols.Count); foreach (KeyValuePair runtimeSymbol in runtimeSymbols) { if (runtimeSymbol.Value != 0L && !string.IsNullOrWhiteSpace(runtimeSymbol.Key)) { list.Add(runtimeSymbol); _runtimeSymbolsByName[runtimeSymbol.Key] = runtimeSymbol.Value; } } list.Sort((a, b) => a.Value.CompareTo(b.Value)); _runtimeSymbolsByAddress = list.ToArray(); } private bool TryFormatNearestRuntimeSymbol(ulong address, out string text) { text = string.Empty; KeyValuePair[] runtimeSymbolsByAddress = _runtimeSymbolsByAddress; if (runtimeSymbolsByAddress.Length == 0) { return false; } int low = 0; int high = runtimeSymbolsByAddress.Length - 1; int best = -1; while (low <= high) { int mid = low + ((high - low) >> 1); ulong value = runtimeSymbolsByAddress[mid].Value; if (value <= address) { best = mid; low = mid + 1; } else { high = mid - 1; } } if (best < 0) { return false; } KeyValuePair symbol = runtimeSymbolsByAddress[best]; ulong delta = address - symbol.Value; text = delta == 0 ? $"{symbol.Key} (0x{symbol.Value:X16})" : $"{symbol.Key}+0x{delta:X} (0x{symbol.Value:X16})"; return true; } private unsafe bool TryHandleLazyCommittedPage(EXCEPTION_RECORD* exceptionRecord, ulong rip, ulong rsp) { if (exceptionRecord->NumberParameters < 2) { return false; } ulong accessType = *exceptionRecord->ExceptionInformation; ulong faultAddress = exceptionRecord->ExceptionInformation[1]; if (accessType == 8 && faultAddress < 4294967296L) { return false; } if (faultAddress < 65536 || faultAddress >= 140737488355328L) { return false; } if (!IsGuestOwnedLazyCommitAddress(faultAddress, out var owner)) { return false; } if (!_hostMemory.Query(faultAddress, out var mbi)) { return false; } ulong pageBase = faultAddress & 0xFFFFFFFFFFFFF000uL; uint commitProtect = ResolveLazyCommitProtection(accessType, mbi.RawAllocationProtection); int traceIndex = Interlocked.Increment(ref _lazyCommitTraceCount); bool traceLazyCommit = ShouldTraceLazyCommit(traceIndex); if (traceLazyCommit) { Console.Error.WriteLine($"[LOADER][TRACE] lazy-query#{traceIndex}: fault=0x{faultAddress:X16} owner={owner} rip=0x{rip:X16} rsp=0x{rsp:X16} state=0x{mbi.RawState:X08} base=0x{mbi.BaseAddress:X16} size=0x{mbi.RegionSize:X16} alloc=0x{mbi.RawAllocationProtection:X08} prot=0x{mbi.RawProtection:X08}"); } if (mbi.State == HostRegionState.Committed && IsAccessCompatible(accessType, mbi.RawProtection)) { if (traceLazyCommit) { Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit-race#{traceIndex}: fault=0x{faultAddress:X16} protect=0x{mbi.RawProtection:X08}"); } return true; } bool committed = false; ulong committedBase = 0; ulong committedSize = 0; if (mbi.State == HostRegionState.Free) { if (TryGetLazyCommitWindow(faultAddress, mbi.BaseAddress, mbi.RegionSize, out var windowBase, out var windowSize) && TryReserveThenCommit(_hostMemory, windowBase, windowSize, windowBase, windowSize, commitProtect)) { committed = true; committedBase = windowBase; committedSize = windowSize; } else { ulong largeBase = faultAddress & 0xFFFFFFFFFFE00000uL; if (TryReserveThenCommit(_hostMemory, largeBase, 2097152uL, largeBase, 2097152uL, commitProtect)) { committed = true; committedBase = largeBase; committedSize = 2097152uL; } } if (!committed) { ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL; if (TryReserveThenCommit(_hostMemory, region64kBase, 65536uL, region64kBase, 65536uL, commitProtect)) { committed = true; committedBase = region64kBase; committedSize = 65536uL; } else if (TryReserveThenCommit(_hostMemory, pageBase, 4096uL, pageBase, 4096uL, commitProtect)) { committed = true; committedBase = pageBase; committedSize = 4096uL; } } if (!committed) { return false; } TryCommitRange(_hostMemory, pageBase + 4096, 4096uL, commitProtect); if (traceLazyCommit) { Console.Error.WriteLine($"[LOADER][TRACE] lazy-reserve-commit#{traceIndex}: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}"); } return true; } if (mbi.State != HostRegionState.Reserved) { return false; } if (TryGetLazyCommitWindow(faultAddress, mbi.BaseAddress, mbi.RegionSize, out var commitWindowBase, out var commitWindowSize) && TryCommitRange(_hostMemory, commitWindowBase, commitWindowSize, commitProtect)) { committed = true; committedBase = commitWindowBase; committedSize = commitWindowSize; } else { ulong largeCommitBase = faultAddress & 0xFFFFFFFFFFE00000uL; if (TryCommitRange(_hostMemory, largeCommitBase, 2097152uL, commitProtect)) { committed = true; committedBase = largeCommitBase; committedSize = 2097152uL; } } if (!committed) { ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL; if (TryCommitRange(_hostMemory, region64kBase, 65536uL, commitProtect)) { committed = true; committedBase = region64kBase; committedSize = 65536uL; } else if (TryCommitRange(_hostMemory, pageBase, 8192uL, commitProtect)) { committed = true; committedBase = pageBase; committedSize = 8192uL; } else if (TryCommitRange(_hostMemory, pageBase, 4096uL, commitProtect)) { committed = true; committedBase = pageBase; committedSize = 4096uL; } } if (!committed) { return false; } TryCommitRange(_hostMemory, pageBase + 4096, 4096uL, commitProtect); if (traceLazyCommit) { Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit#{traceIndex}: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}"); } return true; static bool TryGetLazyCommitWindow(ulong fault, ulong regionBase, ulong regionSize, out ulong baseAddress, out ulong length) { baseAddress = 0; length = 0; if (regionSize == 0 || ulong.MaxValue - regionBase < regionSize) { return false; } ulong regionEnd = regionBase + regionSize; ulong windowBase = fault & ~(LazyCommitWindowBytes - 1); if (windowBase < regionBase) { windowBase = regionBase; } if (windowBase >= regionEnd) { return false; } ulong windowEnd = Math.Min(regionEnd, windowBase + LazyCommitWindowBytes); ulong windowSize = windowEnd - windowBase; windowSize &= 0xFFFFFFFFFFFFF000uL; if (windowSize == 0) { return false; } baseAddress = windowBase; length = windowSize; return true; } // The commit protection is one of the two raw values ResolveLazyCommitProtection // produces (0x40 RWX / 0x04 RW); the enum mapping reproduces those exactly. static bool TryCommitRange(IHostMemory hostMemory, ulong baseAddress, ulong length, uint protection) { if (length == 0) { return false; } return hostMemory.Commit(baseAddress, length, protection == 64u ? HostPageProtection.ReadWriteExecute : HostPageProtection.ReadWrite); } static bool TryReserveRange(IHostMemory hostMemory, ulong baseAddress, ulong length) { if (length == 0) { return false; } return hostMemory.Reserve(baseAddress, length, HostPageProtection.ReadWrite) != 0; } static bool TryReserveThenCommit(IHostMemory hostMemory, ulong reserveAddress, ulong reserveSize, ulong commitAddress, ulong commitSize, uint protection) { if (!TryReserveRange(hostMemory, reserveAddress, reserveSize)) { return false; } return TryCommitRange(hostMemory, commitAddress, commitSize, protection); } static bool IsAccessCompatible(ulong accessType, uint protection) { const uint pageNoAccess = 0x01; const uint pageReadOnly = 0x02; const uint pageReadWrite = 0x04; const uint pageWriteCopy = 0x08; const uint pageExecute = 0x10; const uint pageExecuteRead = 0x20; const uint pageExecuteReadWrite = 0x40; const uint pageExecuteWriteCopy = 0x80; const uint pageGuard = 0x100; const uint accessMask = 0xFF; if ((protection & pageGuard) != 0) { return false; } uint access = protection & accessMask; if (access == pageNoAccess) { return false; } return accessType switch { 0 => access is pageReadOnly or pageReadWrite or pageWriteCopy or pageExecuteRead or pageExecuteReadWrite or pageExecuteWriteCopy, 1 => access is pageReadWrite or pageWriteCopy or pageExecuteReadWrite or pageExecuteWriteCopy, 8 => access is pageExecute or pageExecuteRead or pageExecuteReadWrite or pageExecuteWriteCopy, _ => false }; } } private static bool ShouldTraceLazyCommit(int traceIndex) { if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_LAZY_COMMIT"), "1", StringComparison.Ordinal)) { return true; } return traceIndex <= 16 || traceIndex % 256 == 0; } private static uint ResolveLazyCommitProtection(ulong accessType, uint allocationProtect) { if (accessType == 8 || (allocationProtect & 0xF0) != 0) { return 64u; } return 4u; } }