Files
sharpemu/src/SharpEmu.Core/Cpu/CpuDispatcher.cs
T
Berk fbafd3f429 [GUI] Add native Vulkan host surface support and more (#337)
* [GUI] Add native Vulkan host surface support and more

* reuse

* [GUI] set width session menu
2026-07-17 18:57:10 +03:00

714 lines
26 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Buffers.Binary;
using System.Text;
using SharpEmu.Core.Cpu.Native;
using SharpEmu.Core.Loader;
using SharpEmu.Core.Memory;
using SharpEmu.HLE;
namespace SharpEmu.Core.Cpu;
public sealed class CpuDispatcher : ICpuDispatcher, IDisposable
{
private enum EntryFrameKind
{
ProcessEntry,
ModuleInitializer,
}
// The top of the x86-64 user address space (0x7FFD..0x7FFF) is only
// freely mappable on Windows; on macOS/Linux it hosts the dyld shared
// cache / vdso and (under Rosetta 2) the translator runtime, so POSIX
// hosts use the equivalent layout one slot lower at 0x6FFx.
private static readonly ulong StackBaseAddress = OperatingSystem.IsWindows() ? 0x7FFF_F000_0000UL : 0x6FFF_F000_0000UL;
private const ulong StackSize = 0x0020_0000UL;
private static readonly ulong TlsBaseAddress = OperatingSystem.IsWindows() ? 0x7FFE_0000_0000UL : 0x6FFE_0000_0000UL;
private const ulong TlsSize = 0x0001_0000UL;
// The static TLS blocks live at negative offsets from the TCB (FreeBSD
// amd64 variant II). Keep every host in sync with GuestTlsTemplate's
// startup reservation; PS5 modules routinely reach beyond one host page.
private const ulong TlsPrefixSize = GuestTlsTemplate.StartupStaticTlsReservation;
private static readonly ulong BootstrapStubBaseAddress = OperatingSystem.IsWindows() ? 0x7FFD_F000_0000UL : 0x6FFD_F000_0000UL;
private static readonly ulong BootstrapPayloadBaseAddress = OperatingSystem.IsWindows() ? 0x7FFD_E000_0000UL : 0x6FFD_E000_0000UL;
private static readonly ulong DynlibFallbackStubBaseAddress = OperatingSystem.IsWindows() ? 0x7FFD_D000_0000UL : 0x6FFD_D000_0000UL;
private static readonly ulong ReturnToHostStubBaseAddress = OperatingSystem.IsWindows() ? 0x7FFD_C000_0000UL : 0x6FFD_C000_0000UL;
private const ulong BootstrapRegionSize = 0x0000_1000UL;
private const ulong ReturnToHostStubStride = 0x0100_0000UL;
private const ulong BootstrapPayloadResultOffset = 0x28UL;
private const ulong BootstrapStatusOffset = 0x100UL;
private static readonly byte[] BootstrapStartSignature =
[
0x55, 0x48, 0x89, 0xE5, 0x41, 0x57, 0x41, 0x56,
0x41, 0x55, 0x41, 0x54, 0x53, 0x50, 0x48, 0x89,
];
private readonly IVirtualMemory _virtualMemory;
private readonly IModuleManager _moduleManager;
private INativeCpuBackend? _nativeCpuBackend;
public CpuDispatcher(
IVirtualMemory virtualMemory,
IModuleManager moduleManager,
INativeCpuBackend? nativeCpuBackend = null)
{
_virtualMemory = virtualMemory ?? throw new ArgumentNullException(nameof(virtualMemory));
_moduleManager = moduleManager ?? throw new ArgumentNullException(nameof(moduleManager));
_nativeCpuBackend = nativeCpuBackend;
}
public ulong? LastEntryPoint { get; private set; }
public CpuTrapInfo? LastTrapInfo { get; private set; }
public CpuMemoryFaultInfo? LastMemoryFaultInfo { get; private set; }
public CpuControlTransferInfo? LastControlTransferInfo { get; private set; }
public CpuNotImplementedInfo? LastNotImplementedInfo { get; private set; }
public string? LastImportResolutionTrace { get; private set; }
public string? LastBasicBlockTrace { get; private set; }
public string? LastMilestoneLog { get; private set; }
public string? LastRecentInstructionWindow { get; private set; }
public string? LastRecentControlTransferTrace { get; private set; }
public CpuSessionSummary LastSessionSummary { get; private set; }
public OrbisGen2Result DispatchEntry(
ulong entryPoint,
Generation generation,
IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string processImageName = "eboot.bin",
CpuExecutionOptions executionOptions = default)
{
Console.Error.WriteLine("[DISPATCHER] === DispatchEntry START ===");
Console.Error.WriteLine($"[DISPATCHER] entryPoint=0x{entryPoint:X16}, generation={generation}");
try
{
return DispatchEntryCore(entryPoint, generation, importStubs, runtimeSymbols, processImageName, executionOptions);
}
catch (Exception ex)
{
Console.Error.WriteLine($"[DISPATCHER] FATAL EXCEPTION in DispatchEntry: {ex.GetType().Name}: {ex.Message}");
Console.Error.WriteLine($"[DISPATCHER] Stack trace: {ex.StackTrace}");
throw;
}
}
public OrbisGen2Result DispatchModuleInitializer(
ulong entryPoint,
Generation generation,
IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string moduleName = "module",
CpuExecutionOptions executionOptions = default)
{
Console.Error.WriteLine("[DISPATCHER] === DispatchModuleInitializer START ===");
Console.Error.WriteLine($"[DISPATCHER] moduleInit=0x{entryPoint:X16}, generation={generation}, module={moduleName}");
try
{
return DispatchEntryCore(
entryPoint,
generation,
importStubs,
runtimeSymbols,
moduleName,
executionOptions,
EntryFrameKind.ModuleInitializer);
}
catch (Exception ex)
{
Console.Error.WriteLine($"[DISPATCHER] FATAL EXCEPTION in DispatchModuleInitializer: {ex.GetType().Name}: {ex.Message}");
Console.Error.WriteLine($"[DISPATCHER] Stack trace: {ex.StackTrace}");
throw;
}
}
private OrbisGen2Result DispatchEntryCore(
ulong entryPoint,
Generation generation,
IReadOnlyDictionary<ulong, string>? importStubs = null,
IReadOnlyDictionary<string, ulong>? runtimeSymbols = null,
string processImageName = "eboot.bin",
CpuExecutionOptions executionOptions = default,
EntryFrameKind frameKind = EntryFrameKind.ProcessEntry)
{
Console.Error.WriteLine("[DISPATCHER] DispatchEntryCore STARTING...");
LastEntryPoint = entryPoint;
LastTrapInfo = null;
LastMemoryFaultInfo = null;
LastControlTransferInfo = null;
LastNotImplementedInfo = null;
LastImportResolutionTrace = null;
LastBasicBlockTrace = null;
LastMilestoneLog = null;
LastRecentInstructionWindow = null;
LastRecentControlTransferTrace = null;
LastSessionSummary = default;
OrbisGen2Result FailEarly(OrbisGen2Result result, CpuExitReason reason = CpuExitReason.UnhandledException)
{
LastSessionSummary = new CpuSessionSummary(
result,
reason,
exitCode: null,
lastGuestRip: entryPoint,
lastStubRip: 0,
totalInstructions: 0,
importsHit: 0,
uniqueNidsHit: 0);
return result;
}
var stackBase = TryMapStackRegion();
if (stackBase == 0)
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
var tlsBase = TryMapTlsRegion();
if (tlsBase == 0)
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
var trackedMemory = new TrackedCpuMemory(_virtualMemory);
var context = new CpuContext(trackedMemory, generation)
{
Rip = entryPoint,
Rflags = 0x202,
FsBase = tlsBase,
GsBase = tlsBase,
};
var returnToHostStubAddress = TryMapReturnToHostStubRegion();
if (returnToHostStubAddress == 0)
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
context[CpuRegister.Rsp] = stackBase + StackSize - sizeof(ulong);
if (!context.TryWriteUInt64(context[CpuRegister.Rsp], returnToHostStubAddress))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (!InitializeGuestFrameChainSentinel(context))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (!InitializeTls(context, tlsBase))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
var effectiveImportStubs = importStubs is null
? new Dictionary<ulong, string>()
: new Dictionary<ulong, string>(importStubs);
var entryParamsConfigured = false;
if (frameKind == EntryFrameKind.ProcessEntry)
{
var programExitHandlerStubAddress = TryMapDynlibFallbackStubRegion();
if (programExitHandlerStubAddress == 0)
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
if (!InitializeProcessEntryFrame(context, processImageName, programExitHandlerStubAddress))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
entryParamsConfigured = true;
if (ShouldInjectBootstrapPayload(entryPoint))
{
if (!TryInstallBootstrapPayload(context, effectiveImportStubs))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
}
}
else if (!InitializeModuleInitializerFrame(context))
{
return FailEarly(OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
}
var entryFrameDiagnostic = BuildEntryFrameDiagnostic(
entryPoint,
context,
sentinelEnabled: true,
sentinelValue: returnToHostStubAddress,
entryParamsConfigured: entryParamsConfigured);
if (executionOptions.CpuEngine != CpuExecutionEngine.NativeOnly)
{
LastMilestoneLog = string.Concat(
entryFrameDiagnostic,
Environment.NewLine,
$"CpuEngine: {executionOptions.CpuEngine} (unsupported)");
LastNotImplementedInfo = new CpuNotImplementedInfo(
CpuNotImplementedSource.NativeBackend,
entryPoint,
nid: null,
exportName: "cpu_engine_unsupported",
libraryName: executionOptions.CpuEngine.ToString(),
detail: "Unsupported CPU engine mode.");
return FailEarly(
OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_IMPLEMENTED,
CpuExitReason.NativeBackendUnavailable);
}
LastMilestoneLog = string.Concat(
entryFrameDiagnostic,
Environment.NewLine,
"CpuEngine: native-only");
_nativeCpuBackend ??= new DirectExecutionBackend(_moduleManager);
if (_nativeCpuBackend.TryExecute(
context,
entryPoint,
generation,
effectiveImportStubs,
runtimeSymbols ?? new Dictionary<string, ulong>(StringComparer.Ordinal),
executionOptions,
out var nativeResult))
{
LastSessionSummary = new CpuSessionSummary(
nativeResult,
nativeResult == OrbisGen2Result.ORBIS_GEN2_OK
? CpuExitReason.ReturnedToHost
: CpuExitReason.UnhandledException,
exitCode: null,
lastGuestRip: context.Rip,
lastStubRip: 0,
totalInstructions: 0,
importsHit: 0,
uniqueNidsHit: 0);
return nativeResult;
}
var backendName = string.IsNullOrWhiteSpace(_nativeCpuBackend.BackendName)
? "native-backend"
: _nativeCpuBackend.BackendName;
var backendError = string.IsNullOrWhiteSpace(_nativeCpuBackend.LastError)
? "unknown backend error"
: _nativeCpuBackend.LastError;
LastNotImplementedInfo = new CpuNotImplementedInfo(
CpuNotImplementedSource.NativeBackend,
entryPoint,
nid: null,
exportName: "cpu_engine_native_only",
libraryName: backendName,
detail: backendError);
LastMilestoneLog = string.Concat(
LastMilestoneLog,
Environment.NewLine,
$"CpuEngine native-only failed: {backendError}");
Console.Error.WriteLine($"[DISPATCHER] Native backend FAILED: {backendError}");
return FailEarly(
OrbisGen2Result.ORBIS_GEN2_ERROR_NOT_IMPLEMENTED,
CpuExitReason.NativeBackendUnavailable);
}
private ulong TryMapStackRegion()
{
const ulong stackStride = 0x0100_0000UL;
for (var i = 0; i < 32; i++)
{
var candidateBase = StackBaseAddress - ((ulong)i * stackStride);
try
{
_virtualMemory.Map(
candidateBase,
StackSize,
fileOffset: 0,
fileData: ReadOnlySpan<byte>.Empty,
ProgramHeaderFlags.Read | ProgramHeaderFlags.Write);
return candidateBase;
}
catch (InvalidOperationException)
{
continue;
}
}
return 0;
}
private ulong TryMapTlsRegion()
{
const ulong tlsStride = 0x0100_0000UL;
for (var i = 0; i < 32; i++)
{
var candidateBase = TlsBaseAddress - ((ulong)i * tlsStride);
var mappedBase = candidateBase - TlsPrefixSize;
try
{
_virtualMemory.Map(
mappedBase,
TlsSize + TlsPrefixSize,
fileOffset: 0,
fileData: ReadOnlySpan<byte>.Empty,
ProgramHeaderFlags.Read | ProgramHeaderFlags.Write);
return candidateBase;
}
catch (InvalidOperationException)
{
continue;
}
}
return 0;
}
private static bool InitializeTls(CpuContext context, ulong tlsBase)
{
if (!context.TryWriteUInt64(tlsBase - 0xF0, 0) ||
!context.TryWriteUInt64(tlsBase + 0x00, tlsBase) ||
!context.TryWriteUInt64(tlsBase + 0x10, tlsBase) ||
!context.TryWriteUInt64(tlsBase + 0x28, 0xC0DEC0DECAFEBA00UL) ||
!context.TryWriteUInt64(tlsBase + 0x60, tlsBase))
{
return false;
}
// Seed the static TLS block below the thread pointer with the main
// module's initialized thread-locals (variant II layout).
SharpEmu.HLE.GuestTlsTemplate.SeedThreadBlock(context, tlsBase);
return true;
}
private static bool InitializeGuestFrameChainSentinel(CpuContext context)
{
var stackTop = context[CpuRegister.Rsp] + sizeof(ulong);
var sentinelFrame = AlignDown(stackTop - 0x20, 16);
var seedRsp = sentinelFrame - sizeof(ulong);
if (!context.TryWriteUInt64(sentinelFrame, 0) ||
!context.TryWriteUInt64(sentinelFrame + sizeof(ulong), 0) ||
!context.TryWriteUInt64(seedRsp, 0))
{
return false;
}
context[CpuRegister.Rbp] = sentinelFrame;
context[CpuRegister.Rsp] = seedRsp;
return true;
}
private static bool InitializeProcessEntryFrame(
CpuContext context,
string processImageName,
ulong programExitHandlerAddress)
{
var imageName = string.IsNullOrWhiteSpace(processImageName) ? "eboot.bin" : processImageName;
var arguments = new List<string>(3) { imageName };
var configuredArguments = Environment.GetEnvironmentVariable("SHARPEMU_GUEST_ARGS");
if (!string.IsNullOrWhiteSpace(configuredArguments))
{
// The PS5 entry-parameter ABI exposes three inline argv pointers.
// Two compatibility arguments are therefore safe without changing
// the fixed 0x20-byte structure expected by existing titles.
var compatibilityArguments = configuredArguments.Split(
(char[]?)null,
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
arguments.AddRange(compatibilityArguments.Take(2));
}
var cursor = context[CpuRegister.Rsp];
var argumentAddresses = new ulong[arguments.Count];
for (var index = arguments.Count - 1; index >= 0; index--)
{
var encoded = Encoding.UTF8.GetBytes(arguments[index] + '\0');
cursor = AlignDown(cursor - (ulong)encoded.Length, 16);
if (!context.Memory.TryWrite(cursor, encoded))
{
return false;
}
argumentAddresses[index] = cursor;
}
const ulong entryParamsSize = 0x20;
var entryParamsAddress = AlignDown(cursor - entryParamsSize, 16);
if (!TryWriteUInt32(context, entryParamsAddress + 0x00, (uint)arguments.Count) ||
!TryWriteUInt32(context, entryParamsAddress + 0x04, 0) ||
!context.TryWriteUInt64(entryParamsAddress + 0x08, argumentAddresses[0]) ||
!context.TryWriteUInt64(
entryParamsAddress + 0x10,
argumentAddresses.Length > 1 ? argumentAddresses[1] : 0) ||
!context.TryWriteUInt64(
entryParamsAddress + 0x18,
argumentAddresses.Length > 2 ? argumentAddresses[2] : 0))
{
return false;
}
if (arguments.Count > 1)
{
Console.Error.WriteLine(
$"[DISPATCHER] Guest arguments: {string.Join(' ', arguments.Skip(1))}");
}
var entryStackPointer = entryParamsAddress - sizeof(ulong);
if (!context.TryWriteUInt64(entryStackPointer, 0))
{
return false;
}
context[CpuRegister.Rsp] = entryStackPointer;
context[CpuRegister.Rdi] = entryParamsAddress;
context[CpuRegister.Rsi] = programExitHandlerAddress;
context[CpuRegister.Rdx] = 0;
context[CpuRegister.Rcx] = 0;
context[CpuRegister.R8] = 0;
context[CpuRegister.R9] = 0;
return true;
}
private static bool InitializeModuleInitializerFrame(CpuContext context)
{
context[CpuRegister.Rdi] = 0;
context[CpuRegister.Rsi] = 0;
context[CpuRegister.Rdx] = 0;
context[CpuRegister.Rcx] = 0;
context[CpuRegister.R8] = 0;
context[CpuRegister.R9] = 0;
return true;
}
private static ulong AlignDown(ulong value, ulong alignment)
{
return value & ~(alignment - 1);
}
private static bool TryWriteUInt32(CpuContext context, ulong address, uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
return context.Memory.TryWrite(address, buffer);
}
private static string BuildEntryFrameDiagnostic(
ulong entryPoint,
CpuContext context,
bool sentinelEnabled,
ulong sentinelValue,
bool entryParamsConfigured)
{
var initialRsp = context[CpuRegister.Rsp];
var stackTopText = context.TryReadUInt64(initialRsp, out var stackTop)
? $"0x{stackTop:X16}"
: "??";
var sentinelText = sentinelEnabled ? "yes" : "no";
var sentinelValueText = sentinelEnabled ? $"0x{sentinelValue:X16}" : "??";
var entryParamsText = entryParamsConfigured ? "yes" : "no";
return
$"EntryFrame: entry_rip=0x{entryPoint:X16} initial_rsp=0x{initialRsp:X16} [rsp]={stackTopText} sentinel_enabled={sentinelText} sentinel_value={sentinelValueText} entry_params_configured={entryParamsText}";
}
private bool ShouldInjectBootstrapPayload(ulong entryPoint)
{
Span<byte> probe = stackalloc byte[16];
if (!_virtualMemory.TryRead(entryPoint, probe))
{
return false;
}
for (var i = 0; i < BootstrapStartSignature.Length; i++)
{
if (probe[i] != BootstrapStartSignature[i])
{
return false;
}
}
return true;
}
private bool TryInstallBootstrapPayload(CpuContext context, IDictionary<ulong, string> importStubs)
{
var stubAddress = TryMapBootstrapStubRegion();
if (stubAddress == 0)
{
return false;
}
var payloadAddress = TryMapBootstrapPayloadRegion();
if (payloadAddress == 0)
{
return false;
}
var statusAddress = payloadAddress + BootstrapStatusOffset;
if (!TryWriteUInt64(payloadAddress, stubAddress) ||
!TryWriteUInt64(payloadAddress + 0x08, statusAddress) ||
!TryWriteUInt64(payloadAddress + 0x10, statusAddress) ||
!TryWriteUInt64(payloadAddress + 0x18, statusAddress) ||
!TryWriteUInt64(payloadAddress + 0x20, statusAddress) ||
!TryWriteUInt64(payloadAddress + BootstrapPayloadResultOffset, statusAddress) ||
!TryWriteUInt32(statusAddress, 0))
{
return false;
}
importStubs[stubAddress] = RuntimeStubNids.BootstrapBridge;
importStubs[stubAddress + 0x0A] = RuntimeStubNids.BootstrapBridge;
context[CpuRegister.Rdi] = payloadAddress;
return true;
}
private ulong TryMapBootstrapStubRegion()
{
var stubData = new byte[(int)BootstrapRegionSize];
stubData[0] = 0xCC;
stubData[1] = 0xC3;
const ulong stride = 0x0100_0000UL;
for (var i = 0; i < 16; i++)
{
var candidateBase = BootstrapStubBaseAddress - ((ulong)i * stride);
try
{
_virtualMemory.Map(
candidateBase,
BootstrapRegionSize,
fileOffset: 0,
stubData,
ProgramHeaderFlags.Read | ProgramHeaderFlags.Execute);
return candidateBase;
}
catch (InvalidOperationException)
{
continue;
}
}
return 0;
}
private ulong TryMapBootstrapPayloadRegion()
{
const ulong stride = 0x0100_0000UL;
for (var i = 0; i < 16; i++)
{
var candidateBase = BootstrapPayloadBaseAddress - ((ulong)i * stride);
try
{
_virtualMemory.Map(
candidateBase,
BootstrapRegionSize,
fileOffset: 0,
ReadOnlySpan<byte>.Empty,
ProgramHeaderFlags.Read | ProgramHeaderFlags.Write);
return candidateBase;
}
catch (InvalidOperationException)
{
continue;
}
}
return 0;
}
private ulong TryMapDynlibFallbackStubRegion()
{
var stubData = new byte[(int)BootstrapRegionSize];
stubData[0] = 0x31;
stubData[1] = 0xC0;
stubData[2] = 0xC3;
const ulong stride = 0x0100_0000UL;
for (var i = 0; i < 16; i++)
{
var candidateBase = DynlibFallbackStubBaseAddress - ((ulong)i * stride);
try
{
_virtualMemory.Map(
candidateBase,
BootstrapRegionSize,
fileOffset: 0,
stubData,
ProgramHeaderFlags.Read | ProgramHeaderFlags.Execute);
return candidateBase;
}
catch (InvalidOperationException)
{
continue;
}
}
return 0;
}
private ulong TryMapReturnToHostStubRegion()
{
var stubData = new byte[(int)BootstrapRegionSize];
stubData[0] = 0xF4;
stubData[1] = 0xCC;
for (var i = 0; i < 16; i++)
{
var candidateBase = ReturnToHostStubBaseAddress - ((ulong)i * ReturnToHostStubStride);
try
{
_virtualMemory.Map(
candidateBase,
BootstrapRegionSize,
fileOffset: 0,
stubData,
ProgramHeaderFlags.Read | ProgramHeaderFlags.Execute);
return candidateBase;
}
catch (InvalidOperationException)
{
continue;
}
}
return 0;
}
private bool TryWriteUInt64(ulong address, ulong value)
{
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
return _virtualMemory.TryWrite(address, buffer);
}
private bool TryWriteUInt32(ulong address, uint value)
{
Span<byte> buffer = stackalloc byte[sizeof(uint)];
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
return _virtualMemory.TryWrite(address, buffer);
}
/// <summary>
/// True when the disposed native backend left its session state alive
/// because guest workers were still executing guest code. The guest
/// address space must then stay mapped as well.
/// </summary>
internal bool NativeSessionLeaked { get; private set; }
public void Dispose()
{
if (_nativeCpuBackend is IDisposable disposableBackend)
{
disposableBackend.Dispose();
}
NativeSessionLeaked = _nativeCpuBackend is DirectExecutionBackend { GuestSessionLeaked: true };
_nativeCpuBackend = null;
}
}