116 lines
4.8 KiB
C#
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]));
|
|
}
|
|
}
|