diff --git a/src/SharpEmu.Core/Cpu/CpuDispatcher.cs b/src/SharpEmu.Core/Cpu/CpuDispatcher.cs index 7bdc5430..f09d49d8 100644 --- a/src/SharpEmu.Core/Cpu/CpuDispatcher.cs +++ b/src/SharpEmu.Core/Cpu/CpuDispatcher.cs @@ -7,6 +7,7 @@ using SharpEmu.Core.Cpu.Native; using SharpEmu.Core.Loader; using SharpEmu.Core.Memory; using SharpEmu.HLE; +using SharpEmu.HLE.Host; using SharpEmu.Logging; namespace SharpEmu.Core.Cpu; @@ -41,16 +42,19 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable ]; private readonly IVirtualMemory _virtualMemory; private readonly IModuleManager _moduleManager; + private readonly IHostPlatform? _hostPlatform; private INativeCpuBackend? _nativeCpuBackend; public CpuDispatcher( IVirtualMemory virtualMemory, IModuleManager moduleManager, - INativeCpuBackend? nativeCpuBackend = null) + INativeCpuBackend? nativeCpuBackend = null, + IHostPlatform? hostPlatform = null) { _virtualMemory = virtualMemory ?? throw new ArgumentNullException(nameof(virtualMemory)); _moduleManager = moduleManager ?? throw new ArgumentNullException(nameof(moduleManager)); _nativeCpuBackend = nativeCpuBackend; + _hostPlatform = hostPlatform; } public ulong? LastEntryPoint { get; private set; } @@ -266,7 +270,7 @@ public sealed class CpuDispatcher : ICpuDispatcher, IDisposable entryFrameDiagnostic, Environment.NewLine, "CpuEngine: native-only"); - _nativeCpuBackend ??= new DirectExecutionBackend(_moduleManager); + _nativeCpuBackend ??= new DirectExecutionBackend(_moduleManager, _hostPlatform); if (_nativeCpuBackend.TryExecute( context, entryPoint, diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs index f93f8819..2bc8b5e8 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading; using SharpEmu.HLE; +using SharpEmu.HLE.Host; using SharpEmu.Logging; namespace SharpEmu.Core.Cpu.Native; @@ -134,8 +135,9 @@ public sealed partial class DirectExecutionBackend int num2 = 0; List list = new List(16); ulong num3 = scanStart; - MEMORY_BASIC_INFORMATION64 lpBuffer; - while (num3 < scanEnd && VirtualQuery((void*)num3, out lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0) + var hostMemory = ResolveDiagnosticsHostMemory(); + HostRegionInfo lpBuffer; + while (num3 < scanEnd && hostMemory.Query(num3, out lpBuffer)) { ulong baseAddress = lpBuffer.BaseAddress; ulong num4 = baseAddress + lpBuffer.RegionSize; @@ -145,7 +147,7 @@ public sealed partial class DirectExecutionBackend } ulong value = Math.Max(num3, baseAddress); ulong num5 = Math.Min(num4, scanEnd); - if (lpBuffer.State == 4096 && IsReadableProtection(lpBuffer.Protect) && !IsExecutableProtection(lpBuffer.Protect)) + if (lpBuffer.State == HostRegionState.Committed && IsReadableProtection(lpBuffer.RawProtection) && !IsExecutableProtection(lpBuffer.RawProtection)) { ulong num6 = AlignUp(value, 8uL); for (ulong num7 = num6; num7 + 8 <= num5; num7 += 8) @@ -350,7 +352,7 @@ public sealed partial class DirectExecutionBackend { return false; } - if (VirtualQuery((void*)address, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0) + if (!ResolveDiagnosticsHostMemory().Query(address, out var lpBuffer)) { return false; } @@ -359,7 +361,7 @@ public sealed partial class DirectExecutionBackend { return false; } - if (lpBuffer.State != 4096 || !IsReadableProtection(lpBuffer.Protect)) + if (lpBuffer.State != HostRegionState.Committed || !IsReadableProtection(lpBuffer.RawProtection)) { return false; } @@ -391,12 +393,12 @@ public sealed partial class DirectExecutionBackend return true; } - if (VirtualQuery((void*)address, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0) + if (!ResolveDiagnosticsHostMemory().Query(address, out var lpBuffer)) { return false; } - var executable = lpBuffer.State == 4096 && IsExecutableProtection(lpBuffer.Protect); + var executable = lpBuffer.State == HostRegionState.Committed && IsExecutableProtection(lpBuffer.RawProtection); if (executable) { _knownExecutablePages.TryAdd(pageAddress, 0); @@ -415,6 +417,14 @@ public sealed partial class DirectExecutionBackend return (value + num) & ~num; } + // Diagnostics helpers are static (reachable from static handler paths), so + // they use the platform injected into the backend active on this thread and + // fall back to the process-wide singleton only when no run is bound. + private static IHostMemory ResolveDiagnosticsHostMemory() + { + return _activeExecutionBackend?._hostMemory ?? HostPlatform.Current.Memory; + } + private static bool IsReadableProtection(uint protect) { if ((protect & 0x100) != 0 || (protect & 1) != 0) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs index 4e1a28a4..a16de8d7 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs @@ -9,7 +9,9 @@ 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; @@ -22,12 +24,12 @@ public sealed partial class DirectExecutionBackend { if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_RAW_HANDLER"), "1", StringComparison.Ordinal)) { - _rawExceptionHandlerStub = CreateExceptionHandlerTrampoline(RawVectoredHandlerPtrManaged); + _rawExceptionHandlerStub = _faultHandling.CreateHandlerThunk(RawVectoredHandlerPtrManaged, _hostRspSlotTlsIndex, _tlsGetValueAddress); if (_rawExceptionHandlerStub == 0) { throw new InvalidOperationException("Failed to create raw exception handler trampoline"); } - _rawExceptionHandler = (nint)AddVectoredExceptionHandler(1u, _rawExceptionHandlerStub); + _rawExceptionHandler = _faultHandling.AddFirstChanceHandler(_rawExceptionHandlerStub); Console.Error.WriteLine($"[LOADER][INFO] Raw exception handler installed: 0x{_rawExceptionHandler:X16}"); } else @@ -37,22 +39,22 @@ public sealed partial class DirectExecutionBackend _handlerDelegate = VectoredHandler; _handlerHandle = GCHandle.Alloc(_handlerDelegate); - _exceptionHandlerStub = CreateExceptionHandlerTrampoline(Marshal.GetFunctionPointerForDelegate(_handlerDelegate)); + _exceptionHandlerStub = _faultHandling.CreateHandlerThunk(Marshal.GetFunctionPointerForDelegate(_handlerDelegate), _hostRspSlotTlsIndex, _tlsGetValueAddress); if (_exceptionHandlerStub == 0) { throw new InvalidOperationException("Failed to create exception handler trampoline"); } - _exceptionHandler = (nint)AddVectoredExceptionHandler(1u, _exceptionHandlerStub); + _exceptionHandler = _faultHandling.AddFirstChanceHandler(_exceptionHandlerStub); Console.Error.WriteLine($"[LOADER][INFO] Exception handler installed: 0x{_exceptionHandler:X16}"); _unhandledFilterDelegate = UnhandledExceptionFilter; _unhandledFilterHandle = GCHandle.Alloc(_unhandledFilterDelegate); - _unhandledFilterStub = CreateExceptionHandlerTrampoline(Marshal.GetFunctionPointerForDelegate(_unhandledFilterDelegate)); + _unhandledFilterStub = _faultHandling.CreateHandlerThunk(Marshal.GetFunctionPointerForDelegate(_unhandledFilterDelegate), _hostRspSlotTlsIndex, _tlsGetValueAddress); if (_unhandledFilterStub == 0) { throw new InvalidOperationException("Failed to create unhandled exception filter trampoline"); } - SetUnhandledExceptionFilter(_unhandledFilterStub); + _faultHandling.SetUnhandledFilter(_unhandledFilterStub); } private unsafe int UnhandledExceptionFilter(void* exceptionInfo) @@ -60,8 +62,8 @@ public sealed partial class DirectExecutionBackend try { EXCEPTION_RECORD* exceptionRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ExceptionRecord; - ulong rip = ReadCtxU64(((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord, 248); - ulong rsp = ReadCtxU64(((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord, 152); + 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}"); @@ -100,8 +102,8 @@ public sealed partial class DirectExecutionBackend return 0; } - ulong rip = ReadCtxU64(contextRecord, 248); - ulong rsp = ReadCtxU64(contextRecord, 152); + 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 @@ -112,7 +114,7 @@ public sealed partial class DirectExecutionBackend $"veh_in_gateway code=0x{exceptionCode:X8} rip=0x{rip:X16} gateway_depth={_threadModeGatewayDepth}"); } - if (exceptionCode == 3221225477u && TryHandleLazyCommittedPage(exceptionRecord, rip, rsp)) + if (exceptionCode == WindowsFaultCodes.AccessViolation && TryHandleLazyCommittedPage(exceptionRecord, rip, rsp)) { return -1; } @@ -127,10 +129,10 @@ public sealed partial class DirectExecutionBackend switch (exceptionCode) { - case 3221225477u: + case WindowsFaultCodes.AccessViolation: LogAccessViolationTrace(exceptionAddress, exceptionRecord); break; - case 3221226505u: + case WindowsFaultCodes.FastFail: { ulong p0 = exceptionRecord->NumberParameters >= 1 ? (*exceptionRecord->ExceptionInformation) : 0; ulong p1 = exceptionRecord->NumberParameters >= 2 ? exceptionRecord->ExceptionInformation[1] : 0; @@ -140,21 +142,21 @@ public sealed partial class DirectExecutionBackend } } - ulong rax = ReadCtxU64(contextRecord, 120); - ulong rbx = ReadCtxU64(contextRecord, 144); - ulong rcx = ReadCtxU64(contextRecord, 128); - ulong rdx = ReadCtxU64(contextRecord, 136); - ulong rsi = ReadCtxU64(contextRecord, 168); - ulong rdi = ReadCtxU64(contextRecord, 176); - ulong rbp = ReadCtxU64(contextRecord, 160); - ulong r8 = ReadCtxU64(contextRecord, 184); - ulong r9 = ReadCtxU64(contextRecord, 192); - ulong r10 = ReadCtxU64(contextRecord, 200); - ulong r11 = ReadCtxU64(contextRecord, 208); - ulong r12 = ReadCtxU64(contextRecord, 216); - ulong r13 = ReadCtxU64(contextRecord, 224); - ulong r14 = ReadCtxU64(contextRecord, 232); - ulong r15 = ReadCtxU64(contextRecord, 240); + 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!"); @@ -185,7 +187,7 @@ public sealed partial class DirectExecutionBackend ulong accessType = 0; ulong target = 0; - if (exceptionCode == 3221225477u && exceptionRecord->NumberParameters >= 2) + if (exceptionCode == WindowsFaultCodes.AccessViolation && exceptionRecord->NumberParameters >= 2) { accessType = *exceptionRecord->ExceptionInformation; target = exceptionRecord->ExceptionInformation[1]; @@ -198,9 +200,9 @@ public sealed partial class DirectExecutionBackend }; Console.Error.WriteLine("[LOADER][INFO] AV access: " + accessText); Console.Error.WriteLine($"[LOADER][INFO] AV target: 0x{target:X16}"); - if (VirtualQuery((void*)target, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0) + 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.State:X08} protect=0x{mbi.Protect:X08}"); + 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}"); } } @@ -248,7 +250,7 @@ public sealed partial class DirectExecutionBackend switch (exceptionCode) { - case 3221225477u: + 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"); @@ -295,11 +297,11 @@ public sealed partial class DirectExecutionBackend DumpGuestReferenceDiagnostics(); DumpGuestPointerWindowDiagnostics(); break; - case 2147483651u: + case WindowsFaultCodes.Breakpoint: Console.Error.WriteLine("[LOADER][WARNING] Type: Breakpoint (int3)"); Console.Error.WriteLine("[LOADER][WARNING] Unexpected breakpoint in direct-bridge mode"); break; - case 3221225501u: + case WindowsFaultCodes.IllegalInstruction: Console.Error.WriteLine("[LOADER][INFO] Type: Illegal Instruction"); break; } @@ -332,8 +334,8 @@ public sealed partial class DirectExecutionBackend EXCEPTION_POINTERS* pointers = (EXCEPTION_POINTERS*)exceptionInfo; EXCEPTION_RECORD* record = pointers->ExceptionRecord; void* contextRecord = pointers->ContextRecord; - ulong rip = contextRecord != null ? ReadCtxU64(contextRecord, 248) : 0; - ulong rsp = contextRecord != null ? ReadCtxU64(contextRecord, 152) : 0; + 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( @@ -479,7 +481,7 @@ public sealed partial class DirectExecutionBackend ulong address = scanBase; while (address < scanEnd) { - if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0) + if (!_hostMemory.Query(address, out var mbi)) { break; } @@ -491,9 +493,9 @@ public sealed partial class DirectExecutionBackend break; } - if (mbi.State == MEM_COMMIT && - IsReadableProtection(mbi.Protect) && - IsExecutableProtection(mbi.Protect)) + if (mbi.State == HostRegionState.Committed && + IsReadableProtection(mbi.RawProtection) && + IsExecutableProtection(mbi.RawProtection)) { ScanExecutableRegionForTargetReferences(regionBase, regionEnd, targetList, hitCounts, maxHitsPerTarget); } @@ -798,13 +800,13 @@ public sealed partial class DirectExecutionBackend return false; } - if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0) + if (!_hostMemory.Query(address, out var mbi)) { return false; } ulong regionEnd = mbi.BaseAddress + mbi.RegionSize; - if (mbi.State != MEM_COMMIT || !IsReadableProtection(mbi.Protect) || regionEnd <= address || address > regionEnd - 8) + if (mbi.State != HostRegionState.Committed || !IsReadableProtection(mbi.RawProtection) || regionEnd <= address || address > regionEnd - 8) { return false; } @@ -916,25 +918,25 @@ public sealed partial class DirectExecutionBackend { return false; } - if (VirtualQuery((void*)faultAddress, out var mbi, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0) + if (!_hostMemory.Query(faultAddress, out var mbi)) { return false; } ulong pageBase = faultAddress & 0xFFFFFFFFFFFFF000uL; - uint commitProtect = ResolveLazyCommitProtection(accessType, mbi.AllocationProtect); + 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.State:X08} base=0x{mbi.BaseAddress:X16} size=0x{mbi.RegionSize:X16} alloc=0x{mbi.AllocationProtect:X08} prot=0x{mbi.Protect:X08}"); + 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 == 4096 && IsAccessCompatible(accessType, mbi.Protect)) + 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.Protect:X08}"); + Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit-race#{traceIndex}: fault=0x{faultAddress:X16} protect=0x{mbi.RawProtection:X08}"); } return true; } @@ -943,10 +945,10 @@ public sealed partial class DirectExecutionBackend ulong committedBase = 0; ulong committedSize = 0; - if (mbi.State == 65536) + if (mbi.State == HostRegionState.Free) { if (TryGetLazyCommitWindow(faultAddress, mbi.BaseAddress, mbi.RegionSize, out var windowBase, out var windowSize) && - TryReserveThenCommit(windowBase, windowSize, windowBase, windowSize, commitProtect)) + TryReserveThenCommit(_hostMemory, windowBase, windowSize, windowBase, windowSize, commitProtect)) { committed = true; committedBase = windowBase; @@ -955,7 +957,7 @@ public sealed partial class DirectExecutionBackend else { ulong largeBase = faultAddress & 0xFFFFFFFFFFE00000uL; - if (TryReserveThenCommit(largeBase, 2097152uL, largeBase, 2097152uL, commitProtect)) + if (TryReserveThenCommit(_hostMemory, largeBase, 2097152uL, largeBase, 2097152uL, commitProtect)) { committed = true; committedBase = largeBase; @@ -966,13 +968,13 @@ public sealed partial class DirectExecutionBackend if (!committed) { ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL; - if (TryReserveThenCommit(region64kBase, 65536uL, region64kBase, 65536uL, commitProtect)) + if (TryReserveThenCommit(_hostMemory, region64kBase, 65536uL, region64kBase, 65536uL, commitProtect)) { committed = true; committedBase = region64kBase; committedSize = 65536uL; } - else if (TryReserveThenCommit(pageBase, 4096uL, pageBase, 4096uL, commitProtect)) + else if (TryReserveThenCommit(_hostMemory, pageBase, 4096uL, pageBase, 4096uL, commitProtect)) { committed = true; committedBase = pageBase; @@ -985,7 +987,7 @@ public sealed partial class DirectExecutionBackend return false; } - TryCommitRange(pageBase + 4096, 4096uL, commitProtect); + 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}"); @@ -993,13 +995,13 @@ public sealed partial class DirectExecutionBackend return true; } - if (mbi.State != 8192) + if (mbi.State != HostRegionState.Reserved) { return false; } if (TryGetLazyCommitWindow(faultAddress, mbi.BaseAddress, mbi.RegionSize, out var commitWindowBase, out var commitWindowSize) && - TryCommitRange(commitWindowBase, commitWindowSize, commitProtect)) + TryCommitRange(_hostMemory, commitWindowBase, commitWindowSize, commitProtect)) { committed = true; committedBase = commitWindowBase; @@ -1008,7 +1010,7 @@ public sealed partial class DirectExecutionBackend else { ulong largeCommitBase = faultAddress & 0xFFFFFFFFFFE00000uL; - if (TryCommitRange(largeCommitBase, 2097152uL, commitProtect)) + if (TryCommitRange(_hostMemory, largeCommitBase, 2097152uL, commitProtect)) { committed = true; committedBase = largeCommitBase; @@ -1019,19 +1021,19 @@ public sealed partial class DirectExecutionBackend if (!committed) { ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL; - if (TryCommitRange(region64kBase, 65536uL, commitProtect)) + if (TryCommitRange(_hostMemory, region64kBase, 65536uL, commitProtect)) { committed = true; committedBase = region64kBase; committedSize = 65536uL; } - else if (TryCommitRange(pageBase, 8192uL, commitProtect)) + else if (TryCommitRange(_hostMemory, pageBase, 8192uL, commitProtect)) { committed = true; committedBase = pageBase; committedSize = 8192uL; } - else if (TryCommitRange(pageBase, 4096uL, commitProtect)) + else if (TryCommitRange(_hostMemory, pageBase, 4096uL, commitProtect)) { committed = true; committedBase = pageBase; @@ -1044,7 +1046,7 @@ public sealed partial class DirectExecutionBackend return false; } - TryCommitRange(pageBase + 4096, 4096uL, commitProtect); + 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}"); @@ -1085,31 +1087,33 @@ public sealed partial class DirectExecutionBackend return true; } - static unsafe bool TryCommitRange(ulong baseAddress, ulong length, uint protection) + // 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 VirtualAlloc((void*)baseAddress, (nuint)length, 4096u, protection) != null; + return hostMemory.Commit(baseAddress, length, protection == 64u ? HostPageProtection.ReadWriteExecute : HostPageProtection.ReadWrite); } - static unsafe bool TryReserveRange(ulong baseAddress, ulong length) + static bool TryReserveRange(IHostMemory hostMemory, ulong baseAddress, ulong length) { if (length == 0) { return false; } - return VirtualAlloc((void*)baseAddress, (nuint)length, 8192u, 4u) != null; + return hostMemory.Reserve(baseAddress, length, HostPageProtection.ReadWrite) != 0; } - static bool TryReserveThenCommit(ulong reserveAddress, ulong reserveSize, ulong commitAddress, ulong commitSize, uint protection) + static bool TryReserveThenCommit(IHostMemory hostMemory, ulong reserveAddress, ulong reserveSize, ulong commitAddress, ulong commitSize, uint protection) { - if (!TryReserveRange(reserveAddress, reserveSize)) + if (!TryReserveRange(hostMemory, reserveAddress, reserveSize)) { return false; } - return TryCommitRange(commitAddress, commitSize, protection); + return TryCommitRange(hostMemory, commitAddress, commitSize, protection); } static bool IsAccessCompatible(ulong accessType, uint protection) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs index 5a26d07f..f393bb9f 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs @@ -8,8 +8,10 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading; using SharpEmu.Core.Cpu; +using SharpEmu.Core.Cpu.Native.Windows; using SharpEmu.Core.Loader; using SharpEmu.HLE; +using SharpEmu.HLE.Host; namespace SharpEmu.Core.Cpu.Native; @@ -69,23 +71,23 @@ public sealed partial class DirectExecutionBackend private unsafe static int TryRecoverUnresolvedSentinel(void* exceptionInfo) { EXCEPTION_RECORD* exceptionRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ExceptionRecord; - if (exceptionRecord->ExceptionCode != 3221225477u) + if (exceptionRecord->ExceptionCode != WindowsFaultCodes.AccessViolation) { return 0; } void* contextRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ContextRecord; - ulong value = ReadCtxU64(contextRecord, 248); + ulong value = ReadCtxU64(contextRecord, CTX_RIP); ulong value2 = (ulong)exceptionRecord->ExceptionAddress; if (!IsUnresolvedSentinel(value) && !IsUnresolvedSentinel(value2)) { return 0; } - ulong rsp = ReadCtxU64(contextRecord, 152); - WriteCtxU64(contextRecord, 120, 0uL); + ulong rsp = ReadCtxU64(contextRecord, CTX_RSP); + WriteCtxU64(contextRecord, CTX_RAX, 0uL); if (TryGetPlausibleReturnFromStack(rsp, out var returnRip, out var nextRsp)) { - WriteCtxU64(contextRecord, 152, nextRsp); - WriteCtxU64(contextRecord, 248, returnRip); + WriteCtxU64(contextRecord, CTX_RSP, nextRsp); + WriteCtxU64(contextRecord, CTX_RIP, returnRip); Interlocked.Increment(ref _rawSentinelRecoveries); if (LogThreadMode) { @@ -1725,9 +1727,9 @@ public sealed partial class DirectExecutionBackend { var candidateBase = ImportStubRegionCanonicalBase - (ulong)candidateIndex * ImportStubRegionAddressStride; - if (VirtualQuery((void*)candidateBase, out var memoryInfo, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0 || + if (!_hostMemory.Query(candidateBase, out var memoryInfo) || memoryInfo.RegionSize == 0 || - memoryInfo.State != 4096) + memoryInfo.State != HostRegionState.Committed) { continue; } @@ -2066,7 +2068,7 @@ public sealed partial class DirectExecutionBackend uint flNewProtect = default(uint); try { - if (Marshal.ReadByte(num2) != 232 || !VirtualProtect((void*)num, 5u, 64u, &flNewProtect)) + if (Marshal.ReadByte(num2) != 232 || !_hostMemory.Protect((ulong)(void*)num, 5u, HostPageProtection.ReadWriteExecute, out flNewProtect)) { return; } @@ -2074,7 +2076,7 @@ public sealed partial class DirectExecutionBackend { Marshal.WriteByte(num2 + i, 144); } - FlushInstructionCache(GetCurrentProcess(), (void*)num, 5u); + _hostMemory.FlushInstructionCache((ulong)(void*)num, 5u); _patchedEa020eLookupCall = true; Console.Error.WriteLine($"[LOADER][WARNING] Import#{dispatchIndex}: patched hash-lookup call at 0x{num:X16} -> NOP*5"); } @@ -2085,7 +2087,7 @@ public sealed partial class DirectExecutionBackend { if (flNewProtect != 0) { - VirtualProtect((void*)num, 5u, flNewProtect, &flNewProtect); + _hostMemory.ProtectRaw((ulong)(void*)num, 5u, flNewProtect, out flNewProtect); } } } diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.NativeWorker.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.NativeWorker.cs index b77cf624..c5c50dbb 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.NativeWorker.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.NativeWorker.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; using SharpEmu.HLE; +using SharpEmu.HLE.Host; namespace SharpEmu.Core.Cpu.Native; @@ -35,20 +36,6 @@ public sealed partial class DirectExecutionBackend private bool _nativeWorkersDisposed; private int _nativeWorkerCreationFailedLogged; - private const uint StackSizeParamIsAReservation = 0x00010000u; - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern nint CreateThread( - nint lpThreadAttributes, - nuint dwStackSize, - nint lpStartAddress, - nint lpParameter, - uint dwCreationFlags, - out uint lpThreadId); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern uint WaitForSingleObject(nint hHandle, uint dwMilliseconds); - // Runs an emitted guest entry stub. Preferred path is a pooled native worker // thread; falls back to the historical inline calli (guest frames above this // thread's managed frames) when workers are disabled or unavailable. @@ -61,7 +48,7 @@ public sealed partial class DirectExecutionBackend var worker = RentNativeGuestExecutor(); if (worker is null) { - TlsSetValue(_hostRspSlotTlsIndex, (nint)hostRspSlot); + _hostThreading.SetTlsValue(_hostRspSlotTlsIndex, (nint)hostRspSlot); return CallNativeEntry(entryStub); } try @@ -229,7 +216,7 @@ public sealed partial class DirectExecutionBackend public static NativeGuestExecutor? TryCreate(DirectExecutionBackend backend) { - if (!EnsureKernel32Exports()) + if (!EnsureHostRuntimeExports(backend._hostSymbols)) { return null; } @@ -242,32 +229,27 @@ public sealed partial class DirectExecutionBackend return executor; } - private static bool EnsureKernel32Exports() + private static bool EnsureHostRuntimeExports(IHostSymbolResolver symbols) { if (_exitThreadAddress != 0) { return _waitForSingleObjectAddress != 0 && _setEventAddress != 0; } - nint kernel32 = GetModuleHandle("kernel32.dll"); - if (kernel32 == 0) - { - return false; - } - _waitForSingleObjectAddress = GetProcAddress(kernel32, "WaitForSingleObject"); - _setEventAddress = GetProcAddress(kernel32, "SetEvent"); - _exitThreadAddress = GetProcAddress(kernel32, "ExitThread"); + _waitForSingleObjectAddress = symbols.GetAddress(HostRuntimeFunction.WaitForSingleObject); + _setEventAddress = symbols.GetAddress(HostRuntimeFunction.SetEvent); + _exitThreadAddress = symbols.GetAddress(HostRuntimeFunction.ExitThread); return _waitForSingleObjectAddress != 0 && _setEventAddress != 0 && _exitThreadAddress != 0; } private bool Initialize() { _selfHandle = GCHandle.Alloc(this); - _controlBlock = VirtualAlloc(null, 4096u, 12288u, 4u); + _controlBlock = (void*)_backend._hostMemory.Allocate(0, 4096u, HostPageProtection.ReadWrite); if (_controlBlock == null) { return false; } - _loopStub = VirtualAlloc(null, LoopStubSize, 12288u, 64u); + _loopStub = (void*)_backend._hostMemory.Allocate(0, LoopStubSize, HostPageProtection.ReadWriteExecute); if (_loopStub == null) { return false; @@ -349,17 +331,15 @@ public sealed partial class DirectExecutionBackend *(int*)(code + skipJump) = skipEntryOffset - (skipJump + sizeof(int)); uint oldProtect = 0; - if (!VirtualProtect(_loopStub, LoopStubSize, 32u, &oldProtect)) + if (!_backend._hostMemory.Protect((ulong)_loopStub, LoopStubSize, HostPageProtection.ReadExecute, out oldProtect)) { return false; } - FlushInstructionCache(GetCurrentProcess(), _loopStub, LoopStubSize); - _threadHandle = CreateThread( - 0, - WorkerStackReservation, + _backend._hostMemory.FlushInstructionCache((ulong)_loopStub, LoopStubSize); + _threadHandle = _backend._hostThreading.CreateNativeThread( (nint)_loopStub, 0, - StackSizeParamIsAReservation, + WorkerStackReservation, out _nativeThreadId); if (_threadHandle == 0) { @@ -465,7 +445,7 @@ public sealed partial class DirectExecutionBackend _prevYieldRequested = _activeGuestThreadYieldRequested; _prevYieldReason = _activeGuestThreadYieldReason; _prevState = _activeGuestThreadState; - _prevHostRspSlot = TlsGetValue(backend._hostRspSlotTlsIndex); + _prevHostRspSlot = backend._hostThreading.GetTlsValue(backend._hostRspSlotTlsIndex); _prevGuestThreadHandle = GuestThreadExecution.EnterGuestThread(_runGuestThreadHandle); _entered = true; _activeExecutionBackend = backend; @@ -477,11 +457,11 @@ public sealed partial class DirectExecutionBackend _activeGuestThreadYieldReason = null; _activeGuestThreadState = _runState; backend.BindTlsBase(_runContext!); - TlsSetValue(backend._hostRspSlotTlsIndex, _runHostRspSlot); + backend._hostThreading.SetTlsValue(backend._hostRspSlotTlsIndex, _runHostRspSlot); if (_runState is { } state) { _prevHostThreadId = Volatile.Read(ref state.HostThreadId); - Volatile.Write(ref state.HostThreadId, unchecked((int)GetCurrentThreadId())); + Volatile.Write(ref state.HostThreadId, unchecked((int)backend._hostThreading.CurrentThreadId)); } if (_runAffinityMask != 0) { @@ -511,7 +491,7 @@ public sealed partial class DirectExecutionBackend { Volatile.Write(ref state.HostThreadId, _prevHostThreadId); } - TlsSetValue(_backend._hostRspSlotTlsIndex, _prevHostRspSlot); + _backend._hostThreading.SetTlsValue(_backend._hostRspSlotTlsIndex, _prevHostRspSlot); GuestThreadExecution.RestoreGuestThread(_prevGuestThreadHandle); _activeExecutionBackend = _prevBackend; _activeCpuContext = _prevContext; @@ -548,8 +528,8 @@ public sealed partial class DirectExecutionBackend var exited = _threadHandle == 0; if (_threadHandle != 0) { - exited = WaitForSingleObject(_threadHandle, 1000u) == 0u; - CloseHandle(_threadHandle); + exited = _backend._hostThreading.WaitForThreadExit(_threadHandle, 1000u); + _backend._hostThreading.CloseThreadHandle(_threadHandle); _threadHandle = 0; } if (!exited) @@ -563,12 +543,12 @@ public sealed partial class DirectExecutionBackend } if (_loopStub != null) { - VirtualFree(_loopStub, 0u, 32768u); + _backend._hostMemory.Free((ulong)_loopStub); _loopStub = null; } if (_controlBlock != null) { - VirtualFree(_controlBlock, 0u, 32768u); + _backend._hostMemory.Free((ulong)_controlBlock); _controlBlock = null; } if (_selfHandle.IsAllocated) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs index 05f0c10b..d4a43fe3 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs @@ -9,9 +9,11 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading; using SharpEmu.Core.Cpu; +using SharpEmu.Core.Cpu.Native.Windows; using SharpEmu.Core.Loader; using SharpEmu.Core.Memory; using SharpEmu.HLE; +using SharpEmu.HLE.Host; using SharpEmu.Logging; namespace SharpEmu.Core.Cpu.Native; @@ -126,27 +128,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private delegate int ExceptionHandlerDelegate(void* exceptionInfo); - private struct MEMORY_BASIC_INFORMATION64 - { - public ulong BaseAddress; - - public ulong AllocationBase; - - public uint AllocationProtect; - - public uint __alignment1; - - public ulong RegionSize; - - public uint State; - - public uint Protect; - - public uint Type; - - public uint __alignment2; - } - private const ulong SYSTEM_RESERVED = 34359738368uL; private const ulong CODE_BASE_OFFSET = 4294967296uL; @@ -612,6 +593,16 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private nint _selfHandlePtr; + private readonly IHostPlatform _hostPlatform; + + private readonly IHostThreading _hostThreading; + + private readonly IHostSymbolResolver _hostSymbols; + + private readonly IHostMemory _hostMemory; + + private readonly IHostFaultHandling _faultHandling; + private const int MinTlsPatchInstructionBytes = 9; private delegate ulong ImportGatewayDelegate(nint backendHandle, int importIndex, nint argPackPtr); @@ -629,41 +620,41 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private static readonly nint RawUnhandledFilterPtrManaged = Marshal.GetFunctionPointerForDelegate(RawUnhandledFilterDelegateInstance); - private const int CTX_MXCSR = 52; + private const int CTX_MXCSR = Win64ContextOffsets.Mxcsr; - private const int CTX_RAX = 120; + private const int CTX_RAX = Win64ContextOffsets.Rax; - private const int CTX_RCX = 128; + private const int CTX_RCX = Win64ContextOffsets.Rcx; - private const int CTX_RDX = 136; + private const int CTX_RDX = Win64ContextOffsets.Rdx; - private const int CTX_RBX = 144; + private const int CTX_RBX = Win64ContextOffsets.Rbx; - private const int CTX_RSP = 152; + private const int CTX_RSP = Win64ContextOffsets.Rsp; - private const int CTX_RBP = 160; + private const int CTX_RBP = Win64ContextOffsets.Rbp; - private const int CTX_RSI = 168; + private const int CTX_RSI = Win64ContextOffsets.Rsi; - private const int CTX_RDI = 176; + private const int CTX_RDI = Win64ContextOffsets.Rdi; - private const int CTX_R8 = 184; + private const int CTX_R8 = Win64ContextOffsets.R8; - private const int CTX_R9 = 192; + private const int CTX_R9 = Win64ContextOffsets.R9; - private const int CTX_R10 = 200; + private const int CTX_R10 = Win64ContextOffsets.R10; - private const int CTX_R11 = 208; + private const int CTX_R11 = Win64ContextOffsets.R11; - private const int CTX_R12 = 216; + private const int CTX_R12 = Win64ContextOffsets.R12; - private const int CTX_R13 = 224; + private const int CTX_R13 = Win64ContextOffsets.R13; - private const int CTX_R14 = 232; + private const int CTX_R14 = Win64ContextOffsets.R14; - private const int CTX_R15 = 240; + private const int CTX_R15 = Win64ContextOffsets.R15; - private const int CTX_RIP = 248; + private const int CTX_RIP = Win64ContextOffsets.Rip; private ExceptionHandlerDelegate? _handlerDelegate; @@ -700,19 +691,16 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private static void TraceThreadMode(string message) { + // Prefer the platform injected into the backend bound to this thread; + // the ambient is set on every guest/import/VEH transition this tracer + // observes. The singleton fallback only covers pre-run traces, where a + // constructed backend implies the platform resolved successfully. + var threading = _activeExecutionBackend?._hostThreading ?? HostPlatform.Current.Threading; Console.Error.WriteLine( - $"[THREADMODE] {message} cycle={_threadModeCycleId} tid={GetCurrentThreadId()} managed={Environment.CurrentManagedThreadId}"); + $"[THREADMODE] {message} cycle={_threadModeCycleId} tid={threading.CurrentThreadId} managed={Environment.CurrentManagedThreadId}"); Console.Error.Flush(); } - private const uint MEM_COMMIT = 4096u; - - private const uint MEM_RESERVE = 8192u; - - private const uint MEM_FREE = 65536u; - - private const uint MEM_RELEASE = 32768u; - private const uint PAGE_EXECUTE = 16u; private const uint PAGE_EXECUTE_WRITECOPY = 128u; @@ -731,16 +719,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private const uint HostXmmSaveAreaSize = 0xA0u; - private const uint ContextAmd64ControlInteger = 0x00100003u; - - private const uint ThreadGetContext = 0x0008u; - - private const uint ThreadSuspendResume = 0x0002u; - - private const int Win64ContextSize = 0x4D0; - - private const int Win64ContextFlagsOffset = 0x30; - private readonly record struct HostThreadContextSnapshot( bool IsValid, ulong Rip, @@ -881,35 +859,39 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _activeGuestThreadYieldReason = previousYieldReason; } - public unsafe DirectExecutionBackend(IModuleManager moduleManager) + public unsafe DirectExecutionBackend(IModuleManager moduleManager, IHostPlatform? hostPlatform = null, IHostFaultHandling? faultHandling = null) { _moduleManager = moduleManager ?? throw new ArgumentNullException("moduleManager"); + _hostPlatform = hostPlatform ?? HostPlatform.Current; + _hostThreading = _hostPlatform.Threading; + _hostSymbols = _hostPlatform.Symbols; + _hostMemory = _hostPlatform.Memory; + _faultHandling = faultHandling ?? new WindowsFaultHandling(_hostMemory); _selfHandle = GCHandle.Alloc(this); _selfHandlePtr = GCHandle.ToIntPtr(_selfHandle); - _guestTlsBaseTlsIndex = TlsAlloc(); - _hostRspSlotTlsIndex = TlsAlloc(); + _guestTlsBaseTlsIndex = _hostThreading.AllocateTlsSlot(); + _hostRspSlotTlsIndex = _hostThreading.AllocateTlsSlot(); if (_guestTlsBaseTlsIndex == uint.MaxValue || _hostRspSlotTlsIndex == uint.MaxValue) { throw new OutOfMemoryException("Failed to allocate native TLS slots"); } - nint kernel32 = GetModuleHandle("kernel32.dll"); - _tlsGetValueAddress = kernel32 != 0 ? GetProcAddress(kernel32, "TlsGetValue") : 0; + _tlsGetValueAddress = _hostSymbols.GetAddress(HostRuntimeFunction.TlsGetValue); if (_tlsGetValueAddress == 0) { throw new InvalidOperationException("Failed to resolve kernel32!TlsGetValue"); } - _queryPerformanceCounterAddress = kernel32 != 0 ? GetProcAddress(kernel32, "QueryPerformanceCounter") : 0; + _queryPerformanceCounterAddress = _hostSymbols.GetAddress(HostRuntimeFunction.QueryPerformanceCounter); if (_queryPerformanceCounterAddress == 0) { throw new InvalidOperationException("Failed to resolve kernel32!QueryPerformanceCounter"); } - _switchToThreadAddress = kernel32 != 0 ? GetProcAddress(kernel32, "SwitchToThread") : 0; - _sleepAddress = kernel32 != 0 ? GetProcAddress(kernel32, "Sleep") : 0; + _switchToThreadAddress = _hostSymbols.GetAddress(HostRuntimeFunction.SwitchToThread); + _sleepAddress = _hostSymbols.GetAddress(HostRuntimeFunction.Sleep); if (_switchToThreadAddress == 0 || _sleepAddress == 0) { throw new InvalidOperationException("Failed to resolve kernel32 thread timing functions"); } - _tlsBaseAddress = (nint)VirtualAlloc(null, 4096u, 12288u, 4u); + _tlsBaseAddress = (nint)_hostMemory.Allocate(0, 4096u, HostPageProtection.ReadWrite); if (_tlsBaseAddress == 0) { throw new OutOfMemoryException("Failed to allocate TLS base"); @@ -917,7 +899,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _ownedTlsBaseAddress = _tlsBaseAddress; _ownsTlsBaseAddress = true; SeedTlsLayout(_tlsBaseAddress); - _hostRspSlotStorage = (nint)VirtualAlloc(null, 4096u, 12288u, 4u); + _hostRspSlotStorage = (nint)_hostMemory.Allocate(0, 4096u, HostPageProtection.ReadWrite); if (_hostRspSlotStorage == 0) { throw new OutOfMemoryException("Failed to allocate host stack slot storage"); @@ -1374,7 +1356,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } const uint intrinsicAllocationSize = 128u; - void* memory = VirtualAlloc(null, intrinsicAllocationSize, 12288u, 64u); + void* memory = (void*)_hostMemory.Allocate(0, intrinsicAllocationSize, HostPageProtection.ReadWriteExecute); if (memory == null) { address = 0; @@ -1392,14 +1374,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I *(nint*)((byte*)memory + 64) = _sleepAddress; } uint oldProtect = 0; - if (!VirtualProtect(memory, intrinsicAllocationSize, 32u, &oldProtect)) + if (!_hostMemory.Protect((ulong)memory, intrinsicAllocationSize, HostPageProtection.ReadExecute, out oldProtect)) { - VirtualFree(memory, 0u, 32768u); + _hostMemory.Free((ulong)memory); address = 0; return false; } - FlushInstructionCache(GetCurrentProcess(), memory, (nuint)code.Length); + _hostMemory.FlushInstructionCache((ulong)memory, (nuint)code.Length); address = (nint)memory; _importHandlerTrampolines.Add(address); return true; @@ -1660,7 +1642,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I context.FsBase = (ulong)num; context.GsBase = (ulong)num; SeedTlsLayout(num); - TlsSetValue(_guestTlsBaseTlsIndex, num); + _hostThreading.SetTlsValue(_guestTlsBaseTlsIndex, num); } } @@ -1684,7 +1666,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } uint oldProtect = default; - if (!VirtualProtect((void*)_tlsHandlerAddress, 16u, 64u, &oldProtect)) + if (!_hostMemory.Protect((ulong)(void*)_tlsHandlerAddress, 16u, HostPageProtection.ReadWriteExecute, out oldProtect)) { return; } @@ -1695,8 +1677,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } finally { - VirtualProtect((void*)_tlsHandlerAddress, 16u, oldProtect, &oldProtect); - FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, 16u); + _hostMemory.ProtectRaw((ulong)(void*)_tlsHandlerAddress, 16u, oldProtect, out oldProtect); + _hostMemory.FlushInstructionCache((ulong)(void*)_tlsHandlerAddress, 16u); } } @@ -1763,7 +1745,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } const uint stubSize = 128; - var code = (byte*)VirtualAlloc(null, stubSize, 12288u, 64u); + var code = (byte*)_hostMemory.Allocate(0, stubSize, HostPageProtection.ReadWriteExecute); if (code == null) { return 0; @@ -1798,13 +1780,13 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I Emit(0x41); Emit(0xFF); Emit(0xE2); // jmp r10 uint oldProtect = 0; - if (!VirtualProtect(code, stubSize, 32u, &oldProtect)) + if (!_hostMemory.Protect((ulong)code, stubSize, HostPageProtection.ReadExecute, out oldProtect)) { - VirtualFree(code, 0u, 32768u); + _hostMemory.Free((ulong)code); return 0; } - FlushInstructionCache(GetCurrentProcess(), code, stubSize); + _hostMemory.FlushInstructionCache((ulong)code, stubSize); Volatile.Write(ref _guestContextTransferStub, (nint)code); return (nint)code; } @@ -1812,7 +1794,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private unsafe nint CreateImportHandlerTrampoline(int importIndex) { - void* ptr = VirtualAlloc(null, 256u, 12288u, 64u); + void* ptr = (void*)_hostMemory.Allocate(0, 256u, HostPageProtection.ReadWriteExecute); if (ptr == null) { return 0; @@ -1930,12 +1912,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ptr2[num++] = 95; ptr2[num++] = 195; uint num2 = default(uint); - if (!VirtualProtect(ptr, 256u, 32u, &num2)) + if (!_hostMemory.Protect((ulong)ptr, 256u, HostPageProtection.ReadExecute, out num2)) { Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for import dispatch stub at 0x{(nint)ptr:X16}"); return 0; } - FlushInstructionCache(GetCurrentProcess(), ptr, 256u); + _hostMemory.FlushInstructionCache((ulong)ptr, 256u); return (nint)ptr; } catch @@ -1947,7 +1929,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private unsafe bool PatchImportStub(nint address, nint trampoline) { uint flNewProtect = default(uint); - if (!VirtualProtect((void*)address, 16u, 64u, &flNewProtect)) + if (!_hostMemory.Protect((ulong)(void*)address, 16u, HostPageProtection.ReadWriteExecute, out flNewProtect)) { Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for import stub at 0x{address:X16}"); return false; @@ -1967,8 +1949,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } finally { - VirtualProtect((void*)address, 16u, flNewProtect, &flNewProtect); - FlushInstructionCache(GetCurrentProcess(), (void*)address, 16u); + _hostMemory.ProtectRaw((ulong)(void*)address, 16u, flNewProtect, out flNewProtect); + _hostMemory.FlushInstructionCache((ulong)(void*)address, 16u); } } @@ -1978,7 +1960,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { if (importHandlerTrampoline != 0) { - VirtualFree((void*)importHandlerTrampoline, 0u, 32768u); + _hostMemory.Free((ulong)importHandlerTrampoline); } } _importHandlerTrampolines.Clear(); @@ -1989,7 +1971,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _tlsHandlerAddress = (nint)TryAllocateNearEntry(TlsHandlerRegionSize); if (_tlsHandlerAddress == 0) { - _tlsHandlerAddress = (nint)VirtualAlloc(null, TlsHandlerRegionSize, 12288u, 64u); + _tlsHandlerAddress = (nint)_hostMemory.Allocate(0, TlsHandlerRegionSize, HostPageProtection.ReadWriteExecute); } if (_tlsHandlerAddress == 0) { @@ -2017,18 +1999,18 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I tlsHandlerAddress[num++] = 195; _tlsPatchStubOffset = (num + 15) & ~15; uint num2 = default(uint); - if (!VirtualProtect((void*)_tlsHandlerAddress, TlsHandlerRegionSize, 32u, &num2)) + if (!_hostMemory.Protect((ulong)(void*)_tlsHandlerAddress, TlsHandlerRegionSize, HostPageProtection.ReadExecute, out num2)) { Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for TLS handler at 0x{_tlsHandlerAddress:X16}"); return; } - FlushInstructionCache(GetCurrentProcess(), (void*)_tlsHandlerAddress, TlsHandlerRegionSize); + _hostMemory.FlushInstructionCache((ulong)(void*)_tlsHandlerAddress, TlsHandlerRegionSize); Console.Error.WriteLine($"[LOADER][INFO] TLS handler at 0x{_tlsHandlerAddress:X16}"); } - private unsafe static nint CreateUnresolvedReturnStub() + private unsafe nint CreateUnresolvedReturnStub() { - void* ptr = VirtualAlloc(null, 4096u, 12288u, 64u); + void* ptr = (void*)_hostMemory.Allocate(0, 4096u, HostPageProtection.ReadWriteExecute); if (ptr == null) { return 0; @@ -2042,19 +2024,19 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ptr2[i] = 144; } uint num = default(uint); - if (!VirtualProtect(ptr, 4096u, 32u, &num)) + if (!_hostMemory.Protect((ulong)ptr, 4096u, HostPageProtection.ReadExecute, out num)) { Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for unresolved return stub at 0x{(nint)ptr:X16}"); return 0; } - FlushInstructionCache(GetCurrentProcess(), ptr, 16u); + _hostMemory.FlushInstructionCache((ulong)ptr, 16u); return (nint)ptr; } private unsafe nint CreateGuestReturnStub() { const uint stubSize = 256u; - void* ptr = VirtualAlloc(null, stubSize, 12288u, 64u); + void* ptr = (void*)_hostMemory.Allocate(0, stubSize, HostPageProtection.ReadWriteExecute); if (ptr == null) { return 0; @@ -2108,138 +2090,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I EmitByte(code, ref offset, 0xC3); uint oldProtect = default; - if (!VirtualProtect(ptr, stubSize, 32u, &oldProtect)) + if (!_hostMemory.Protect((ulong)ptr, stubSize, HostPageProtection.ReadExecute, out oldProtect)) { Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for guest return stub at 0x{(nint)ptr:X16}"); return 0; } - FlushInstructionCache(GetCurrentProcess(), ptr, (nuint)offset); - return (nint)ptr; - } - - private unsafe nint CreateExceptionHandlerTrampoline(nint managedHandler) - { - const uint stubSize = 256u; - void* ptr = VirtualAlloc(null, stubSize, 12288u, 64u); - if (ptr == null) - { - return 0; - } - - byte* code = (byte*)ptr; - int offset = 0; - // Native pre-filter: these exception codes are raised while the thread can be in - // cooperative GC mode (a C# throw is RaiseException(0xE0434352) on the throwing - // thread; FailFast/stack-overflow arrive mid-runtime-failure). Entering the managed - // handler then trips the CLR's reverse-P/Invoke check and kills the process with - // "Invalid Program: attempted to call a UnmanagedCallersOnly method from managed - // code" — this is why no managed throw (even one with a catch handler) ever - // survived inside the emulator. Continue the handler search without touching - // managed code; the CLR's own VEH handles its exceptions. MSVC C++ exceptions - // (Vulkan drivers, host CRT) are excluded too: the managed handler only ever - // returned CONTINUE_SEARCH for them. - ReadOnlySpan nonManagedExceptionCodes = [0xE0434352u, MSVC_CPP_EXCEPTION, 0xC0000409u, 0xC00000FDu]; - EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x01); // mov rax, [rcx] (ExceptionRecord*) - EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x00); // mov eax, [rax] (ExceptionCode) - var passJumpOffsets = stackalloc int[nonManagedExceptionCodes.Length]; - for (int i = 0; i < nonManagedExceptionCodes.Length; i++) - { - EmitByte(code, ref offset, 0x3D); // cmp eax, imm32 - EmitUInt32(code, ref offset, nonManagedExceptionCodes[i]); - EmitByte(code, ref offset, 0x74); // je pass - passJumpOffsets[i] = offset; - EmitByte(code, ref offset, 0x00); - } - EmitByte(code, ref offset, 0xEB); EmitByte(code, ref offset, 0x03); // jmp over pass block - int passOffset = offset; - EmitByte(code, ref offset, 0x31); EmitByte(code, ref offset, 0xC0); // pass: xor eax, eax (EXCEPTION_CONTINUE_SEARCH) - EmitByte(code, ref offset, 0xC3); // ret - for (int i = 0; i < nonManagedExceptionCodes.Length; i++) - { - code[passJumpOffsets[i]] = checked((byte)(passOffset - (passJumpOffsets[i] + 1))); - } - EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x54); // push r12 - EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x55); // push r13 - EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE4); // mov r12, rsp - EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xCD); // mov r13, rcx - EmitByte(code, ref offset, 0x65); EmitByte(code, ref offset, 0x48); // mov rax, gs:[8] - EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x04); EmitByte(code, ref offset, 0x25); - EmitUInt32(code, ref offset, 8u); - EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x39); EmitByte(code, ref offset, 0xC4); // cmp r12, rax - EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x83); // jae guestStack - int aboveStackJump = offset; - EmitUInt32(code, ref offset, 0u); - EmitByte(code, ref offset, 0x65); EmitByte(code, ref offset, 0x48); // mov rax, gs:[0x10] - EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x04); EmitByte(code, ref offset, 0x25); - EmitUInt32(code, ref offset, 0x10u); - EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x39); EmitByte(code, ref offset, 0xC4); // cmp r12, rax - EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x82); // jb guestStack - int belowStackJump = offset; - EmitUInt32(code, ref offset, 0u); - - EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xEC); EmitByte(code, ref offset, 0x28); - EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE9); // mov rcx, r13 - EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0xB8); - *(nint*)(code + offset) = managedHandler; - offset += sizeof(nint); - EmitByte(code, ref offset, 0xFF); EmitByte(code, ref offset, 0xD0); - EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xC4); EmitByte(code, ref offset, 0x28); - EmitByte(code, ref offset, 0xE9); - int hostRestoreJump = offset; - EmitUInt32(code, ref offset, 0u); - - int guestStackOffset = offset; - EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xEC); EmitByte(code, ref offset, 0x28); - EmitByte(code, ref offset, 0xB9); - EmitUInt32(code, ref offset, _hostRspSlotTlsIndex); - EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0xB8); - *(nint*)(code + offset) = _tlsGetValueAddress; - offset += sizeof(nint); - EmitByte(code, ref offset, 0xFF); EmitByte(code, ref offset, 0xD0); - EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xC4); EmitByte(code, ref offset, 0x28); - EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x85); EmitByte(code, ref offset, 0xC0); // test rax, rax - EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x84); - int missingTlsJump = offset; - EmitUInt32(code, ref offset, 0u); - EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x18); // mov r11, [rax] - EmitByte(code, ref offset, 0x4D); EmitByte(code, ref offset, 0x85); EmitByte(code, ref offset, 0xDB); // test r11, r11 - EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x84); - int missingHostStackJump = offset; - EmitUInt32(code, ref offset, 0u); - EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xDC); // mov rsp, r11 - EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xEC); EmitByte(code, ref offset, 0x28); - EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE9); // mov rcx, r13 - EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0xB8); - *(nint*)(code + offset) = managedHandler; - offset += sizeof(nint); - EmitByte(code, ref offset, 0xFF); EmitByte(code, ref offset, 0xD0); - EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xC4); EmitByte(code, ref offset, 0x28); - EmitByte(code, ref offset, 0xE9); - int guestRestoreJump = offset; - EmitUInt32(code, ref offset, 0u); - - int passThroughOffset = offset; - EmitByte(code, ref offset, 0x31); EmitByte(code, ref offset, 0xC0); // xor eax, eax - int restoreOffset = offset; - EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE4); // mov rsp, r12 - EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5D); - EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5C); - EmitByte(code, ref offset, 0xC3); - - *(int*)(code + aboveStackJump) = guestStackOffset - (aboveStackJump + sizeof(int)); - *(int*)(code + belowStackJump) = guestStackOffset - (belowStackJump + sizeof(int)); - *(int*)(code + hostRestoreJump) = restoreOffset - (hostRestoreJump + sizeof(int)); - *(int*)(code + missingTlsJump) = passThroughOffset - (missingTlsJump + sizeof(int)); - *(int*)(code + missingHostStackJump) = passThroughOffset - (missingHostStackJump + sizeof(int)); - *(int*)(code + guestRestoreJump) = restoreOffset - (guestRestoreJump + sizeof(int)); - - uint oldProtect = default; - if (!VirtualProtect(ptr, stubSize, 32u, &oldProtect)) - { - Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for exception handler trampoline at 0x{(nint)ptr:X16}"); - return 0; - } - FlushInstructionCache(GetCurrentProcess(), ptr, (nuint)offset); + _hostMemory.FlushInstructionCache((ulong)ptr, (nuint)offset); return (nint)ptr; } @@ -2261,7 +2117,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return null; } - private unsafe static bool TryAllocAt(ulong baseAddress, long signedDelta, nuint size, out void* memory) + private unsafe bool TryAllocAt(ulong baseAddress, long signedDelta, nuint size, out void* memory) { memory = null; ulong num; @@ -2282,7 +2138,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } num = baseAddress - num2; } - void* ptr = VirtualAlloc((void*)num, size, 12288u, 64u); + void* ptr = (void*)_hostMemory.Allocate(num, size, HostPageProtection.ReadWriteExecute); if (ptr == null) { return false; @@ -2301,7 +2157,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I int num9 = 0; while (num < num2) { - if (VirtualQuery((void*)num, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0 || lpBuffer.RegionSize == 0) + if (!_hostMemory.Query(num, out var lpBuffer) || lpBuffer.RegionSize == 0) { num += 4096uL; continue; @@ -2312,8 +2168,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { num6 = num2; } - uint num7 = lpBuffer.Protect & 0xFF; - bool flag = lpBuffer.State == 4096 && (lpBuffer.Protect & PAGE_GUARD) == 0 && num7 != PAGE_NOACCESS; + uint num7 = lpBuffer.RawProtection & 0xFF; + bool flag = lpBuffer.State == HostRegionState.Committed && (lpBuffer.RawProtection & PAGE_GUARD) == 0 && num7 != PAGE_NOACCESS; bool flag2 = num7 == PAGE_EXECUTE || num7 == 32 || num7 == 64 || num7 == PAGE_EXECUTE_WRITECOPY; if (flag && flag2 && num6 > num5 + MinTlsPatchInstructionBytes) { @@ -2398,7 +2254,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } byte b5 = (byte)(0xC0 | ((num4 & 7) << 3) | (num4 & 7)); uint flNewProtect = default(uint); - if (!VirtualProtect((void*)address, (nuint)num2, 64u, &flNewProtect)) + if (!_hostMemory.Protect((ulong)(void*)address, (nuint)num2, HostPageProtection.ReadWriteExecute, out flNewProtect)) { return false; } @@ -2414,8 +2270,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } finally { - VirtualProtect((void*)address, (nuint)num2, flNewProtect, &flNewProtect); - FlushInstructionCache(GetCurrentProcess(), (void*)address, (nuint)num2); + _hostMemory.ProtectRaw((ulong)(void*)address, (nuint)num2, flNewProtect, out flNewProtect); + _hostMemory.FlushInstructionCache((ulong)(void*)address, (nuint)num2); } return true; } @@ -2482,7 +2338,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private unsafe bool PatchTlsLoadInstruction(nint address, int instructionLength, int destinationRegister) { uint flNewProtect = default(uint); - if (!VirtualProtect((void*)address, (nuint)instructionLength, 64u, &flNewProtect)) + if (!_hostMemory.Protect((ulong)(void*)address, (nuint)instructionLength, HostPageProtection.ReadWriteExecute, out flNewProtect)) { return false; } @@ -2516,8 +2372,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } finally { - VirtualProtect((void*)address, (nuint)instructionLength, flNewProtect, &flNewProtect); - FlushInstructionCache(GetCurrentProcess(), (void*)address, (nuint)instructionLength); + _hostMemory.ProtectRaw((ulong)(void*)address, (nuint)instructionLength, flNewProtect, out flNewProtect); + _hostMemory.FlushInstructionCache((ulong)(void*)address, (nuint)instructionLength); } } @@ -2569,12 +2425,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ptr[num2++] = 144; } uint flNewProtect = default(uint); - if (!VirtualProtect((void*)num, 32u, 32u, &flNewProtect)) + if (!_hostMemory.Protect((ulong)(void*)num, 32u, HostPageProtection.ReadExecute, out flNewProtect)) { Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for TLS store helper at 0x{num:X16}"); return 0; } - FlushInstructionCache(GetCurrentProcess(), (void*)num, 32u); + _hostMemory.FlushInstructionCache((ulong)(void*)num, 32u); return num; } @@ -2593,7 +2449,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I nint result = _tlsHandlerAddress + _tlsPatchStubOffset; _tlsPatchStubOffset += num; uint flNewProtect = default(uint); - if (!VirtualProtect((void*)result, (nuint)num, 64u, &flNewProtect)) + if (!_hostMemory.Protect((ulong)(void*)result, (nuint)num, HostPageProtection.ReadWriteExecute, out flNewProtect)) { return 0; } @@ -2607,7 +2463,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return false; } uint flNewProtect = default(uint); - if (!VirtualProtect((void*)address, (nuint)instructionLength, 64u, &flNewProtect)) + if (!_hostMemory.Protect((ulong)(void*)address, (nuint)instructionLength, HostPageProtection.ReadWriteExecute, out flNewProtect)) { return false; } @@ -2628,17 +2484,17 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } finally { - VirtualProtect((void*)address, (nuint)instructionLength, flNewProtect, &flNewProtect); - FlushInstructionCache(GetCurrentProcess(), (void*)address, (nuint)instructionLength); + _hostMemory.ProtectRaw((ulong)(void*)address, (nuint)instructionLength, flNewProtect, out flNewProtect); + _hostMemory.FlushInstructionCache((ulong)(void*)address, (nuint)instructionLength); } return true; } private unsafe void TryPreReservePrtAperture(ulong baseAddress, ulong size) { - if (VirtualQuery((void*)baseAddress, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) != 0 && lpBuffer.State != 65536) + if (_hostMemory.Query(baseAddress, out var lpBuffer) && lpBuffer.State != HostRegionState.Free) { - Console.Error.WriteLine($"[LOADER][INFO] PRT aperture at 0x{baseAddress:X16} already in use (state=0x{lpBuffer.State:X}), will use lazy-commit"); + Console.Error.WriteLine($"[LOADER][INFO] PRT aperture at 0x{baseAddress:X16} already in use (state=0x{lpBuffer.RawState:X}), will use lazy-commit"); return; } ulong num = baseAddress; @@ -2650,8 +2506,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { ulong val = num2 - num; num5 = (nuint)Math.Min(2097152uL, val); - void* ptr = VirtualAlloc((void*)num, num5, 8192u, 4u); - if (ptr != null) + if (_hostMemory.Reserve(num, num5, HostPageProtection.ReadWrite) != 0) { num3++; } @@ -2673,8 +2528,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I int num8 = 0; for (; num6 < num7; num6 += 2097152) { - void* ptr2 = VirtualAlloc((void*)num6, 2097152u, 4096u, 4u); - if (ptr2 != null) + if (_hostMemory.Commit(num6, 2097152u, HostPageProtection.ReadWrite)) { num8++; } @@ -3659,7 +3513,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return; } - if (SetThreadAffinityMask(GetCurrentThread(), (nuint)hostAffinityMask) == 0 && _logGuestThreads) + if (!_hostThreading.TrySetCurrentThreadAffinity((nuint)hostAffinityMask) && _logGuestThreads) { Console.Error.WriteLine( $"[LOADER][WARN] Failed to set guest thread affinity guest=0x{guestAffinityMask:X} " + @@ -3708,14 +3562,14 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I var previousGuestThreadHandle = GuestThreadExecution.EnterGuestThread(thread.ThreadHandle); var previousGuestThreadState = _activeGuestThreadState; ApplyGuestThreadAffinity(thread.AffinityMask); - Volatile.Write(ref thread.HostThreadId, unchecked((int)GetCurrentThreadId())); + Volatile.Write(ref thread.HostThreadId, unchecked((int)_hostThreading.CurrentThreadId)); _activeGuestThreadState = thread; if (LogThreadMode) { _threadModeCycleId = Interlocked.Increment(ref _threadModeCycleCounter); TraceThreadMode( $"cycle_start name='{thread.Name}' guest=0x{thread.ThreadHandle:X16} reason={reason} " + - $"rsp_slot=0x{(ulong)TlsGetValue(_hostRspSlotTlsIndex):X}"); + $"rsp_slot=0x{(ulong)_hostThreading.GetTlsValue(_hostRspSlotTlsIndex):X}"); } try { @@ -3796,7 +3650,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I TraceThreadMode( $"cycle_end name='{thread.Name}' state={thread.State} " + $"imports={Interlocked.Read(ref thread.ImportCount)} " + - $"rsp_slot=0x{(ulong)TlsGetValue(_hostRspSlotTlsIndex):X}"); + $"rsp_slot=0x{(ulong)_hostThreading.GetTlsValue(_hostRspSlotTlsIndex):X}"); } _activeGuestThreadState = previousGuestThreadState; Volatile.Write(ref thread.HostThreadId, 0); @@ -3858,7 +3712,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return GuestNativeCallExitReason.Exception; } const uint stubSize = 512u; - void* ptr = VirtualAlloc(null, stubSize, 12288u, 64u); + void* ptr = (void*)_hostMemory.Allocate(0, stubSize, HostPageProtection.ReadWriteExecute); if (ptr == null) { reason = "failed to allocate executable memory for guest thread stub"; @@ -3871,7 +3725,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I var previousForcedExit = _activeForcedGuestExit; var previousYieldRequested = _activeGuestThreadYieldRequested; var previousYieldReason = _activeGuestThreadYieldReason; - nint previousHostRspSlotValue = TlsGetValue(_hostRspSlotTlsIndex); + nint previousHostRspSlotValue = _hostThreading.GetTlsValue(_hostRspSlotTlsIndex); if (LogThreadMode) { TraceThreadMode( @@ -3995,12 +3849,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return GuestNativeCallExitReason.Exception; } uint oldProtect = default(uint); - if (!VirtualProtect(ptr, stubSize, 64u, &oldProtect)) + if (!_hostMemory.Protect((ulong)ptr, stubSize, HostPageProtection.ReadWriteExecute, out oldProtect)) { reason = $"VirtualProtect failed for guest thread entry stub at 0x{(nint)ptr:X16}"; return GuestNativeCallExitReason.Exception; } - FlushInstructionCache(GetCurrentProcess(), ptr, stubSize); + _hostMemory.FlushInstructionCache((ulong)ptr, stubSize); ActiveGuestThreadYieldRequested = false; ActiveGuestThreadYieldReason = null; var nativeReturn = RunGuestEntryStub(ptr, hostRspSlot); @@ -4029,7 +3883,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } finally { - TlsSetValue(_hostRspSlotTlsIndex, previousHostRspSlotValue); + _hostThreading.SetTlsValue(_hostRspSlotTlsIndex, previousHostRspSlotValue); RestoreActiveExecutionThread( previousActiveBackend, previousActiveContext, @@ -4038,7 +3892,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I previousForcedExit, previousYieldRequested, previousYieldReason); - VirtualFree(ptr, 0u, 32768u); + _hostMemory.Free((ulong)ptr); } } @@ -4056,7 +3910,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return GuestNativeCallExitReason.Exception; } const uint stubSize = 512u; - void* ptr = VirtualAlloc(null, stubSize, 12288u, 64u); + void* ptr = (void*)_hostMemory.Allocate(0, stubSize, HostPageProtection.ReadWriteExecute); if (ptr == null) { reason = "failed to allocate executable memory for guest thread stub"; @@ -4069,7 +3923,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I var previousForcedExit = _activeForcedGuestExit; var previousYieldRequested = _activeGuestThreadYieldRequested; var previousYieldReason = _activeGuestThreadYieldReason; - nint previousHostRspSlotValue = TlsGetValue(_hostRspSlotTlsIndex); + nint previousHostRspSlotValue = _hostThreading.GetTlsValue(_hostRspSlotTlsIndex); if (LogThreadMode) { TraceThreadMode( @@ -4138,12 +3992,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return GuestNativeCallExitReason.Exception; } uint oldProtect = default(uint); - if (!VirtualProtect(ptr, stubSize, 64u, &oldProtect)) + if (!_hostMemory.Protect((ulong)ptr, stubSize, HostPageProtection.ReadWriteExecute, out oldProtect)) { reason = $"VirtualProtect failed for guest continuation stub at 0x{(nint)ptr:X16}"; return GuestNativeCallExitReason.Exception; } - FlushInstructionCache(GetCurrentProcess(), ptr, stubSize); + _hostMemory.FlushInstructionCache((ulong)ptr, stubSize); ActiveGuestThreadYieldRequested = false; ActiveGuestThreadYieldReason = null; @@ -4176,7 +4030,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } finally { - TlsSetValue(_hostRspSlotTlsIndex, previousHostRspSlotValue); + _hostThreading.SetTlsValue(_hostRspSlotTlsIndex, previousHostRspSlotValue); RestoreActiveExecutionThread( previousActiveBackend, previousActiveContext, @@ -4185,7 +4039,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I previousForcedExit, previousYieldRequested, previousYieldReason); - VirtualFree(ptr, 0u, 32768u); + _hostMemory.Free((ulong)ptr); } } @@ -4269,7 +4123,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } Console.Error.WriteLine($"[LOADER][INFO] StackTop: 0x{num:X16}"); const uint stubSize = 512u; - void* ptr = VirtualAlloc(null, stubSize, 12288u, 64u); + void* ptr = (void*)_hostMemory.Allocate(0, stubSize, HostPageProtection.ReadWriteExecute); if (ptr == null) { LastError = "Failed to allocate executable memory for stub"; @@ -4283,7 +4137,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I var previousForcedExit = _activeForcedGuestExit; var previousYieldRequested = _activeGuestThreadYieldRequested; var previousYieldReason = _activeGuestThreadYieldReason; - nint previousHostRspSlotValue = TlsGetValue(_hostRspSlotTlsIndex); + nint previousHostRspSlotValue = _hostThreading.GetTlsValue(_hostRspSlotTlsIndex); try { _activeExecutionBackend = this; @@ -4402,18 +4256,18 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return false; } uint num5 = default(uint); - if (!VirtualProtect(ptr, stubSize, 64u, &num5)) + if (!_hostMemory.Protect((ulong)ptr, stubSize, HostPageProtection.ReadWriteExecute, out num5)) { LastError = $"VirtualProtect failed for guest entry stub at 0x{(nint)ptr:X16}"; result = OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT; return false; } - FlushInstructionCache(GetCurrentProcess(), ptr, stubSize); + _hostMemory.FlushInstructionCache((ulong)ptr, stubSize); if (_hostRspSlotStorage != 0) { *(ulong*)_hostRspSlotStorage = num2; } - TlsSetValue(_hostRspSlotTlsIndex, (nint)num2); + _hostThreading.SetTlsValue(_hostRspSlotTlsIndex, (nint)num2); if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_SENTINEL_PROBE"), "1", StringComparison.Ordinal)) { Console.Error.WriteLine("[LOADER][INFO] Running unresolved sentinel probe..."); @@ -4476,7 +4330,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { StopStallWatchdog(); ActiveEntryReturnSentinelRip = 0uL; - TlsSetValue(_hostRspSlotTlsIndex, previousHostRspSlotValue); + _hostThreading.SetTlsValue(_hostRspSlotTlsIndex, previousHostRspSlotValue); if (_hostRspSlotStorage != 0) { *(long*)_hostRspSlotStorage = 0L; @@ -4489,7 +4343,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I previousForcedExit, previousYieldRequested, previousYieldReason); - VirtualFree(ptr, 0u, 32768u); + _hostMemory.Free((ulong)ptr); } } @@ -4744,116 +4598,31 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } } - private unsafe static bool TryCaptureHostThreadContext(int hostThreadId, out HostThreadContextSnapshot snapshot) + private bool TryCaptureHostThreadContext(int hostThreadId, out HostThreadContextSnapshot snapshot) { snapshot = default; - if (hostThreadId == 0 || unchecked((uint)hostThreadId) == GetCurrentThreadId()) + if (hostThreadId == 0 || unchecked((uint)hostThreadId) == _hostThreading.CurrentThreadId) { return false; } - var threadHandle = OpenThread(ThreadGetContext | ThreadSuspendResume, false, unchecked((uint)hostThreadId)); - if (threadHandle == 0) + if (!_hostThreading.TryCaptureThreadRegisters(unchecked((uint)hostThreadId), out var registers)) { return false; } - void* contextRecord = null; - var suspended = false; - try - { - if (SuspendThread(threadHandle) == uint.MaxValue) - { - return false; - } - - suspended = true; - contextRecord = NativeMemory.AllocZeroed((nuint)Win64ContextSize); - WriteCtxU32(contextRecord, Win64ContextFlagsOffset, ContextAmd64ControlInteger); - if (!GetThreadContext(threadHandle, contextRecord)) - { - return false; - } - - snapshot = new HostThreadContextSnapshot( - true, - ReadCtxU64(contextRecord, 248), - ReadCtxU64(contextRecord, 152), - ReadCtxU64(contextRecord, 160), - ReadCtxU64(contextRecord, 120), - ReadCtxU64(contextRecord, 144), - ReadCtxU64(contextRecord, 128), - ReadCtxU64(contextRecord, 136)); - return true; - } - finally - { - if (contextRecord != null) - { - NativeMemory.Free(contextRecord); - } - if (suspended) - { - _ = ResumeThread(threadHandle); - } - _ = CloseHandle(threadHandle); - } + snapshot = new HostThreadContextSnapshot( + true, + registers.Rip, + registers.Rsp, + registers.Rbp, + registers.Rax, + registers.Rbx, + registers.Rcx, + registers.Rdx); + return true; } - - [DllImport("kernel32.dll")] - private static extern uint TlsAlloc(); - - [DllImport("kernel32.dll")] - private static extern bool TlsFree(uint dwTlsIndex); - - [DllImport("kernel32.dll")] - private static extern bool TlsSetValue(uint dwTlsIndex, nint lpTlsValue); - - [DllImport("kernel32.dll")] - private static extern nint TlsGetValue(uint dwTlsIndex); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] - private static extern nint GetModuleHandle(string lpModuleName); - - [DllImport("kernel32.dll", CharSet = CharSet.Ansi)] - private static extern nint GetProcAddress(nint hModule, string procName); - - [DllImport("kernel32.dll")] - private unsafe static extern void* AddVectoredExceptionHandler(uint first, IntPtr handler); - - [DllImport("kernel32.dll")] - private unsafe static extern uint RemoveVectoredExceptionHandler(void* handle); - - [DllImport("kernel32.dll")] - private static extern IntPtr SetUnhandledExceptionFilter(IntPtr lpTopLevelExceptionFilter); - - [DllImport("kernel32.dll")] - private static extern uint GetCurrentThreadId(); - - [DllImport("kernel32.dll")] - private static extern nint GetCurrentThread(); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern nuint SetThreadAffinityMask(nint hThread, nuint dwThreadAffinityMask); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern nint OpenThread(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwThreadId); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern uint SuspendThread(nint hThread); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern uint ResumeThread(nint hThread); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private unsafe static extern bool GetThreadContext(nint hThread, void* lpContext); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool CloseHandle(nint hObject); - public unsafe void Dispose() { if (!RequestGuestThreadTeardown(2000)) @@ -4876,28 +4645,28 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I StopStallWatchdog(); if (_exceptionHandler != 0) { - RemoveVectoredExceptionHandler((void*)_exceptionHandler); + _faultHandling.RemoveHandler(_exceptionHandler); _exceptionHandler = 0; } if (_rawExceptionHandler != 0) { - RemoveVectoredExceptionHandler((void*)_rawExceptionHandler); + _faultHandling.RemoveHandler(_rawExceptionHandler); _rawExceptionHandler = 0; } if (_rawExceptionHandlerStub != 0) { - VirtualFree((void*)_rawExceptionHandlerStub, 0u, 32768u); + _faultHandling.FreeThunk(_rawExceptionHandlerStub); _rawExceptionHandlerStub = 0; } if (_exceptionHandlerStub != 0) { - VirtualFree((void*)_exceptionHandlerStub, 0u, 32768u); + _faultHandling.FreeThunk(_exceptionHandlerStub); _exceptionHandlerStub = 0; } if (_unhandledFilterStub != 0) { - SetUnhandledExceptionFilter(0); - VirtualFree((void*)_unhandledFilterStub, 0u, 32768u); + _faultHandling.SetUnhandledFilter(0); + _faultHandling.FreeThunk(_unhandledFilterStub); _unhandledFilterStub = 0; } if (_handlerHandle.IsAllocated) @@ -4915,7 +4684,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } if (_ownedTlsBaseAddress != 0) { - VirtualFree((void*)_ownedTlsBaseAddress, 0u, 32768u); + _hostMemory.Free((ulong)_ownedTlsBaseAddress); _ownedTlsBaseAddress = 0; } _tlsBaseAddress = 0; @@ -4926,44 +4695,44 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { if (num3 != 0) { - VirtualFree((void*)num3, 0u, 32768u); + _hostMemory.Free((ulong)num3); } } _tlsModuleBases.Clear(); } if (_tlsHandlerAddress != 0) { - VirtualFree((void*)_tlsHandlerAddress, 0u, 32768u); + _hostMemory.Free((ulong)_tlsHandlerAddress); _tlsHandlerAddress = 0; } if (_hostRspSlotStorage != 0) { - VirtualFree((void*)_hostRspSlotStorage, 0u, 32768u); + _hostMemory.Free((ulong)_hostRspSlotStorage); _hostRspSlotStorage = 0; } if (_guestTlsBaseTlsIndex != uint.MaxValue) { - TlsFree(_guestTlsBaseTlsIndex); + _hostThreading.FreeTlsSlot(_guestTlsBaseTlsIndex); _guestTlsBaseTlsIndex = uint.MaxValue; } if (_hostRspSlotTlsIndex != uint.MaxValue) { - TlsFree(_hostRspSlotTlsIndex); + _hostThreading.FreeTlsSlot(_hostRspSlotTlsIndex); _hostRspSlotTlsIndex = uint.MaxValue; } if (_unresolvedReturnStub != 0) { - VirtualFree((void*)_unresolvedReturnStub, 0u, 32768u); + _hostMemory.Free((ulong)_unresolvedReturnStub); _unresolvedReturnStub = 0; } if (_guestReturnStub != 0) { - VirtualFree((void*)_guestReturnStub, 0u, 32768u); + _hostMemory.Free((ulong)_guestReturnStub); _guestReturnStub = 0; } if (_guestContextTransferStub != 0) { - VirtualFree((void*)_guestContextTransferStub, 0u, 32768u); + _hostMemory.Free((ulong)_guestContextTransferStub); _guestContextTransferStub = 0; } foreach (var frame in _guestContextTransferFrames.Values) @@ -4976,40 +4745,19 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _guestContextTransferFrames.Dispose(); if (_lowIndexedTableScratch != 0) { - VirtualFree((void*)_lowIndexedTableScratch, 0u, 32768u); + _hostMemory.Free((ulong)_lowIndexedTableScratch); _lowIndexedTableScratch = 0; } if (_stackGuardCompareScratch != 0) { - VirtualFree((void*)_stackGuardCompareScratch, 0u, 32768u); + _hostMemory.Free((ulong)_stackGuardCompareScratch); _stackGuardCompareScratch = 0; } if (_nullObjectStoreScratch != 0) { - VirtualFree((void*)_nullObjectStoreScratch, 0u, 32768u); + _hostMemory.Free((ulong)_nullObjectStoreScratch); _nullObjectStoreScratch = 0; } Volatile.Write(ref _globalUnresolvedReturnStub, 0uL); } - - [DllImport("kernel32.dll")] - private unsafe static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect); - - [DllImport("kernel32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private unsafe static extern bool VirtualFree(void* lpAddress, nuint dwSize, uint dwFreeType); - - [DllImport("kernel32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private unsafe static extern bool VirtualProtect(void* lpAddress, nuint dwSize, uint flNewProtect, uint* lpflOldProtect); - - [DllImport("kernel32.dll")] - private unsafe static extern void* GetCurrentProcess(); - - [DllImport("kernel32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private unsafe static extern bool FlushInstructionCache(void* hProcess, void* lpBaseAddress, nuint dwSize); - - [DllImport("kernel32.dll")] - private unsafe static extern nuint VirtualQuery(void* lpAddress, out MEMORY_BASIC_INFORMATION64 lpBuffer, nuint dwLength); } diff --git a/src/SharpEmu.Core/Cpu/Native/StubManager.cs b/src/SharpEmu.Core/Cpu/Native/StubManager.cs index 852430cf..37a2453f 100644 --- a/src/SharpEmu.Core/Cpu/Native/StubManager.cs +++ b/src/SharpEmu.Core/Cpu/Native/StubManager.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using SharpEmu.HLE; +using SharpEmu.HLE.Host; namespace SharpEmu.Core.Cpu.Native; @@ -11,17 +12,15 @@ public sealed unsafe class StubManager : IDisposable private readonly List _allocatedStubs = new(); private readonly Dictionary _importHandlers = new(); private readonly Dictionary _stubAddresses = new(); + private readonly IHostMemory _hostMemory; private byte* _pltMemory; private int _pltOffset; private const int PltMemorySize = 1024 * 1024; // 1MB for stubs - public StubManager() + public StubManager(IHostMemory? hostMemory = null) { - _pltMemory = (byte*)VirtualAlloc( - null, - (nuint)PltMemorySize, - AllocationType.Reserve | AllocationType.Commit, - MemoryProtection.ExecuteReadWrite); + _hostMemory = hostMemory ?? HostPlatform.Current.Memory; + _pltMemory = (byte*)_hostMemory.Allocate(0, PltMemorySize, HostPageProtection.ReadWriteExecute); if (_pltMemory == null) { @@ -185,7 +184,7 @@ public sealed unsafe class StubManager : IDisposable { if (_pltMemory != null) { - VirtualFree(_pltMemory, 0, FreeType.Release); + _hostMemory.Free((ulong)_pltMemory); _pltMemory = null; } @@ -194,29 +193,5 @@ public sealed unsafe class StubManager : IDisposable _stubAddresses.Clear(); } - [DllImport("kernel32.dll", SetLastError = true)] - private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, AllocationType flAllocationType, MemoryProtection flProtect); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool VirtualFree(void* lpAddress, nuint dwSize, FreeType dwFreeType); - - [Flags] - private enum AllocationType : uint - { - Commit = 0x1000, - Reserve = 0x2000, - } - - [Flags] - private enum MemoryProtection : uint - { - ExecuteReadWrite = 0x40, - } - - private enum FreeType : uint - { - Release = 0x8000, - } - public delegate void ImportHandler(CpuContext context); } diff --git a/src/SharpEmu.Core/Cpu/Native/Windows/Win64ContextOffsets.cs b/src/SharpEmu.Core/Cpu/Native/Windows/Win64ContextOffsets.cs new file mode 100644 index 00000000..10e22e88 --- /dev/null +++ b/src/SharpEmu.Core/Cpu/Native/Windows/Win64ContextOffsets.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.Core.Cpu.Native.Windows; + +/// +/// Byte offsets into the Win64 CONTEXT record delivered to vectored exception +/// handlers. The handlers read/write guest registers directly at these offsets +/// (no managed CONTEXT struct exists); a future POSIX backend gets a sibling +/// class for its mcontext layout. +/// +internal static class Win64ContextOffsets +{ + public const int Mxcsr = 52; + public const int Rax = 120; + public const int Rcx = 128; + public const int Rdx = 136; + public const int Rbx = 144; + public const int Rsp = 152; + public const int Rbp = 160; + public const int Rsi = 168; + public const int Rdi = 176; + public const int R8 = 184; + public const int R9 = 192; + public const int R10 = 200; + public const int R11 = 208; + public const int R12 = 216; + public const int R13 = 224; + public const int R14 = 232; + public const int R15 = 240; + public const int Rip = 248; +} diff --git a/src/SharpEmu.Core/Cpu/Native/Windows/WindowsFaultCodes.cs b/src/SharpEmu.Core/Cpu/Native/Windows/WindowsFaultCodes.cs new file mode 100644 index 00000000..5c0a6212 --- /dev/null +++ b/src/SharpEmu.Core/Cpu/Native/Windows/WindowsFaultCodes.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.Core.Cpu.Native.Windows; + +/// +/// Windows NTSTATUS exception codes and EXCEPTION_RECORD access-type values the +/// fault handlers filter on. Values are the same numbers the handlers previously +/// compared as bare literals; only the spelling changed. +/// +internal static class WindowsFaultCodes +{ + public const uint AccessViolation = 0xC0000005u; // 3221225477 + public const uint Breakpoint = 0x80000003u; // 2147483651 + public const uint IllegalInstruction = 0xC000001Du; // 3221225501 + public const uint FastFail = 0xC0000409u; // 3221226505 + public const uint StackOverflow = 0xC00000FDu; + public const uint ClrManagedException = 0xE0434352u; + + // EXCEPTION_RECORD.ExceptionInformation[0] for access violations. + public const ulong AccessRead = 0; + public const ulong AccessWrite = 1; + public const ulong AccessExecute = 8; +} diff --git a/src/SharpEmu.Core/Cpu/Native/Windows/WindowsFaultHandling.cs b/src/SharpEmu.Core/Cpu/Native/Windows/WindowsFaultHandling.cs new file mode 100644 index 00000000..568b830e --- /dev/null +++ b/src/SharpEmu.Core/Cpu/Native/Windows/WindowsFaultHandling.cs @@ -0,0 +1,191 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Runtime.InteropServices; +using SharpEmu.HLE.Host; + +namespace SharpEmu.Core.Cpu.Native.Windows; + +/// +/// Vectored-exception-handler installation and the handler pre-filter thunk. +/// The thunk is inherently Windows-shaped (TEB stack-limit reads via gs:, +/// NTSTATUS pre-filtering, Win64 calling convention) and moved here whole from +/// DirectExecutionBackend; a POSIX backend supplies a sibling built around +/// sigaction/sigaltstack instead. +/// +internal sealed unsafe partial class WindowsFaultHandling : IHostFaultHandling +{ + private readonly IHostMemory _memory; + + public WindowsFaultHandling(IHostMemory memory) + { + _memory = memory; + } + + public nint CreateHandlerThunk(nint managedCallback, uint hostRspSwitchTlsSlot, nint tlsGetValueAddress) + { + const uint stubSize = 256u; + void* ptr = (void*)_memory.Allocate(0, stubSize, HostPageProtection.ReadWriteExecute); + if (ptr == null) + { + return 0; + } + + byte* code = (byte*)ptr; + int offset = 0; + // Native pre-filter: these exception codes are raised while the thread can be in + // cooperative GC mode (a C# throw is RaiseException(0xE0434352) on the throwing + // thread; FailFast/stack-overflow arrive mid-runtime-failure). Entering the managed + // handler then trips the CLR's reverse-P/Invoke check and kills the process with + // "Invalid Program: attempted to call a UnmanagedCallersOnly method from managed + // code" — this is why no managed throw (even one with a catch handler) ever + // survived inside the emulator. Continue the handler search without touching + // managed code; the CLR's own VEH handles its exceptions. MSVC C++ exceptions + // (Vulkan drivers, host CRT) are excluded too: the managed handler only ever + // returned CONTINUE_SEARCH for them. + ReadOnlySpan nonManagedExceptionCodes = + [WindowsFaultCodes.ClrManagedException, 0xE06D7363u, WindowsFaultCodes.FastFail, WindowsFaultCodes.StackOverflow]; + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x01); // mov rax, [rcx] (ExceptionRecord*) + EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x00); // mov eax, [rax] (ExceptionCode) + var passJumpOffsets = stackalloc int[nonManagedExceptionCodes.Length]; + for (int i = 0; i < nonManagedExceptionCodes.Length; i++) + { + EmitByte(code, ref offset, 0x3D); // cmp eax, imm32 + EmitUInt32(code, ref offset, nonManagedExceptionCodes[i]); + EmitByte(code, ref offset, 0x74); // je pass + passJumpOffsets[i] = offset; + EmitByte(code, ref offset, 0x00); + } + EmitByte(code, ref offset, 0xEB); EmitByte(code, ref offset, 0x03); // jmp over pass block + int passOffset = offset; + EmitByte(code, ref offset, 0x31); EmitByte(code, ref offset, 0xC0); // pass: xor eax, eax (EXCEPTION_CONTINUE_SEARCH) + EmitByte(code, ref offset, 0xC3); // ret + for (int i = 0; i < nonManagedExceptionCodes.Length; i++) + { + code[passJumpOffsets[i]] = checked((byte)(passOffset - (passJumpOffsets[i] + 1))); + } + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x54); // push r12 + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x55); // push r13 + EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE4); // mov r12, rsp + EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xCD); // mov r13, rcx + EmitByte(code, ref offset, 0x65); EmitByte(code, ref offset, 0x48); // mov rax, gs:[8] + EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x04); EmitByte(code, ref offset, 0x25); + EmitUInt32(code, ref offset, 8u); + EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x39); EmitByte(code, ref offset, 0xC4); // cmp r12, rax + EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x83); // jae guestStack + int aboveStackJump = offset; + EmitUInt32(code, ref offset, 0u); + EmitByte(code, ref offset, 0x65); EmitByte(code, ref offset, 0x48); // mov rax, gs:[0x10] + EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x04); EmitByte(code, ref offset, 0x25); + EmitUInt32(code, ref offset, 0x10u); + EmitByte(code, ref offset, 0x49); EmitByte(code, ref offset, 0x39); EmitByte(code, ref offset, 0xC4); // cmp r12, rax + EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x82); // jb guestStack + int belowStackJump = offset; + EmitUInt32(code, ref offset, 0u); + + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xEC); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE9); // mov rcx, r13 + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0xB8); + *(nint*)(code + offset) = managedCallback; + offset += sizeof(nint); + EmitByte(code, ref offset, 0xFF); EmitByte(code, ref offset, 0xD0); + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xC4); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0xE9); + int hostRestoreJump = offset; + EmitUInt32(code, ref offset, 0u); + + int guestStackOffset = offset; + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xEC); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0xB9); + EmitUInt32(code, ref offset, hostRspSwitchTlsSlot); + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0xB8); + *(nint*)(code + offset) = tlsGetValueAddress; + offset += sizeof(nint); + EmitByte(code, ref offset, 0xFF); EmitByte(code, ref offset, 0xD0); + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xC4); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x85); EmitByte(code, ref offset, 0xC0); // test rax, rax + EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x84); + int missingTlsJump = offset; + EmitUInt32(code, ref offset, 0u); + EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x8B); EmitByte(code, ref offset, 0x18); // mov r11, [rax] + EmitByte(code, ref offset, 0x4D); EmitByte(code, ref offset, 0x85); EmitByte(code, ref offset, 0xDB); // test r11, r11 + EmitByte(code, ref offset, 0x0F); EmitByte(code, ref offset, 0x84); + int missingHostStackJump = offset; + EmitUInt32(code, ref offset, 0u); + EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xDC); // mov rsp, r11 + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xEC); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE9); // mov rcx, r13 + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0xB8); + *(nint*)(code + offset) = managedCallback; + offset += sizeof(nint); + EmitByte(code, ref offset, 0xFF); EmitByte(code, ref offset, 0xD0); + EmitByte(code, ref offset, 0x48); EmitByte(code, ref offset, 0x83); EmitByte(code, ref offset, 0xC4); EmitByte(code, ref offset, 0x28); + EmitByte(code, ref offset, 0xE9); + int guestRestoreJump = offset; + EmitUInt32(code, ref offset, 0u); + + int passThroughOffset = offset; + EmitByte(code, ref offset, 0x31); EmitByte(code, ref offset, 0xC0); // xor eax, eax + int restoreOffset = offset; + EmitByte(code, ref offset, 0x4C); EmitByte(code, ref offset, 0x89); EmitByte(code, ref offset, 0xE4); // mov rsp, r12 + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5D); + EmitByte(code, ref offset, 0x41); EmitByte(code, ref offset, 0x5C); + EmitByte(code, ref offset, 0xC3); + + *(int*)(code + aboveStackJump) = guestStackOffset - (aboveStackJump + sizeof(int)); + *(int*)(code + belowStackJump) = guestStackOffset - (belowStackJump + sizeof(int)); + *(int*)(code + hostRestoreJump) = restoreOffset - (hostRestoreJump + sizeof(int)); + *(int*)(code + missingTlsJump) = passThroughOffset - (missingTlsJump + sizeof(int)); + *(int*)(code + missingHostStackJump) = passThroughOffset - (missingHostStackJump + sizeof(int)); + *(int*)(code + guestRestoreJump) = restoreOffset - (guestRestoreJump + sizeof(int)); + + if (!_memory.Protect((ulong)ptr, stubSize, HostPageProtection.ReadExecute, out _)) + { + Console.Error.WriteLine($"[LOADER][ERROR] VirtualProtect failed for exception handler trampoline at 0x{(nint)ptr:X16}"); + _ = _memory.Free((ulong)ptr); + return 0; + } + _memory.FlushInstructionCache((ulong)ptr, (ulong)offset); + return (nint)ptr; + } + + public void FreeThunk(nint thunk) + { + _ = _memory.Free((ulong)thunk); + } + + public nint AddFirstChanceHandler(nint thunk) + { + return (nint)AddVectoredExceptionHandler(1u, thunk); + } + + public void RemoveHandler(nint handle) + { + _ = RemoveVectoredExceptionHandler((void*)handle); + } + + public void SetUnhandledFilter(nint thunk) + { + _ = SetUnhandledExceptionFilter(thunk); + } + + private static void EmitByte(byte* code, ref int offset, byte value) + { + code[offset++] = value; + } + + private static void EmitUInt32(byte* code, ref int offset, uint value) + { + *(uint*)(code + offset) = value; + offset += sizeof(uint); + } + + [LibraryImport("kernel32.dll")] + private static partial void* AddVectoredExceptionHandler(uint first, IntPtr handler); + + [LibraryImport("kernel32.dll")] + private static partial uint RemoveVectoredExceptionHandler(void* handle); + + [LibraryImport("kernel32.dll")] + private static partial IntPtr SetUnhandledExceptionFilter(IntPtr lpTopLevelExceptionFilter); +} diff --git a/src/SharpEmu.Core/Cpu/TrackedCpuMemory.cs b/src/SharpEmu.Core/Cpu/TrackedCpuMemory.cs index 239dc61f..3f35b8c1 100644 --- a/src/SharpEmu.Core/Cpu/TrackedCpuMemory.cs +++ b/src/SharpEmu.Core/Cpu/TrackedCpuMemory.cs @@ -5,7 +5,7 @@ using SharpEmu.HLE; namespace SharpEmu.Core.Cpu; -public sealed class TrackedCpuMemory : ICpuMemory, ITrackedCpuMemory, IGuestMemoryAllocator +public sealed class TrackedCpuMemory : ICpuMemory, ITrackedCpuMemory, IGuestMemoryAllocator, ICpuMemoryWrapper { private readonly ICpuMemory _inner; diff --git a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs index b6da6eba..fa75ce18 100644 --- a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs +++ b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs @@ -4,11 +4,12 @@ using System.Runtime.InteropServices; using SharpEmu.Core.Loader; using SharpEmu.HLE; +using SharpEmu.HLE.Host; using SharpEmu.Logging; namespace SharpEmu.Core.Memory; -public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryAllocator, IDisposable +public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryAllocator, IGuestAddressSpace, IDisposable { private static readonly SharpEmuLogger Log = SharpEmuLog.For("VMEM"); @@ -28,41 +29,26 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA private const ulong DefaultLazyReservePrimeBytes = 0x0400_0000UL; // 64 MiB private const ulong LazyReservePrimeChunkBytes = 0x0200_0000UL; // 32 MiB - private const uint MEM_COMMIT = 0x1000; - private const uint MEM_RESERVE = 0x2000; - private const uint MEM_RELEASE = 0x8000; + // Raw Windows PAGE_* values retained for the internal region/protection + // bookkeeping: regions and saved old-protection values always carry the raw + // value of the host platform in use, and these classification helpers only + // ever see values this class itself assigned (see IHostMemory.ProtectRaw). private const uint PAGE_EXECUTE_READ = 0x20; private const uint PAGE_EXECUTE_READWRITE = 0x40; private const uint PAGE_EXECUTE = 0x10; private const uint PAGE_EXECUTE_WRITECOPY = 0x80; - private const uint PAGE_NOACCESS = 0x01; private const uint PAGE_READWRITE = 0x04; private const uint PAGE_READONLY = 0x02; + private readonly IHostMemory _hostMemory; private ulong _guestAllocationArenaBase; private ulong _guestAllocationOffset; private static readonly ulong LazyReservePrimeBytes = ResolveLazyReservePrimeBytes(); - [DllImport("kernel32.dll", SetLastError = true)] - private static extern void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool VirtualFree(void* lpAddress, nuint dwSize, uint dwFreeType); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool VirtualProtect(void* lpAddress, nuint dwSize, uint flNewProtect, out uint lpflOldProtect); - - [DllImport("kernel32.dll")] - private static extern nuint VirtualQuery(void* lpAddress, out MemoryBasicInformation64 lpBuffer, nuint dwLength); - - [DllImport("kernel32.dll")] - private static extern void* GetCurrentProcess(); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool FlushInstructionCache(void* hProcess, void* lpBaseAddress, nuint dwSize); + public PhysicalVirtualMemory(IHostMemory? hostMemory = null) + { + _hostMemory = hostMemory ?? HostPlatform.Current.Memory; + } public bool TryAllocateAtExact(ulong desiredAddress, ulong size, bool executable, out ulong actualAddress) { @@ -74,17 +60,17 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA var alignedSize = (size + 0xFFF) & ~0xFFFUL; var protection = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; - var allocationType = MEM_COMMIT | MEM_RESERVE; - var result = VirtualAlloc((void*)desiredAddress, (nuint)alignedSize, allocationType, protection); - if (result == null) + var hostProtection = executable ? HostPageProtection.ReadWriteExecute : HostPageProtection.ReadWrite; + var result = _hostMemory.Allocate(desiredAddress, alignedSize, hostProtection); + if (result == 0) { return false; } - actualAddress = (ulong)result; + actualAddress = result; if (actualAddress != desiredAddress) { - VirtualFree(result, 0, MEM_RELEASE); + _hostMemory.Free(result); actualAddress = 0; return false; } @@ -119,33 +105,33 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA var alignedSize = (size + 0xFFF) & ~0xFFFUL; var protection = executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; - var allocationType = MEM_COMMIT | MEM_RESERVE; + var hostProtection = executable ? HostPageProtection.ReadWriteExecute : HostPageProtection.ReadWrite; var reservedOnly = false; var preferReserveOnly = !executable && alignedSize >= LargeDataReserveThreshold && alignedSize > FullCommitRegionLimit; - void* result = null; + ulong result = 0; if (preferReserveOnly) { - result = VirtualAlloc((void*)desiredAddress, (nuint)alignedSize, MEM_RESERVE, PAGE_READWRITE); - if (result == null && allowAlternative) + result = _hostMemory.Reserve(desiredAddress, alignedSize, HostPageProtection.ReadWrite); + if (result == 0 && allowAlternative) { - result = VirtualAlloc(null, (nuint)alignedSize, MEM_RESERVE, PAGE_READWRITE); + result = _hostMemory.Reserve(0, alignedSize, HostPageProtection.ReadWrite); } - if (result != null) + if (result != 0) { reservedOnly = true; } } - if (result == null) + if (result == 0) { - result = VirtualAlloc((void*)desiredAddress, (nuint)alignedSize, allocationType, protection); + result = _hostMemory.Allocate(desiredAddress, alignedSize, hostProtection); } - if (result == null) + if (result == 0) { if (!allowAlternative) { @@ -153,32 +139,32 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA } TraceVmem($"Could not allocate at 0x{desiredAddress:X16}, trying any address..."); - result = VirtualAlloc(null, (nuint)alignedSize, allocationType, protection); + result = _hostMemory.Allocate(0, alignedSize, hostProtection); - if (result == null) + if (result == 0) { if (!executable) { - result = VirtualAlloc((void*)desiredAddress, (nuint)alignedSize, MEM_RESERVE, PAGE_READWRITE); - if (result == null && allowAlternative) + result = _hostMemory.Reserve(desiredAddress, alignedSize, HostPageProtection.ReadWrite); + if (result == 0 && allowAlternative) { - result = VirtualAlloc(null, (nuint)alignedSize, MEM_RESERVE, PAGE_READWRITE); + result = _hostMemory.Reserve(0, alignedSize, HostPageProtection.ReadWrite); } - if (result != null) + if (result != 0) { reservedOnly = true; } } - if (result == null) + if (result == 0) { throw new OutOfMemoryException($"Failed to allocate {alignedSize} bytes of virtual memory"); } } } - var actualAddress = (ulong)result; + var actualAddress = result; var lazyPrimeState = "n/a"; if (reservedOnly) @@ -191,9 +177,8 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA { var remaining = primeBytes - committedBytes; var chunkBytes = Math.Min(remaining, LazyReservePrimeChunkBytes); - var commitAddress = (void*)(actualAddress + committedBytes); - var committed = VirtualAlloc(commitAddress, (nuint)chunkBytes, MEM_COMMIT, PAGE_READWRITE); - if (committed == null) + var commitAddress = actualAddress + committedBytes; + if (!_hostMemory.Commit(commitAddress, chunkBytes, HostPageProtection.ReadWrite)) { break; } @@ -336,6 +321,41 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA } } + public bool TryProtect(ulong address, ulong size, GuestPageProtection protection) + { + if (size == 0) + { + return false; + } + + return _hostMemory.Protect(address, size, ResolveProtection(protection), out _); + } + + // Reproduces the decomposition KernelMemoryCompatExports.ResolveHostProtection + // performed before this seam existed; the Windows backend maps each case back + // to the identical PAGE_* value. + private static HostPageProtection ResolveProtection(GuestPageProtection protection) + { + var read = (protection & GuestPageProtection.Read) != 0; + var write = (protection & GuestPageProtection.Write) != 0; + var execute = (protection & GuestPageProtection.Execute) != 0; + + if (execute) + { + return write + ? HostPageProtection.ReadWriteExecute + : read + ? HostPageProtection.ReadExecute + : HostPageProtection.Execute; + } + + return write + ? HostPageProtection.ReadWrite + : read + ? HostPageProtection.ReadOnly + : HostPageProtection.NoAccess; + } + public void Clear() { lock (_guestAllocationGate) @@ -345,7 +365,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA { foreach (var region in _regions) { - VirtualFree((void*)region.VirtualAddress, 0, MEM_RELEASE); + _hostMemory.Free(region.VirtualAddress); } _regions.Clear(); _pageProtections.Clear(); @@ -430,35 +450,35 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA private void SetProtection(ulong address, ulong size, ProgramHeaderFlags flags) { - uint protection; + HostPageProtection protection; if (flags == ProgramHeaderFlags.None) { - protection = PAGE_NOACCESS; + protection = HostPageProtection.NoAccess; } else if ((flags & ProgramHeaderFlags.Execute) != 0) { protection = (flags & ProgramHeaderFlags.Write) != 0 - ? PAGE_EXECUTE_READWRITE - : PAGE_EXECUTE_READ; + ? HostPageProtection.ReadWriteExecute + : HostPageProtection.ReadExecute; } else if ((flags & ProgramHeaderFlags.Write) != 0) { - protection = PAGE_READWRITE; + protection = HostPageProtection.ReadWrite; } else { - protection = PAGE_READONLY; + protection = HostPageProtection.ReadOnly; } - if (!VirtualProtect((void*)address, (nuint)size, protection, out _)) + if (!_hostMemory.Protect(address, size, protection, out _)) { throw new InvalidOperationException($"Failed to set memory protection at 0x{address:X16}"); } if ((flags & ProgramHeaderFlags.Execute) != 0) { - FlushInstructionCache(GetCurrentProcess(), (void*)address, (nuint)size); + _hostMemory.FlushInstructionCache(address, size); } } @@ -730,7 +750,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA return true; } - if (!VirtualProtect(destPtr, (nuint)source.Length, PAGE_EXECUTE_READWRITE, out var oldProtect)) + if (!_hostMemory.Protect((ulong)destPtr, (ulong)source.Length, HostPageProtection.ReadWriteExecute, out var oldProtect)) { return false; } @@ -744,10 +764,10 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA } finally { - VirtualProtect(destPtr, (nuint)source.Length, oldProtect, out _); + _hostMemory.ProtectRaw((ulong)destPtr, (ulong)source.Length, oldProtect, out _); if (IsExecutableProtection(oldProtect)) { - FlushInstructionCache(GetCurrentProcess(), destPtr, (nuint)source.Length); + _hostMemory.FlushInstructionCache((ulong)destPtr, (ulong)source.Length); } } @@ -973,12 +993,12 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA return protection is PAGE_READWRITE or PAGE_EXECUTE_READWRITE; } - private static uint GetCommitProtection(MemoryRegion region) + private static HostPageProtection GetCommitProtection(MemoryRegion region) { - return region.IsExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; + return region.IsExecutable ? HostPageProtection.ReadWriteExecute : HostPageProtection.ReadWrite; } - private static unsafe bool EnsureRangeCommitted(ulong address, ulong size, MemoryRegion region) + private bool EnsureRangeCommitted(ulong address, ulong size, MemoryRegion region) { if (size == 0 || !region.IsReservedOnly) { @@ -992,7 +1012,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA var pageAddress = startPage; while (pageAddress < endPage) { - if (VirtualQuery((void*)pageAddress, out var info, (nuint)sizeof(MemoryBasicInformation64)) == 0) + if (!_hostMemory.Query(pageAddress, out var info)) { return false; } @@ -1006,19 +1026,19 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA return false; } - if (info.State == MEM_COMMIT) + if (info.State == HostRegionState.Committed) { pageAddress = rangeEnd; continue; } - if (info.State != MEM_RESERVE) + if (info.State != HostRegionState.Reserved) { return false; } var commitSize = rangeEnd - pageAddress; - if (VirtualAlloc((void*)pageAddress, (nuint)commitSize, MEM_COMMIT, commitProtection) == null) + if (!_hostMemory.Commit(pageAddress, commitSize, commitProtection)) { return false; } @@ -1039,11 +1059,11 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA var startPage = AlignDown(address, PageSize); var endPage = AlignUp(address + size, PageSize); - var temporaryProtection = region.IsExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; + var temporaryProtection = region.IsExecutable ? HostPageProtection.ReadWriteExecute : HostPageProtection.ReadWrite; for (var pageAddress = startPage; pageAddress < endPage; pageAddress += PageSize) { - if (!VirtualProtect((void*)pageAddress, (nuint)PageSize, temporaryProtection, out var oldProtection)) + if (!_hostMemory.Protect(pageAddress, PageSize, temporaryProtection, out var oldProtection)) { RestorePageProtections(touchedPages); touchedPages.Clear(); @@ -1056,11 +1076,11 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA return true; } - private static void RestorePageProtections(List<(ulong Address, uint Protection)> touchedPages) + private void RestorePageProtections(List<(ulong Address, uint Protection)> touchedPages) { foreach (var (pageAddress, protection) in touchedPages) { - VirtualProtect((void*)pageAddress, (nuint)PageSize, protection, out _); + _hostMemory.ProtectRaw(pageAddress, PageSize, protection, out _); } } @@ -1117,16 +1137,4 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA public uint Protection { get; set; } } - private struct MemoryBasicInformation64 - { - public ulong BaseAddress; - public ulong AllocationBase; - public uint AllocationProtect; - public uint Alignment1; - public ulong RegionSize; - public uint State; - public uint Protect; - public uint Type; - public uint Alignment2; - } } diff --git a/src/SharpEmu.Core/Runtime/SharpEmuRuntime.cs b/src/SharpEmu.Core/Runtime/SharpEmuRuntime.cs index e54186b0..dbe47205 100644 --- a/src/SharpEmu.Core/Runtime/SharpEmuRuntime.cs +++ b/src/SharpEmu.Core/Runtime/SharpEmuRuntime.cs @@ -7,6 +7,7 @@ using SharpEmu.Core.Cpu.Disasm; using SharpEmu.Core.Loader; using SharpEmu.Core.Memory; using SharpEmu.HLE; +using SharpEmu.HLE.Host; using SharpEmu.Libs.VideoOut; using SharpEmu.Libs.Kernel; using SharpEmu.Libs.AppContent; @@ -86,14 +87,19 @@ public sealed class SharpEmuRuntime : ISharpEmuRuntime moduleManager.RegisterFromAssembly(typeof(KernelExports).Assembly, Generation.Gen4 | Generation.Gen5, Aerolib.Instance); moduleManager.Freeze(); - var virtualMemory = new PhysicalVirtualMemory(); + // Resolve the host platform once at the composition root; on unsupported + // OSes this throws PlatformNotSupportedException with a clear message + // instead of failing on the first native call. + var hostPlatform = HostPlatform.Current; + + var virtualMemory = new PhysicalVirtualMemory(hostPlatform.Memory); var fileSystem = new PhysicalFileSystem(); return new SharpEmuRuntime( new SelfLoader(), virtualMemory, - new CpuDispatcher(virtualMemory, moduleManager), + new CpuDispatcher(virtualMemory, moduleManager, hostPlatform: hostPlatform), moduleManager, Aerolib.Instance, cpuExecutionOptions, diff --git a/src/SharpEmu.HLE/GuestPageProtection.cs b/src/SharpEmu.HLE/GuestPageProtection.cs new file mode 100644 index 00000000..61d2d72a --- /dev/null +++ b/src/SharpEmu.HLE/GuestPageProtection.cs @@ -0,0 +1,13 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE; + +[Flags] +public enum GuestPageProtection +{ + None = 0, + Read = 1, + Write = 2, + Execute = 4, +} diff --git a/src/SharpEmu.HLE/Host/HostCapturedRegisters.cs b/src/SharpEmu.HLE/Host/HostCapturedRegisters.cs new file mode 100644 index 00000000..d8996303 --- /dev/null +++ b/src/SharpEmu.HLE/Host/HostCapturedRegisters.cs @@ -0,0 +1,18 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE.Host; + +/// +/// General-purpose register snapshot of a suspended thread, produced by +/// . Registers are named +/// after the guest ISA (x86-64), which every supported host executes natively. +/// +public readonly record struct HostCapturedRegisters( + ulong Rip, + ulong Rsp, + ulong Rbp, + ulong Rax, + ulong Rbx, + ulong Rcx, + ulong Rdx); diff --git a/src/SharpEmu.HLE/Host/HostPageProtection.cs b/src/SharpEmu.HLE/Host/HostPageProtection.cs new file mode 100644 index 00000000..5f0a9ccb --- /dev/null +++ b/src/SharpEmu.HLE/Host/HostPageProtection.cs @@ -0,0 +1,20 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE.Host; + +/// +/// Platform-neutral page protection. Values intentionally enumerate the exact +/// combinations the emulator uses today so each maps 1:1 onto a single native +/// protection constant (PAGE_* on Windows, PROT_* elsewhere). +/// +public enum HostPageProtection +{ + NoAccess, + ReadOnly, + ReadWrite, + Execute, + ReadExecute, + ReadWriteExecute, + ExecuteWriteCopy, +} diff --git a/src/SharpEmu.HLE/Host/HostPlatform.cs b/src/SharpEmu.HLE/Host/HostPlatform.cs new file mode 100644 index 00000000..15a60f5b --- /dev/null +++ b/src/SharpEmu.HLE/Host/HostPlatform.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Runtime.InteropServices; +using SharpEmu.HLE.Host.Windows; + +namespace SharpEmu.HLE.Host; + +/// +/// Process-wide access point for the host platform backend. Static HLE export +/// classes (which cannot receive constructor injection) resolve host primitives +/// through ; injectable components should instead accept an +/// and merely default to this. +/// +public static class HostPlatform +{ + private static readonly Lazy Instance = new(Create); + + public static IHostPlatform Current => Instance.Value; + + private static IHostPlatform Create() + { + // The Windows backend executes guest x86-64 natively and emits x86-64 + // stubs, so a native ARM64 process must be rejected here rather than + // crash undefined later (x64 processes under emulation report X64). + if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.X64) + { + return new WindowsHostPlatform(); + } + + throw new PlatformNotSupportedException( + "SharpEmu native guest execution requires a host platform backend and none exists for this OS/architecture yet (currently Windows x64 only)."); + } +} diff --git a/src/SharpEmu.HLE/Host/HostRegionInfo.cs b/src/SharpEmu.HLE/Host/HostRegionInfo.cs new file mode 100644 index 00000000..6ee17095 --- /dev/null +++ b/src/SharpEmu.HLE/Host/HostRegionInfo.cs @@ -0,0 +1,20 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE.Host; + +/// +/// Result of . The Raw* fields carry the +/// untranslated OS values so call sites migrated from direct VirtualQuery use +/// keep comparing (and logging) the exact native words they did before; +/// and are neutral views. +/// +public readonly record struct HostRegionInfo( + ulong BaseAddress, + ulong AllocationBase, + ulong RegionSize, + HostRegionState State, + uint RawState, + HostPageProtection Protection, + uint RawProtection, + uint RawAllocationProtection); diff --git a/src/SharpEmu.HLE/Host/HostRegionState.cs b/src/SharpEmu.HLE/Host/HostRegionState.cs new file mode 100644 index 00000000..a83229c6 --- /dev/null +++ b/src/SharpEmu.HLE/Host/HostRegionState.cs @@ -0,0 +1,11 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE.Host; + +public enum HostRegionState +{ + Free, + Reserved, + Committed, +} diff --git a/src/SharpEmu.HLE/Host/HostRuntimeFunction.cs b/src/SharpEmu.HLE/Host/HostRuntimeFunction.cs new file mode 100644 index 00000000..b651d9fe --- /dev/null +++ b/src/SharpEmu.HLE/Host/HostRuntimeFunction.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE.Host; + +/// +/// Host functions whose addresses the execution engine bakes into emitted +/// stubs (spin-waits, worker run loops, TLS reads). Enum-keyed rather than a +/// free-form name lookup: each platform's emitters need their own specific +/// functions, and this set is exactly what the current emitters consume. +/// +public enum HostRuntimeFunction +{ + TlsGetValue, + QueryPerformanceCounter, + SwitchToThread, + Sleep, + WaitForSingleObject, + SetEvent, + ExitThread, +} diff --git a/src/SharpEmu.HLE/Host/IHostFaultHandling.cs b/src/SharpEmu.HLE/Host/IHostFaultHandling.cs new file mode 100644 index 00000000..114959a6 --- /dev/null +++ b/src/SharpEmu.HLE/Host/IHostFaultHandling.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE.Host; + +/// +/// Installation mechanics for the process-wide fault interception the execution +/// engine relies on to catch guest faults. Deliberately thin: the managed +/// handlers keep receiving the platform's raw exception data, and the emitted +/// pre-filter thunk is an opaque per-platform unit. Implementations live next +/// to the execution backend (SharpEmu.Core), not behind HostPlatform.Current. +/// +public interface IHostFaultHandling +{ + /// + /// Emits the native thunk that wraps a managed fault handler: it pre-filters + /// exception codes that must never enter managed code and, when the fault + /// happened on a guest stack, switches to the host stack saved in + /// before the call. Returns 0 on failure. + /// + nint CreateHandlerThunk(nint managedCallback, uint hostRspSwitchTlsSlot, nint tlsGetValueAddress); + + void FreeThunk(nint thunk); + + /// Installs a first-chance handler ahead of existing ones; returns a removal handle (0 on failure). + nint AddFirstChanceHandler(nint thunk); + + void RemoveHandler(nint handle); + + /// Installs the last-resort filter; pass 0 to clear. + void SetUnhandledFilter(nint thunk); +} diff --git a/src/SharpEmu.HLE/Host/IHostMemory.cs b/src/SharpEmu.HLE/Host/IHostMemory.cs new file mode 100644 index 00000000..ee743408 --- /dev/null +++ b/src/SharpEmu.HLE/Host/IHostMemory.cs @@ -0,0 +1,48 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE.Host; + +/// +/// Host page-allocation primitives used by the native execution engine. +/// Allocate/Reserve/Commit are deliberately separate members (rather than a +/// flags parameter) so every call site maps 1:1 onto the exact native call it +/// replaced, keeping the Windows behavior byte-for-byte identical. +/// +public interface IHostMemory +{ + /// + /// Reserves and commits pages in one step. of 0 + /// lets the OS choose the address. Returns the base address, or 0 on failure. + /// The OS may satisfy the request at a different address than desired; callers + /// that require an exact placement must check the result themselves. + /// + ulong Allocate(ulong desiredAddress, ulong size, HostPageProtection protection); + + /// Reserves address space without committing pages (lazy regions). + ulong Reserve(ulong desiredAddress, ulong size, HostPageProtection protection); + + /// Commits pages inside a previously reserved range (fault-path lazy commit). + bool Commit(ulong address, ulong size, HostPageProtection protection); + + /// Releases an entire allocation or reservation by its base address. + bool Free(ulong address); + + /// + /// Changes protection on committed pages. is the + /// untranslated previous OS protection value (see ). + /// + bool Protect(ulong address, ulong size, HostPageProtection protection, out uint rawOldProtection); + + /// + /// Restores a raw protection value previously returned by or + /// on this same platform. Raw values are opaque to callers and + /// must never cross platforms; this exists so save/restore protection sequences + /// round-trip OS-specific modifier bits the neutral enum cannot represent. + /// + bool ProtectRaw(ulong address, ulong size, uint rawProtection, out uint rawOldProtection); + + bool Query(ulong address, out HostRegionInfo info); + + void FlushInstructionCache(ulong address, ulong size); +} diff --git a/src/SharpEmu.HLE/Host/IHostPlatform.cs b/src/SharpEmu.HLE/Host/IHostPlatform.cs new file mode 100644 index 00000000..3dfeac60 --- /dev/null +++ b/src/SharpEmu.HLE/Host/IHostPlatform.cs @@ -0,0 +1,19 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE.Host; + +/// +/// Aggregates the host-OS primitives the native execution engine depends on. +/// Each supported platform provides one implementation; consumers reach the +/// process-wide instance through or accept +/// one by injection. +/// +public interface IHostPlatform +{ + IHostMemory Memory { get; } + + IHostThreading Threading { get; } + + IHostSymbolResolver Symbols { get; } +} diff --git a/src/SharpEmu.HLE/Host/IHostSymbolResolver.cs b/src/SharpEmu.HLE/Host/IHostSymbolResolver.cs new file mode 100644 index 00000000..e3f0c807 --- /dev/null +++ b/src/SharpEmu.HLE/Host/IHostSymbolResolver.cs @@ -0,0 +1,10 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE.Host; + +public interface IHostSymbolResolver +{ + /// Returns the native address of the function, or 0 if unavailable. + nint GetAddress(HostRuntimeFunction function); +} diff --git a/src/SharpEmu.HLE/Host/IHostThreading.cs b/src/SharpEmu.HLE/Host/IHostThreading.cs new file mode 100644 index 00000000..85cc0c9e --- /dev/null +++ b/src/SharpEmu.HLE/Host/IHostThreading.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE.Host; + +/// +/// Raw host thread and native-TLS primitives for the execution engine. Guest +/// code must run on threads the CLR did not create (no managed frames below +/// guest frames), so thread creation takes a native entry point and is not +/// expressible with managed threads. +/// +public interface IHostThreading +{ + /// Allocates a native TLS slot; returns on failure. + uint AllocateTlsSlot(); + + bool FreeTlsSlot(uint slot); + + bool SetTlsValue(uint slot, nint value); + + nint GetTlsValue(uint slot); + + uint CurrentThreadId { get; } + + bool TrySetCurrentThreadAffinity(nuint affinityMask); + + /// + /// Creates a raw OS thread executing native code at with + /// of reserved (not committed) stack. + /// Returns the thread handle, or 0 on failure. + /// + nint CreateNativeThread(nint entry, nint parameter, nuint stackReserveBytes, out uint threadId); + + /// Waits for the thread to exit; true when it did within the timeout. + bool WaitForThreadExit(nint threadHandle, uint timeoutMilliseconds); + + void CloseThreadHandle(nint threadHandle); + + /// + /// Suspends the thread, snapshots its general-purpose registers, and resumes it — + /// one indivisible operation (diagnostics only). The caller must not pass the + /// current thread. Returns false if the thread cannot be opened or suspended. + /// + bool TryCaptureThreadRegisters(uint threadId, out HostCapturedRegisters registers); +} diff --git a/src/SharpEmu.HLE/Host/Windows/WindowsHostMemory.cs b/src/SharpEmu.HLE/Host/Windows/WindowsHostMemory.cs new file mode 100644 index 00000000..d08878c3 --- /dev/null +++ b/src/SharpEmu.HLE/Host/Windows/WindowsHostMemory.cs @@ -0,0 +1,153 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Runtime.InteropServices; + +namespace SharpEmu.HLE.Host.Windows; + +/// +/// Windows implementation over VirtualAlloc/VirtualFree/VirtualProtect/VirtualQuery. +/// Sealed so the JIT can devirtualize interface calls on fault-handling hot paths. +/// +internal sealed unsafe partial class WindowsHostMemory : IHostMemory +{ + private const uint MEM_COMMIT = 0x1000; + private const uint MEM_RESERVE = 0x2000; + private const uint MEM_RELEASE = 0x8000; + private const uint MEM_FREE = 0x10000; + + private const uint PAGE_NOACCESS = 0x01; + private const uint PAGE_READONLY = 0x02; + private const uint PAGE_READWRITE = 0x04; + private const uint PAGE_WRITECOPY = 0x08; + private const uint PAGE_EXECUTE = 0x10; + private const uint PAGE_EXECUTE_READ = 0x20; + private const uint PAGE_EXECUTE_READWRITE = 0x40; + private const uint PAGE_EXECUTE_WRITECOPY = 0x80; + + public ulong Allocate(ulong desiredAddress, ulong size, HostPageProtection protection) + { + return (ulong)VirtualAlloc((void*)desiredAddress, (nuint)size, MEM_COMMIT | MEM_RESERVE, ToNativeProtection(protection)); + } + + public ulong Reserve(ulong desiredAddress, ulong size, HostPageProtection protection) + { + return (ulong)VirtualAlloc((void*)desiredAddress, (nuint)size, MEM_RESERVE, ToNativeProtection(protection)); + } + + public bool Commit(ulong address, ulong size, HostPageProtection protection) + { + return VirtualAlloc((void*)address, (nuint)size, MEM_COMMIT, ToNativeProtection(protection)) != null; + } + + public bool Free(ulong address) + { + return VirtualFree((void*)address, 0, MEM_RELEASE); + } + + public bool Protect(ulong address, ulong size, HostPageProtection protection, out uint rawOldProtection) + { + return VirtualProtect((void*)address, (nuint)size, ToNativeProtection(protection), out rawOldProtection); + } + + public bool ProtectRaw(ulong address, ulong size, uint rawProtection, out uint rawOldProtection) + { + return VirtualProtect((void*)address, (nuint)size, rawProtection, out rawOldProtection); + } + + public bool Query(ulong address, out HostRegionInfo info) + { + if (VirtualQuery((void*)address, out var mbi, (nuint)sizeof(MemoryBasicInformation64)) == 0) + { + info = default; + return false; + } + + info = new HostRegionInfo( + mbi.BaseAddress, + mbi.AllocationBase, + mbi.RegionSize, + ToRegionState(mbi.State), + mbi.State, + ToHostProtection(mbi.Protect), + mbi.Protect, + mbi.AllocationProtect); + return true; + } + + public void FlushInstructionCache(ulong address, ulong size) + { + FlushInstructionCache(GetCurrentProcess(), (void*)address, (nuint)size); + } + + private static uint ToNativeProtection(HostPageProtection protection) => protection switch + { + HostPageProtection.NoAccess => PAGE_NOACCESS, + HostPageProtection.ReadOnly => PAGE_READONLY, + HostPageProtection.ReadWrite => PAGE_READWRITE, + HostPageProtection.Execute => PAGE_EXECUTE, + HostPageProtection.ReadExecute => PAGE_EXECUTE_READ, + HostPageProtection.ReadWriteExecute => PAGE_EXECUTE_READWRITE, + HostPageProtection.ExecuteWriteCopy => PAGE_EXECUTE_WRITECOPY, + _ => throw new ArgumentOutOfRangeException(nameof(protection), protection, null), + }; + + private static HostRegionState ToRegionState(uint state) => state switch + { + MEM_COMMIT => HostRegionState.Committed, + MEM_RESERVE => HostRegionState.Reserved, + MEM_FREE => HostRegionState.Free, + _ => HostRegionState.Free, + }; + + private static HostPageProtection ToHostProtection(uint rawProtection) + { + // Strip PAGE_GUARD/PAGE_NOCACHE/PAGE_WRITECOMBINE modifiers; callers needing + // them compare HostRegionInfo.RawProtection directly. + return (rawProtection & 0xFF) switch + { + PAGE_READONLY => HostPageProtection.ReadOnly, + PAGE_READWRITE => HostPageProtection.ReadWrite, + PAGE_WRITECOPY => HostPageProtection.ReadWrite, + PAGE_EXECUTE => HostPageProtection.Execute, + PAGE_EXECUTE_READ => HostPageProtection.ReadExecute, + PAGE_EXECUTE_READWRITE => HostPageProtection.ReadWriteExecute, + PAGE_EXECUTE_WRITECOPY => HostPageProtection.ExecuteWriteCopy, + _ => HostPageProtection.NoAccess, + }; + } + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial void* VirtualAlloc(void* lpAddress, nuint dwSize, uint flAllocationType, uint flProtect); + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool VirtualFree(void* lpAddress, nuint dwSize, uint dwFreeType); + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool VirtualProtect(void* lpAddress, nuint dwSize, uint flNewProtect, out uint lpflOldProtect); + + [LibraryImport("kernel32.dll")] + private static partial nuint VirtualQuery(void* lpAddress, out MemoryBasicInformation64 lpBuffer, nuint dwLength); + + [LibraryImport("kernel32.dll")] + private static partial void* GetCurrentProcess(); + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool FlushInstructionCache(void* hProcess, void* lpBaseAddress, nuint dwSize); + + private struct MemoryBasicInformation64 + { + public ulong BaseAddress; + public ulong AllocationBase; + public uint AllocationProtect; + public uint Alignment1; + public ulong RegionSize; + public uint State; + public uint Protect; + public uint Type; + public uint Alignment2; + } +} diff --git a/src/SharpEmu.HLE/Host/Windows/WindowsHostPlatform.cs b/src/SharpEmu.HLE/Host/Windows/WindowsHostPlatform.cs new file mode 100644 index 00000000..d8fdf28d --- /dev/null +++ b/src/SharpEmu.HLE/Host/Windows/WindowsHostPlatform.cs @@ -0,0 +1,13 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE.Host.Windows; + +internal sealed class WindowsHostPlatform : IHostPlatform +{ + public IHostMemory Memory { get; } = new WindowsHostMemory(); + + public IHostThreading Threading { get; } = new WindowsHostThreading(); + + public IHostSymbolResolver Symbols { get; } = new WindowsHostSymbolResolver(); +} diff --git a/src/SharpEmu.HLE/Host/Windows/WindowsHostSymbolResolver.cs b/src/SharpEmu.HLE/Host/Windows/WindowsHostSymbolResolver.cs new file mode 100644 index 00000000..a69f37c4 --- /dev/null +++ b/src/SharpEmu.HLE/Host/Windows/WindowsHostSymbolResolver.cs @@ -0,0 +1,39 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Runtime.InteropServices; + +namespace SharpEmu.HLE.Host.Windows; + +internal sealed partial class WindowsHostSymbolResolver : IHostSymbolResolver +{ + public nint GetAddress(HostRuntimeFunction function) + { + var kernel32 = GetModuleHandle("kernel32.dll"); + if (kernel32 == 0) + { + return 0; + } + + return GetProcAddress(kernel32, function switch + { + HostRuntimeFunction.TlsGetValue => "TlsGetValue", + HostRuntimeFunction.QueryPerformanceCounter => "QueryPerformanceCounter", + HostRuntimeFunction.SwitchToThread => "SwitchToThread", + HostRuntimeFunction.Sleep => "Sleep", + HostRuntimeFunction.WaitForSingleObject => "WaitForSingleObject", + HostRuntimeFunction.SetEvent => "SetEvent", + HostRuntimeFunction.ExitThread => "ExitThread", + _ => throw new ArgumentOutOfRangeException(nameof(function), function, null), + }); + } + + // Utf16 marshalling pins the managed string and passes its address directly + // (no copy); Utf8 stack-allocates the transient buffer for these short + // ASCII export names. LibraryImport is exact-spelling, hence the W entry point. + [LibraryImport("kernel32.dll", EntryPoint = "GetModuleHandleW", StringMarshalling = StringMarshalling.Utf16)] + private static partial nint GetModuleHandle(string lpModuleName); + + [LibraryImport("kernel32.dll", StringMarshalling = StringMarshalling.Utf8)] + private static partial nint GetProcAddress(nint hModule, string procName); +} diff --git a/src/SharpEmu.HLE/Host/Windows/WindowsHostThreading.cs b/src/SharpEmu.HLE/Host/Windows/WindowsHostThreading.cs new file mode 100644 index 00000000..44b36059 --- /dev/null +++ b/src/SharpEmu.HLE/Host/Windows/WindowsHostThreading.cs @@ -0,0 +1,165 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using System.Runtime.InteropServices; + +namespace SharpEmu.HLE.Host.Windows; + +internal sealed unsafe partial class WindowsHostThreading : IHostThreading +{ + private const uint StackSizeParamIsAReservation = 0x00010000u; + private const uint ThreadGetContext = 0x0008u; + private const uint ThreadSuspendResume = 0x0002u; + + // Win64 CONTEXT layout (CONTROL | INTEGER only — no XMM state is requested). + private const int Win64ContextSize = 0x4D0; + private const int Win64ContextFlagsOffset = 0x30; + private const uint ContextAmd64ControlInteger = 0x00100003u; + private const int CtxRax = 120; + private const int CtxRcx = 128; + private const int CtxRdx = 136; + private const int CtxRbx = 144; + private const int CtxRsp = 152; + private const int CtxRbp = 160; + private const int CtxRip = 248; + + public uint AllocateTlsSlot() => TlsAlloc(); + + public bool FreeTlsSlot(uint slot) => TlsFree(slot); + + public bool SetTlsValue(uint slot, nint value) => TlsSetValue(slot, value); + + public nint GetTlsValue(uint slot) => TlsGetValue(slot); + + public uint CurrentThreadId => GetCurrentThreadId(); + + public bool TrySetCurrentThreadAffinity(nuint affinityMask) + { + return SetThreadAffinityMask(GetCurrentThread(), affinityMask) != 0; + } + + public nint CreateNativeThread(nint entry, nint parameter, nuint stackReserveBytes, out uint threadId) + { + return CreateThread(0, stackReserveBytes, entry, parameter, StackSizeParamIsAReservation, out threadId); + } + + public bool WaitForThreadExit(nint threadHandle, uint timeoutMilliseconds) + { + return WaitForSingleObject(threadHandle, timeoutMilliseconds) == 0u; + } + + public void CloseThreadHandle(nint threadHandle) + { + _ = CloseHandle(threadHandle); + } + + public bool TryCaptureThreadRegisters(uint threadId, out HostCapturedRegisters registers) + { + registers = default; + var threadHandle = OpenThread(ThreadGetContext | ThreadSuspendResume, false, threadId); + if (threadHandle == 0) + { + return false; + } + + void* contextRecord = null; + var suspended = false; + try + { + if (SuspendThread(threadHandle) == uint.MaxValue) + { + return false; + } + + suspended = true; + // CONTEXT requires 16-byte alignment (it embeds M128A fields); + // NativeMemory.AllocZeroed guarantees max_align_t, stackalloc only + // pointer-size — so this stays a native allocation. + contextRecord = NativeMemory.AllocZeroed((nuint)Win64ContextSize); + *(uint*)((byte*)contextRecord + Win64ContextFlagsOffset) = ContextAmd64ControlInteger; + if (!GetThreadContext(threadHandle, contextRecord)) + { + return false; + } + + registers = new HostCapturedRegisters( + ReadU64(contextRecord, CtxRip), + ReadU64(contextRecord, CtxRsp), + ReadU64(contextRecord, CtxRbp), + ReadU64(contextRecord, CtxRax), + ReadU64(contextRecord, CtxRbx), + ReadU64(contextRecord, CtxRcx), + ReadU64(contextRecord, CtxRdx)); + return true; + } + finally + { + if (contextRecord != null) + { + NativeMemory.Free(contextRecord); + } + if (suspended) + { + _ = ResumeThread(threadHandle); + } + _ = CloseHandle(threadHandle); + } + } + + private static ulong ReadU64(void* contextRecord, int offset) + { + return *(ulong*)((byte*)contextRecord + offset); + } + + [LibraryImport("kernel32.dll")] + private static partial uint TlsAlloc(); + + [LibraryImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool TlsFree(uint dwTlsIndex); + + [LibraryImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool TlsSetValue(uint dwTlsIndex, nint lpTlsValue); + + [LibraryImport("kernel32.dll")] + private static partial nint TlsGetValue(uint dwTlsIndex); + + [LibraryImport("kernel32.dll")] + private static partial uint GetCurrentThreadId(); + + [LibraryImport("kernel32.dll")] + private static partial nint GetCurrentThread(); + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial nuint SetThreadAffinityMask(nint hThread, nuint dwThreadAffinityMask); + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial nint CreateThread( + nint lpThreadAttributes, + nuint dwStackSize, + nint lpStartAddress, + nint lpParameter, + uint dwCreationFlags, + out uint lpThreadId); + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial uint WaitForSingleObject(nint hHandle, uint dwMilliseconds); + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial nint OpenThread(uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwThreadId); + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial uint SuspendThread(nint hThread); + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial uint ResumeThread(nint hThread); + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool GetThreadContext(nint hThread, void* lpContext); + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool CloseHandle(nint hObject); +} diff --git a/src/SharpEmu.HLE/ICpuMemoryWrapper.cs b/src/SharpEmu.HLE/ICpuMemoryWrapper.cs new file mode 100644 index 00000000..0cc47985 --- /dev/null +++ b/src/SharpEmu.HLE/ICpuMemoryWrapper.cs @@ -0,0 +1,14 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE; + +/// +/// Implemented by memories that decorate another +/// (e.g. access trackers) so capability lookups can unwrap to the real +/// implementation without reflection. +/// +public interface ICpuMemoryWrapper +{ + ICpuMemory Inner { get; } +} diff --git a/src/SharpEmu.HLE/IGuestAddressSpace.cs b/src/SharpEmu.HLE/IGuestAddressSpace.cs new file mode 100644 index 00000000..f7a22c25 --- /dev/null +++ b/src/SharpEmu.HLE/IGuestAddressSpace.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +namespace SharpEmu.HLE; + +/// +/// Guest address-space manipulation beyond plain allocation: fixed-address +/// mapping and page-protection changes. Guest addresses are identity-mapped +/// onto host pages by the implementing memory, so HLE exports (mmap, mprotect) +/// reach these operations through ctx.Memory instead of calling host +/// APIs directly. Member signatures deliberately mirror the implementation in +/// SharpEmu.Core so existing call sites migrate call-for-call. +/// +public interface IGuestAddressSpace : IGuestMemoryAllocator +{ + ulong AllocateAt(ulong desiredAddress, ulong size, bool executable = true, bool allowAlternative = true); + + bool TryAllocateAtOrAbove(ulong desiredAddress, ulong size, bool executable, ulong alignment, out ulong actualAddress); + + bool TryProtect(ulong address, ulong size, GuestPageProtection protection); +} diff --git a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs index 13e73c3b..5a363a47 100644 --- a/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelMemoryCompatExports.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.HLE; +using SharpEmu.HLE.Host; using SharpEmu.Libs.Ampr; using System.Buffers; using System.Buffers.Binary; @@ -52,14 +53,13 @@ public static class KernelMemoryCompatExports private const ulong FlexibleMemorySizeBytes = 448UL * 1024 * 1024; private const int OrbisVirtualQueryInfoSize = 72; private const int OrbisKernelMaximumNameLength = 32; - private const uint MemCommit = 0x1000; - private const uint MemReserve = 0x2000; - private const uint MemRelease = 0x8000; + // Raw Windows PAGE_* values used only against HostRegionInfo.RawProtection, + // which by contract carries the untranslated protection word of the host + // platform in use (see IHostMemory). private const uint HostPageNoAccess = 0x01; private const uint HostPageReadOnly = 0x02; private const uint HostPageReadWrite = 0x04; private const uint HostPageWriteCopy = 0x08; - private const uint HostPageExecute = 0x10; private const uint HostPageExecuteRead = 0x20; private const uint HostPageExecuteReadWrite = 0x40; private const uint HostPageExecuteWriteCopy = 0x80; @@ -136,31 +136,9 @@ public static class KernelMemoryCompatExports private static string? _cachedApp0Root; private static string? _cachedDownload0Root; - [StructLayout(LayoutKind.Sequential)] - private struct MemoryBasicInformation - { - public nint BaseAddress; - public nint AllocationBase; - public uint AllocationProtect; - public nuint RegionSize; - public uint State; - public uint Protect; - public uint Type; - } - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern nuint VirtualQuery(nint lpAddress, out MemoryBasicInformation lpBuffer, nuint dwLength); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool VirtualProtect(nint lpAddress, nuint dwSize, uint flNewProtect, out uint lpflOldProtect); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern nint VirtualAlloc(nint lpAddress, nuint dwSize, uint flAllocationType, uint flProtect); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool VirtualFree(nint lpAddress, nuint dwSize, uint dwFreeType); + // Property (not a cached field) so merely touching this type never resolves + // the platform backend; non-Windows hosts only throw if a call is reached. + private static IHostMemory HostMemory => HostPlatform.Current.Memory; private sealed class OpenDirectory { @@ -3563,7 +3541,7 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - if (!TryProtectHostRange(alignedAddress, alignedLength, protection)) + if (!TryProtectHostRange(ctx, alignedAddress, alignedLength, protection)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } @@ -3597,7 +3575,7 @@ public static class KernelMemoryCompatExports return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT; } - if (!TryProtectHostRange(alignedAddress, alignedLength, protection)) + if (!TryProtectHostRange(ctx, alignedAddress, alignedLength, protection)) { return (int)OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; } @@ -5797,42 +5775,40 @@ public static class KernelMemoryCompatExports return alignedLength != 0; } - private static bool TryProtectHostRange(ulong address, ulong length, int orbisProtection) + private static bool TryProtectHostRange(CpuContext ctx, ulong address, ulong length, int orbisProtection) { if (length == 0 || length > nuint.MaxValue) { return false; } - var hostProtection = ResolveHostProtection(orbisProtection); - if (!VirtualProtect((nint)address, (nuint)length, hostProtection, out _)) + if (!KernelVirtualRangeAllocator.TryResolveAddressSpace(ctx.Memory, out var addressSpace)) { return false; } - return true; + return addressSpace.TryProtect(address, length, ResolveGuestProtection(orbisProtection)); } - private static uint ResolveHostProtection(int orbisProtection) + private static GuestPageProtection ResolveGuestProtection(int orbisProtection) { - var read = (orbisProtection & (OrbisProtCpuRead | OrbisProtGpuRead)) != 0; - var write = (orbisProtection & (OrbisProtCpuWrite | OrbisProtGpuWrite)) != 0; - var execute = (orbisProtection & OrbisProtCpuExec) != 0; - - if (execute) + var protection = GuestPageProtection.None; + if ((orbisProtection & (OrbisProtCpuRead | OrbisProtGpuRead)) != 0) { - return write - ? HostPageExecuteReadWrite - : read - ? HostPageExecuteRead - : HostPageExecute; + protection |= GuestPageProtection.Read; } - return write - ? HostPageReadWrite - : read - ? HostPageReadOnly - : HostPageNoAccess; + if ((orbisProtection & (OrbisProtCpuWrite | OrbisProtGpuWrite)) != 0) + { + protection |= GuestPageProtection.Write; + } + + if ((orbisProtection & OrbisProtCpuExec) != 0) + { + protection |= GuestPageProtection.Execute; + } + + return protection; } private static bool TryFindVirtualQueryRegionLocked(ulong queryAddress, bool findNext, out MappedRegion region) @@ -6218,7 +6194,7 @@ public static class KernelMemoryCompatExports var effectiveAlignment = Math.Max(alignment, pageSize); var usableSize = checked((nuint)AlignUp((ulong)actualSize, (ulong)pageSize)); var reservationSize = checked(pageSize + effectiveAlignment - 1 + usableSize + pageSize); - var baseAddress = VirtualAlloc(0, reservationSize, MemCommit | MemReserve, HostPageReadWrite); + var baseAddress = unchecked((nint)HostMemory.Allocate(0, reservationSize, HostPageProtection.ReadWrite)); if (baseAddress == 0) { return false; @@ -6226,9 +6202,9 @@ public static class KernelMemoryCompatExports var alignedAddress = AlignUp(unchecked((ulong)baseAddress) + (ulong)pageSize, (ulong)effectiveAlignment); var guardAddress = alignedAddress + (ulong)usableSize; - if (!VirtualProtect((nint)guardAddress, pageSize, HostPageNoAccess, out _)) + if (!HostMemory.Protect(guardAddress, pageSize, HostPageProtection.NoAccess, out _)) { - _ = VirtualFree(baseAddress, 0, MemRelease); + _ = HostMemory.Free(unchecked((ulong)baseAddress)); return false; } @@ -6363,7 +6339,7 @@ public static class KernelMemoryCompatExports if (allocation.IsGuarded) { - _ = VirtualFree(allocation.BaseAddress, 0, MemRelease); + _ = HostMemory.Free(unchecked((ulong)allocation.BaseAddress)); } else { @@ -6459,7 +6435,7 @@ public static class KernelMemoryCompatExports return false; } - if (!TryQueryHostPage(address, out var startInfo) || !HasRequiredProtection(startInfo.Protect, writeAccess)) + if (!TryQueryHostPage(address, out var startInfo) || !HasRequiredProtection(startInfo.RawProtection, writeAccess)) { return false; } @@ -6470,7 +6446,7 @@ public static class KernelMemoryCompatExports return true; } - if (!TryQueryHostPage(endAddress, out var endInfo) || !HasRequiredProtection(endInfo.Protect, writeAccess)) + if (!TryQueryHostPage(endAddress, out var endInfo) || !HasRequiredProtection(endInfo.RawProtection, writeAccess)) { return false; } @@ -6478,16 +6454,14 @@ public static class KernelMemoryCompatExports return true; } - private static bool TryQueryHostPage(ulong address, out MemoryBasicInformation info) + private static bool TryQueryHostPage(ulong address, out HostRegionInfo info) { - info = default; - var size = (nuint)Marshal.SizeOf(); - if (VirtualQuery((nint)address, out info, size) == 0) + if (!HostMemory.Query(address, out info)) { return false; } - return info.State == MemCommit; + return info.State == HostRegionState.Committed; } private static bool HasRequiredProtection(uint protect, bool writeAccess) diff --git a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs index 577fe56c..69910c71 100644 --- a/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs +++ b/src/SharpEmu.Libs/Kernel/KernelRuntimeCompatExports.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later using SharpEmu.HLE; +using SharpEmu.HLE.Host; using SharpEmu.Libs.Fiber; using System.Buffers.Binary; using System.Diagnostics; @@ -48,9 +49,6 @@ public static class KernelRuntimeCompatExports private const int MapFlagFixed = 0x10; private const ulong DefaultVirtualRangeAlignment = 0x4000UL; private const int AioInitParamSize = 0x3C; - private const uint MemCommit = 0x1000; - private const uint MemReserve = 0x2000; - private const uint PageExecuteReadWrite = 0x40; private static readonly object _stateGate = new(); private static readonly long _processStartCounter = Stopwatch.GetTimestamp(); private static readonly RdtscDelegate? _rdtscReader = CreateRdtscReader(); @@ -1893,7 +1891,7 @@ public static class KernelRuntimeCompatExports try { - nint stubAddress = VirtualAlloc(nint.Zero, (nuint)16, MemCommit | MemReserve, PageExecuteReadWrite); + nint stubAddress = unchecked((nint)HostPlatform.Current.Memory.Allocate(0, 16, HostPageProtection.ReadWriteExecute)); if (stubAddress == 0) { return null; @@ -1923,9 +1921,6 @@ public static class KernelRuntimeCompatExports } } - [DllImport("kernel32.dll", SetLastError = true)] - private static extern nint VirtualAlloc(nint lpAddress, nuint dwSize, uint flAllocationType, uint flProtect); - private static bool TryReserveVirtualRange( CpuContext ctx, ulong desiredAddress, diff --git a/src/SharpEmu.Libs/Kernel/KernelVirtualRangeAllocator.cs b/src/SharpEmu.Libs/Kernel/KernelVirtualRangeAllocator.cs index b79e66b8..8971dc87 100644 --- a/src/SharpEmu.Libs/Kernel/KernelVirtualRangeAllocator.cs +++ b/src/SharpEmu.Libs/Kernel/KernelVirtualRangeAllocator.cs @@ -3,15 +3,12 @@ using SharpEmu.HLE; using System; -using System.Collections.Concurrent; -using System.Reflection; +using System.Diagnostics.CodeAnalysis; namespace SharpEmu.Libs.Kernel; internal static class KernelVirtualRangeAllocator { - private static readonly ConcurrentDictionary _accessors = new(); - public static bool TryReserve( CpuContext ctx, ulong desiredAddress, @@ -31,38 +28,24 @@ internal static class KernelVirtualRangeAllocator try { - if (!TryResolveAccessor(ctx.Memory, out var target, out var accessor)) + if (!TryResolveAddressSpace(ctx.Memory, out var addressSpace)) { Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt missing on {ctx.Memory.GetType().FullName}"); return false; } - if (allowSearch && accessor.AllocateAtOrAbove is not null) + if (allowSearch && + addressSpace.TryAllocateAtOrAbove(desiredAddress, length, executable, alignment, out var searchedAddress) && + searchedAddress != 0) { - var searchArgs = new object[] { desiredAddress, length, executable, alignment, 0UL }; - var searchResult = accessor.AllocateAtOrAbove.Invoke(target, searchArgs); - if (searchResult is bool trueValue && trueValue && - searchArgs[4] is ulong searchedAddress && searchedAddress != 0) - { - mappedAddress = searchedAddress; - return true; - } + mappedAddress = searchedAddress; + return true; } - if (accessor.AllocateAt is null) + var allocated = addressSpace.AllocateAt(desiredAddress, length, executable, allowAllocateAtAlternative); + if (allocated == 0) { - Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt missing on {target.GetType().FullName}"); - return false; - } - - var invokeArgs = accessor.AllocateAtHasAllowAlternativeArg - ? new object[] { desiredAddress, length, executable, allowAllocateAtAlternative } - : new object[] { desiredAddress, length, executable }; - var result = accessor.AllocateAt.Invoke(target, invokeArgs); - if (result is not ulong allocated || allocated == 0) - { - var resultType = result?.GetType().FullName ?? "null"; - Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt returned {resultType} value={result ?? "null"}"); + Console.Error.WriteLine($"[LOADER][TRACE] {traceName}: AllocateAt returned {typeof(ulong).FullName} value=0"); return false; } @@ -76,84 +59,36 @@ internal static class KernelVirtualRangeAllocator } } - private static bool TryResolveAccessor(object rootMemory, out object target, out Accessor accessor) + /// + /// Finds the behind , + /// unwrapping decorators (bounded, like the reflection walker this replaced). + /// + public static bool TryResolveAddressSpace(ICpuMemory rootMemory, [NotNullWhen(true)] out IGuestAddressSpace? addressSpace) { - target = rootMemory; - accessor = default; - + var target = rootMemory; for (var depth = 0; depth < 4; depth++) { - accessor = _accessors.GetOrAdd(target.GetType(), DiscoverAccessor); - if (accessor.AllocateAt is not null || accessor.AllocateAtOrAbove is not null) + if (target is IGuestAddressSpace resolved) { + addressSpace = resolved; return true; } - if (accessor.InnerProperty is null) + if (target is not ICpuMemoryWrapper wrapper) { break; } - var innerValue = accessor.InnerProperty.GetValue(target); - if (innerValue is null || ReferenceEquals(innerValue, target)) + var inner = wrapper.Inner; + if (inner is null || ReferenceEquals(inner, target)) { break; } - target = innerValue; + target = inner; } + addressSpace = null; return false; } - - private static Accessor DiscoverAccessor(Type type) - { - MethodInfo? allocateAt = null; - MethodInfo? allocateAtOrAbove = null; - var allocateAtHasAllowAlternativeArg = false; - - foreach (var candidate in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) - { - var parameters = candidate.GetParameters(); - if (string.Equals(candidate.Name, "TryAllocateAtOrAbove", StringComparison.Ordinal) && - parameters.Length == 5 && - parameters[0].ParameterType == typeof(ulong) && - parameters[1].ParameterType == typeof(ulong) && - parameters[2].ParameterType == typeof(bool) && - parameters[3].ParameterType == typeof(ulong) && - parameters[4].ParameterType == typeof(ulong).MakeByRefType()) - { - allocateAtOrAbove = candidate; - } - else if (string.Equals(candidate.Name, "AllocateAt", StringComparison.Ordinal)) - { - if (parameters.Length == 3 && - parameters[0].ParameterType == typeof(ulong) && - parameters[1].ParameterType == typeof(ulong) && - parameters[2].ParameterType == typeof(bool)) - { - allocateAt = candidate; - allocateAtHasAllowAlternativeArg = false; - } - else if (parameters.Length == 4 && - parameters[0].ParameterType == typeof(ulong) && - parameters[1].ParameterType == typeof(ulong) && - parameters[2].ParameterType == typeof(bool) && - parameters[3].ParameterType == typeof(bool)) - { - allocateAt = candidate; - allocateAtHasAllowAlternativeArg = true; - } - } - } - - var innerProperty = type.GetProperty("Inner", BindingFlags.Public | BindingFlags.Instance); - return new Accessor(allocateAt, allocateAtOrAbove, allocateAtHasAllowAlternativeArg, innerProperty); - } - - private readonly record struct Accessor( - MethodInfo? AllocateAt, - MethodInfo? AllocateAtOrAbove, - bool AllocateAtHasAllowAlternativeArg, - PropertyInfo? InnerProperty); }