mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 06:59:45 +08:00
Host platform abstraction layer for the execution engine (#181)
* [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.
This commit is contained in:
committed by
GitHub
parent
081760be3f
commit
f23161be9a
@@ -0,0 +1,18 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.HLE.Host;
|
||||
|
||||
/// <summary>
|
||||
/// General-purpose register snapshot of a suspended thread, produced by
|
||||
/// <see cref="IHostThreading.TryCaptureThreadRegisters"/>. Registers are named
|
||||
/// after the guest ISA (x86-64), which every supported host executes natively.
|
||||
/// </summary>
|
||||
public readonly record struct HostCapturedRegisters(
|
||||
ulong Rip,
|
||||
ulong Rsp,
|
||||
ulong Rbp,
|
||||
ulong Rax,
|
||||
ulong Rbx,
|
||||
ulong Rcx,
|
||||
ulong Rdx);
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.HLE.Host;
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
public enum HostPageProtection
|
||||
{
|
||||
NoAccess,
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
Execute,
|
||||
ReadExecute,
|
||||
ReadWriteExecute,
|
||||
ExecuteWriteCopy,
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Process-wide access point for the host platform backend. Static HLE export
|
||||
/// classes (which cannot receive constructor injection) resolve host primitives
|
||||
/// through <see cref="Current"/>; injectable components should instead accept an
|
||||
/// <see cref="IHostPlatform"/> and merely default to this.
|
||||
/// </summary>
|
||||
public static class HostPlatform
|
||||
{
|
||||
private static readonly Lazy<IHostPlatform> 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).");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.HLE.Host;
|
||||
|
||||
/// <summary>
|
||||
/// Result of <see cref="IHostMemory.Query"/>. 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;
|
||||
/// <see cref="State"/> and <see cref="Protection"/> are neutral views.
|
||||
/// </summary>
|
||||
public readonly record struct HostRegionInfo(
|
||||
ulong BaseAddress,
|
||||
ulong AllocationBase,
|
||||
ulong RegionSize,
|
||||
HostRegionState State,
|
||||
uint RawState,
|
||||
HostPageProtection Protection,
|
||||
uint RawProtection,
|
||||
uint RawAllocationProtection);
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.HLE.Host;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public enum HostRuntimeFunction
|
||||
{
|
||||
TlsGetValue,
|
||||
QueryPerformanceCounter,
|
||||
SwitchToThread,
|
||||
Sleep,
|
||||
WaitForSingleObject,
|
||||
SetEvent,
|
||||
ExitThread,
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.HLE.Host;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public interface IHostFaultHandling
|
||||
{
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// <paramref name="hostRspSwitchTlsSlot"/> before the call. Returns 0 on failure.
|
||||
/// </summary>
|
||||
nint CreateHandlerThunk(nint managedCallback, uint hostRspSwitchTlsSlot, nint tlsGetValueAddress);
|
||||
|
||||
void FreeThunk(nint thunk);
|
||||
|
||||
/// <summary>Installs a first-chance handler ahead of existing ones; returns a removal handle (0 on failure).</summary>
|
||||
nint AddFirstChanceHandler(nint thunk);
|
||||
|
||||
void RemoveHandler(nint handle);
|
||||
|
||||
/// <summary>Installs the last-resort filter; pass 0 to clear.</summary>
|
||||
void SetUnhandledFilter(nint thunk);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.HLE.Host;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public interface IHostMemory
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserves and commits pages in one step. <paramref name="desiredAddress"/> 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.
|
||||
/// </summary>
|
||||
ulong Allocate(ulong desiredAddress, ulong size, HostPageProtection protection);
|
||||
|
||||
/// <summary>Reserves address space without committing pages (lazy regions).</summary>
|
||||
ulong Reserve(ulong desiredAddress, ulong size, HostPageProtection protection);
|
||||
|
||||
/// <summary>Commits pages inside a previously reserved range (fault-path lazy commit).</summary>
|
||||
bool Commit(ulong address, ulong size, HostPageProtection protection);
|
||||
|
||||
/// <summary>Releases an entire allocation or reservation by its base address.</summary>
|
||||
bool Free(ulong address);
|
||||
|
||||
/// <summary>
|
||||
/// Changes protection on committed pages. <paramref name="rawOldProtection"/> is the
|
||||
/// untranslated previous OS protection value (see <see cref="HostRegionInfo.RawProtection"/>).
|
||||
/// </summary>
|
||||
bool Protect(ulong address, ulong size, HostPageProtection protection, out uint rawOldProtection);
|
||||
|
||||
/// <summary>
|
||||
/// Restores a raw protection value previously returned by <see cref="Protect"/> or
|
||||
/// <see cref="Query"/> 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.
|
||||
/// </summary>
|
||||
bool ProtectRaw(ulong address, ulong size, uint rawProtection, out uint rawOldProtection);
|
||||
|
||||
bool Query(ulong address, out HostRegionInfo info);
|
||||
|
||||
void FlushInstructionCache(ulong address, ulong size);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.HLE.Host;
|
||||
|
||||
/// <summary>
|
||||
/// Aggregates the host-OS primitives the native execution engine depends on.
|
||||
/// Each supported platform provides one implementation; consumers reach the
|
||||
/// process-wide instance through <see cref="HostPlatform.Current"/> or accept
|
||||
/// one by injection.
|
||||
/// </summary>
|
||||
public interface IHostPlatform
|
||||
{
|
||||
IHostMemory Memory { get; }
|
||||
|
||||
IHostThreading Threading { get; }
|
||||
|
||||
IHostSymbolResolver Symbols { get; }
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>Returns the native address of the function, or 0 if unavailable.</summary>
|
||||
nint GetAddress(HostRuntimeFunction function);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.HLE.Host;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public interface IHostThreading
|
||||
{
|
||||
/// <summary>Allocates a native TLS slot; returns <see cref="uint.MaxValue"/> on failure.</summary>
|
||||
uint AllocateTlsSlot();
|
||||
|
||||
bool FreeTlsSlot(uint slot);
|
||||
|
||||
bool SetTlsValue(uint slot, nint value);
|
||||
|
||||
nint GetTlsValue(uint slot);
|
||||
|
||||
uint CurrentThreadId { get; }
|
||||
|
||||
bool TrySetCurrentThreadAffinity(nuint affinityMask);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a raw OS thread executing native code at <paramref name="entry"/> with
|
||||
/// <paramref name="stackReserveBytes"/> of reserved (not committed) stack.
|
||||
/// Returns the thread handle, or 0 on failure.
|
||||
/// </summary>
|
||||
nint CreateNativeThread(nint entry, nint parameter, nuint stackReserveBytes, out uint threadId);
|
||||
|
||||
/// <summary>Waits for the thread to exit; true when it did within the timeout.</summary>
|
||||
bool WaitForThreadExit(nint threadHandle, uint timeoutMilliseconds);
|
||||
|
||||
void CloseThreadHandle(nint threadHandle);
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
bool TryCaptureThreadRegisters(uint threadId, out HostCapturedRegisters registers);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Windows implementation over VirtualAlloc/VirtualFree/VirtualProtect/VirtualQuery.
|
||||
/// Sealed so the JIT can devirtualize interface calls on fault-handling hot paths.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user