mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-25 20:28:48 +08:00
fix(hle): enable GuestImageWriteTracker CPU sync on Windows (#550)
Windows previously hard-disabled the tracker, so CPU-written guest planes never marked dirty and host textures stayed empty. Arm pages with VirtualProtect, handle write AVs in VEH, and warm/test on VirtualAlloc memory so protect cannot poison the CRT heap. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -55,6 +55,7 @@ public sealed partial class DirectExecutionBackend
|
||||
}
|
||||
_exceptionHandler = (nint)AddVectoredExceptionHandler(1u, _exceptionHandlerStub);
|
||||
Console.Error.WriteLine($"[LOADER][INFO] Exception handler installed: 0x{_exceptionHandler:X16}");
|
||||
SharpEmu.HLE.GuestImageWriteTracker.WarmUp();
|
||||
|
||||
_unhandledFilterDelegate = UnhandledExceptionFilter;
|
||||
_unhandledFilterHandle = GCHandle.Alloc(_unhandledFilterDelegate);
|
||||
@@ -117,6 +118,13 @@ public sealed partial class DirectExecutionBackend
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (exceptionCode == 3221225477u &&
|
||||
exceptionRecord->NumberParameters >= 2 &&
|
||||
SharpEmu.HLE.GuestImageWriteTracker.TryHandleWriteFault(
|
||||
exceptionRecord->ExceptionInformation[1]))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (TryRecoverAuxiliaryThreadExecuteFault(exceptionRecord, contextRecord, rip))
|
||||
{
|
||||
return -1;
|
||||
|
||||
@@ -69,6 +69,15 @@ public sealed partial class DirectExecutionBackend
|
||||
|
||||
private unsafe static int RawVectoredHandlerManaged(void* exceptionInfo)
|
||||
{
|
||||
EXCEPTION_RECORD* exceptionRecord = ((EXCEPTION_POINTERS*)exceptionInfo)->ExceptionRecord;
|
||||
if (exceptionRecord->ExceptionCode == 3221225477u &&
|
||||
exceptionRecord->NumberParameters >= 2 &&
|
||||
SharpEmu.HLE.GuestImageWriteTracker.TryHandleWriteFault(
|
||||
exceptionRecord->ExceptionInformation[1]))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return TryRecoverUnresolvedSentinel(exceptionInfo);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -80,7 +81,7 @@ public static unsafe class GuestImageWriteTracker
|
||||
|
||||
private static RangeSnapshot _rangeSnapshot = RangeSnapshot.Empty;
|
||||
|
||||
private static readonly bool _enabled = !OperatingSystem.IsWindows() &&
|
||||
private static readonly bool _enabled =
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_GUEST_IMAGE_CPU_SYNC") != "0";
|
||||
private static readonly (bool Wildcard, ulong[] Addresses) _lifetimeTraceFilter =
|
||||
ParseAddressList(Environment.GetEnvironmentVariable("SHARPEMU_TRACE_GUEST_IMAGE_ADDRS"));
|
||||
@@ -95,12 +96,36 @@ public static unsafe class GuestImageWriteTracker
|
||||
_enabled && _lifetimeTraceEnabled ? GetMonotonicNanoseconds() : 0;
|
||||
private static long _lifetimeTraceSequence;
|
||||
|
||||
private const uint PageReadonly = 0x02;
|
||||
private const uint PageReadWrite = 0x04;
|
||||
|
||||
[DllImport("libc", EntryPoint = "mprotect", SetLastError = true)]
|
||||
private static extern int Mprotect(nint address, nuint length, int protection);
|
||||
|
||||
[DllImport("libc", EntryPoint = "clock_gettime", SetLastError = false)]
|
||||
private static extern int ClockGetTime(int clockId, Timespec* time);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern int 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)]
|
||||
private static extern int VirtualFree(nint lpAddress, nuint dwSize, uint dwFreeType);
|
||||
|
||||
private const uint MemCommit = 0x1000;
|
||||
private const uint MemReserve = 0x2000;
|
||||
private const uint MemRelease = 0x8000;
|
||||
|
||||
public static bool Enabled => _enabled;
|
||||
|
||||
/// <summary>
|
||||
@@ -115,7 +140,17 @@ public static unsafe class GuestImageWriteTracker
|
||||
return;
|
||||
}
|
||||
|
||||
var scratch = NativeMemory.AllocZeroed(4096);
|
||||
// VirtualProtect only belongs on VirtualAlloc/mmap pages. Warming on
|
||||
// CRT heap memory makes neighbouring heap metadata read-only and
|
||||
// crashes the process on Windows.
|
||||
var scratch = OperatingSystem.IsWindows()
|
||||
? VirtualAlloc(0, 4096, MemCommit | MemReserve, PageReadWrite)
|
||||
: (nint)NativeMemory.AllocZeroed(4096);
|
||||
if (scratch == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Warm the timestamp P/Invoke used by the signal-safe scalar
|
||||
@@ -129,7 +164,14 @@ public static unsafe class GuestImageWriteTracker
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeMemory.Free(scratch);
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
_ = VirtualFree(scratch, 0, MemRelease);
|
||||
}
|
||||
else
|
||||
{
|
||||
NativeMemory.Free((void*)scratch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -445,10 +487,7 @@ public static unsafe class GuestImageWriteTracker
|
||||
}
|
||||
|
||||
if (needsUnprotect &&
|
||||
Mprotect(
|
||||
(nint)writableStart,
|
||||
(nuint)(writableEnd - writableStart),
|
||||
ProtRead | ProtWrite) != 0)
|
||||
!TrySetProtection(writableStart, writableEnd - writableStart, writable: true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -497,10 +536,7 @@ public static unsafe class GuestImageWriteTracker
|
||||
|
||||
// A new publication/rearm starts a new first-write lifetime.
|
||||
Volatile.Write(ref range.FirstCpuWriteSeen, 0);
|
||||
var failed = Mprotect(
|
||||
(nint)range.Start,
|
||||
(nuint)(range.End - range.Start),
|
||||
ProtRead) != 0;
|
||||
var failed = !TrySetProtection(range.Start, range.End - range.Start, writable: false);
|
||||
if (failed)
|
||||
{
|
||||
Volatile.Write(ref range.Armed, 0);
|
||||
@@ -520,10 +556,7 @@ public static unsafe class GuestImageWriteTracker
|
||||
var wasArmed = Interlocked.Exchange(ref range.Armed, 0) == 1;
|
||||
if (wasArmed)
|
||||
{
|
||||
_ = Mprotect(
|
||||
(nint)range.Start,
|
||||
(nuint)(range.End - range.Start),
|
||||
ProtRead | ProtWrite);
|
||||
_ = TrySetProtection(range.Start, range.End - range.Start, writable: true);
|
||||
}
|
||||
|
||||
if (range.TraceLifetime)
|
||||
@@ -679,8 +712,35 @@ public static unsafe class GuestImageWriteTracker
|
||||
$"fault=0x{faultAddress:X16} page=0x{faultPage:X16}");
|
||||
}
|
||||
|
||||
private static bool TrySetProtection(ulong start, ulong length, bool writable)
|
||||
{
|
||||
if (length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return VirtualProtect(
|
||||
(nint)start,
|
||||
(nuint)length,
|
||||
writable ? PageReadWrite : PageReadonly,
|
||||
out _) != 0;
|
||||
}
|
||||
|
||||
return Mprotect(
|
||||
(nint)start,
|
||||
(nuint)length,
|
||||
writable ? ProtRead | ProtWrite : ProtRead) == 0;
|
||||
}
|
||||
|
||||
private static long GetMonotonicNanoseconds()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return Stopwatch.GetTimestamp() * 1_000_000_000L / Stopwatch.Frequency;
|
||||
}
|
||||
|
||||
Timespec time;
|
||||
return ClockGetTime(ClockMonotonicRaw, &time) == 0
|
||||
? unchecked((time.Seconds * 1_000_000_000L) + time.Nanoseconds)
|
||||
|
||||
@@ -24,13 +24,53 @@ public sealed unsafe class GuestImageWriteTrackerTests
|
||||
// spilling onto neighbouring heap pages.
|
||||
private const nuint TrackedByteCount = 4096;
|
||||
private const nuint HostPageAlignment = 16384;
|
||||
private const uint MemCommit = 0x1000;
|
||||
private const uint MemReserve = 0x2000;
|
||||
private const uint MemRelease = 0x8000;
|
||||
private const uint PageReadWrite = 0x04;
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern nint VirtualAlloc(
|
||||
nint lpAddress,
|
||||
nuint dwSize,
|
||||
uint flAllocationType,
|
||||
uint flProtect);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern int VirtualFree(nint lpAddress, nuint dwSize, uint dwFreeType);
|
||||
|
||||
private static ulong AllocateTrackedPages(out void* allocation)
|
||||
{
|
||||
// VirtualProtect (Windows) / mprotect (POSIX) must target
|
||||
// VirtualAlloc/mmap pages. Protecting CRT heap pages poisons
|
||||
// neighbouring allocator metadata and crashes the test host.
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
var windowsAllocation = VirtualAlloc(
|
||||
0,
|
||||
HostPageAlignment,
|
||||
MemCommit | MemReserve,
|
||||
PageReadWrite);
|
||||
Assert.NotEqual(nint.Zero, windowsAllocation);
|
||||
allocation = (void*)windowsAllocation;
|
||||
return (ulong)windowsAllocation;
|
||||
}
|
||||
|
||||
allocation = NativeMemory.AlignedAlloc(2 * HostPageAlignment, HostPageAlignment);
|
||||
return (ulong)allocation;
|
||||
}
|
||||
|
||||
private static void FreeTrackedPages(void* allocation)
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
_ = VirtualFree((nint)allocation, 0, MemRelease);
|
||||
return;
|
||||
}
|
||||
|
||||
NativeMemory.Free(allocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenerationSurvivesDirtyConsume()
|
||||
{
|
||||
@@ -58,7 +98,7 @@ public sealed unsafe class GuestImageWriteTrackerTests
|
||||
finally
|
||||
{
|
||||
GuestImageWriteTracker.Untrack(address);
|
||||
NativeMemory.Free(allocation);
|
||||
FreeTrackedPages(allocation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +129,7 @@ public sealed unsafe class GuestImageWriteTrackerTests
|
||||
finally
|
||||
{
|
||||
GuestImageWriteTracker.Untrack(address);
|
||||
NativeMemory.Free(allocation);
|
||||
FreeTrackedPages(allocation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +158,7 @@ public sealed unsafe class GuestImageWriteTrackerTests
|
||||
finally
|
||||
{
|
||||
GuestImageWriteTracker.Untrack(address);
|
||||
NativeMemory.Free(allocation);
|
||||
FreeTrackedPages(allocation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user