diff --git a/src/OneWare.Essentials/Debugger/Entities/DebugLaunchRequest.cs b/src/OneWare.Essentials/Debugger/Entities/DebugLaunchRequest.cs
new file mode 100644
index 00000000..ff05b612
--- /dev/null
+++ b/src/OneWare.Essentials/Debugger/Entities/DebugLaunchRequest.cs
@@ -0,0 +1,20 @@
+namespace OneWare.Essentials.Debugger.Entities;
+
+///
+/// What the user asked to debug. A request with neither an executable nor a remote endpoint is
+/// valid — the backend then comes up without a target, which is what makes its command line
+/// usable for checking the installation or attaching by hand.
+/// is the whole remote seam: a plugin that brings up a target
+/// passes the address it is listening on and never learns which backend connects.
+///
+/// Identifies the backend, e.g. GDB.
+///
+/// Path to the executable, e.g. an ELF file. Carries the program and its debug symbols.
+///
+/// Remote stub address, e.g. localhost:1234.
+/// Working directory for the debug session.
+public sealed record DebugLaunchRequest(
+ string AdapterId,
+ string? ExecutablePath = null,
+ string? RemoteEndpoint = null,
+ string? WorkingDirectory = null);
diff --git a/src/OneWare.Essentials/Debugger/Entities/DebugSessionState.cs b/src/OneWare.Essentials/Debugger/Entities/DebugSessionState.cs
new file mode 100644
index 00000000..2ceea45e
--- /dev/null
+++ b/src/OneWare.Essentials/Debugger/Entities/DebugSessionState.cs
@@ -0,0 +1,40 @@
+namespace OneWare.Essentials.Debugger.Entities;
+
+///
+/// Everything the UI knows about the session and the target at one point in time.
+/// A snapshot rather than a lifecycle enum plus a pile of separate events — the session
+/// publishes a complete replacement on every change, so a panel binds one thing and cannot end
+/// up showing registers from before the last step next to a frame from after it.
+///
+public sealed record DebugSessionState
+{
+ ///
+ /// No session, or a session that has ended. Also the state a session starts out in.
+ ///
+ public static DebugSessionState Empty { get; } = new();
+
+ ///
+ /// while the target is executing — nothing can be inspected and only
+ /// pausing is meaningful.
+ ///
+ public bool IsRunning { get; init; }
+
+ ///
+ /// Where the target is halted, or while it runs.
+ ///
+ public DebugStackFrame? CurrentFrame { get; init; }
+
+ ///
+ /// Register contents as of the last halt. Empty while the target runs, and empty for a
+ /// backend that cannot read registers — the panel then simply shows nothing, which is what a
+ /// separate capability flag would have told it to do anyway.
+ ///
+ public IReadOnlyList Registers { get; init; } = [];
+
+ ///
+ /// Locals of as of the last halt. Empty while the target runs,
+ /// and empty without debug symbols — a program linked without them has no names to report,
+ /// only registers.
+ ///
+ public IReadOnlyList Locals { get; init; } = [];
+}
diff --git a/src/OneWare.Essentials/Debugger/Entities/DebugStackFrame.cs b/src/OneWare.Essentials/Debugger/Entities/DebugStackFrame.cs
new file mode 100644
index 00000000..b076e0c6
--- /dev/null
+++ b/src/OneWare.Essentials/Debugger/Entities/DebugStackFrame.cs
@@ -0,0 +1,20 @@
+namespace OneWare.Essentials.Debugger.Entities;
+
+///
+/// Where the target is halted.
+///
+/// Name of the function, if the backend reported one.
+///
+/// Absolute source path, or if the address could not be mapped.
+/// The editor only jumps to the source location when this is set.
+///
+/// One-based line number, or 0 if unknown.
+///
+/// Program counter as formatted by the backend, e.g. 0x00000108. The only location
+/// available when no debug symbols are present.
+///
+public sealed record DebugStackFrame(
+ string? Function,
+ string? File,
+ int Line,
+ string? Address);
diff --git a/src/OneWare.Essentials/Debugger/Entities/DebugVariable.cs b/src/OneWare.Essentials/Debugger/Entities/DebugVariable.cs
new file mode 100644
index 00000000..bada7288
--- /dev/null
+++ b/src/OneWare.Essentials/Debugger/Entities/DebugVariable.cs
@@ -0,0 +1,12 @@
+namespace OneWare.Essentials.Debugger.Entities;
+
+///
+/// A local variable of the frame the target is halted in.
+///
+/// As it appears in the source.
+/// Formatted by the backend; the UI displays the string unchanged.
+/// Declared type, or if the backend did not report one.
+public sealed record DebugVariable(
+ string Name,
+ string Value,
+ string? TypeName);
diff --git a/src/OneWare.Essentials/Debugger/Entities/RegisterValue.cs b/src/OneWare.Essentials/Debugger/Entities/RegisterValue.cs
new file mode 100644
index 00000000..15288c7e
--- /dev/null
+++ b/src/OneWare.Essentials/Debugger/Entities/RegisterValue.cs
@@ -0,0 +1,10 @@
+namespace OneWare.Essentials.Debugger.Entities;
+
+///
+/// A single register as read from the target.
+///
+/// As reported by the target, e.g. sp or pc.
+/// Formatted by the backend; the UI displays the string unchanged.
+public sealed record RegisterValue(
+ string Name,
+ string Value);
diff --git a/src/OneWare.Essentials/Debugger/Interfaces/IDebugAdapter.cs b/src/OneWare.Essentials/Debugger/Interfaces/IDebugAdapter.cs
new file mode 100644
index 00000000..17892d44
--- /dev/null
+++ b/src/OneWare.Essentials/Debugger/Interfaces/IDebugAdapter.cs
@@ -0,0 +1,34 @@
+using OneWare.Essentials.Debugger.Entities;
+
+namespace OneWare.Essentials.Debugger.Interfaces;
+
+///
+/// More of a session factory than a real adapter. The name is borrowed from VS Code's DAP
+/// (Debug Adapter Protocol), where "debug adapter" is the term for the backend itself.
+/// is synchronous by intent, so that everything which can block or
+/// fail happens in — one failure path instead of two.
+///
+public interface IDebugAdapter
+{
+ ///
+ /// Stable identifier, referenced by .
+ ///
+ public string Id { get; }
+
+ ///
+ /// Shown when the user picks a backend.
+ ///
+ public string DisplayName { get; }
+
+ ///
+ /// Returns if this adapter can handle the given request.
+ /// Must be cheap and free of side effects — it decides whether to offer this adapter at all.
+ ///
+ public bool CanLaunch(DebugLaunchRequest launchRequest);
+
+ ///
+ /// Only constructs the session object; launching happens inside
+ /// .
+ ///
+ public IDebugSession CreateSession(DebugLaunchRequest launchRequest);
+}
\ No newline at end of file
diff --git a/src/OneWare.Essentials/Debugger/Interfaces/IDebugLaunchProvider.cs b/src/OneWare.Essentials/Debugger/Interfaces/IDebugLaunchProvider.cs
new file mode 100644
index 00000000..7beb1871
--- /dev/null
+++ b/src/OneWare.Essentials/Debugger/Interfaces/IDebugLaunchProvider.cs
@@ -0,0 +1,36 @@
+using OneWare.Essentials.Debugger.Entities;
+
+namespace OneWare.Essentials.Debugger.Interfaces;
+
+///
+/// Analogous to , but as the preparation step. The core asks
+/// which provider fits the current project, has it prepare, and starts with whatever request
+/// comes back. That keeps the entry point in the generic UI while everything target-specific
+/// stays in the plugin.
+///
+public interface IDebugLaunchProvider
+{
+ ///
+ /// Shown in the launch selection of the debug panel.
+ ///
+ public string DisplayName { get; }
+
+ ///
+ /// Returns if this provider can handle the active project.
+ /// Must be cheap and free of side effects — the UI calls it to fill the selection.
+ ///
+ public bool CanPrepare();
+
+ ///
+ /// Brings the target up and returns the matching launch request.
+ /// Returns if preparation failed or was cancelled; the user has
+ /// already been notified in that case.
+ ///
+ public Task PrepareAsync(CancellationToken ct = default);
+
+ ///
+ /// Releases whatever claimed. Also runs when the session ended
+ /// on its own.
+ ///
+ public Task CleanupAsync();
+}
diff --git a/src/OneWare.Essentials/Debugger/Interfaces/IDebugSession.cs b/src/OneWare.Essentials/Debugger/Interfaces/IDebugSession.cs
new file mode 100644
index 00000000..f2efe466
--- /dev/null
+++ b/src/OneWare.Essentials/Debugger/Interfaces/IDebugSession.cs
@@ -0,0 +1,114 @@
+using OneWare.Essentials.Debugger.Entities;
+using OneWare.Essentials.EditorExtensions;
+
+namespace OneWare.Essentials.Debugger.Interfaces;
+
+///
+/// Encapsulates one debug session and exposes to the UI.
+/// Backend syntax does not cross this interface (no GDB/MI).
+/// is the one deliberate exception — it backs the console's
+/// command line. Control commands return no result: what the target did afterwards arrives
+/// through , which is also how an unsolicited halt (e.g. a breakpoint
+/// being hit) reaches the panels.
+///
+public interface IDebugSession
+{
+ ///
+ /// Identifies the backend, e.g. GDB.
+ ///
+ public string AdapterId { get; }
+
+ ///
+ /// Latest published state.
+ ///
+ public DebugSessionState State { get; }
+
+ ///
+ /// Fired whenever is replaced. May arrive on any thread.
+ ///
+ public event EventHandler? StateChanged;
+
+ ///
+ /// Output of the debugged program, and readable messages from the backend.
+ ///
+ public event EventHandler? OutputReceived;
+
+ ///
+ /// Every command sent to the backend, so the console can echo it.
+ ///
+ public event EventHandler? CommandSent;
+
+ ///
+ /// The backend process ended, whether asked to or not.
+ ///
+ public event EventHandler? Exited;
+
+ ///
+ /// Brings the backend up and, for a remote request, attaches to the stub.
+ /// Returns if it did not come up and the session is unusable.
+ ///
+ public Task StartAsync();
+
+ ///
+ /// Starts the program. Separate from — an attached target is
+ /// already loaded and only needs resuming.
+ ///
+ public Task RunAsync();
+
+ ///
+ /// Resumes the halted target.
+ ///
+ public Task ContinueAsync();
+
+ ///
+ /// Halts the running target.
+ ///
+ public Task PauseAsync();
+
+ ///
+ /// Steps one source line, entering called functions.
+ ///
+ public Task StepIntoAsync();
+
+ ///
+ /// Steps one source line, stepping over called functions.
+ ///
+ public Task StepOverAsync();
+
+ ///
+ /// Runs until the current function returns.
+ ///
+ public Task StepOutAsync();
+
+ ///
+ /// Arms a breakpoint on the target.
+ /// Returns if the target refused it, e.g. because it ran out of
+ /// hardware breakpoints.
+ ///
+ public Task SetBreakpointAsync(BreakPoint breakpoint);
+
+ ///
+ /// Removes a previously armed breakpoint.
+ ///
+ public Task RemoveBreakpointAsync(BreakPoint breakpoint);
+
+ ///
+ /// Reads memory from the target. is whatever the backend accepts —
+ /// a literal such as 0x2001ff80, or an expression like &buffer when symbols
+ /// exist. Returns if the memory could not be read; a running target
+ /// cannot be read, so call only while halted.
+ ///
+ public Task ReadMemoryAsync(string address, int byteCount);
+
+ ///
+ /// Sends a command verbatim to the backend. The response arrives through
+ /// , like any other backend output.
+ ///
+ public Task SendRawCommandAsync(string command);
+
+ ///
+ /// Tears the backend down. Synchronous and best-effort — also runs on application shutdown,
+ /// where there is nothing left to await on.
+ ///
+ public void Stop();
+}
\ No newline at end of file
diff --git a/src/OneWare.Essentials/Debugger/Interfaces/IDebuggerService.cs b/src/OneWare.Essentials/Debugger/Interfaces/IDebuggerService.cs
new file mode 100644
index 00000000..defc105b
--- /dev/null
+++ b/src/OneWare.Essentials/Debugger/Interfaces/IDebuggerService.cs
@@ -0,0 +1,116 @@
+using OneWare.Essentials.Debugger.Entities;
+
+namespace OneWare.Essentials.Debugger.Interfaces;
+
+///
+/// The service a plugin resolves in order to take part in debugging. The dependency runs one
+/// way only — plugins depend on these contracts, the core never learns that a given plugin
+/// exists.
+///
+public interface IDebuggerService
+{
+ ///
+ /// Registered backends, including the core's own.
+ ///
+ public IReadOnlyList Adapters { get; }
+
+ ///
+ /// Registered launch providers. Whoever fits the active project shows up in the launch
+ /// selection of the debug panel.
+ ///
+ public IReadOnlyList LaunchProviders { get; }
+
+ ///
+ /// The active session, or if none is running.
+ ///
+ public IDebugSession? CurrentSession { get; }
+
+ ///
+ /// State of the active session, or if none is running.
+ ///
+ public DebugSessionState State { get; }
+
+ ///
+ /// while a session is running. Gates the breakpoint margin in the
+ /// editor.
+ ///
+ public bool IsActive { get; }
+
+ ///
+ /// Fired when , , or
+ /// changed. Always raised on the UI thread, so handlers can touch bound collections directly.
+ ///
+ public event EventHandler? StateChanged;
+
+ ///
+ /// Registers an adapter. Resolved from the container — the implementation gets constructor
+ /// injection like any other service.
+ ///
+ public void RegisterAdapter() where T : IDebugAdapter;
+
+ ///
+ /// Registers a launch provider. Resolved from the container like adapters.
+ ///
+ public void RegisterLaunchProvider() where T : IDebugLaunchProvider;
+
+ ///
+ /// Starts a session, arms the breakpoints currently set in the editor and runs the program.
+ /// Returns if no adapter accepted the request or the backend did not
+ /// come up; nothing is left running in that case.
+ ///
+ public Task StartAsync(DebugLaunchRequest launchRequest);
+
+ ///
+ /// Calls first, then starts with the
+ /// returned request. runs as soon as the
+ /// session ends, no matter how it ended.
+ ///
+ public Task StartAsync(IDebugLaunchProvider provider, CancellationToken ct = default);
+
+ ///
+ /// Ends the active session. Does nothing if none is running.
+ ///
+ public Task StopAsync();
+
+ ///
+ /// Forwards to on .
+ /// Does nothing when no session is active.
+ ///
+ public Task ContinueAsync();
+
+ ///
+ /// Forwards to on .
+ /// Does nothing when no session is active.
+ ///
+ public Task PauseAsync();
+
+ ///
+ /// Forwards to on .
+ /// Does nothing when no session is active.
+ ///
+ public Task StepIntoAsync();
+
+ ///
+ /// Forwards to on .
+ /// Does nothing when no session is active.
+ ///
+ public Task StepOverAsync();
+
+ ///
+ /// Forwards to on .
+ /// Does nothing when no session is active.
+ ///
+ public Task StepOutAsync();
+
+ ///
+ /// Forwards to on .
+ /// Returns when no session is active.
+ ///
+ public Task ReadMemoryAsync(string address, int byteCount);
+
+ ///
+ /// Forwards to on
+ /// . Does nothing when no session is active.
+ ///
+ public Task SendRawCommandAsync(string command);
+}