sharpemu/tests/SharpEmu.Libs.Tests/Cpu/GuestExceptionExecutorHando...

116 lines
4.8 KiB
C#

// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections;
using System.Reflection;
using SharpEmu.Core.Cpu.Native;
using SharpEmu.HLE;
using Xunit;
namespace SharpEmu.Libs.Tests.Cpu;
public sealed class GuestExceptionExecutorHandoffTests
{
[NativeX64Theory]
[InlineData(false)]
[InlineData(true)]
public void BlockedThreadTakesPendingExceptionWhenExecutorReleasesIt(bool signalArrivesBeforeYield)
{
var moduleManager = new ModuleManager();
moduleManager.Freeze();
using var backend = new DirectExecutionBackend(moduleManager);
var backendType = typeof(DirectExecutionBackend);
var threadType = backendType.GetNestedType(
"GuestThreadState",
BindingFlags.NonPublic);
var runStateType = backendType.GetNestedType(
"GuestThreadRunState",
BindingFlags.NonPublic);
var pendingType = backendType.GetNestedType(
"PendingGuestException",
BindingFlags.NonPublic);
Assert.NotNull(threadType);
Assert.NotNull(runStateType);
Assert.NotNull(pendingType);
const ulong threadHandle = 0x1234;
const ulong handler = 0x5678;
const int exceptionType = 0x1E;
const ulong exceptionStack = 0x9000;
var thread = Activator.CreateInstance(threadType);
Assert.NotNull(thread);
threadType.GetProperty("ThreadHandle")!.SetValue(thread, threadHandle);
threadType.GetProperty("State")!.SetValue(
thread,
Enum.Parse(runStateType, "Blocked"));
threadType.GetProperty("ExecutorActive")!.SetValue(thread, true);
threadType.GetProperty("ExceptionDeliveryActive")!.SetValue(thread, false);
var pending = Activator.CreateInstance(
pendingType,
handler,
exceptionType,
exceptionStack);
Assert.NotNull(pending);
var pendingDictionary = Assert.IsAssignableFrom<IDictionary>(
backendType.GetField(
"_pendingGuestExceptions",
BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(backend));
backendType.GetMethod(
"QueuePendingGuestExceptionLocked",
BindingFlags.Instance | BindingFlags.NonPublic)!.Invoke(backend, [threadHandle, pending]);
if (signalArrivesBeforeYield)
{
// The import requested a wait that the executor has not saved.
// Keep the signal callback separate from this wait.
var context = new CpuContext(new FakeCpuMemory(0x10000, 0x1000), Generation.Gen5);
var previousThread = GuestThreadExecution.EnterGuestThread(threadHandle);
var previousFrame = GuestThreadExecution.EnterImportCallFrame(0x10000, 0x20000, 0x30000);
try
{
Assert.True(GuestThreadExecution.RequestCurrentThreadBlock(
context, "job_wait", "event:3", blockDeadlineTimestamp: 123));
backendType.GetMethod(
"DeliverPendingGuestExceptionAtSafePoint",
BindingFlags.Instance | BindingFlags.NonPublic)!.Invoke(
backend, [context, default(GuestCpuContinuation)]);
Assert.True(pendingDictionary.Contains(threadHandle));
Assert.True(GuestThreadExecution.TryConsumeCurrentThreadBlock(
out var reason, out var continuation, out var hasContinuation,
out var wakeKey, out _, out var deadline));
Assert.Equal("job_wait", reason);
Assert.True(hasContinuation);
Assert.Equal(0x10000UL, continuation.Rip);
Assert.Equal(0x20000UL, continuation.Rsp);
Assert.Equal("event:3", wakeKey);
Assert.Equal(123, deadline);
}
finally
{
GuestThreadExecution.RestoreImportCallFrame(previousFrame);
GuestThreadExecution.RestoreGuestThread(previousThread);
}
}
var handoff = backendType.GetMethod(
"TryReleaseGuestThreadExecutorLocked",
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.NotNull(handoff);
object?[] arguments = [thread, null];
Assert.True(Assert.IsType<bool>(handoff.Invoke(backend, arguments)));
Assert.False(Assert.IsType<bool>(
threadType.GetProperty("ExecutorActive")!.GetValue(thread)));
Assert.Empty(pendingDictionary);
Assert.NotNull(arguments[1]);
Assert.Equal(handler, pendingType.GetProperty("Handler")!.GetValue(arguments[1]));
Assert.Equal(exceptionType, pendingType.GetProperty("ExceptionType")!.GetValue(arguments[1]));
Assert.Equal(exceptionStack, pendingType.GetProperty("ExceptionStackBase")!.GetValue(arguments[1]));
}
}