mirror of
https://github.com/par274/sharpemu.git
synced 2026-08-07 10:29:46 +08:00
Add live debugger frontend and mutex stall recovery (#383)
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Core.Cpu.Debugging;
|
||||
using SharpEmu.Debugger.Breakpoints;
|
||||
using SharpEmu.HLE;
|
||||
|
||||
namespace SharpEmu.Debugger.Session;
|
||||
|
||||
/// <summary>
|
||||
/// Describes a stop delivered to debugger clients: why the target stopped,
|
||||
/// where, and the register snapshot at that point.
|
||||
/// </summary>
|
||||
public sealed class DebugStopEvent
|
||||
{
|
||||
public DebugStopEvent(
|
||||
DebugStopReason reason,
|
||||
DebugRegisterFile registers,
|
||||
CpuDebugFrameKind frameKind,
|
||||
string frameLabel,
|
||||
Breakpoint? breakpoint = null,
|
||||
OrbisGen2Result? result = null,
|
||||
string? detail = null,
|
||||
string? opcodeBytes = null,
|
||||
CpuStallInfo? stallInfo = null)
|
||||
{
|
||||
Reason = reason;
|
||||
Registers = registers;
|
||||
FrameKind = frameKind;
|
||||
FrameLabel = frameLabel ?? string.Empty;
|
||||
Breakpoint = breakpoint;
|
||||
Result = result;
|
||||
Detail = detail;
|
||||
OpcodeBytes = opcodeBytes;
|
||||
StallInfo = stallInfo;
|
||||
}
|
||||
|
||||
public DebugStopReason Reason { get; }
|
||||
|
||||
/// <summary>The instruction pointer where the target stopped.</summary>
|
||||
public ulong Address => Registers.Rip;
|
||||
|
||||
public DebugRegisterFile Registers { get; }
|
||||
|
||||
public CpuDebugFrameKind FrameKind { get; }
|
||||
|
||||
public string FrameLabel { get; }
|
||||
|
||||
/// <summary>The breakpoint responsible for the stop, when applicable.</summary>
|
||||
public Breakpoint? Breakpoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The frame result for a <see cref="DebugStopReason.Fault"/> stop; null for
|
||||
/// non-fault stops.
|
||||
/// </summary>
|
||||
public OrbisGen2Result? Result { get; }
|
||||
|
||||
/// <summary>A human-readable summary of a fault, when applicable.</summary>
|
||||
public string? Detail { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A hex preview of the bytes at <see cref="Address"/> (the faulting
|
||||
/// instruction), when the stop is a fault and the bytes were readable.
|
||||
/// </summary>
|
||||
public string? OpcodeBytes { get; }
|
||||
|
||||
/// <summary>Structured backend evidence for a stall stop.</summary>
|
||||
public CpuStallInfo? StallInfo { get; }
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Debugger.Session;
|
||||
|
||||
/// <summary>Why the target stopped and handed control to the debugger.</summary>
|
||||
public enum DebugStopReason
|
||||
{
|
||||
/// <summary>Stopped at the configured entry point before running any frame.</summary>
|
||||
EntryPoint,
|
||||
|
||||
/// <summary>An execution breakpoint was hit.</summary>
|
||||
Breakpoint,
|
||||
|
||||
/// <summary>A data watchpoint was hit.</summary>
|
||||
Watchpoint,
|
||||
|
||||
/// <summary>A single-step (frame step) request completed.</summary>
|
||||
Step,
|
||||
|
||||
/// <summary>A client-requested pause took effect.</summary>
|
||||
Pause,
|
||||
|
||||
/// <summary>The guest raised a fault or trap.</summary>
|
||||
Fault,
|
||||
|
||||
/// <summary>
|
||||
/// The backend detected an execution stall (for example a mutex spin loop /
|
||||
/// livelock) with no forward progress.
|
||||
/// </summary>
|
||||
Stall,
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Debugger.Session;
|
||||
|
||||
/// <summary>The execution state of a debugged target as seen by the debugger.</summary>
|
||||
public enum DebuggerRunState
|
||||
{
|
||||
/// <summary>No guest frame has entered the debugger yet.</summary>
|
||||
Detached,
|
||||
|
||||
/// <summary>The guest is executing and cannot be inspected safely.</summary>
|
||||
Running,
|
||||
|
||||
/// <summary>
|
||||
/// The guest is parked at a frame boundary. Registers and memory can be
|
||||
/// read and written, and breakpoints can be edited.
|
||||
/// </summary>
|
||||
Paused,
|
||||
|
||||
/// <summary>The guest has finished; no further frames will run.</summary>
|
||||
Terminated,
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Core.Cpu.Debugging;
|
||||
using SharpEmu.Debugger.Breakpoints;
|
||||
using SharpEmu.HLE;
|
||||
using SharpEmu.Logging;
|
||||
|
||||
namespace SharpEmu.Debugger.Session;
|
||||
|
||||
/// <summary>
|
||||
/// The default <see cref="IDebuggerSession"/>. It plugs into the CPU dispatcher
|
||||
/// as an <see cref="ICpuDebugHook"/>: when a frame boundary warrants a stop it
|
||||
/// parks the emulation thread inside <see cref="ICpuDebugHook.OnFrameEnter"/>
|
||||
/// while a debug client inspects and edits state, then releases it on
|
||||
/// continue/step.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Pausing works by blocking the emulation thread on <see cref="_resumeGate"/>
|
||||
/// from within the hook call. Because that thread is the one that owns the guest
|
||||
/// context, register and memory accessors are safe to serve from other threads
|
||||
/// only while it is parked — which is exactly the <see cref="DebuggerRunState.Paused"/>
|
||||
/// window the accessors gate on.
|
||||
/// </remarks>
|
||||
public sealed class DebuggerSession : IDebuggerSession, ICpuDebugHook
|
||||
{
|
||||
private static readonly SharpEmuLogger Log = SharpEmuLog.For("SharpEmu.Debugger");
|
||||
|
||||
private readonly object _sync = new();
|
||||
private readonly ManualResetEventSlim _resumeGate = new(initialState: false);
|
||||
private readonly DebuggerSessionOptions _options;
|
||||
|
||||
private ICpuDebugFrame? _currentFrame;
|
||||
private DebuggerRunState _state = DebuggerRunState.Detached;
|
||||
private DebugStopEvent? _lastStop;
|
||||
private bool _seenFirstFrame;
|
||||
private bool _pausePending;
|
||||
private bool _stepPending;
|
||||
|
||||
public DebuggerSession(DebuggerSessionOptions? options = null)
|
||||
{
|
||||
_options = options ?? new DebuggerSessionOptions();
|
||||
Breakpoints = new BreakpointStore();
|
||||
}
|
||||
|
||||
public BreakpointStore Breakpoints { get; }
|
||||
|
||||
public ICpuDebugHook Hook => this;
|
||||
|
||||
public event EventHandler<DebugStopEvent>? Stopped;
|
||||
|
||||
public event EventHandler? Resumed;
|
||||
|
||||
public event EventHandler? Terminated;
|
||||
|
||||
public DebuggerRunState State
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _state;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DebugStopEvent? LastStop
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return _lastStop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ICpuDebugHook.OnFrameEnter(ICpuDebugFrame frame)
|
||||
{
|
||||
DebugStopEvent? stop;
|
||||
lock (_sync)
|
||||
{
|
||||
_currentFrame = frame;
|
||||
var firstFrame = !_seenFirstFrame;
|
||||
_seenFirstFrame = true;
|
||||
|
||||
var reason = ResolveStopReason(frame, firstFrame, out var breakpoint);
|
||||
if (reason is null)
|
||||
{
|
||||
_state = DebuggerRunState.Running;
|
||||
return;
|
||||
}
|
||||
|
||||
_state = DebuggerRunState.Paused;
|
||||
_lastStop = new DebugStopEvent(
|
||||
reason.Value,
|
||||
DebugRegisterFile.Capture(frame),
|
||||
frame.Kind,
|
||||
frame.Label,
|
||||
breakpoint);
|
||||
stop = _lastStop;
|
||||
_resumeGate.Reset();
|
||||
}
|
||||
|
||||
Log.Debug($"Debugger stop: {stop!.Reason} at 0x{stop.Address:X16} ({stop.FrameLabel})");
|
||||
Stopped?.Invoke(this, stop);
|
||||
|
||||
// Park the emulation thread until a client resumes the target. The frame
|
||||
// stays live and inspectable for the whole wait.
|
||||
_resumeGate.Wait();
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (_state != DebuggerRunState.Terminated)
|
||||
{
|
||||
_state = DebuggerRunState.Running;
|
||||
}
|
||||
}
|
||||
|
||||
Resumed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
void ICpuDebugHook.OnFrameExit(ICpuDebugFrame frame, OrbisGen2Result result)
|
||||
{
|
||||
DebugStopEvent? stop = null;
|
||||
lock (_sync)
|
||||
{
|
||||
if (_options.BreakOnFault &&
|
||||
result != OrbisGen2Result.ORBIS_GEN2_OK &&
|
||||
_state != DebuggerRunState.Terminated)
|
||||
{
|
||||
// Parking here keeps the post-fault frame inspectable.
|
||||
_currentFrame = frame;
|
||||
_state = DebuggerRunState.Paused;
|
||||
_lastStop = BuildFaultStop(frame, result);
|
||||
stop = _lastStop;
|
||||
_resumeGate.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
if (stop is not null)
|
||||
{
|
||||
Log.Debug($"Debugger fault stop: {stop.Result} at 0x{stop.Address:X16} ({stop.FrameLabel})");
|
||||
Stopped?.Invoke(this, stop);
|
||||
_resumeGate.Wait();
|
||||
Resumed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (ReferenceEquals(_currentFrame, frame))
|
||||
{
|
||||
_currentFrame = null;
|
||||
}
|
||||
|
||||
if (_state != DebuggerRunState.Terminated)
|
||||
{
|
||||
_state = DebuggerRunState.Running;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ICpuDebugHook.OnStall(ICpuDebugFrame frame, CpuStallInfo info)
|
||||
{
|
||||
if (!_options.BreakOnStall)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DebugStopEvent? stop = null;
|
||||
lock (_sync)
|
||||
{
|
||||
if (_state == DebuggerRunState.Terminated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentFrame = frame;
|
||||
_state = DebuggerRunState.Paused;
|
||||
_lastStop = new DebugStopEvent(
|
||||
DebugStopReason.Stall,
|
||||
DebugRegisterFile.Capture(frame),
|
||||
frame.Kind,
|
||||
frame.Label,
|
||||
breakpoint: null,
|
||||
result: null,
|
||||
detail: info.Detail,
|
||||
opcodeBytes: ReadOpcodePreview(frame, info.InstructionPointer, 16),
|
||||
stallInfo: info);
|
||||
stop = _lastStop;
|
||||
_resumeGate.Reset();
|
||||
}
|
||||
|
||||
Log.Debug($"Debugger stall stop: {info.Kind} nid={info.Nid} at 0x{info.InstructionPointer:X16}");
|
||||
Stopped?.Invoke(this, stop);
|
||||
_resumeGate.Wait();
|
||||
|
||||
lock (_sync)
|
||||
{
|
||||
if (ReferenceEquals(_currentFrame, frame))
|
||||
{
|
||||
_currentFrame = null;
|
||||
}
|
||||
|
||||
if (_state != DebuggerRunState.Terminated)
|
||||
{
|
||||
_state = DebuggerRunState.Running;
|
||||
}
|
||||
}
|
||||
|
||||
Resumed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private static DebugStopEvent BuildFaultStop(ICpuDebugFrame frame, OrbisGen2Result result)
|
||||
{
|
||||
var opcodeBytes = ReadOpcodePreview(frame, frame.Rip, 16);
|
||||
var detail = $"result={result}";
|
||||
if (opcodeBytes is not null)
|
||||
{
|
||||
detail += $", bytes={opcodeBytes}";
|
||||
}
|
||||
|
||||
return new DebugStopEvent(
|
||||
DebugStopReason.Fault,
|
||||
DebugRegisterFile.Capture(frame),
|
||||
frame.Kind,
|
||||
frame.Label,
|
||||
breakpoint: null,
|
||||
result: result,
|
||||
detail: detail,
|
||||
opcodeBytes: opcodeBytes);
|
||||
}
|
||||
|
||||
private static string? ReadOpcodePreview(ICpuDebugFrame frame, ulong address, int maxBytes)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[maxBytes];
|
||||
var count = 0;
|
||||
for (; count < maxBytes; count++)
|
||||
{
|
||||
if (!frame.Memory.TryRead(address + (ulong)count, buffer.Slice(count, 1)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return count == 0 ? null : Convert.ToHexString(buffer[..count]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signals that the whole guest run has finished. Releases any parked
|
||||
/// emulation thread and moves the session to
|
||||
/// <see cref="DebuggerRunState.Terminated"/>.
|
||||
/// </summary>
|
||||
public void NotifyTerminated()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
_state = DebuggerRunState.Terminated;
|
||||
_currentFrame = null;
|
||||
}
|
||||
|
||||
_resumeGate.Set();
|
||||
Terminated?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private DebugStopReason? ResolveStopReason(ICpuDebugFrame frame, bool firstFrame, out Breakpoint? breakpoint)
|
||||
{
|
||||
breakpoint = null;
|
||||
if (_pausePending)
|
||||
{
|
||||
_pausePending = false;
|
||||
return DebugStopReason.Pause;
|
||||
}
|
||||
|
||||
if (_stepPending)
|
||||
{
|
||||
_stepPending = false;
|
||||
return DebugStopReason.Step;
|
||||
}
|
||||
|
||||
var hit = Breakpoints.FindExecuteHit(frame.EntryPoint);
|
||||
if (hit is not null)
|
||||
{
|
||||
breakpoint = hit;
|
||||
return DebugStopReason.Breakpoint;
|
||||
}
|
||||
|
||||
if (_options.StopAtEntry && firstFrame)
|
||||
{
|
||||
return DebugStopReason.EntryPoint;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool TryGetRegisters(out DebugRegisterFile registers)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (!IsPausedWithFrame(out var frame))
|
||||
{
|
||||
registers = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
registers = DebugRegisterFile.Capture(frame);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TrySetRegister(DebugRegisterId id, ulong value)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (!IsPausedWithFrame(out var frame))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (id.IsGeneralPurpose())
|
||||
{
|
||||
frame.SetRegister(id.ToCpuRegister(), value);
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case DebugRegisterId.Rip:
|
||||
frame.Rip = value;
|
||||
return true;
|
||||
case DebugRegisterId.Rflags:
|
||||
frame.Rflags = value;
|
||||
return true;
|
||||
default:
|
||||
// FS/GS bases are owned by the TLS setup and are read-only here.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryReadMemory(ulong address, Span<byte> destination)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return IsPausedWithFrame(out var frame) && frame.Memory.TryRead(address, destination);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryWriteMemory(ulong address, ReadOnlySpan<byte> source)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
return IsPausedWithFrame(out var frame) && frame.Memory.TryWrite(address, source);
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryReadXmm(int registerIndex, out ulong low, out ulong high)
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (!IsPausedWithFrame(out var frame) || (uint)registerIndex >= 16)
|
||||
{
|
||||
low = 0;
|
||||
high = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
frame.GetXmm(registerIndex, out low, out high);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Continue()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_state != DebuggerRunState.Paused)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_resumeGate.Set();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool StepFrame()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_state != DebuggerRunState.Paused)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_stepPending = true;
|
||||
_resumeGate.Set();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void RequestPause()
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
if (_state == DebuggerRunState.Running)
|
||||
{
|
||||
_pausePending = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPausedWithFrame(out ICpuDebugFrame frame)
|
||||
{
|
||||
// Callers must hold _sync.
|
||||
if (_state == DebuggerRunState.Paused && _currentFrame is not null)
|
||||
{
|
||||
frame = _currentFrame;
|
||||
return true;
|
||||
}
|
||||
|
||||
frame = null!;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Debugger.Session;
|
||||
|
||||
/// <summary>Configuration for a <see cref="DebuggerSession"/>.</summary>
|
||||
public sealed class DebuggerSessionOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// When true, the session pauses at the first frame it observes so a client
|
||||
/// can attach breakpoints before the guest runs. Defaults to true, matching
|
||||
/// the "stop at entry" behaviour most debuggers expose.
|
||||
/// </summary>
|
||||
public bool StopAtEntry { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// When true, the session pauses when a frame ends with a non-OK result (a
|
||||
/// CPU trap, memory fault, or unimplemented path) so a client can inspect the
|
||||
/// post-fault register/memory state before the frame is torn down. Defaults
|
||||
/// to true. The stop reports <see cref="DebugStopReason.Fault"/>.
|
||||
/// </summary>
|
||||
public bool BreakOnFault { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// When true, the session pauses when the backend detects an execution stall
|
||||
/// (a mutex spin loop / livelock) before the guest is forced out of the loop,
|
||||
/// so a client can inspect the stalled state. Defaults to true. The stop
|
||||
/// reports <see cref="DebugStopReason.Stall"/>.
|
||||
/// </summary>
|
||||
public bool BreakOnStall { get; init; } = true;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
namespace SharpEmu.Debugger.Session;
|
||||
|
||||
/// <summary>
|
||||
/// The inspection and control surface a debugger front-end (for example a
|
||||
/// network server) drives. Register and memory accessors succeed only while the
|
||||
/// target is <see cref="DebuggerRunState.Paused"/>; they return <c>false</c>
|
||||
/// otherwise so callers never read torn state from a running guest.
|
||||
/// </summary>
|
||||
public interface IDebugTarget
|
||||
{
|
||||
/// <summary>The current execution state.</summary>
|
||||
DebuggerRunState State { get; }
|
||||
|
||||
/// <summary>The most recent stop, or null if the target has not stopped yet.</summary>
|
||||
DebugStopEvent? LastStop { get; }
|
||||
|
||||
/// <summary>Reads the integer register file. Fails unless paused.</summary>
|
||||
bool TryGetRegisters(out DebugRegisterFile registers);
|
||||
|
||||
/// <summary>Writes a single register. Fails unless paused.</summary>
|
||||
bool TrySetRegister(DebugRegisterId id, ulong value);
|
||||
|
||||
/// <summary>Reads guest memory into <paramref name="destination"/>. Fails unless paused.</summary>
|
||||
bool TryReadMemory(ulong address, Span<byte> destination);
|
||||
|
||||
/// <summary>Writes guest memory from <paramref name="source"/>. Fails unless paused.</summary>
|
||||
bool TryWriteMemory(ulong address, ReadOnlySpan<byte> source);
|
||||
|
||||
/// <summary>Reads a 128-bit XMM register. Fails unless paused.</summary>
|
||||
bool TryReadXmm(int registerIndex, out ulong low, out ulong high);
|
||||
|
||||
/// <summary>
|
||||
/// Resumes a paused target. Returns false when the target was not paused.
|
||||
/// </summary>
|
||||
bool Continue();
|
||||
|
||||
/// <summary>
|
||||
/// Resumes a paused target and stops again at the next frame boundary.
|
||||
/// Returns false when the target was not paused.
|
||||
/// </summary>
|
||||
bool StepFrame();
|
||||
|
||||
/// <summary>
|
||||
/// Requests that a running target stop at the next frame boundary. Has no
|
||||
/// effect if the target is already paused or terminated.
|
||||
/// </summary>
|
||||
void RequestPause();
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Core.Cpu.Debugging;
|
||||
using SharpEmu.Debugger.Breakpoints;
|
||||
|
||||
namespace SharpEmu.Debugger.Session;
|
||||
|
||||
/// <summary>
|
||||
/// The debugger's coordination point. It bridges the CPU dispatcher seam
|
||||
/// (<see cref="Hook"/>) to the inspection surface (<see cref="IDebugTarget"/>),
|
||||
/// owns breakpoint state, and raises lifecycle events that a server relays to
|
||||
/// connected clients.
|
||||
/// </summary>
|
||||
public interface IDebuggerSession : IDebugTarget
|
||||
{
|
||||
/// <summary>The breakpoints armed for this session.</summary>
|
||||
BreakpointStore Breakpoints { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The dispatcher-facing hook. Assign this to
|
||||
/// <c>SharpEmuRuntimeOptions.DebugHook</c> so guest frames are routed through
|
||||
/// the session.
|
||||
/// </summary>
|
||||
ICpuDebugHook Hook { get; }
|
||||
|
||||
/// <summary>Raised on the emulation thread each time the target stops.</summary>
|
||||
event EventHandler<DebugStopEvent>? Stopped;
|
||||
|
||||
/// <summary>Raised when a paused target resumes.</summary>
|
||||
event EventHandler? Resumed;
|
||||
|
||||
/// <summary>Raised once the target has terminated.</summary>
|
||||
event EventHandler? Terminated;
|
||||
|
||||
/// <summary>
|
||||
/// Signals that the guest run has finished. Releases any parked emulation
|
||||
/// thread and transitions the session to
|
||||
/// <see cref="DebuggerRunState.Terminated"/>. Hosts call this after the
|
||||
/// runtime returns.
|
||||
/// </summary>
|
||||
void NotifyTerminated();
|
||||
}
|
||||
Reference in New Issue
Block a user