test(debugger): add C# unit tests for protocol and breakpoints (#568)
Cover DebugRequest parsing, BreakpointStore, and DebugCommandDispatcher with a fake session. Wire the project into SharpEmu.slnx.
This commit is contained in:
parent
4c6cff1116
commit
8779c96c3a
|
|
@ -19,6 +19,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||
<Project Path="src/SharpEmu.SourceGenerators/SharpEmu.SourceGenerators.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/tests/">
|
||||
<Project Path="tests/SharpEmu.Debugger.Tests/SharpEmu.Debugger.Tests.csproj" />
|
||||
<Project Path="tests/SharpEmu.Libs.Tests/SharpEmu.Libs.Tests.csproj" />
|
||||
<Project Path="tests/SharpEmu.ShaderCompiler.Metal.Tests/SharpEmu.ShaderCompiler.Metal.Tests.csproj" />
|
||||
<Project Path="tests/SharpEmu.SourceGenerators.Tests/SharpEmu.SourceGenerators.Tests.csproj" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Debugger.Breakpoints;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Debugger.Tests;
|
||||
|
||||
public sealed class BreakpointStoreTests
|
||||
{
|
||||
[Fact]
|
||||
public void Add_AssignsIncrementalIds()
|
||||
{
|
||||
var store = new BreakpointStore();
|
||||
|
||||
var first = store.Add(BreakpointKind.Execute, 0x1000);
|
||||
var second = store.Add(BreakpointKind.WriteWatch, 0x2000, length: 8);
|
||||
|
||||
Assert.Equal(1, first.Id);
|
||||
Assert.Equal(2, second.Id);
|
||||
Assert.Equal(2, store.Snapshot().Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_ForcesExecuteLengthToOne()
|
||||
{
|
||||
var store = new BreakpointStore();
|
||||
|
||||
var breakpoint = store.Add(BreakpointKind.Execute, 0x4000, length: 64);
|
||||
|
||||
Assert.Equal(BreakpointKind.Execute, breakpoint.Kind);
|
||||
Assert.Equal(1UL, breakpoint.Length);
|
||||
Assert.True(breakpoint.Covers(0x4000));
|
||||
Assert.False(breakpoint.Covers(0x4001));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_PreservesWatchLengthAtLeastOne()
|
||||
{
|
||||
var store = new BreakpointStore();
|
||||
|
||||
var withLength = store.Add(BreakpointKind.ReadWatch, 0x5000, length: 16);
|
||||
var zeroClamped = store.Add(BreakpointKind.AccessWatch, 0x6000, length: 0);
|
||||
|
||||
Assert.Equal(16UL, withLength.Length);
|
||||
Assert.Equal(1UL, zeroClamped.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Remove_And_SetEnabled_MutateStore()
|
||||
{
|
||||
var store = new BreakpointStore();
|
||||
var breakpoint = store.Add(BreakpointKind.Execute, 0x1000);
|
||||
|
||||
Assert.True(store.SetEnabled(breakpoint.Id, enabled: false));
|
||||
Assert.False(store.Snapshot().Single().Enabled);
|
||||
Assert.True(store.Remove(breakpoint.Id));
|
||||
Assert.Empty(store.Snapshot());
|
||||
Assert.False(store.Remove(breakpoint.Id));
|
||||
Assert.False(store.SetEnabled(breakpoint.Id, enabled: true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindExecuteHit_ReturnsFirstEnabledExecuteMatch()
|
||||
{
|
||||
var store = new BreakpointStore();
|
||||
var disabled = store.Add(BreakpointKind.Execute, 0x1000);
|
||||
var watch = store.Add(BreakpointKind.WriteWatch, 0x1000, length: 4);
|
||||
var hit = store.Add(BreakpointKind.Execute, 0x1000);
|
||||
|
||||
store.SetEnabled(disabled.Id, enabled: false);
|
||||
|
||||
var found = store.FindExecuteHit(0x1000);
|
||||
|
||||
Assert.NotNull(found);
|
||||
Assert.Equal(hit.Id, found!.Id);
|
||||
Assert.Equal(BreakpointKind.Execute, found.Kind);
|
||||
Assert.NotEqual(watch.Id, found.Id);
|
||||
Assert.Null(store.FindExecuteHit(0x1001));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Clear_RemovesAllBreakpoints()
|
||||
{
|
||||
var store = new BreakpointStore();
|
||||
store.Add(BreakpointKind.Execute, 0x1);
|
||||
store.Add(BreakpointKind.Execute, 0x2);
|
||||
|
||||
store.Clear();
|
||||
|
||||
Assert.Empty(store.Snapshot());
|
||||
Assert.Null(store.FindExecuteHit(0x1));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Debugger.Protocol;
|
||||
using SharpEmu.Debugger.Session;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Debugger.Tests;
|
||||
|
||||
public sealed class DebugCommandDispatcherTests
|
||||
{
|
||||
private const int MaxMemoryChunk = 64 * 1024;
|
||||
|
||||
[Fact]
|
||||
public void Dispatch_UnknownCommand_Fails()
|
||||
{
|
||||
var dispatcher = CreateDispatcher();
|
||||
|
||||
var response = dispatcher.Dispatch(Parse("""{"command":"nope"}"""));
|
||||
|
||||
Assert.False(response.Ok);
|
||||
Assert.Equal("nope", response.Command);
|
||||
Assert.Contains("Unknown command", response.Error, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispatch_Registers_WhenNotPaused_Fails()
|
||||
{
|
||||
var session = new FakeDebuggerSession(DebuggerRunState.Running);
|
||||
var dispatcher = new DebugCommandDispatcher(session);
|
||||
|
||||
var response = dispatcher.Dispatch(Parse("""{"command":"registers"}"""));
|
||||
|
||||
Assert.False(response.Ok);
|
||||
Assert.Equal("Target is not paused.", response.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispatch_ContinueAndStep_WhenNotPaused_Fail()
|
||||
{
|
||||
var session = new FakeDebuggerSession(DebuggerRunState.Running);
|
||||
var dispatcher = new DebugCommandDispatcher(session);
|
||||
|
||||
var cont = dispatcher.Dispatch(Parse("""{"command":"continue"}"""));
|
||||
var step = dispatcher.Dispatch(Parse("""{"command":"step"}"""));
|
||||
|
||||
Assert.False(cont.Ok);
|
||||
Assert.Equal("Target is not paused.", cont.Error);
|
||||
Assert.False(step.Ok);
|
||||
Assert.Equal("Target is not paused.", step.Error);
|
||||
Assert.Equal(1, session.ContinueCallCount);
|
||||
Assert.Equal(1, session.StepFrameCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispatch_ReadMemory_RejectsLengthAboveCap()
|
||||
{
|
||||
var session = new FakeDebuggerSession();
|
||||
session.SeedMemory(0x1000, new byte[16]);
|
||||
var dispatcher = new DebugCommandDispatcher(session);
|
||||
|
||||
var oversize = dispatcher.Dispatch(Parse(
|
||||
$$"""{"command":"read-memory","address":"0x1000","length":{{MaxMemoryChunk + 1}}}"""));
|
||||
var zero = dispatcher.Dispatch(Parse(
|
||||
"""{"command":"read-memory","address":"0x1000","length":0}"""));
|
||||
|
||||
Assert.False(oversize.Ok);
|
||||
Assert.Contains(MaxMemoryChunk.ToString(), oversize.Error);
|
||||
Assert.False(zero.Ok);
|
||||
Assert.Contains(MaxMemoryChunk.ToString(), zero.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispatch_WriteMemory_RejectsPayloadAboveCap()
|
||||
{
|
||||
var session = new FakeDebuggerSession();
|
||||
var dispatcher = new DebugCommandDispatcher(session);
|
||||
var hex = new string('A', (MaxMemoryChunk + 1) * 2);
|
||||
|
||||
var response = dispatcher.Dispatch(Parse(
|
||||
$$"""{"command":"write-memory","address":"0x1000","bytes":"{{hex}}"}"""));
|
||||
|
||||
Assert.False(response.Ok);
|
||||
Assert.Contains(MaxMemoryChunk.ToString(), response.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispatch_ReadMemory_WhenPaused_ReturnsHexBytes()
|
||||
{
|
||||
var session = new FakeDebuggerSession();
|
||||
session.SeedMemory(0x2000, [0xDE, 0xAD, 0xBE, 0xEF]);
|
||||
var dispatcher = new DebugCommandDispatcher(session);
|
||||
|
||||
var response = dispatcher.Dispatch(Parse(
|
||||
"""{"command":"read-memory","address":"0x2000","length":4}"""));
|
||||
|
||||
Assert.True(response.Ok);
|
||||
Assert.NotNull(response.Data);
|
||||
Assert.Equal("DEADBEEF", GetString(response.Data!, "bytes"));
|
||||
Assert.Equal("0x0000000000002000", GetString(response.Data!, "address"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispatch_BreakpointCrud_HappyPath()
|
||||
{
|
||||
var session = new FakeDebuggerSession();
|
||||
var dispatcher = new DebugCommandDispatcher(session);
|
||||
|
||||
var added = dispatcher.Dispatch(Parse(
|
||||
"""{"command":"add-breakpoint","address":"0x401000","kind":"Execute","length":8}"""));
|
||||
Assert.True(added.Ok);
|
||||
Assert.NotNull(added.Data);
|
||||
|
||||
var breakpoint = GetDict(added.Data!, "breakpoint");
|
||||
Assert.Equal(1, Convert.ToInt32(breakpoint["id"]));
|
||||
Assert.Equal("Execute", Assert.IsType<string>(breakpoint["kind"]));
|
||||
Assert.Equal(1UL, Convert.ToUInt64(breakpoint["length"]));
|
||||
Assert.True(Assert.IsType<bool>(breakpoint["enabled"]));
|
||||
|
||||
var listed = dispatcher.Dispatch(Parse("""{"command":"list-breakpoints"}"""));
|
||||
Assert.True(listed.Ok);
|
||||
var list = Assert.IsAssignableFrom<System.Collections.IEnumerable>(listed.Data!["breakpoints"]);
|
||||
Assert.Single(list.Cast<object?>());
|
||||
|
||||
var disabled = dispatcher.Dispatch(Parse(
|
||||
"""{"command":"enable-breakpoint","id":1,"enabled":false}"""));
|
||||
Assert.True(disabled.Ok);
|
||||
Assert.False(session.Breakpoints.Snapshot().Single().Enabled);
|
||||
|
||||
var removed = dispatcher.Dispatch(Parse(
|
||||
"""{"command":"remove-breakpoint","id":1}"""));
|
||||
Assert.True(removed.Ok);
|
||||
Assert.Empty(session.Breakpoints.Snapshot());
|
||||
|
||||
var missing = dispatcher.Dispatch(Parse(
|
||||
"""{"command":"remove-breakpoint","id":1}"""));
|
||||
Assert.False(missing.Ok);
|
||||
Assert.Contains("No breakpoint", missing.Error, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispatch_PingAndState_Succeed()
|
||||
{
|
||||
var session = new FakeDebuggerSession(DebuggerRunState.Running);
|
||||
var dispatcher = new DebugCommandDispatcher(session);
|
||||
|
||||
var ping = dispatcher.Dispatch(Parse("""{"command":"ping"}"""));
|
||||
var state = dispatcher.Dispatch(Parse("""{"command":"state"}"""));
|
||||
|
||||
Assert.True(ping.Ok);
|
||||
Assert.True(state.Ok);
|
||||
Assert.Equal("Running", GetString(state.Data!, "state"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispatch_Pause_RequestsPause()
|
||||
{
|
||||
var session = new FakeDebuggerSession(DebuggerRunState.Running);
|
||||
var dispatcher = new DebugCommandDispatcher(session);
|
||||
|
||||
var response = dispatcher.Dispatch(Parse("""{"command":"pause"}"""));
|
||||
|
||||
Assert.True(response.Ok);
|
||||
Assert.True(session.PauseRequested);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Dispatch_ParseErrorCommand_SurfacesMessage()
|
||||
{
|
||||
var dispatcher = CreateDispatcher();
|
||||
|
||||
var response = dispatcher.Dispatch(Parse(
|
||||
$$"""{"command":"{{JsonLineDebugProtocol.ParseErrorCommand}}","message":"bad line"}"""));
|
||||
|
||||
Assert.False(response.Ok);
|
||||
Assert.Equal("bad line", response.Error);
|
||||
}
|
||||
|
||||
private static DebugCommandDispatcher CreateDispatcher()
|
||||
=> new(new FakeDebuggerSession());
|
||||
|
||||
private static DebugRequest Parse(string json)
|
||||
{
|
||||
Assert.True(DebugRequest.TryParse(json, out var request, out var error), error);
|
||||
return request;
|
||||
}
|
||||
|
||||
private static string GetString(IReadOnlyDictionary<string, object?> data, string key)
|
||||
{
|
||||
Assert.True(data.TryGetValue(key, out var value));
|
||||
Assert.NotNull(value);
|
||||
return Assert.IsType<string>(value);
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, object?> GetDict(
|
||||
IReadOnlyDictionary<string, object?> data,
|
||||
string key)
|
||||
{
|
||||
Assert.True(data.TryGetValue(key, out var value));
|
||||
Assert.NotNull(value);
|
||||
return Assert.IsAssignableFrom<IReadOnlyDictionary<string, object?>>(value);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Debugger.Protocol;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Debugger.Tests;
|
||||
|
||||
public sealed class DebugRequestTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryParse_AcceptsValidCommandAndNormalizesCase()
|
||||
{
|
||||
Assert.True(DebugRequest.TryParse("""{"command":"Ping"}""", out var request, out var error));
|
||||
Assert.Equal(string.Empty, error);
|
||||
Assert.Equal("ping", request.Command);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_ReadsNumericAndHexAddresses()
|
||||
{
|
||||
Assert.True(DebugRequest.TryParse(
|
||||
"""{"command":"read-memory","address":"0x1000","length":16}""",
|
||||
out var request,
|
||||
out _));
|
||||
|
||||
Assert.True(request.TryGetUInt64("address", out var address));
|
||||
Assert.Equal(0x1000UL, address);
|
||||
Assert.True(request.TryGetInt32("length", out var length));
|
||||
Assert.Equal(16, length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_ReadsHexLengthStrings()
|
||||
{
|
||||
Assert.True(DebugRequest.TryParse(
|
||||
"""{"command":"read-memory","address":4096,"length":"0x20"}""",
|
||||
out var request,
|
||||
out _));
|
||||
|
||||
Assert.True(request.TryGetUInt64("address", out var address));
|
||||
Assert.Equal(4096UL, address);
|
||||
Assert.True(request.TryGetInt32("length", out var length));
|
||||
Assert.Equal(0x20, length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_RejectsMissingCommand()
|
||||
{
|
||||
Assert.False(DebugRequest.TryParse("""{"address":1}""", out _, out var error));
|
||||
Assert.Contains("command", error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_RejectsNonObjectRoot()
|
||||
{
|
||||
Assert.False(DebugRequest.TryParse("""["ping"]""", out _, out var error));
|
||||
Assert.Contains("object", error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryParse_RejectsMalformedJson()
|
||||
{
|
||||
Assert.False(DebugRequest.TryParse("""{command:""", out _, out var error));
|
||||
Assert.Contains("Malformed JSON", error, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetBool_ReadsJsonBooleans()
|
||||
{
|
||||
Assert.True(DebugRequest.TryParse(
|
||||
"""{"command":"enable-breakpoint","id":1,"enabled":false}""",
|
||||
out var request,
|
||||
out _));
|
||||
|
||||
Assert.True(request.TryGetBool("enabled", out var enabled));
|
||||
Assert.False(enabled);
|
||||
Assert.False(request.TryGetBool("missing", out _));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,215 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Core.Cpu.Debugging;
|
||||
using SharpEmu.Debugger;
|
||||
using SharpEmu.Debugger.Breakpoints;
|
||||
using SharpEmu.Debugger.Session;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Debugger.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// In-memory session for dispatcher tests. Tracks run state and an optional
|
||||
/// flat guest memory region without parking a real emulation thread.
|
||||
/// </summary>
|
||||
internal sealed class FakeDebuggerSession : IDebuggerSession
|
||||
{
|
||||
private readonly Dictionary<ulong, byte> _memory = new();
|
||||
private DebugRegisterFile _registers = new(
|
||||
new ulong[16],
|
||||
rip: 0,
|
||||
rflags: 0,
|
||||
fsBase: 0,
|
||||
gsBase: 0);
|
||||
|
||||
public FakeDebuggerSession(DebuggerRunState state = DebuggerRunState.Paused)
|
||||
{
|
||||
State = state;
|
||||
Breakpoints = new BreakpointStore();
|
||||
Hook = NullCpuDebugHook.Instance;
|
||||
}
|
||||
|
||||
public BreakpointStore Breakpoints { get; }
|
||||
|
||||
public ICpuDebugHook Hook { get; }
|
||||
|
||||
public DebuggerRunState State { get; set; }
|
||||
|
||||
public DebugStopEvent? LastStop { get; set; }
|
||||
|
||||
public bool PauseRequested { get; private set; }
|
||||
|
||||
public int ContinueCallCount { get; private set; }
|
||||
|
||||
public int StepFrameCallCount { get; private set; }
|
||||
|
||||
public event EventHandler<DebugStopEvent>? Stopped
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event EventHandler? Resumed
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public event EventHandler? Terminated
|
||||
{
|
||||
add { }
|
||||
remove { }
|
||||
}
|
||||
|
||||
public void SetRegisters(DebugRegisterFile registers) => _registers = registers;
|
||||
|
||||
public void SeedMemory(ulong address, ReadOnlySpan<byte> bytes)
|
||||
{
|
||||
for (var i = 0; i < bytes.Length; i++)
|
||||
{
|
||||
_memory[address + (ulong)i] = bytes[i];
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetRegisters(out DebugRegisterFile registers)
|
||||
{
|
||||
if (State != DebuggerRunState.Paused)
|
||||
{
|
||||
registers = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
registers = _registers;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TrySetRegister(DebugRegisterId id, ulong value)
|
||||
{
|
||||
if (State != DebuggerRunState.Paused)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var gpr = new ulong[16];
|
||||
for (var i = 0; i < 16; i++)
|
||||
{
|
||||
gpr[i] = _registers[(CpuRegister)i];
|
||||
}
|
||||
|
||||
var rip = _registers.Rip;
|
||||
var rflags = _registers.Rflags;
|
||||
var fsBase = _registers.FsBase;
|
||||
var gsBase = _registers.GsBase;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case DebugRegisterId.Rip:
|
||||
rip = value;
|
||||
break;
|
||||
case DebugRegisterId.Rflags:
|
||||
rflags = value;
|
||||
break;
|
||||
case DebugRegisterId.FsBase:
|
||||
fsBase = value;
|
||||
break;
|
||||
case DebugRegisterId.GsBase:
|
||||
gsBase = value;
|
||||
break;
|
||||
default:
|
||||
if (!id.IsGeneralPurpose())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
gpr[(int)id] = value;
|
||||
break;
|
||||
}
|
||||
|
||||
_registers = new DebugRegisterFile(gpr, rip, rflags, fsBase, gsBase);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryReadMemory(ulong address, Span<byte> destination)
|
||||
{
|
||||
if (State != DebuggerRunState.Paused)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < destination.Length; i++)
|
||||
{
|
||||
if (!_memory.TryGetValue(address + (ulong)i, out var value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
destination[i] = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryWriteMemory(ulong address, ReadOnlySpan<byte> source)
|
||||
{
|
||||
if (State != DebuggerRunState.Paused)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
SeedMemory(address, source);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryReadXmm(int registerIndex, out ulong low, out ulong high)
|
||||
{
|
||||
low = 0;
|
||||
high = 0;
|
||||
return State == DebuggerRunState.Paused;
|
||||
}
|
||||
|
||||
public bool Continue()
|
||||
{
|
||||
ContinueCallCount++;
|
||||
if (State != DebuggerRunState.Paused)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
State = DebuggerRunState.Running;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool StepFrame()
|
||||
{
|
||||
StepFrameCallCount++;
|
||||
if (State != DebuggerRunState.Paused)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
State = DebuggerRunState.Running;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RequestPause() => PauseRequested = true;
|
||||
|
||||
public void NotifyTerminated() => State = DebuggerRunState.Terminated;
|
||||
|
||||
private sealed class NullCpuDebugHook : ICpuDebugHook
|
||||
{
|
||||
public static readonly NullCpuDebugHook Instance = new();
|
||||
|
||||
public void OnFrameEnter(ICpuDebugFrame frame)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnFrameExit(ICpuDebugFrame frame, OrbisGen2Result result)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnStall(ICpuDebugFrame frame, CpuStallInfo info)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
<!--
|
||||
Copyright (C) 2026 SharpEmu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
-->
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\SharpEmu.Debugger\SharpEmu.Debugger.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
Loading…
Reference in New Issue