diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs index b9034dd..e39b4c5 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Diagnostics.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; @@ -12,10 +13,23 @@ namespace SharpEmu.Core.Cpu.Native; public sealed partial class DirectExecutionBackend { + private static readonly ConcurrentDictionary _knownExecutablePages = new(); - private void RecordRecentImportTrace(string traceLine) + private void RecordRecentImportTrace( + long dispatchIndex, + string nid, + ulong returnRip, + ulong arg0, + ulong arg1, + ulong arg2) { - _recentImportTrace[_recentImportTraceWriteIndex] = traceLine; + _recentImportTrace[_recentImportTraceWriteIndex] = new RecentImportTraceEntry( + dispatchIndex, + nid, + returnRip, + arg0, + arg1, + arg2); _recentImportTraceWriteIndex = (_recentImportTraceWriteIndex + 1) % _recentImportTrace.Length; if (_recentImportTraceCount < _recentImportTrace.Length) { @@ -34,10 +48,12 @@ public sealed partial class DirectExecutionBackend for (int i = 0; i < _recentImportTraceCount; i++) { int num2 = (num + i) % _recentImportTrace.Length; - string text = _recentImportTrace[num2]; - if (!string.IsNullOrEmpty(text)) + var entry = _recentImportTrace[num2]; + if (!string.IsNullOrEmpty(entry.Nid)) { - Console.Error.WriteLine("[LOADER][INFO] " + text); + Console.Error.WriteLine( + $"[LOADER][INFO] #{entry.DispatchIndex} nid={entry.Nid} ret=0x{entry.ReturnRip:X16} " + + $"rdi=0x{entry.Arg0:X16} rsi=0x{entry.Arg1:X16} rdx=0x{entry.Arg2:X16}"); } } } @@ -302,11 +318,24 @@ public sealed partial class DirectExecutionBackend private unsafe static bool IsExecutableAddress(ulong address) { + var pageAddress = address & ~0xFFFUL; + if (_knownExecutablePages.ContainsKey(pageAddress)) + { + return true; + } + if (VirtualQuery((void*)address, out var lpBuffer, (nuint)sizeof(MEMORY_BASIC_INFORMATION64)) == 0) { return false; } - return lpBuffer.State == 4096 && IsExecutableProtection(lpBuffer.Protect); + + var executable = lpBuffer.State == 4096 && IsExecutableProtection(lpBuffer.Protect); + if (executable) + { + _knownExecutablePages.TryAdd(pageAddress, 0); + } + + return executable; } private static ulong AlignUp(ulong value, ulong alignment) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs index 643da44..8b1b2f2 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Exceptions.cs @@ -15,6 +15,9 @@ namespace SharpEmu.Core.Cpu.Native; public sealed partial class DirectExecutionBackend { + private const ulong LazyCommitWindowBytes = 0x0200_0000UL; + private static int _lazyCommitTraceCount; + private unsafe void SetupExceptionHandler() { if (!string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_RAW_HANDLER"), "1", StringComparison.Ordinal)) @@ -911,7 +914,21 @@ public sealed partial class DirectExecutionBackend ulong pageBase = faultAddress & 0xFFFFFFFFFFFFF000uL; uint commitProtect = ResolveLazyCommitProtection(accessType, mbi.AllocationProtect); - Console.Error.WriteLine($"[LOADER][TRACE] lazy-query: 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}"); + 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}"); + } + + if (mbi.State == 4096 && IsAccessCompatible(accessType, mbi.Protect)) + { + if (traceLazyCommit) + { + Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit-race#{traceIndex}: fault=0x{faultAddress:X16} protect=0x{mbi.Protect:X08}"); + } + return true; + } bool committed = false; ulong committedBase = 0; @@ -919,14 +936,25 @@ public sealed partial class DirectExecutionBackend if (mbi.State == 65536) { - ulong largeBase = faultAddress & 0xFFFFFFFFFFE00000uL; - if (TryReserveThenCommit(largeBase, 2097152uL, largeBase, 2097152uL, commitProtect)) + if (TryGetLazyCommitWindow(faultAddress, mbi.BaseAddress, mbi.RegionSize, out var windowBase, out var windowSize) && + TryReserveThenCommit(windowBase, windowSize, windowBase, windowSize, commitProtect)) { committed = true; - committedBase = largeBase; - committedSize = 2097152uL; + committedBase = windowBase; + committedSize = windowSize; } else + { + ulong largeBase = faultAddress & 0xFFFFFFFFFFE00000uL; + if (TryReserveThenCommit(largeBase, 2097152uL, largeBase, 2097152uL, commitProtect)) + { + committed = true; + committedBase = largeBase; + committedSize = 2097152uL; + } + } + + if (!committed) { ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL; if (TryReserveThenCommit(region64kBase, 65536uL, region64kBase, 65536uL, commitProtect)) @@ -949,7 +977,10 @@ public sealed partial class DirectExecutionBackend } TryCommitRange(pageBase + 4096, 4096uL, commitProtect); - Console.Error.WriteLine($"[LOADER][TRACE] lazy-reserve-commit: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}"); + if (traceLazyCommit) + { + Console.Error.WriteLine($"[LOADER][TRACE] lazy-reserve-commit#{traceIndex}: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}"); + } return true; } @@ -958,14 +989,25 @@ public sealed partial class DirectExecutionBackend return false; } - ulong largeCommitBase = faultAddress & 0xFFFFFFFFFFE00000uL; - if (TryCommitRange(largeCommitBase, 2097152uL, commitProtect)) + if (TryGetLazyCommitWindow(faultAddress, mbi.BaseAddress, mbi.RegionSize, out var commitWindowBase, out var commitWindowSize) && + TryCommitRange(commitWindowBase, commitWindowSize, commitProtect)) { committed = true; - committedBase = largeCommitBase; - committedSize = 2097152uL; + committedBase = commitWindowBase; + committedSize = commitWindowSize; } else + { + ulong largeCommitBase = faultAddress & 0xFFFFFFFFFFE00000uL; + if (TryCommitRange(largeCommitBase, 2097152uL, commitProtect)) + { + committed = true; + committedBase = largeCommitBase; + committedSize = 2097152uL; + } + } + + if (!committed) { ulong region64kBase = faultAddress & 0xFFFFFFFFFFFF0000uL; if (TryCommitRange(region64kBase, 65536uL, commitProtect)) @@ -994,9 +1036,46 @@ public sealed partial class DirectExecutionBackend } TryCommitRange(pageBase + 4096, 4096uL, commitProtect); - Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}"); + if (traceLazyCommit) + { + Console.Error.WriteLine($"[LOADER][TRACE] lazy-commit#{traceIndex}: addr=0x{committedBase:X16} size=0x{committedSize:X16} access={accessType} protect=0x{commitProtect:X8}"); + } return true; + static bool TryGetLazyCommitWindow(ulong fault, ulong regionBase, ulong regionSize, out ulong baseAddress, out ulong length) + { + baseAddress = 0; + length = 0; + if (regionSize == 0 || ulong.MaxValue - regionBase < regionSize) + { + return false; + } + + ulong regionEnd = regionBase + regionSize; + ulong windowBase = fault & ~(LazyCommitWindowBytes - 1); + if (windowBase < regionBase) + { + windowBase = regionBase; + } + + if (windowBase >= regionEnd) + { + return false; + } + + ulong windowEnd = Math.Min(regionEnd, windowBase + LazyCommitWindowBytes); + ulong windowSize = windowEnd - windowBase; + windowSize &= 0xFFFFFFFFFFFFF000uL; + if (windowSize == 0) + { + return false; + } + + baseAddress = windowBase; + length = windowSize; + return true; + } + static unsafe bool TryCommitRange(ulong baseAddress, ulong length, uint protection) { if (length == 0) @@ -1023,6 +1102,49 @@ public sealed partial class DirectExecutionBackend } return TryCommitRange(commitAddress, commitSize, protection); } + + static bool IsAccessCompatible(ulong accessType, uint protection) + { + const uint pageNoAccess = 0x01; + const uint pageReadOnly = 0x02; + const uint pageReadWrite = 0x04; + const uint pageWriteCopy = 0x08; + const uint pageExecute = 0x10; + const uint pageExecuteRead = 0x20; + const uint pageExecuteReadWrite = 0x40; + const uint pageExecuteWriteCopy = 0x80; + const uint pageGuard = 0x100; + const uint accessMask = 0xFF; + + if ((protection & pageGuard) != 0) + { + return false; + } + + uint access = protection & accessMask; + if (access == pageNoAccess) + { + return false; + } + + return accessType switch + { + 0 => access is pageReadOnly or pageReadWrite or pageWriteCopy or pageExecuteRead or pageExecuteReadWrite or pageExecuteWriteCopy, + 1 => access is pageReadWrite or pageWriteCopy or pageExecuteReadWrite or pageExecuteWriteCopy, + 8 => access is pageExecute or pageExecuteRead or pageExecuteReadWrite or pageExecuteWriteCopy, + _ => false + }; + } + } + + private static bool ShouldTraceLazyCommit(int traceIndex) + { + if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_LAZY_COMMIT"), "1", StringComparison.Ordinal)) + { + return true; + } + + return traceIndex <= 16 || traceIndex % 256 == 0; } private static uint ResolveLazyCommitProtection(ulong accessType, uint allocationProtect) diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs index 3a63e96..68b238c 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.Imports.cs @@ -14,6 +14,9 @@ namespace SharpEmu.Core.Cpu.Native; public sealed partial class DirectExecutionBackend { + private readonly object _importResultLogSampleGate = new(); + private readonly Dictionary _importResultLogSamples = new(StringComparer.Ordinal); + private static ulong ImportDispatchGatewayManaged(nint backendHandle, int importIndex, nint argPackPtr) { try @@ -73,8 +76,11 @@ public sealed partial class DirectExecutionBackend private unsafe ulong DispatchImport(int importIndex, nint argPackPtr) { - long num = Interlocked.Increment(ref _importDispatchCount); - MarkExecutionProgress(); + long num = NextImportDispatchIndex(); + if ((num & 0x3F) == 0) + { + MarkExecutionProgress(); + } var cpuContext = ActiveCpuContext; if (cpuContext == null) { @@ -93,6 +99,12 @@ public sealed partial class DirectExecutionBackend Console.Error.WriteLine($"[LOADER][TRACE] Raw sentinel recoveries: {num2} (last import index={importIndex})"); _lastReportedRawSentinelRecoveries = num2; } + if (IsLeafImport(importStubEntry.Nid) && + TryDispatchLeafImport(cpuContext, importStubEntry, argPackPtr, num, out var leafResult)) + { + return leafResult; + } + cpuContext.Rip = importStubEntry.Address; cpuContext[CpuRegister.Rdi] = *(ulong*)argPackPtr; cpuContext[CpuRegister.Rsi] = *(ulong*)(argPackPtr + 8); @@ -106,6 +118,10 @@ public sealed partial class DirectExecutionBackend cpuContext[CpuRegister.R13] = *(ulong*)(argPackPtr + 72); cpuContext[CpuRegister.R14] = *(ulong*)(argPackPtr + 80); cpuContext[CpuRegister.R15] = *(ulong*)(argPackPtr + 88); + cpuContext.SetXmmRegister( + 0, + *(ulong*)(argPackPtr - 16), + *(ulong*)(argPackPtr - 8)); cpuContext[CpuRegister.Rsp] = (ulong)argPackPtr + 96uL; ulong value = cpuContext[CpuRegister.Rdi]; ulong value2 = cpuContext[CpuRegister.Rsi]; @@ -120,6 +136,7 @@ public sealed partial class DirectExecutionBackend ulong value7 = cpuContext[CpuRegister.R14]; ulong value8 = cpuContext[CpuRegister.R15]; ulong num7 = *(ulong*)(argPackPtr + 96); + var isGuestWorker = GuestThreadExecution.IsGuestThread; if (!IsLikelyReturnAddress(num7)) { for (int i = 1; i <= 4; i++) @@ -134,19 +151,29 @@ public sealed partial class DirectExecutionBackend } } } - TrackDistinctImportNid(importStubEntry.Nid); - var probeImportReturn = Environment.GetEnvironmentVariable("SHARPEMU_PROBE_IMPORT_RET"); - if (!string.IsNullOrWhiteSpace(probeImportReturn) && - (string.Equals(probeImportReturn, "*", StringComparison.Ordinal) || - string.Equals(probeImportReturn, importStubEntry.Nid, StringComparison.Ordinal))) + if (_activeGuestThreadState is { } activeGuestThreadState) + { + Interlocked.Increment(ref activeGuestThreadState.ImportCount); + Volatile.Write(ref activeGuestThreadState.LastImportNid, importStubEntry.Nid); + Volatile.Write(ref activeGuestThreadState.LastReturnRip, num7); + } + if (_logStrlenBursts) + { + TrackDistinctImportNid(importStubEntry.Nid); + TrackStrlenPrelude(importStubEntry.Nid, num, num7); + } + if (!string.IsNullOrWhiteSpace(_probeImportReturn) && + (string.Equals(_probeImportReturn, "*", StringComparison.Ordinal) || + string.Equals(_probeImportReturn, importStubEntry.Nid, StringComparison.Ordinal))) { ProbeReturnRip(num7, num); } - TrackStrlenPrelude(importStubEntry.Nid, num, num7); - TraceGuestContext( - $"import dispatch={num} nid={importStubEntry.Nid} ret=0x{num7:X16} managed={Environment.CurrentManagedThreadId} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} fiber=0x{GuestThreadExecution.CurrentFiberAddress:X16} active={HasActiveExecutionThread}"); - bool logBootstrap = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_BOOTSTRAP"), "1", StringComparison.Ordinal); - if (logBootstrap && string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) + if (_logGuestContext) + { + TraceGuestContext( + $"import dispatch={num} nid={importStubEntry.Nid} ret=0x{num7:X16} managed={Environment.CurrentManagedThreadId} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} fiber=0x{GuestThreadExecution.CurrentFiberAddress:X16} active={HasActiveExecutionThread}"); + } + if (_logBootstrap && string.Equals(importStubEntry.Nid, RuntimeStubNids.BootstrapBridge, StringComparison.Ordinal)) { string symbolText = ""; if (TryReadAsciiZ(value2, 256, out var sym)) @@ -156,7 +183,10 @@ public sealed partial class DirectExecutionBackend Console.Error.WriteLine( $"[LOADER][TRACE] bootstrap_call#{num}: op=0x{value:X16} sym_ptr=0x{value2:X16} sym='{symbolText}' out_ptr=0x{num3:X16} ret=0x{num7:X16}"); } - if (!ActiveForcedGuestExit && ShouldForceGuestExitOnImportLoop(importStubEntry.Nid, num7, num, value, value2) && TryForceGuestExitToHostStub(argPackPtr, num, num7, importStubEntry.Nid)) + if (!isGuestWorker && + !ActiveForcedGuestExit && + ShouldForceGuestExitOnImportLoop(importStubEntry.Nid, num7, num, value, value2) && + TryForceGuestExitToHostStub(argPackPtr, num, num7, importStubEntry.Nid)) { cpuContext[CpuRegister.Rax] = 1uL; return 1uL; @@ -165,27 +195,33 @@ public sealed partial class DirectExecutionBackend bool flag = num7 >= 2156221920u && num7 <= 2156225024u; bool flag2 = num7 >= 2156351360u && num7 <= 2156352080u; bool flag3 = num >= 1020 && num <= 1040; - bool logAllImports = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_ALL_IMPORTS"), "1", StringComparison.Ordinal); - string importFilter = Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_FILTER"); - bool flag4 = !string.IsNullOrWhiteSpace(importFilter); + bool flag4 = !string.IsNullOrWhiteSpace(_importFilter); bool flag5 = false; - ExportedFunction matchedExport = null; - if (_moduleManager.TryGetExport(importStubEntry.Nid, out ExportedFunction export)) + ExportedFunction? matchedExport = importStubEntry.Export; + bool periodicTrace = num <= 128 || + (num >= 240 && num <= 400) || + (num >= 900 && num <= 1300) || + num % 100000 == 0L || + (importStubEntry.Nid == "tsvEmnenz48" && (num <= 256 || num % 1000 == 0L)) || + (importStubEntry.Nid == "rTXw65xmLIA" && (num <= 256 || num % 128 == 0)) || + flag || + flag2 || + flag3; + if (matchedExport is not null) { - matchedExport = export; if (flag4) { - flag5 = export.LibraryName.Contains(importFilter, StringComparison.OrdinalIgnoreCase) - || export.Name.Contains(importFilter, StringComparison.OrdinalIgnoreCase) - || importStubEntry.Nid.Contains(importFilter, StringComparison.OrdinalIgnoreCase); + flag5 = matchedExport.LibraryName.Contains(_importFilter!, StringComparison.OrdinalIgnoreCase) + || matchedExport.Name.Contains(_importFilter!, StringComparison.OrdinalIgnoreCase) + || importStubEntry.Nid.Contains(_importFilter!, StringComparison.OrdinalIgnoreCase); } } else if (flag4) { - flag5 = importStubEntry.Nid.Contains(importFilter, StringComparison.OrdinalIgnoreCase); + flag5 = importStubEntry.Nid.Contains(_importFilter!, StringComparison.OrdinalIgnoreCase); } - bool flag6 = logAllImports || flag5; - if (!flag0 && (flag6 || num <= 128 || (num >= 240 && num <= 400) || (num >= 900 && num <= 1300) || num % 100000 == 0L || (importStubEntry.Nid == "tsvEmnenz48" && (num <= 256 || num % 1000 == 0L)) || (importStubEntry.Nid == "rTXw65xmLIA" && (num <= 256 || num % 128 == 0)) || flag || flag2 || flag3)) + bool flag6 = _logAllImports || flag5; + if (!flag0 && (flag6 || periodicTrace)) { if (matchedExport != null) { @@ -218,9 +254,15 @@ public sealed partial class DirectExecutionBackend Console.Error.Flush(); } } - if (!flag0) + if (!flag0 && !isGuestWorker) { - RecordRecentImportTrace($"#{num} nid={importStubEntry.Nid} ret=0x{num7:X16} rdi=0x{cpuContext[CpuRegister.Rdi]:X16} rsi=0x{cpuContext[CpuRegister.Rsi]:X16} rdx=0x{cpuContext[CpuRegister.Rdx]:X16}"); + RecordRecentImportTrace( + num, + importStubEntry.Nid, + num7, + cpuContext[CpuRegister.Rdi], + cpuContext[CpuRegister.Rsi], + cpuContext[CpuRegister.Rdx]); } if (importStubEntry.Nid == "8zTFvBIAIN8" && num <= 256) { @@ -247,11 +289,11 @@ public sealed partial class DirectExecutionBackend Console.Error.WriteLine($"[LOADER][TRACE] ImportStack#{num}: rsp=0x{num9:X16} [0]=0x{value9:X16} [8]=0x{value10:X16} [10]=0x{value11:X16} [18]=0x{value12:X16} [20]=0x{value13:X16} [28]=0x{value14:X16} [30]=0x{value15:X16} [38]=0x{value16:X16} [40]=0x{value17:X16}"); } } - if (flag6 && string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_FRAMES"), "1", StringComparison.Ordinal)) + if (flag6 && _logImportFrames) { TraceImportFrameChain(cpuContext, num); } - if (flag6 && string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_RECENT"), "1", StringComparison.Ordinal)) + if (flag6 && _logImportRecent) { DumpRecentImportTrace(); } @@ -262,7 +304,7 @@ public sealed partial class DirectExecutionBackend } if (importStubEntry.Nid == "Ou3iL1abvng") { - if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_STACK_CHK"), "1", StringComparison.Ordinal)) + if (_logStackCheck) { var savedGuardAddress = value4 >= 0x10 ? value4 - 0x10 : 0; var guardKnown = TryReadUInt64Compat(value3, out var guardValue); @@ -300,9 +342,22 @@ public sealed partial class DirectExecutionBackend { orbisGen2Result = DispatchKernelDynlibDlsym(); } + else if (importStubEntry.Export is { } cachedExport && + (cachedExport.Target & cpuContext.TargetGeneration) != 0) + { + cpuContext.ClearRaxWriteFlag(); + var returnValue = cachedExport.Function(cpuContext); + if (!cpuContext.WasRaxWritten) + { + cpuContext[CpuRegister.Rax] = unchecked((ulong)returnValue); + } + orbisGen2Result = (OrbisGen2Result)returnValue; + } else { - dispatchResolved = _moduleManager.TryDispatch(importStubEntry.Nid, cpuContext, out orbisGen2Result); + dispatchResolved = false; + orbisGen2Result = OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND; + cpuContext[CpuRegister.Rax] = unchecked((ulong)(int)orbisGen2Result); } } finally @@ -318,7 +373,9 @@ public sealed partial class DirectExecutionBackend if (!dispatchResolved) { LastError = "Missing HLE export for NID: " + importStubEntry.Nid; - Console.Error.WriteLine($"[LOADER][WARN] Import#{num} unresolved: nid={importStubEntry.Nid} ret=0x{num7:X16}"); + Console.Error.WriteLine( + $"[LOADER][WARN] Import#{num} unresolved: nid={importStubEntry.Nid} ret=0x{num7:X16} " + + $"rdi=0x{value:X16} rsi=0x{value2:X16} rdx=0x{num3:X16} rcx=0x{num4:X16} r8=0x{num5:X16} r9=0x{num6:X16}"); if (importStubEntry.Nid == "L-Q3LEjIbgA") { string value18 = string.Join(" ", importStubEntry.Nid.Select(delegate (char c) @@ -337,9 +394,12 @@ public sealed partial class DirectExecutionBackend } else if (orbisGen2Result != OrbisGen2Result.ORBIS_GEN2_OK) { - Console.Error.WriteLine( - $"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid}) " + - $"rdi=0x{value:X16} rsi=0x{value2:X16} rdx=0x{num3:X16} rcx=0x{num4:X16} ret=0x{num7:X16}"); + if (ShouldLogImportResult(importStubEntry.Nid, orbisGen2Result)) + { + Console.Error.WriteLine( + $"[LOADER][WARN] Import#{num} result: {orbisGen2Result} ({importStubEntry.Nid}) " + + $"rdi=0x{value:X16} rsi=0x{value2:X16} rdx=0x{num3:X16} rcx=0x{num4:X16} ret=0x{num7:X16}"); + } } cpuContext[CpuRegister.Rbx] = value3; cpuContext[CpuRegister.Rbp] = value4; @@ -349,11 +409,36 @@ public sealed partial class DirectExecutionBackend cpuContext[CpuRegister.R15] = value8; cpuContext[CpuRegister.Rdi] = value; cpuContext[CpuRegister.Rsi] = value2; - if (GuestThreadExecution.TryConsumeCurrentEntryExit(out var exitStatus, out var exitReason)) + if (GuestThreadExecution.TryConsumeCurrentContextTransfer(out var transferTarget)) { - if (TryCompleteGuestEntryToHostStub(argPackPtr, num, num7, importStubEntry.Nid, exitReason, exitStatus)) + if (!TryPrepareGuestContextTransfer( + transferTarget, + out var transferFrame, + out var transferStub, + out var transferError)) { - cpuContext[CpuRegister.Rax] = unchecked((ulong)exitStatus); + LastError = transferError ?? "failed to prepare guest context transfer"; + ActiveForcedGuestExit = true; + cpuContext[CpuRegister.Rax] = 18446744071562199298uL; + return cpuContext[CpuRegister.Rax]; + } + + *(ulong*)(argPackPtr + 96) = unchecked((ulong)transferStub); + if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_FIBER"), "1", StringComparison.Ordinal)) + { + Console.Error.WriteLine( + $"[LOADER][TRACE] fiber.context-transfer rip=0x{transferTarget.Rip:X16} " + + $"rsp=0x{transferTarget.Rsp:X16} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} " + + $"fiber=0x{GuestThreadExecution.CurrentFiberAddress:X16}"); + } + + return unchecked((ulong)transferFrame); + } + if (GuestThreadExecution.TryConsumeCurrentEntryExit(out var exitValue, out var exitReason)) + { + if (TryCompleteGuestEntryToHostStub(argPackPtr, num, num7, importStubEntry.Nid, exitReason, exitValue)) + { + cpuContext[CpuRegister.Rax] = exitValue; } else { @@ -361,9 +446,27 @@ public sealed partial class DirectExecutionBackend cpuContext[CpuRegister.Rax] = 18446744071562199298uL; } } - if (GuestThreadExecution.TryConsumeCurrentThreadBlock(out var blockReason) && + if (GuestThreadExecution.TryConsumeCurrentThreadBlock( + out var blockReason, + out var blockContinuation, + out var hasBlockContinuation, + out var blockWakeKey, + out var blockResumeHandler, + out var blockWakeHandler, + out var blockDeadlineTimestamp) && TryYieldGuestThreadToHostStub(argPackPtr, num, num7, importStubEntry.Nid, blockReason)) { + if (hasBlockContinuation) + { + RegisterBlockedGuestThreadContinuation( + GuestThreadExecution.CurrentGuestThreadHandle, + blockContinuation, + blockWakeKey, + blockResumeHandler, + blockWakeHandler, + blockDeadlineTimestamp); + } + cpuContext[CpuRegister.Rax] = 0uL; } if (flag || flag2 || flag3) @@ -384,6 +487,221 @@ public sealed partial class DirectExecutionBackend } } + private unsafe bool TryDispatchLeafImport( + CpuContext cpuContext, + ImportStubEntry importStubEntry, + nint argPackPtr, + long dispatchIndex, + out ulong result) + { + result = 0; + if (importStubEntry.Export is not { } export || + (export.Target & cpuContext.TargetGeneration) == 0) + { + return false; + } + + var arg0 = *(ulong*)argPackPtr; + var returnRip = *(ulong*)(argPackPtr + 96); + cpuContext.Rip = importStubEntry.Address; + cpuContext[CpuRegister.Rdi] = arg0; + cpuContext[CpuRegister.Rsi] = *(ulong*)(argPackPtr + 8); + cpuContext[CpuRegister.Rdx] = *(ulong*)(argPackPtr + 16); + cpuContext[CpuRegister.Rcx] = *(ulong*)(argPackPtr + 24); + cpuContext[CpuRegister.R8] = *(ulong*)(argPackPtr + 32); + cpuContext[CpuRegister.R9] = *(ulong*)(argPackPtr + 40); + cpuContext[CpuRegister.Rbx] = *(ulong*)(argPackPtr + 48); + cpuContext[CpuRegister.Rbp] = *(ulong*)(argPackPtr + 56); + cpuContext[CpuRegister.R12] = *(ulong*)(argPackPtr + 64); + cpuContext[CpuRegister.R13] = *(ulong*)(argPackPtr + 72); + cpuContext[CpuRegister.R14] = *(ulong*)(argPackPtr + 80); + cpuContext[CpuRegister.R15] = *(ulong*)(argPackPtr + 88); + cpuContext[CpuRegister.Rsp] = (ulong)argPackPtr + 96uL; + + if (_activeGuestThreadState is { } activeGuestThreadState) + { + Interlocked.Increment(ref activeGuestThreadState.ImportCount); + Volatile.Write(ref activeGuestThreadState.LastImportNid, importStubEntry.Nid); + Volatile.Write(ref activeGuestThreadState.LastReturnRip, returnRip); + } + if (dispatchIndex % 100000 == 0) + { + Console.Error.WriteLine( + $"[LOADER][TRACE] Import#{dispatchIndex}: {export.LibraryName}:{export.Name} ({importStubEntry.Nid})"); + } + + var previousImportCallFrame = GuestThreadExecution.EnterImportCallFrame( + returnRip, + (ulong)argPackPtr + 104uL, + ActiveGuestReturnSlotAddress); + int returnValue; + try + { + cpuContext.ClearRaxWriteFlag(); + returnValue = export.Function(cpuContext); + if (!cpuContext.WasRaxWritten) + { + cpuContext[CpuRegister.Rax] = unchecked((ulong)returnValue); + } + } + finally + { + GuestThreadExecution.RestoreImportCallFrame(previousImportCallFrame); + } + + if (returnValue != (int)OrbisGen2Result.ORBIS_GEN2_OK) + { + var returnResult = (OrbisGen2Result)returnValue; + if (ShouldLogImportResult(importStubEntry.Nid, returnResult)) + { + Console.Error.WriteLine( + $"[LOADER][WARN] Import#{dispatchIndex} result: {returnResult} ({importStubEntry.Nid}) " + + $"rdi=0x{arg0:X16} rsi=0x{cpuContext[CpuRegister.Rsi]:X16} " + + $"rdx=0x{cpuContext[CpuRegister.Rdx]:X16} rcx=0x{cpuContext[CpuRegister.Rcx]:X16} " + + $"r8=0x{cpuContext[CpuRegister.R8]:X16} r9=0x{cpuContext[CpuRegister.R9]:X16} " + + $"ret=0x{returnRip:X16}"); + } + } + + if (GuestThreadExecution.TryConsumeCurrentThreadBlock( + out var blockReason, + out var blockContinuation, + out var hasBlockContinuation, + out var blockWakeKey, + out var blockResumeHandler, + out var blockWakeHandler, + out var blockDeadlineTimestamp) && + TryYieldGuestThreadToHostStub(argPackPtr, dispatchIndex, returnRip, importStubEntry.Nid, blockReason)) + { + if (hasBlockContinuation) + { + RegisterBlockedGuestThreadContinuation( + GuestThreadExecution.CurrentGuestThreadHandle, + blockContinuation, + blockWakeKey, + blockResumeHandler, + blockWakeHandler, + blockDeadlineTimestamp); + } + + cpuContext[CpuRegister.Rax] = 0uL; + } + + result = cpuContext[CpuRegister.Rax]; + return true; + } + + private bool ShouldLogImportResult(string nid, OrbisGen2Result result) + { + var expectedFileProbeMiss = + result == OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_FOUND && + IsExpectedFileProbeNotFoundNid(nid); + var expectedTimedWaitTimeout = + string.Equals(nid, "27bAgiJmOh0", StringComparison.Ordinal) && + unchecked((int)result) == 60; + if (!expectedFileProbeMiss && !expectedTimedWaitTimeout) + { + return true; + } + + var key = nid + "\0" + (int)result; + int count; + lock (_importResultLogSampleGate) + { + _importResultLogSamples.TryGetValue(key, out count); + count++; + _importResultLogSamples[key] = count; + } + + return count <= 8 || count % 10000 == 0; + } + + private static bool IsExpectedFileProbeNotFoundNid(string nid) => + nid is + "eV9wAD2riIA" or // sceKernelStat + "1G3lF1Gg1k8" or // sceKernelOpen + "gEpBkcwxUjw"; // sceKernelAprResolveFilepathsToIdsAndFileSizes + + private bool IsLeafImport(string nid) + { + if (nid == "1jfXLRVzisc") + { + return !_logUsleep; + } + + return nid is + "9UK1vLZQft4" or + "tn3VlD0hG60" or + "7H0iTOciTLo" or + "2Z+PpY6CaJg" or + "8aI7R7WaOlc" or + "a8uLzYY--tM" or + "Qs1xtplKo0U" or + "GuchCTefuZw" or + "N-FSPA4S3nI" or + "baQO9ez2gL4" or + "ULvXMDz56po" or + "mQ16-QdKv7k" or + "vWU-odnS+fU" or + "sSAUCCU1dv4" or + "C+IEj+BsAFM" or + "tZDDEo2tE5k" or + "GnxKOHEawhk" or + "H896Pt-yB4I" or + "sJXyWHjP-F8" or + "ASoW5WE-UPo" or + "rqwFKI4PAiM" or + "eE4Szl8sil8" or + "qvMUCyyaCSI" or + "27bAgiJmOh0" or // pthread_cond_timedwait + "j4ViWNHEgww" or // strlen + "5jNubw4vlAA" or // strnlen + "LHMrG7e8G78" or // wcslen + "WkkeywLJcgU" or // wcslen + "Ovb2dSJOAuE" or // strcmp + "aesyjrHVWy4" or // strncmp + "pNtJdE3x49E" or // wcscmp + "fV2xHER+bKE" or // wcscoll + "E8wCoUEbfzk" or // wcsncmp + "Q3VBxCXhUHs" or // memcpy + "+P6FRGH4LfA" or // memmove + "DfivPArhucg" or // memcmp + "ytQULN-nhL4" or // pthread_rwlock_init + "6ULAa0fq4jA" or // scePthreadRwlockInit + "1471ajPzxh0" or // pthread_rwlock_destroy + "BB+kb08Tl9A" or // scePthreadRwlockDestroy + "iGjsr1WAtI0" or // pthread_rwlock_rdlock + "Ox9i0c7L5w0" or // scePthreadRwlockRdlock + "sIlRvQqsN2Y" or // pthread_rwlock_wrlock + "mqdNorrB+gI" or // scePthreadRwlockWrlock + "EgmLo6EWgso" or // pthread_rwlock_unlock + "+L98PIbGttk" or // scePthreadRwlockUnlock + "aI+OeCz8xrQ" or // scePthreadSelf + "EotR8a3ASf4" or // pthread_self + "eoht7mQOCmo" or // scePthreadGetspecific + "0-KXaS70xy4" or // pthread_getspecific + "+BzXYkqYeLE" or // scePthreadSetspecific + "WrOLvHU0yQM" or // pthread_setspecific + "vz+pg2zdopI" or // sceKernelGetEventUserData + "mJ7aghmgvfc" or // sceKernelGetEventId + "23CPPI1tyBY" or // sceKernelGetEventFilter + "kwGyyjohI50"; // sceKernelGetEventData + } + + private long NextImportDispatchIndex() + { + if (!ReferenceEquals(_importCounterOwner, this) || + _nextImportDispatchIndex >= _importDispatchBlockEnd) + { + var blockEnd = Interlocked.Add(ref _importDispatchCount, ImportDispatchBlockSize); + _importCounterOwner = this; + _nextImportDispatchIndex = blockEnd - ImportDispatchBlockSize + 1; + _importDispatchBlockEnd = blockEnd + 1; + } + + return _nextImportDispatchIndex++; + } + private void TraceImportFrameChain(CpuContext context, long dispatchIndex) { var frame = context[CpuRegister.Rbp]; @@ -431,7 +749,7 @@ public sealed partial class DirectExecutionBackend return true; } - private unsafe bool TryCompleteGuestEntryToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason, int status) + private unsafe bool TryCompleteGuestEntryToHostStub(nint argPackPtr, long dispatchIndex, ulong returnRip, string nid, string reason, ulong value) { ulong hostExit = ActiveEntryReturnSentinelRip; if (hostExit < 65536 || !TryPatchActiveGuestReturnSlot(hostExit)) @@ -447,7 +765,7 @@ public sealed partial class DirectExecutionBackend return false; } Console.Error.WriteLine( - $"[LOADER][INFO] Guest entry exit at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} reason={reason} status={status}"); + $"[LOADER][INFO] Guest entry exit at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} reason={reason} value=0x{value:X16}"); return true; } @@ -469,8 +787,11 @@ public sealed partial class DirectExecutionBackend ActiveGuestThreadYieldRequested = true; ActiveGuestThreadYieldReason = string.IsNullOrWhiteSpace(reason) ? nid : reason; - Console.Error.WriteLine( - $"[LOADER][INFO] Guest thread yield at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} reason={ActiveGuestThreadYieldReason}"); + if (_logGuestThreads) + { + Console.Error.WriteLine( + $"[LOADER][INFO] Guest thread yield at import#{dispatchIndex}: nid={nid} ret=0x{returnRip:X16} reason={ActiveGuestThreadYieldReason}"); + } return true; } @@ -488,7 +809,7 @@ public sealed partial class DirectExecutionBackend { return false; } - if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_IMPORT_LOOP_GUARD"), "1", StringComparison.Ordinal)) + if (_disableImportLoopGuard || _importLoopGuardSeconds <= 0) { return false; } @@ -503,6 +824,10 @@ public sealed partial class DirectExecutionBackend _importNidHashCache[nid] = value; } RecordImportLoopSignature(value, returnRip, BuildImportLoopSignature(value, returnRip, arg0, arg1)); + if ((dispatchIndex & 0x3F) != 0) + { + return false; + } if (!HasRepeatingImportLoopPattern()) { if (_importLoopPatternHits > 0) @@ -520,14 +845,13 @@ public sealed partial class DirectExecutionBackend _importLoopPatternStartTimestamp = Stopwatch.GetTimestamp(); } _importLoopPatternHits++; - var guardSeconds = GetImportLoopGuardSeconds(); - if (guardSeconds <= 0 || _importLoopPatternHits < 6) + if (_importLoopPatternHits < 6) { return false; } var elapsedTicks = Stopwatch.GetTimestamp() - _importLoopPatternStartTimestamp; - return elapsedTicks >= (long)(guardSeconds * Stopwatch.Frequency); + return elapsedTicks >= (long)(_importLoopGuardSeconds * Stopwatch.Frequency); } private static bool IsImportLoopGuardBoundary(string nid) => @@ -810,8 +1134,7 @@ public sealed partial class DirectExecutionBackend { return result; } - bool logBootstrap = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_BOOTSTRAP"), "1", StringComparison.Ordinal); - if (logBootstrap) + if (_logBootstrap) { Console.Error.WriteLine( $"[LOADER][TRACE] bootstrap_dispatch: handle=0x{bridgeHandle:X16} symbol='{symbolName}' out=0x{outputAddress:X16} rax=0x{cpuContext[CpuRegister.Rax]:X16}"); diff --git a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs index c6328d5..688cf25 100644 --- a/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs +++ b/src/SharpEmu.Core/Cpu/Native/DirectExecutionBackend.cs @@ -28,13 +28,24 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I public string Nid { get; } - public ImportStubEntry(ulong address, string nid) + public ExportedFunction? Export { get; } + + public ImportStubEntry(ulong address, string nid, ExportedFunction? export) { Address = address; Nid = nid; + Export = export; } } + private readonly record struct RecentImportTraceEntry( + long DispatchIndex, + string Nid, + ulong ReturnRip, + ulong Arg0, + ulong Arg1, + ulong Arg2); + #pragma warning disable CS0649 private struct EXCEPTION_POINTERS { @@ -132,6 +143,12 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private nint _tlsGetValueAddress; + private nint _queryPerformanceCounterAddress; + + private nint _switchToThreadAddress; + + private nint _sleepAddress; + private int _tlsPatchStubOffset; private nint _unresolvedReturnStub; @@ -181,17 +198,41 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I [ThreadStatic] private static string? _activeGuestThreadYieldReason; + [ThreadStatic] + private static GuestThreadState? _activeGuestThreadState; + + [ThreadStatic] + private static DirectExecutionBackend? _importCounterOwner; + + [ThreadStatic] + private static long _nextImportDispatchIndex; + + [ThreadStatic] + private static long _importDispatchBlockEnd; + private ImportStubEntry[] _importEntries = Array.Empty(); private readonly List _importHandlerTrampolines = new List(); + private const int GuestContextTransferFrameQwords = 15; + + private readonly object _guestContextTransferStubGate = new(); + + private readonly ThreadLocal _guestContextTransferFrames = new( + static () => (nint)NativeMemory.AllocZeroed(GuestContextTransferFrameQwords, sizeof(ulong)), + trackAllValues: true); + + private nint _guestContextTransferStub; + private long _importDispatchCount; + private const int ImportDispatchBlockSize = 256; + private KeyValuePair[] _runtimeSymbolsByAddress = Array.Empty>(); private readonly Dictionary _runtimeSymbolsByName = new Dictionary(StringComparer.Ordinal); - private readonly string[] _recentImportTrace = new string[64]; + private readonly RecentImportTraceEntry[] _recentImportTrace = new RecentImportTraceEntry[64]; private int _recentImportTraceCount; @@ -211,7 +252,31 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private bool _logStrlenImports; - private readonly HashSet _fallbackTrapStubs = new HashSet(); + private bool _logStrlenBursts; + + private bool _logGuestContext; + + private bool _logGuestThreads; + + private bool _logUsleep; + + private bool _logBootstrap; + + private bool _logAllImports; + + private bool _logImportFrames; + + private bool _logImportRecent; + + private bool _logStackCheck; + + private string? _probeImportReturn; + + private string? _importFilter; + + private bool _disableImportLoopGuard; + + private int _importLoopGuardSeconds; private readonly HashSet _patchedResolverReturnSites = new HashSet(); @@ -288,8 +353,28 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I public string? BlockReason { get; set; } + public bool HasBlockedContinuation { get; set; } + + public GuestCpuContinuation BlockedContinuation { get; set; } + + public string? BlockWakeKey { get; set; } + + public Func? BlockResumeHandler { get; set; } + + public Func? BlockWakeHandler { get; set; } + + public long BlockDeadlineTimestamp { get; set; } + + public long ImportCount; + + public string? LastImportNid; + + public ulong LastReturnRip; + public Thread? HostThread { get; set; } + public int HostThreadId; + public GuestContinuationRunner? ContinuationRunner { get; set; } } @@ -376,6 +461,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private readonly Queue _readyGuestThreads = new Queue(); + private int _readyGuestThreadCount; + private readonly Dictionary _guestThreads = new Dictionary(); private int _guestThreadPumpDepth; @@ -498,6 +585,26 @@ 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, + ulong Rsp, + ulong Rbp, + ulong Rax, + ulong Rbx, + ulong Rcx, + ulong Rdx); + public string BackendName => "native-backend"; public string? LastError { get; private set; } @@ -634,6 +741,17 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { throw new InvalidOperationException("Failed to resolve kernel32!TlsGetValue"); } + _queryPerformanceCounterAddress = kernel32 != 0 ? GetProcAddress(kernel32, "QueryPerformanceCounter") : 0; + 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; + if (_switchToThreadAddress == 0 || _sleepAddress == 0) + { + throw new InvalidOperationException("Failed to resolve kernel32 thread timing functions"); + } _tlsBaseAddress = (nint)VirtualAlloc(null, 4096u, 12288u, 4u); if (_tlsBaseAddress == 0) { @@ -679,6 +797,23 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _consecutiveStrlenImports = 0; _strlenPreludeLogged = false; _logStrlenImports = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_STRLEN"), "1", StringComparison.Ordinal); + _logStrlenBursts = _logStrlenImports || + string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_STRLEN_BURSTS"), "1", StringComparison.Ordinal); + _logGuestContext = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_CONTEXT"), "1", StringComparison.Ordinal); + _logGuestThreads = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUEST_THREADS"), "1", StringComparison.Ordinal); + _logUsleep = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal); + _logBootstrap = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_BOOTSTRAP"), "1", StringComparison.Ordinal); + _logAllImports = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_ALL_IMPORTS"), "1", StringComparison.Ordinal); + _logImportFrames = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_FRAMES"), "1", StringComparison.Ordinal); + _logImportRecent = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_RECENT"), "1", StringComparison.Ordinal); + _logStackCheck = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_STACK_CHK"), "1", StringComparison.Ordinal); + _probeImportReturn = Environment.GetEnvironmentVariable("SHARPEMU_PROBE_IMPORT_RET"); + _importFilter = Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_FILTER"); + _disableImportLoopGuard = string.Equals( + Environment.GetEnvironmentVariable("SHARPEMU_DISABLE_IMPORT_LOOP_GUARD"), + "1", + StringComparison.Ordinal); + _importLoopGuardSeconds = GetImportLoopGuardSeconds(); _entryReturnSentinelRip = 0uL; _forcedGuestExit = false; _importLoopSignatureCount = 0; @@ -686,6 +821,10 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _importLoopPatternHits = 0; _importLoopPatternStartTimestamp = 0; _importNidHashCache.Clear(); + lock (_importResultLogSampleGate) + { + _importResultLogSamples.Clear(); + } lock (_lazyCommitRangeGate) { _prtLazyCommitRanges.Clear(); @@ -734,19 +873,19 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I Console.Error.WriteLine($"[LOADER][INFO] Setting up {importStubs.Count} import stubs..."); ClearImportHandlerTrampolines(); _importEntries = new ImportStubEntry[importStubs.Count]; - _fallbackTrapStubs.Clear(); HashSet hashSet = new HashSet(importStubs.Keys); int num = 0; int num2 = 0; int num3 = 0; foreach (var (num4, text2) in importStubs) { - _importEntries[num] = new ImportStubEntry(num4, text2); + _ = _moduleManager.TryGetExport(text2, out var resolvedExport); + _importEntries[num] = new ImportStubEntry(num4, text2, resolvedExport); if ((num4 >= 34393242112L && num4 <= 34393242624L) || (num4 >= 34393258496L && num4 <= 34393259008L)) { - if (_moduleManager.TryGetExport(text2, out ExportedFunction export)) + if (resolvedExport is not null) { - Console.Error.WriteLine($"[LOADER][INFO] ImportStubMap: 0x{num4:X16} -> {export.LibraryName}:{export.Name} ({text2})"); + Console.Error.WriteLine($"[LOADER][INFO] ImportStubMap: 0x{num4:X16} -> {resolvedExport.LibraryName}:{resolvedExport.Name} ({text2})"); } else { @@ -771,6 +910,17 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I num++; continue; } + if (TryCreateNativeImportIntrinsic(text2, out var intrinsicAddress)) + { + if (!PatchImportStub((nint)(long)num4, intrinsicAddress)) + { + LastError = $"Failed to patch native intrinsic import stub at 0x{num4:X16}"; + return false; + } + num2++; + num++; + continue; + } nint num5 = CreateImportHandlerTrampoline(num); if (num5 == 0) { @@ -787,14 +937,239 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I num++; } Console.Error.WriteLine($"[LOADER][INFO] Setup {num2}/{importStubs.Count} import stubs (direct bridge, lle_redirects={num3})"); - int num6 = PatchFallbackTrapStubs(hashSet); - if (num6 > 0) - { - Console.Error.WriteLine($"[LOADER][WARNING] Applied {num6} fallback PLT trap stubs in 0x801FF7A00..0x801FF7C00"); - } return num2 == importStubs.Count; } + private unsafe bool TryCreateNativeImportIntrinsic(string nid, out nint address) + { + if (nid == "1jfXLRVzisc" && + string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal)) + { + address = 0; + return false; + } + + ReadOnlySpan code = nid switch + { + "-2IRUCO--PM" => + [ + 0x0F, 0x31, + 0x48, 0xC1, 0xE2, 0x20, + 0x48, 0x09, 0xD0, + 0xC3, + ], + "fgxnMeTNUtY" => + [ + 0x48, 0x83, 0xEC, 0x28, + 0x48, 0x8D, 0x4C, 0x24, 0x20, + 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xD0, + 0x48, 0x8B, 0x44, 0x24, 0x20, + 0x48, 0x83, 0xC4, 0x28, + 0xC3, + ], + "1jfXLRVzisc" => + [ + 0x48, 0x85, 0xFF, + 0x74, 0x1D, + 0x48, 0x81, 0xFF, 0xE8, 0x03, 0x00, 0x00, + 0x73, 0x17, + 0x48, 0x83, 0xEC, 0x28, + 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xD0, + 0x48, 0x83, 0xC4, 0x28, + 0x31, 0xC0, + 0xC3, + 0x48, 0x89, 0xF8, + 0x48, 0x05, 0xE7, 0x03, 0x00, 0x00, + 0x31, 0xD2, + 0xB9, 0xE8, 0x03, 0x00, 0x00, + 0x48, 0xF7, 0xF1, + 0x89, 0xC1, + 0x48, 0x83, 0xEC, 0x28, + 0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xD0, + 0x48, 0x83, 0xC4, 0x28, + 0x31, 0xC0, + 0xC3, + ], + "j4ViWNHEgww" => + [ + 0x31, 0xC0, + 0x48, 0xC7, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF2, 0xAE, + 0x48, 0xF7, 0xD1, + 0x48, 0x8D, 0x41, 0xFF, + 0xC3, + ], + "5jNubw4vlAA" => + [ + 0x31, 0xC0, + 0x48, 0x85, 0xF6, + 0x74, 0x0E, + 0x80, 0x3C, 0x07, 0x00, + 0x74, 0x08, + 0x48, 0xFF, 0xC0, + 0x48, 0x39, 0xF0, + 0x72, 0xF2, + 0xC3, + ], + "LHMrG7e8G78" or "WkkeywLJcgU" => + [ + 0x31, 0xC0, + 0x66, 0x83, 0x3C, 0x47, 0x00, + 0x74, 0x05, + 0x48, 0xFF, 0xC0, + 0xEB, 0xF4, + 0xC3, + ], + "Ovb2dSJOAuE" => + [ + 0x0F, 0xB6, 0x07, + 0x0F, 0xB6, 0x16, + 0x29, 0xD0, + 0x75, 0x0C, + 0x84, 0xD2, + 0x74, 0x08, + 0x48, 0xFF, 0xC7, + 0x48, 0xFF, 0xC6, + 0xEB, 0xEA, + 0xC3, + ], + "aesyjrHVWy4" => + [ + 0x31, 0xC0, + 0x48, 0x85, 0xD2, + 0x74, 0x19, + 0x0F, 0xB6, 0x07, + 0x0F, 0xB6, 0x0E, + 0x29, 0xC8, + 0x75, 0x0F, + 0x84, 0xC9, + 0x74, 0x0B, + 0x48, 0xFF, 0xC7, + 0x48, 0xFF, 0xC6, + 0x48, 0xFF, 0xCA, + 0x75, 0xE7, + 0xC3, + ], + "pNtJdE3x49E" or "fV2xHER+bKE" => + [ + 0x0F, 0xB7, 0x07, + 0x0F, 0xB7, 0x16, + 0x29, 0xD0, + 0x75, 0x0F, + 0x66, 0x85, 0xD2, + 0x74, 0x0A, + 0x48, 0x83, 0xC7, 0x02, + 0x48, 0x83, 0xC6, 0x02, + 0xEB, 0xE7, + 0xC3, + ], + "E8wCoUEbfzk" => + [ + 0x31, 0xC0, + 0x48, 0x85, 0xD2, + 0x74, 0x1C, + 0x0F, 0xB7, 0x07, + 0x0F, 0xB7, 0x0E, + 0x29, 0xC8, + 0x75, 0x12, + 0x66, 0x85, 0xC9, + 0x74, 0x0D, + 0x48, 0x83, 0xC7, 0x02, + 0x48, 0x83, 0xC6, 0x02, + 0x48, 0xFF, 0xCA, + 0x75, 0xE4, + 0xC3, + ], + "kiZSXIWd9vg" => + [ + 0x48, 0x89, 0xF8, + 0x8A, 0x16, + 0x88, 0x17, + 0x48, 0xFF, 0xC6, + 0x48, 0xFF, 0xC7, + 0x84, 0xD2, + 0x75, 0xF2, + 0xC3, + ], + "6sJWiWSRuqk" => + [ + 0x48, 0x89, 0xF8, + 0x48, 0x85, 0xD2, + 0x74, 0x20, + 0x8A, 0x0E, + 0x88, 0x0F, + 0x48, 0xFF, 0xC7, + 0x48, 0xFF, 0xCA, + 0x74, 0x14, + 0x84, 0xC9, + 0x74, 0x05, + 0x48, 0xFF, 0xC6, + 0xEB, 0xEB, + 0xC6, 0x07, 0x00, + 0x48, 0xFF, 0xC7, + 0x48, 0xFF, 0xCA, + 0x75, 0xF5, + 0xC3, + ], + "Q3VBxCXhUHs" => + [ + 0x48, 0x89, 0xF8, + 0x48, 0x89, 0xD1, + 0xF3, 0xA4, + 0xC3, + ], + "8zTFvBIAIN8" => + [ + 0x49, 0x89, 0xF8, + 0x48, 0x89, 0xF0, + 0x48, 0x89, 0xD1, + 0xF3, 0xAA, + 0x4C, 0x89, 0xC0, + 0xC3, + ], + _ => default, + }; + if (code.IsEmpty) + { + address = 0; + return false; + } + + const uint intrinsicAllocationSize = 128u; + void* memory = VirtualAlloc(null, intrinsicAllocationSize, 12288u, 64u); + if (memory == null) + { + address = 0; + return false; + } + + code.CopyTo(new Span(memory, code.Length)); + if (nid == "fgxnMeTNUtY") + { + *(nint*)((byte*)memory + 11) = _queryPerformanceCounterAddress; + } + else if (nid == "1jfXLRVzisc") + { + *(nint*)((byte*)memory + 20) = _switchToThreadAddress; + *(nint*)((byte*)memory + 64) = _sleepAddress; + } + uint oldProtect = 0; + if (!VirtualProtect(memory, intrinsicAllocationSize, 32u, &oldProtect)) + { + VirtualFree(memory, 0u, 32768u); + address = 0; + return false; + } + + FlushInstructionCache(GetCurrentProcess(), memory, (nuint)code.Length); + address = (nint)memory; + _importHandlerTrampolines.Add(address); + return true; + } + private bool TryResolveDirectImportTarget(string nid, out ulong targetAddress, out string resolvedSymbol) { targetAddress = 0uL; @@ -1026,6 +1401,116 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } } + private unsafe bool TryPrepareGuestContextTransfer( + GuestCpuContinuation target, + out nint frameAddress, + out nint transferStub, + out string? error) + { + frameAddress = 0; + transferStub = 0; + error = null; + if (target.Rip < 65536 || target.Rsp == 0) + { + error = $"invalid guest context transfer target rip=0x{target.Rip:X16} rsp=0x{target.Rsp:X16}"; + return false; + } + + transferStub = GetOrCreateGuestContextTransferStub(); + if (transferStub == 0) + { + error = "failed to allocate guest context transfer stub"; + return false; + } + + frameAddress = _guestContextTransferFrames.Value; + if (frameAddress == 0) + { + error = "failed to allocate guest context transfer frame"; + return false; + } + + var frame = (ulong*)frameAddress; + frame[0] = target.Rip; + frame[1] = target.Rsp; + frame[2] = target.Rax; + frame[3] = target.Rcx; + frame[4] = target.Rdx; + frame[5] = target.Rbx; + frame[6] = target.Rbp; + frame[7] = target.Rsi; + frame[8] = target.Rdi; + frame[9] = target.R8; + frame[10] = target.R9; + frame[11] = target.R12; + frame[12] = target.R13; + frame[13] = target.R14; + frame[14] = target.R15; + return true; + } + + private unsafe nint GetOrCreateGuestContextTransferStub() + { + if (Volatile.Read(ref _guestContextTransferStub) != 0) + { + return _guestContextTransferStub; + } + + lock (_guestContextTransferStubGate) + { + if (_guestContextTransferStub != 0) + { + return _guestContextTransferStub; + } + + const uint stubSize = 128; + var code = (byte*)VirtualAlloc(null, stubSize, 12288u, 64u); + if (code == null) + { + return 0; + } + + var offset = 0; + void Emit(byte value) => code[offset++] = value; + void EmitLoadFromR11(int register, byte displacement) + { + Emit((byte)(0x49 | (register >= 8 ? 0x04 : 0x00))); + Emit(0x8B); + Emit((byte)(0x40 | ((register & 7) << 3) | 0x03)); + Emit(displacement); + } + + Emit(0x49); Emit(0x89); Emit(0xC3); // mov r11, rax + EmitLoadFromR11(10, 0); // target RIP + EmitLoadFromR11(4, 8); // rsp + EmitLoadFromR11(1, 24); // rcx + EmitLoadFromR11(2, 32); // rdx + EmitLoadFromR11(3, 40); // rbx + EmitLoadFromR11(5, 48); // rbp + EmitLoadFromR11(6, 56); // rsi + EmitLoadFromR11(7, 64); // rdi + EmitLoadFromR11(8, 72); // r8 + EmitLoadFromR11(9, 80); // r9 + EmitLoadFromR11(12, 88); // r12 + EmitLoadFromR11(13, 96); // r13 + EmitLoadFromR11(14, 104); // r14 + EmitLoadFromR11(15, 112); // r15 + EmitLoadFromR11(0, 16); // rax + Emit(0x41); Emit(0xFF); Emit(0xE2); // jmp r10 + + uint oldProtect = 0; + if (!VirtualProtect(code, stubSize, 32u, &oldProtect)) + { + VirtualFree(code, 0u, 32768u); + return 0; + } + + FlushInstructionCache(GetCurrentProcess(), code, stubSize); + Volatile.Write(ref _guestContextTransferStub, (nint)code); + return (nint)code; + } + } + private unsafe nint CreateImportHandlerTrampoline(int importIndex) { void* ptr = VirtualAlloc(null, 192u, 12288u, 64u); @@ -1056,9 +1541,20 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I ptr2[num++] = 82; ptr2[num++] = 86; ptr2[num++] = 87; - ptr2[num++] = 73; - ptr2[num++] = 137; - ptr2[num++] = 228; + ptr2[num++] = 72; + ptr2[num++] = 131; + ptr2[num++] = 236; + ptr2[num++] = 16; + ptr2[num++] = 243; + ptr2[num++] = 15; + ptr2[num++] = 127; + ptr2[num++] = 4; + ptr2[num++] = 36; + ptr2[num++] = 76; + ptr2[num++] = 141; + ptr2[num++] = 100; + ptr2[num++] = 36; + ptr2[num++] = 16; ptr2[num++] = 72; ptr2[num++] = 131; ptr2[num++] = 236; @@ -1167,45 +1663,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } } - private int PatchFallbackTrapStubs(HashSet mappedImportStubs) - { - int num = 0; - for (ulong num2 = 34393258496uL; num2 <= 34393259008L; num2 += 16) - { - if (!mappedImportStubs.Contains(num2) && PatchFallbackTrapStub(num2)) - { - _fallbackTrapStubs.Add(num2); - num++; - } - } - return num; - } - - private unsafe static bool PatchFallbackTrapStub(ulong address) - { - uint flNewProtect = default(uint); - if (!VirtualProtect((void*)address, 16u, 64u, &flNewProtect)) - { - return false; - } - try - { - byte* ptr = (byte*)address; - *ptr = 204; - ptr[1] = 195; - for (int i = 3; i < 16; i++) - { - ptr[i] = 144; - } - return true; - } - finally - { - VirtualProtect((void*)address, 16u, flNewProtect, &flNewProtect); - FlushInstructionCache(GetCurrentProcess(), (void*)address, 16u); - } - } - private unsafe void ClearImportHandlerTrampolines() { foreach (nint importHandlerTrampoline in _importHandlerTrampolines) @@ -1933,6 +2390,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { _guestThreads[request.ThreadHandle] = thread; _readyGuestThreads.Enqueue(thread); + Interlocked.Increment(ref _readyGuestThreadCount); } Console.Error.WriteLine( $"[LOADER][INFO] Scheduled guest thread '{thread.Name}' handle=0x{thread.ThreadHandle:X16} entry=0x{thread.EntryPoint:X16} arg=0x{thread.Argument:X16}"); @@ -1940,10 +2398,17 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return true; } + public bool SupportsGuestContextTransfer => true; + public void Pump(CpuContext callerContext, string reason) { _ = callerContext; var runSynchronously = string.Equals(reason, "entry_return", StringComparison.Ordinal); + WakeExpiredBlockedGuestThreads(); + if (Volatile.Read(ref _readyGuestThreadCount) == 0) + { + return; + } if (_guestThreadPumpDepth != 0) { return; @@ -1959,6 +2424,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I while (_readyGuestThreads.Count > 0) { var candidate = _readyGuestThreads.Dequeue(); + Interlocked.Decrement(ref _readyGuestThreadCount); if (candidate.State == GuestThreadRunState.Ready) { thread = candidate; @@ -1989,7 +2455,6 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I thread.HostThread = hostThread; } hostThread.Start(); - return; } } finally @@ -1998,8 +2463,141 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } } + public int WakeBlockedThreads(string wakeKey, int maxCount = int.MaxValue) + { + if (string.IsNullOrWhiteSpace(wakeKey) || maxCount <= 0) + { + return 0; + } + + var wakeCount = 0; + lock (_guestThreadGate) + { + foreach (var thread in _guestThreads.Values) + { + if (wakeCount >= maxCount) + { + break; + } + + if (thread.State != GuestThreadRunState.Blocked || + !thread.HasBlockedContinuation || + !string.Equals(wakeKey, thread.BlockWakeKey, StringComparison.Ordinal)) + { + continue; + } + + if (thread.BlockWakeHandler is not null && !thread.BlockWakeHandler()) + { + continue; + } + + thread.State = GuestThreadRunState.Ready; + thread.BlockReason = null; + thread.BlockWakeHandler = null; + thread.BlockDeadlineTimestamp = 0; + _readyGuestThreads.Enqueue(thread); + Interlocked.Increment(ref _readyGuestThreadCount); + wakeCount++; + } + } + + if (wakeCount != 0 && _logGuestThreads) + { + Console.Error.WriteLine($"[LOADER][INFO] guest_threads.wake key={wakeKey} count={wakeCount}"); + } + + return wakeCount; + } + + public IReadOnlyList SnapshotThreads() + { + lock (_guestThreadGate) + { + var snapshots = new GuestThreadSnapshot[_guestThreads.Count]; + var index = 0; + foreach (var thread in _guestThreads.Values) + { + snapshots[index++] = new GuestThreadSnapshot( + thread.ThreadHandle, + thread.Name, + thread.State.ToString(), + Interlocked.Read(ref thread.ImportCount), + Volatile.Read(ref thread.LastImportNid), + Volatile.Read(ref thread.LastReturnRip), + thread.BlockReason); + } + + return snapshots; + } + } + + private void RegisterBlockedGuestThreadContinuation( + ulong guestThreadHandle, + GuestCpuContinuation continuation, + string wakeKey, + Func? resumeHandler, + Func? wakeHandler, + long blockDeadlineTimestamp) + { + if (guestThreadHandle == 0 || continuation.Rip < 65536 || continuation.Rsp == 0) + { + return; + } + + lock (_guestThreadGate) + { + if (!_guestThreads.TryGetValue(guestThreadHandle, out var thread)) + { + return; + } + + thread.BlockedContinuation = continuation; + thread.HasBlockedContinuation = true; + thread.BlockWakeKey = wakeKey; + thread.BlockResumeHandler = resumeHandler; + thread.BlockWakeHandler = wakeHandler; + thread.BlockDeadlineTimestamp = blockDeadlineTimestamp; + } + } + + private int WakeExpiredBlockedGuestThreads() + { + var now = Stopwatch.GetTimestamp(); + var wakeCount = 0; + lock (_guestThreadGate) + { + foreach (var thread in _guestThreads.Values) + { + if (thread.State != GuestThreadRunState.Blocked || + !thread.HasBlockedContinuation || + thread.BlockDeadlineTimestamp == 0 || + thread.BlockDeadlineTimestamp > now) + { + continue; + } + + thread.State = GuestThreadRunState.Ready; + thread.BlockReason = null; + thread.BlockWakeHandler = null; + thread.BlockDeadlineTimestamp = 0; + _readyGuestThreads.Enqueue(thread); + Interlocked.Increment(ref _readyGuestThreadCount); + wakeCount++; + } + } + + if (wakeCount != 0 && _logGuestThreads) + { + Console.Error.WriteLine($"[LOADER][INFO] guest_threads.timeout_wake count={wakeCount}"); + } + + return wakeCount; + } + private void PumpUntilGuestThreadsIdle(CpuContext callerContext, string reason) { + var nextSnapshotTimestamp = Stopwatch.GetTimestamp() + Stopwatch.Frequency; while (!ActiveForcedGuestExit) { Pump(callerContext, reason); @@ -2011,6 +2609,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } var hasReadyThread = false; + var hasRunningThread = false; var hasBlockedThread = false; foreach (var thread in threads) { @@ -2020,7 +2619,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I hasReadyThread = true; break; case GuestThreadRunState.Running: - hasReadyThread = true; + hasRunningThread = true; break; case GuestThreadRunState.Blocked: hasBlockedThread = true; @@ -2033,11 +2632,25 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I continue; } - if (!hasBlockedThread) + if (!hasRunningThread && !hasBlockedThread) { return; } + if (_logGuestThreads && Stopwatch.GetTimestamp() >= nextSnapshotTimestamp) + { + foreach (var thread in threads) + { + Console.Error.WriteLine( + $"[LOADER][TRACE] guest_thread.idle_wait reason={reason} handle=0x{thread.ThreadHandle:X16} " + + $"name='{thread.Name}' state={thread.State} imports={Interlocked.Read(ref thread.ImportCount)} " + + $"nid={Volatile.Read(ref thread.LastImportNid) ?? "none"} ret=0x{Volatile.Read(ref thread.LastReturnRip):X16} " + + $"block={thread.BlockReason ?? "none"}"); + } + + nextSnapshotTimestamp = Stopwatch.GetTimestamp() + Stopwatch.Frequency; + } + Thread.Sleep(1); } } @@ -2178,6 +2791,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I Exception? callbackException = null; var currentGuestThreadHandle = GuestThreadExecution.CurrentGuestThreadHandle; var currentFiberAddress = GuestThreadExecution.CurrentFiberAddress; + var currentGuestThreadState = _activeGuestThreadState; void RunContinuation() { @@ -2191,6 +2805,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I var previousFiberAddress = restoreFiber ? GuestThreadExecution.EnterFiber(currentFiberAddress) : 0UL; + var previousGuestThreadState = _activeGuestThreadState; + _activeGuestThreadState = currentGuestThreadState; var previousLastError = LastError; try { @@ -2213,6 +2829,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I } finally { + _activeGuestThreadState = previousGuestThreadState; TraceGuestContext( $"continuation-exit reason={reason} managed={Environment.CurrentManagedThreadId} guest=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} fiber=0x{GuestThreadExecution.CurrentFiberAddress:X16} exit={exitReason}"); LastError = previousLastError; @@ -2277,9 +2894,9 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I return true; } - private static void TraceGuestContext(string message) + private void TraceGuestContext(string message) { - if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_CONTEXT"), "1", StringComparison.Ordinal)) + if (_logGuestContext) { Console.Error.WriteLine($"[LOADER][TRACE] guest_context.{message}"); } @@ -2319,6 +2936,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I .Cast() .ToArray(); _readyGuestThreads.Clear(); + Interlocked.Exchange(ref _readyGuestThreadCount, 0); _guestThreads.Clear(); } @@ -2513,14 +3131,48 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I private void RunGuestThread(GuestThreadState thread, string reason) { - Console.Error.WriteLine( - $"[LOADER][INFO] Pumping guest thread '{thread.Name}' reason={reason} entry=0x{thread.EntryPoint:X16}"); var previousLastError = LastError; var previousGuestThreadHandle = GuestThreadExecution.EnterGuestThread(thread.ThreadHandle); + var previousGuestThreadState = _activeGuestThreadState; + Volatile.Write(ref thread.HostThreadId, unchecked((int)GetCurrentThreadId())); + _activeGuestThreadState = thread; try { LastError = null; - var exitReason = ExecuteGuestThreadEntry(thread.Context, thread.EntryPoint, thread.Name, out var blockReason); + GuestCpuContinuation continuation = default; + Func? resumeHandler = null; + var resumeContinuation = false; + lock (_guestThreadGate) + { + if (thread.HasBlockedContinuation) + { + continuation = thread.BlockedContinuation; + thread.BlockedContinuation = default; + thread.HasBlockedContinuation = false; + thread.BlockWakeKey = null; + resumeHandler = thread.BlockResumeHandler; + thread.BlockResumeHandler = null; + thread.BlockWakeHandler = null; + thread.BlockDeadlineTimestamp = 0; + resumeContinuation = true; + } + } + + if (resumeHandler is not null) + { + continuation = continuation with { Rax = unchecked((ulong)(long)resumeHandler()) }; + } + + if (_logGuestThreads) + { + Console.Error.WriteLine( + resumeContinuation + ? $"[LOADER][INFO] Pumping guest thread '{thread.Name}' reason={reason} resume=0x{continuation.Rip:X16}" + : $"[LOADER][INFO] Pumping guest thread '{thread.Name}' reason={reason} entry=0x{thread.EntryPoint:X16}"); + } + var exitReason = resumeContinuation + ? ExecuteBlockedGuestThreadContinuation(thread.Context, continuation, thread.Name, out var blockReason) + : ExecuteGuestThreadEntry(thread.Context, thread.EntryPoint, thread.Name, out blockReason); lock (_guestThreadGate) { switch (exitReason) @@ -2538,16 +3190,65 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I break; } } - Console.Error.WriteLine( - $"[LOADER][INFO] Guest thread '{thread.Name}' state={thread.State} reason={blockReason ?? "none"}"); + if (_logGuestThreads) + { + Console.Error.WriteLine( + $"[LOADER][INFO] Guest thread '{thread.Name}' state={thread.State} reason={blockReason ?? "none"}"); + } } finally { + _activeGuestThreadState = previousGuestThreadState; + Volatile.Write(ref thread.HostThreadId, 0); GuestThreadExecution.RestoreGuestThread(previousGuestThreadHandle); LastError = previousLastError; } } + private GuestNativeCallExitReason ExecuteBlockedGuestThreadContinuation( + CpuContext context, + GuestCpuContinuation continuation, + string name, + out string? reason) + { + ApplyGuestContinuation(context, continuation); + return ExecuteGuestContinuationEntry( + context, + continuation.Rip, + continuation.ReturnSlotAddress, + name, + out reason); + } + + private static void ApplyGuestContinuation(CpuContext context, GuestCpuContinuation continuation) + { + context.Rip = continuation.Rip; + context.Rflags = continuation.Rflags == 0 ? 0x202UL : continuation.Rflags; + if (continuation.FsBase != 0) + { + context.FsBase = continuation.FsBase; + } + if (continuation.GsBase != 0) + { + context.GsBase = continuation.GsBase; + } + + context[CpuRegister.Rax] = continuation.Rax; + context[CpuRegister.Rcx] = continuation.Rcx; + context[CpuRegister.Rdx] = continuation.Rdx; + context[CpuRegister.Rbx] = continuation.Rbx; + context[CpuRegister.Rbp] = continuation.Rbp; + context[CpuRegister.Rsi] = continuation.Rsi; + context[CpuRegister.Rdi] = continuation.Rdi; + context[CpuRegister.R8] = continuation.R8; + context[CpuRegister.R9] = continuation.R9; + context[CpuRegister.R12] = continuation.R12; + context[CpuRegister.R13] = continuation.R13; + context[CpuRegister.R14] = continuation.R14; + context[CpuRegister.R15] = continuation.R15; + context[CpuRegister.Rsp] = continuation.Rsp; + } + private unsafe GuestNativeCallExitReason ExecuteGuestThreadEntry(CpuContext context, ulong entryPoint, string name, out string? reason) { reason = null; @@ -3205,6 +3906,19 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { continue; } + if (HasReadyGuestThread()) + { + if (_cpuContext is { } watchdogContext) + { + Pump(watchdogContext, "watchdog"); + } + Console.Error.WriteLine( + $"[LOADER][WARN] No import progress for {stallWatchdogSeconds}s, but a guest thread is ready; continuing."); + LogStallWatchdogSnapshot(); + Console.Error.Flush(); + MarkExecutionProgress(); + continue; + } if (Interlocked.Exchange(ref _stallWatchdogTriggered, 1) != 0) { continue; @@ -3223,6 +3937,23 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I _stallWatchdogThread.Start(); } + private bool HasReadyGuestThread() + { + WakeExpiredBlockedGuestThreads(); + lock (_guestThreadGate) + { + foreach (var thread in _guestThreads.Values) + { + if (thread.State is GuestThreadRunState.Ready) + { + return true; + } + } + } + + return false; + } + private void StopStallWatchdog() { _stallWatchdogStop = true; @@ -3286,12 +4017,102 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I { Console.Error.WriteLine($"[LOADER][ERROR] Stall stack: [rsp]=0x{value:X16} [rsp+8]=0x{value2:X16}"); } + + var threads = SnapshotGuestThreads(); + if (threads.Length != 0) + { + var logged = 0; + foreach (var thread in threads) + { + var hostThreadId = Volatile.Read(ref thread.HostThreadId); + var hostContextText = string.Empty; + if (TryCaptureHostThreadContext(hostThreadId, out var hostContext)) + { + hostContextText = + $" host_tid={hostThreadId} host_rip=0x{hostContext.Rip:X16} host_rsp=0x{hostContext.Rsp:X16} " + + $"host_rbp=0x{hostContext.Rbp:X16} host_rax=0x{hostContext.Rax:X16} host_rbx=0x{hostContext.Rbx:X16} " + + $"host_rcx=0x{hostContext.Rcx:X16} host_rdx=0x{hostContext.Rdx:X16}"; + } + else if (hostThreadId != 0) + { + hostContextText = $" host_tid={hostThreadId} host_ctx=unavailable"; + } + + Console.Error.WriteLine( + $"[LOADER][ERROR] Stall guest-thread: handle=0x{thread.ThreadHandle:X16} name='{thread.Name}' " + + $"state={thread.State} imports={Interlocked.Read(ref thread.ImportCount)} " + + $"nid={Volatile.Read(ref thread.LastImportNid) ?? "none"} ret=0x{Volatile.Read(ref thread.LastReturnRip):X16} " + + $"block={thread.BlockReason ?? "none"}{hostContextText}"); + logged++; + if (logged >= 48 && threads.Length > logged) + { + Console.Error.WriteLine($"[LOADER][ERROR] Stall guest-thread: ... {threads.Length - logged} more"); + break; + } + } + } } catch { } } + private unsafe static bool TryCaptureHostThreadContext(int hostThreadId, out HostThreadContextSnapshot snapshot) + { + snapshot = default; + if (hostThreadId == 0 || unchecked((uint)hostThreadId) == GetCurrentThreadId()) + { + return false; + } + + var threadHandle = OpenThread(ThreadGetContext | ThreadSuspendResume, false, unchecked((uint)hostThreadId)); + if (threadHandle == 0) + { + 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); + } + } + [DllImport("kernel32.dll")] private static extern uint TlsAlloc(); @@ -3320,6 +4141,26 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I [DllImport("kernel32.dll")] private static extern IntPtr SetUnhandledExceptionFilter(IntPtr lpTopLevelExceptionFilter); + [DllImport("kernel32.dll")] + private static extern uint GetCurrentThreadId(); + + [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() { ClearImportHandlerTrampolines(); @@ -3414,6 +4255,19 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I VirtualFree((void*)_guestReturnStub, 0u, 32768u); _guestReturnStub = 0; } + if (_guestContextTransferStub != 0) + { + VirtualFree((void*)_guestContextTransferStub, 0u, 32768u); + _guestContextTransferStub = 0; + } + foreach (var frame in _guestContextTransferFrames.Values) + { + if (frame != 0) + { + NativeMemory.Free((void*)frame); + } + } + _guestContextTransferFrames.Dispose(); if (_lowIndexedTableScratch != 0) { VirtualFree((void*)_lowIndexedTableScratch, 0u, 32768u); diff --git a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs index 14527c0..586ab29 100644 --- a/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs +++ b/src/SharpEmu.Core/Memory/PhysicalVirtualMemory.cs @@ -461,6 +461,16 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA return false; } + if (CanReadWithoutProtectionChange((ulong)srcPtr, (ulong)destination.Length, region)) + { + fixed (byte* destPtr = destination) + { + Buffer.MemoryCopy(srcPtr, destPtr, (nuint)destination.Length, (nuint)destination.Length); + } + + return true; + } + if (!TryTemporarilyProtectForRead((ulong)srcPtr, (ulong)destination.Length, region, out var touchedPages)) { return false; @@ -504,6 +514,16 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA return false; } + if (CanWriteWithoutProtectionChange((ulong)destPtr, (ulong)source.Length, region)) + { + fixed (byte* srcPtr = source) + { + Buffer.MemoryCopy(srcPtr, destPtr, (nuint)source.Length, (nuint)source.Length); + } + + return true; + } + if (!VirtualProtect(destPtr, (nuint)source.Length, PAGE_EXECUTE_READWRITE, out var oldProtect)) { return false; @@ -633,6 +653,44 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA return protection is PAGE_EXECUTE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE or PAGE_EXECUTE_WRITECOPY; } + private bool CanReadWithoutProtectionChange(ulong address, ulong size, MemoryRegion region) => + CanAccessWithoutProtectionChange(address, size, region, write: false); + + private bool CanWriteWithoutProtectionChange(ulong address, ulong size, MemoryRegion region) => + CanAccessWithoutProtectionChange(address, size, region, write: true); + + private bool CanAccessWithoutProtectionChange(ulong address, ulong size, MemoryRegion region, bool write) + { + var startPage = AlignDown(address, PageSize); + var endPage = AlignUp(address + size, PageSize); + for (var pageAddress = startPage; pageAddress < endPage; pageAddress += PageSize) + { + if (_pageProtections.TryGetValue(pageAddress, out var flags)) + { + if (write ? (flags & ProgramHeaderFlags.Write) == 0 : (flags & ProgramHeaderFlags.Read) == 0) + { + return false; + } + } + else if (write ? !IsWritableProtection(region.Protection) : !IsReadableProtection(region.Protection)) + { + return false; + } + } + + return true; + } + + private static bool IsReadableProtection(uint protection) + { + return protection is PAGE_READONLY or PAGE_READWRITE or PAGE_EXECUTE_READ or PAGE_EXECUTE_READWRITE; + } + + private static bool IsWritableProtection(uint protection) + { + return protection is PAGE_READWRITE or PAGE_EXECUTE_READWRITE; + } + private static uint GetCommitProtection(MemoryRegion region) { return region.IsExecutable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE; diff --git a/src/SharpEmu.HLE/GuestThreadExecution.cs b/src/SharpEmu.HLE/GuestThreadExecution.cs index 5309fad..87bfc2e 100644 --- a/src/SharpEmu.HLE/GuestThreadExecution.cs +++ b/src/SharpEmu.HLE/GuestThreadExecution.cs @@ -1,6 +1,8 @@ // Copyright (C) 2026 SharpEmu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +using System.Diagnostics; + namespace SharpEmu.HLE; public readonly record struct GuestThreadStartRequest( @@ -10,12 +12,27 @@ public readonly record struct GuestThreadStartRequest( ulong AttributeAddress, string Name); +public readonly record struct GuestThreadSnapshot( + ulong ThreadHandle, + string Name, + string State, + long ImportCount, + string? LastImportNid, + ulong LastReturnRip, + string? BlockReason); + public interface IGuestThreadScheduler { + bool SupportsGuestContextTransfer { get; } + bool TryStartThread(CpuContext creatorContext, GuestThreadStartRequest request, out string? error); void Pump(CpuContext callerContext, string reason); + int WakeBlockedThreads(string wakeKey, int maxCount = int.MaxValue); + + IReadOnlyList SnapshotThreads(); + bool TryCallGuestFunction( CpuContext callerContext, ulong entryPoint, @@ -71,15 +88,39 @@ public static class GuestThreadExecution [ThreadStatic] private static string? _pendingBlockReason; + [ThreadStatic] + private static bool _pendingBlockContinuationValid; + + [ThreadStatic] + private static GuestCpuContinuation _pendingBlockContinuation; + + [ThreadStatic] + private static string? _pendingBlockWakeKey; + + [ThreadStatic] + private static Func? _pendingBlockResumeHandler; + + [ThreadStatic] + private static Func? _pendingBlockWakeHandler; + + [ThreadStatic] + private static long _pendingBlockDeadlineTimestamp; + [ThreadStatic] private static bool _pendingEntryExit; [ThreadStatic] - private static int _pendingEntryExitStatus; + private static ulong _pendingEntryExitValue; [ThreadStatic] private static string? _pendingEntryExitReason; + [ThreadStatic] + private static bool _pendingContextTransfer; + + [ThreadStatic] + private static GuestCpuContinuation _pendingContextTransferTarget; + [ThreadStatic] private static bool _hasCurrentImportCallFrame; @@ -105,9 +146,17 @@ public static class GuestThreadExecution var previous = _currentGuestThreadHandle; _currentGuestThreadHandle = threadHandle; _pendingBlockReason = null; + _pendingBlockContinuationValid = false; + _pendingBlockContinuation = default; + _pendingBlockWakeKey = null; + _pendingBlockResumeHandler = null; + _pendingBlockWakeHandler = null; + _pendingBlockDeadlineTimestamp = 0; _pendingEntryExit = false; - _pendingEntryExitStatus = 0; + _pendingEntryExitValue = 0; _pendingEntryExitReason = null; + _pendingContextTransfer = false; + _pendingContextTransferTarget = default; _hasCurrentImportCallFrame = false; _currentImportReturnRip = 0; _currentImportResumeRsp = 0; @@ -119,9 +168,17 @@ public static class GuestThreadExecution { _currentGuestThreadHandle = previousThreadHandle; _pendingBlockReason = null; + _pendingBlockContinuationValid = false; + _pendingBlockContinuation = default; + _pendingBlockWakeKey = null; + _pendingBlockResumeHandler = null; + _pendingBlockWakeHandler = null; + _pendingBlockDeadlineTimestamp = 0; _pendingEntryExit = false; - _pendingEntryExitStatus = 0; + _pendingEntryExitValue = 0; _pendingEntryExitReason = null; + _pendingContextTransfer = false; + _pendingContextTransferTarget = default; _hasCurrentImportCallFrame = false; _currentImportReturnRip = 0; _currentImportResumeRsp = 0; @@ -140,7 +197,15 @@ public static class GuestThreadExecution _currentFiberAddress = previousFiberAddress; } - public static bool RequestCurrentThreadBlock(string reason) + public static bool RequestCurrentThreadBlock(string reason) => RequestCurrentThreadBlock(null, reason); + + public static bool RequestCurrentThreadBlock( + CpuContext? context, + string reason, + string? wakeKey = null, + Func? resumeHandler = null, + Func? wakeHandler = null, + long blockDeadlineTimestamp = 0) { if (!IsGuestThread) { @@ -148,31 +213,167 @@ public static class GuestThreadExecution } _pendingBlockReason = string.IsNullOrWhiteSpace(reason) ? "guest_thread_blocked" : reason; + _pendingBlockWakeKey = string.IsNullOrWhiteSpace(wakeKey) ? _pendingBlockReason : wakeKey; + _pendingBlockResumeHandler = resumeHandler; + _pendingBlockWakeHandler = wakeHandler; + _pendingBlockDeadlineTimestamp = blockDeadlineTimestamp; + if (context is not null && TryCaptureCurrentBlockContinuation(context, out var continuation)) + { + _pendingBlockContinuation = continuation; + _pendingBlockContinuationValid = true; + } + else + { + _pendingBlockContinuation = default; + _pendingBlockContinuationValid = false; + } + return true; } public static bool TryConsumeCurrentThreadBlock(out string reason) + { + return TryConsumeCurrentThreadBlock(out reason, out _, out _); + } + + public static bool TryConsumeCurrentThreadBlock( + out string reason, + out GuestCpuContinuation continuation, + out bool hasContinuation) + { + return TryConsumeCurrentThreadBlock( + out reason, + out continuation, + out hasContinuation, + out _, + out _, + out _, + out _); + } + + public static bool TryConsumeCurrentThreadBlock( + out string reason, + out GuestCpuContinuation continuation, + out bool hasContinuation, + out string wakeKey, + out Func? resumeHandler, + out Func? wakeHandler) + { + return TryConsumeCurrentThreadBlock( + out reason, + out continuation, + out hasContinuation, + out wakeKey, + out resumeHandler, + out wakeHandler, + out _); + } + + public static bool TryConsumeCurrentThreadBlock( + out string reason, + out GuestCpuContinuation continuation, + out bool hasContinuation, + out string wakeKey, + out Func? resumeHandler, + out Func? wakeHandler, + out long blockDeadlineTimestamp) { reason = _pendingBlockReason ?? string.Empty; if (string.IsNullOrEmpty(reason)) { + continuation = default; + hasContinuation = false; + wakeKey = string.Empty; + resumeHandler = null; + wakeHandler = null; + blockDeadlineTimestamp = 0; return false; } + continuation = _pendingBlockContinuation; + hasContinuation = _pendingBlockContinuationValid; + wakeKey = _pendingBlockWakeKey ?? reason; + resumeHandler = _pendingBlockResumeHandler; + wakeHandler = _pendingBlockWakeHandler; + blockDeadlineTimestamp = _pendingBlockDeadlineTimestamp; _pendingBlockReason = null; + _pendingBlockContinuation = default; + _pendingBlockContinuationValid = false; + _pendingBlockWakeKey = null; + _pendingBlockResumeHandler = null; + _pendingBlockWakeHandler = null; + _pendingBlockDeadlineTimestamp = 0; + return true; + } + + public static long ComputeDeadlineTimestamp(TimeSpan timeout) + { + if (timeout <= TimeSpan.Zero) + { + return Stopwatch.GetTimestamp(); + } + + var ticks = timeout.TotalSeconds >= long.MaxValue / (double)Stopwatch.Frequency + ? long.MaxValue + : (long)Math.Ceiling(timeout.TotalSeconds * Stopwatch.Frequency); + var now = Stopwatch.GetTimestamp(); + if (long.MaxValue - now <= ticks) + { + return long.MaxValue; + } + + return now + Math.Max(1, ticks); + } + + private static bool TryCaptureCurrentBlockContinuation(CpuContext context, out GuestCpuContinuation continuation) + { + if (!TryGetCurrentImportCallFrame(out var frame) || + frame.ReturnRip < 65536 || + frame.ResumeRsp == 0 || + frame.ReturnSlotAddress == 0) + { + continuation = default; + return false; + } + + continuation = new GuestCpuContinuation( + frame.ReturnRip, + frame.ResumeRsp, + frame.ReturnSlotAddress, + context.Rflags, + context.FsBase, + context.GsBase, + 0, + context[CpuRegister.Rcx], + context[CpuRegister.Rdx], + context[CpuRegister.Rbx], + context[CpuRegister.Rbp], + context[CpuRegister.Rsi], + context[CpuRegister.Rdi], + context[CpuRegister.R8], + context[CpuRegister.R9], + context[CpuRegister.R12], + context[CpuRegister.R13], + context[CpuRegister.R14], + context[CpuRegister.R15]); return true; } public static void RequestCurrentEntryExit(string reason, int status) + { + RequestCurrentEntryExit(reason, unchecked((ulong)(long)status)); + } + + public static void RequestCurrentEntryExit(string reason, ulong value) { _pendingEntryExit = true; - _pendingEntryExitStatus = status; + _pendingEntryExitValue = value; _pendingEntryExitReason = string.IsNullOrWhiteSpace(reason) ? "guest_entry_exit" : reason; } - public static bool TryConsumeCurrentEntryExit(out int status, out string reason) + public static bool TryConsumeCurrentEntryExit(out ulong value, out string reason) { - status = _pendingEntryExitStatus; + value = _pendingEntryExitValue; reason = _pendingEntryExitReason ?? string.Empty; if (!_pendingEntryExit) { @@ -180,11 +381,30 @@ public static class GuestThreadExecution } _pendingEntryExit = false; - _pendingEntryExitStatus = 0; + _pendingEntryExitValue = 0; _pendingEntryExitReason = null; return true; } + public static void RequestCurrentContextTransfer(GuestCpuContinuation target) + { + _pendingContextTransferTarget = target; + _pendingContextTransfer = true; + } + + public static bool TryConsumeCurrentContextTransfer(out GuestCpuContinuation target) + { + target = _pendingContextTransferTarget; + if (!_pendingContextTransfer) + { + return false; + } + + _pendingContextTransfer = false; + _pendingContextTransferTarget = default; + return true; + } + public static GuestImportCallFrame EnterImportCallFrame( ulong returnRip, ulong resumeRsp,