sharpemu/tests/SharpEmu.Debugger.Tests/FakeDebuggerSession.cs

216 lines
5.0 KiB
C#

// 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)
{
}
}
}