From f23161be9ac20a29f6dcfa0aa78398aafbfdcfd2 Mon Sep 17 00:00:00 2001 From: Gutemberg Ribeiro Date: Wed, 15 Jul 2026 01:15:36 +0100 Subject: [PATCH] Host platform abstraction layer for the execution engine (#181) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Host] Introduce host platform abstraction with IHostMemory Add SharpEmu.HLE/Host with IHostPlatform/IHostMemory interfaces, neutral page-protection/region enums, and a HostPlatform.Current factory that resolves the Windows backend (or throws PlatformNotSupportedException on other OSes, matching today's de-facto behavior). WindowsHostMemory wraps the exact VirtualAlloc/VirtualFree/VirtualProtect/VirtualQuery calls used across the engine today, with identical MEM_*/PAGE_* constants. Migrate StubManager as the first consumer: its private kernel32 P/Invokes and enums are replaced by IHostMemory calls that issue the same two native operations (RWX commit+reserve of the PLT arena, release on Dispose). No behavior change. This is the first step toward supporting non-Windows hosts; subsequent commits move the remaining direct P/Invokes in Core and Libs behind the same seam. * [Host] Route PhysicalVirtualMemory through IHostMemory Replace the class's private VirtualAlloc/VirtualFree/VirtualProtect/ VirtualQuery P/Invokes with IHostMemory calls. Every site maps 1:1 onto the exact native call it issued before: MEM_COMMIT|MEM_RESERVE -> Allocate, MEM_RESERVE -> Reserve, fault-path commits -> Commit, and MEM_RELEASE -> Free, with identical protection values produced by the Windows backend. IHostMemory gains ProtectRaw so the save/restore protection sequences in TryWriteExclusive and TryTemporarilyProtectForRead round-trip the raw OS protection word (including modifier bits the neutral enum cannot represent) exactly as before. Raw PAGE_* constants remain only for the internal region-classification helpers, which only ever see values this class itself assigned. The exact-address free-on-mismatch, lazy reserve-only threshold, prime loop, and all trace strings are unchanged. * [Host] Add IGuestAddressSpace and retire the reflection-based allocator lookup Introduce IGuestAddressSpace in SharpEmu.HLE (fixed-address AllocateAt / TryAllocateAtOrAbove and guest mprotect via TryProtect) with signatures copied from PhysicalVirtualMemory, which now implements it. TryProtect reproduces the read/write/execute decomposition that KernelMemoryCompatExports.ResolveHostProtection performs, yielding the same PAGE_* values through the Windows backend. KernelVirtualRangeAllocator previously located AllocateAt via cached MethodInfo reflection (because SharpEmu.Libs cannot see Core types) and walked wrapper memories through an untyped 'Inner' property. Both are now typed: ICpuMemoryWrapper exposes the decorated memory (implemented by TrackedCpuMemory, whose Inner property already existed) and the allocator type-tests for IGuestAddressSpace with the same bounded unwrap depth. Failure paths keep the exact [LOADER][TRACE] strings. * [Host] Move Kernel HLE memory exports off direct kernel32 P/Invokes KernelMemoryCompatExports loses its private VirtualQuery/VirtualProtect/ VirtualAlloc/VirtualFree declarations and MemoryBasicInformation struct: - Guest mprotect (sceKernelMprotect/sceKernelMtypeprotect) now routes through IGuestAddressSpace.TryProtect resolved from ctx.Memory. The orbis read/write/execute decomposition moves into a GuestPageProtection conversion whose mapping is value-identical to the removed ResolveHostProtection. - The guarded libc heap and host-page accessibility checks go through IHostMemory (same commit+reserve/protect/free sequence; guard-page and protection-mask checks compare HostRegionInfo.RawProtection against the same PAGE_* literals as before). - HostMemory is exposed as a property so merely loading the type never resolves the platform backend on non-Windows hosts. KernelRuntimeCompatExports' RDTSC stub allocates its 16-byte RWX page via IHostMemory.Allocate; the OperatingSystem.IsWindows() gate returning null is unchanged. * [Host] Abstract thread, TLS, and symbol primitives in the execution backend Add IHostThreading (native TLS slots, current-thread id, affinity, raw thread create/join, diagnostic register capture) and IHostSymbolResolver (enum-keyed host function addresses baked into emitted stubs), with Windows implementations wrapping the exact kernel32 calls the backend made directly before. DirectExecutionBackend takes an optional IHostPlatform (defaulting to HostPlatform.Current) and routes every TlsAlloc/TlsFree/TlsSet/GetValue, GetCurrentThreadId, SetThreadAffinityMask, GetModuleHandle/GetProcAddress and the suspend+GetThreadContext diagnostic snapshot through it. The snapshot moves wholesale into WindowsHostThreading (including the Win64 CONTEXT size/flags/offsets, which are Windows-specific by nature) and returns a neutral HostCapturedRegisters. NativeGuestExecutor resolves WaitForSingleObject/SetEvent/ExitThread via the symbol resolver — the same addresses end up in the emitted run loop, so stub bytes are unchanged — and creates/joins its raw worker thread through IHostThreading with the same stack-reservation semantics. The run-loop emitter itself does not move. Marshal.GetLastWin32Error() in the affinity-failure log still observes SetThreadAffinityMask's error because the wrapper makes no intervening SetLastError call. * [Host] Move fault handling and remaining backend memory ops behind the seam Add IHostFaultHandling (handler-thunk creation, first-chance handler install/remove, unhandled-filter set) with WindowsFaultHandling in a new Cpu/Native/Windows/ folder. The exception-handler trampoline emitter moves there whole — same pre-filtered NTSTATUS codes, same TEB gs:[8]/ gs:[0x10] stack-limit reads, same host-RSP TLS switch — parameterized only by (managed callback, TLS slot, TlsGetValue address), which is exactly what SetupExceptionHandler passed it before. Handler installation order, the AddVectoredExceptionHandler(first=1) flag, the SHARPEMU_DISABLE_RAW_HANDLER gate, and all install/teardown log strings are unchanged. Every remaining VirtualAlloc/VirtualProtect/VirtualFree/VirtualQuery/ FlushInstructionCache in the backend partials routes through IHostMemory with 1:1 call mapping (RWX emit -> RX downgrade -> flush for stub emission, reserve/commit for the PRT aperture and lazy-commit fault path, raw-protection round-trips via ProtectRaw). HostRegionInfo gains RawState/RawAllocationProtection so the lazy-commit trace lines and protection-mask checks keep printing and comparing the exact native values. Windows semantics leaked as bare literals become named constants with identical values: NTSTATUS codes (WindowsFaultCodes) and Win64 CONTEXT byte offsets (Win64ContextOffsets, with the existing CTX_* constants aliased to it and handler-local numeric offsets replaced by the names). * [Host] Resolve the host platform explicitly at the composition root SharpEmuRuntime.CreateDefault() now resolves HostPlatform.Current once and passes it explicitly to PhysicalVirtualMemory and (via a new optional CpuDispatcher parameter) to DirectExecutionBackend, replacing the implicit default-argument fallbacks. On unsupported OSes boot now fails at the root with PlatformNotSupportedException and a clear message instead of on the first native call. A future Linux/macOS backend plugs in by returning a different IHostPlatform here. * [Host] Convert the platform backends to source-generated P/Invokes Replace [DllImport] with [LibraryImport] in the four Windows backend files added by this branch (WindowsHostMemory, WindowsHostThreading, WindowsHostSymbolResolver, WindowsFaultHandling). Marshalling stubs are now generated at compile time instead of JIT-emitted at runtime, which fits the pre-JIT-everything boot model and keeps the backends NativeAOT/trimming ready. Interop stays zero-copy: all signatures are blittable, GetModuleHandleW now pins the managed string via Utf16 marshalling instead of copying, and GetProcAddress names marshal through a stack-allocated Utf8 buffer. Implicit contracts become explicit where LibraryImport requires it: TlsFree/TlsSetValue gain [MarshalAs(UnmanagedType.Bool)] (the 4-byte Win32 BOOL DllImport assumed silently), and GetModuleHandle targets the W entry point directly since LibraryImport never probes suffixes. The CONTEXT snapshot buffer stays a NativeMemory allocation rather than stackalloc: CONTEXT requires 16-byte alignment, now documented at the call site. Native call sequences are unchanged. * [Host] Address Copilot review: harden failure paths, honor injected platform - Free the handler thunk page when the RX protection downgrade fails (the leak predates this branch, but the failure path is boot-fatal so releasing the page is unobservable). - TraceThreadMode and the static diagnostics helpers now resolve host primitives through the backend bound to the current thread, falling back to HostPlatform.Current only when no run is active (identical on supported configs, honors injection everywhere a backend exists). - HostPlatform.Create additionally requires an x64 process so native Windows ARM64 fails with the promised PlatformNotSupportedException instead of emitting x86-64 stubs into an ARM64 process. --- src/SharpEmu.Core/Cpu/CpuDispatcher.cs | 8 +- .../DirectExecutionBackend.Diagnostics.cs | 24 +- .../DirectExecutionBackend.Exceptions.cs | 138 ++--- .../Native/DirectExecutionBackend.Imports.cs | 24 +- .../DirectExecutionBackend.NativeWorker.cs | 62 +- .../Cpu/Native/DirectExecutionBackend.cs | 560 +++++------------- src/SharpEmu.Core/Cpu/Native/StubManager.cs | 37 +- .../Cpu/Native/Windows/Win64ContextOffsets.cs | 32 + .../Cpu/Native/Windows/WindowsFaultCodes.cs | 24 + .../Native/Windows/WindowsFaultHandling.cs | 191 ++++++ src/SharpEmu.Core/Cpu/TrackedCpuMemory.cs | 2 +- .../Memory/PhysicalVirtualMemory.cs | 178 +++--- src/SharpEmu.Core/Runtime/SharpEmuRuntime.cs | 10 +- src/SharpEmu.HLE/GuestPageProtection.cs | 13 + .../Host/HostCapturedRegisters.cs | 18 + src/SharpEmu.HLE/Host/HostPageProtection.cs | 20 + src/SharpEmu.HLE/Host/HostPlatform.cs | 34 ++ src/SharpEmu.HLE/Host/HostRegionInfo.cs | 20 + src/SharpEmu.HLE/Host/HostRegionState.cs | 11 + src/SharpEmu.HLE/Host/HostRuntimeFunction.cs | 21 + src/SharpEmu.HLE/Host/IHostFaultHandling.cs | 32 + src/SharpEmu.HLE/Host/IHostMemory.cs | 48 ++ src/SharpEmu.HLE/Host/IHostPlatform.cs | 19 + src/SharpEmu.HLE/Host/IHostSymbolResolver.cs | 10 + src/SharpEmu.HLE/Host/IHostThreading.cs | 45 ++ .../Host/Windows/WindowsHostMemory.cs | 153 +++++ .../Host/Windows/WindowsHostPlatform.cs | 13 + .../Host/Windows/WindowsHostSymbolResolver.cs | 39 ++ .../Host/Windows/WindowsHostThreading.cs | 165 ++++++ src/SharpEmu.HLE/ICpuMemoryWrapper.cs | 14 + src/SharpEmu.HLE/IGuestAddressSpace.cs | 21 + .../Kernel/KernelMemoryCompatExports.cs | 98 ++- .../Kernel/KernelRuntimeCompatExports.cs | 9 +- .../Kernel/KernelVirtualRangeAllocator.cs | 111 +--- 34 files changed, 1394 insertions(+), 810 deletions(-) create mode 100644 src/SharpEmu.Core/Cpu/Native/Windows/Win64ContextOffsets.cs create mode 100644 src/SharpEmu.Core/Cpu/Native/Windows/WindowsFaultCodes.cs create mode 100644 src/SharpEmu.Core/Cpu/Native/Windows/WindowsFaultHandling.cs create mode 100644 src/SharpEmu.HLE/GuestPageProtection.cs create mode 100644 src/SharpEmu.HLE/Host/HostCapturedRegisters.cs create mode 100644 src/SharpEmu.HLE/Host/HostPageProtection.cs create mode 100644 src/SharpEmu.HLE/Host/HostPlatform.cs create mode 100644 src/SharpEmu.HLE/Host/HostRegionInfo.cs create mode 100644 src/SharpEmu.HLE/Host/HostRegionState.cs create mode 100644 src/SharpEmu.HLE/Host/HostRuntimeFunction.cs create mode 100644 src/SharpEmu.HLE/Host/IHostFaultHandling.cs create mode 100644 src/SharpEmu.HLE/Host/IHostMemory.cs create mode 100644 src/SharpEmu.HLE/Host/IHostPlatform.cs create mode 100644 src/SharpEmu.HLE/Host/IHostSymbolResolver.cs create mode 100644 src/SharpEmu.HLE/Host/IHostThreading.cs create mode 100644 src/SharpEmu.HLE/Host/Windows/WindowsHostMemory.cs create mode 100644 src/SharpEmu.HLE/Host/Windows/WindowsHostPlatform.cs create mode 100644 src/SharpEmu.HLE/Host/Windows/WindowsHostSymbolResolver.cs create mode 100644 src/SharpEmu.HLE/Host/Windows/WindowsHostThreading.cs create mode 100644 src/SharpEmu.HLE/ICpuMemoryWrapper.cs create mode 100644 src/SharpEmu.HLE/IGuestAddressSpace.cs 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); }