Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/OneWare.Essentials/Debugger/Entities/DebugBreakPointFrame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace OneWare.Essentials.Debugger.Entities;

/// <summary>
/// A place in the target. Used in both directions: the session reports where the target is
/// halted, and a breakpoint asks it to halt somewhere. Both answer the same question — where —
/// so both use the same type, and putting a breakpoint on the spot the target already stands on
/// needs no conversion.
/// Deliberately not the editor's own breakpoint model: while a published contract named that
/// class, every change to the margin was a change to published API.
/// </summary>
/// <param name="Function">Name of the function, if the backend reported one. May also be set on
/// its own to place a breakpoint on a function by name.</param>
/// <param name="File">
/// Absolute source path, or <see langword="null"/> if the address could not be mapped — a
/// program without debug symbols, code compiled without them, or a halt at an address the line
/// table does not cover. Set on every breakpoint coming from the margin.
/// The editor only jumps to the source location when this is set.
/// </param>
/// <param name="Line">One-based line number. <c>0</c> means "unknown" when read from the target
/// and "no line given" when written to it.</param>
/// <param name="Address">
/// Program counter as formatted by the backend, e.g. <c>0x00000108</c>. The only location
/// available when no debug symbols are present.
/// </param>
public sealed record DebugBreakPointFrame(
string? Function,
string? File,
int Line,
string? Address);
14 changes: 11 additions & 3 deletions src/OneWare.Essentials/Debugger/Entities/DebugLaunchRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,22 @@ namespace OneWare.Essentials.Debugger.Entities;
/// <paramref name="RemoteEndpoint"/> 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.
/// </summary>
/// <param name="AdapterId">Identifies the backend, e.g. <c>GDB</c>.</param>
/// <param name="BackendId">Identifies the backend, e.g. <c>GDB</c>.</param>
/// <param name="ExecutablePath">
/// Path to the executable, e.g. an ELF file. Carries the program and its debug symbols.
/// </param>
/// <param name="RemoteEndpoint">Remote stub address, e.g. <c>localhost:1234</c>.</param>
/// <param name="WorkingDirectory">Working directory for the debug session.</param>
/// <param name="InitCommands">
/// Commands the backend applies at startup, before it connects to the target, in this order.
/// Whoever brings a target up states here what the backend cannot learn from the executable
/// alone — a register description, for instance. Passing them with the request keeps the
/// handover in one place: the alternative is a file next to the executable whose name both
/// sides have to agree on without either contract saying so.
/// </param>
public sealed record DebugLaunchRequest(
string AdapterId,
string BackendId,
string? ExecutablePath = null,
string? RemoteEndpoint = null,
string? WorkingDirectory = null);
string? WorkingDirectory = null,
IReadOnlyList<string>? InitCommands = null);
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ namespace OneWare.Essentials.Debugger.Entities;
/// </summary>
/// <param name="Name">As reported by the target, e.g. <c>sp</c> or <c>pc</c>.</param>
/// <param name="Value">Formatted by the backend; the UI displays the string unchanged.</param>
public sealed record RegisterValue(
public sealed record DebugRegisterValue(
string Name,
string Value);
4 changes: 2 additions & 2 deletions src/OneWare.Essentials/Debugger/Entities/DebugSessionState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ public sealed record DebugSessionState
/// <summary>
/// Where the target is halted, or <see langword="null"/> while it runs.
/// </summary>
public DebugStackFrame? CurrentFrame { get; init; }
public DebugBreakPointFrame? CurrentFrame { get; init; }

/// <summary>
/// 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.
/// </summary>
public IReadOnlyList<RegisterValue> Registers { get; init; } = [];
public IReadOnlyList<DebugRegisterValue> Registers { get; init; } = [];

/// <summary>
/// Locals of <see cref="CurrentFrame"/> as of the last halt. Empty while the target runs,
Expand Down
20 changes: 0 additions & 20 deletions src/OneWare.Essentials/Debugger/Entities/DebugStackFrame.cs

This file was deleted.

10 changes: 5 additions & 5 deletions src/OneWare.Essentials/Debugger/Interfaces/IDebugSession.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using OneWare.Essentials.Debugger.Entities;
using OneWare.Essentials.EditorExtensions;

namespace OneWare.Essentials.Debugger.Interfaces;

Expand All @@ -16,7 +15,7 @@ public interface IDebugSession
/// <summary>
/// Identifies the backend, e.g. <c>GDB</c>.
/// </summary>
public string AdapterId { get; }
public string BackendId { get; }

/// <summary>
/// Latest published state.
Expand Down Expand Up @@ -85,12 +84,13 @@ public interface IDebugSession
/// Returns <see langword="false"/> if the target refused it, e.g. because it ran out of
/// hardware breakpoints.
/// </summary>
public Task<bool> SetBreakpointAsync(BreakPoint breakpoint);
public Task<bool> SetBreakpointAsync(DebugBreakPointFrame frame);

/// <summary>
/// Removes a previously armed breakpoint.
/// Removes a previously armed breakpoint. Removing one that is not armed counts as success —
/// the requested state is what matters, not how it was reached.
/// </summary>
public Task<bool> RemoveBreakpointAsync(BreakPoint breakpoint);
public Task<bool> RemoveBreakpointAsync(DebugBreakPointFrame frame);

/// <summary>
/// Reads memory from the target. <paramref name="address"/> is whatever the backend accepts —
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,30 @@
namespace OneWare.Essentials.Debugger.Interfaces;

/// <summary>
/// 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.
/// Turns a <see cref="DebugLaunchRequest"/> into a session for one particular backend. Together
/// with <see cref="IDebugTargetPreparer"/> it forms the whole chain: the preparer brings the
/// target up and produces the request, the launcher decides who can serve it and builds the
/// session.
/// What VS Code's DAP calls a "debug adapter" is this — the name is deliberately not borrowed,
/// because this one is in-process and never speaks the protocol.
/// <see cref="CreateSession"/> is synchronous by intent, so that everything which can block or
/// fail happens in <see cref="IDebugSession.StartAsync"/> — one failure path instead of two.
/// </summary>
public interface IDebugAdapter
public interface IDebugSessionLauncher
{
/// <summary>
/// Stable identifier, referenced by <see cref="DebugLaunchRequest.AdapterId"/>.
/// Stable identifier, referenced by <see cref="DebugLaunchRequest.BackendId"/>.
/// </summary>
public string Id { get; }

/// <summary>
/// Shown when the user picks a backend.
/// Human-readable name of the backend, e.g. <c>GNU Debugger</c>.
/// </summary>
public string DisplayName { get; }

/// <summary>
/// Returns <see langword="true"/> 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.
/// Returns <see langword="true"/> if this launcher can serve the given request.
/// Must be cheap and free of side effects — it decides whether this launcher is used at all.
/// </summary>
public bool CanLaunch(DebugLaunchRequest launchRequest);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,31 @@ namespace OneWare.Essentials.Debugger.Interfaces;

/// <summary>
/// Analogous to <see cref="DebugLaunchRequest"/>, but as the preparation step. The core asks
/// which provider fits the current project, has it prepare, and starts with whatever request
/// which preparer 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.
/// </summary>
public interface IDebugLaunchProvider
public interface IDebugTargetPreparer
{
/// <summary>
/// Shown in the launch selection of the debug panel.
/// Names the preparer in the status line and in the debug console.
/// </summary>
public string DisplayName { get; }

/// <summary>
/// Returns <see langword="true"/> if this provider can handle the active project.
/// Must be cheap and free of side effects — the UI calls it to fill the selection.
/// Returns <see langword="true"/> if this preparer can handle the active project.
/// Must be cheap and free of side effects — the UI calls it to pick a preparer.
/// </summary>
public bool CanPrepare();

/// <summary>
/// Brings the target up and returns the matching launch request.
/// Returns <see langword="null"/> if preparation failed or was cancelled; the user has
/// already been notified in that case.
/// Brings the target up and returns the matching launch request. A running preparation
/// cannot be aborted; the UI locks the start button and waits for it, so keep the steps
/// short and report what is happening.
/// Returns <see langword="null"/> if preparation failed; the user has already been notified
/// in that case.
/// </summary>
public Task<DebugLaunchRequest?> PrepareAsync(CancellationToken ct = default);
public Task<DebugLaunchRequest?> PrepareAsync();

/// <summary>
/// Releases whatever <see cref="PrepareAsync"/> claimed. Also runs when the session ended
Expand Down
24 changes: 12 additions & 12 deletions src/OneWare.Essentials/Debugger/Interfaces/IDebuggerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ public interface IDebuggerService
/// <summary>
/// Registered backends, including the core's own.
/// </summary>
public IReadOnlyList<IDebugAdapter> Adapters { get; }
public IReadOnlyList<IDebugSessionLauncher> SessionLaunchers { get; }

/// <summary>
/// Registered launch providers. Whoever fits the active project shows up in the launch
/// selection of the debug panel.
/// Registered target preparers. Starting the debugger picks the first one whose
/// <see cref="IDebugTargetPreparer.CanPrepare"/> accepts the active project.
/// </summary>
public IReadOnlyList<IDebugLaunchProvider> LaunchProviders { get; }
public IReadOnlyList<IDebugTargetPreparer> TargetPreparers { get; }

/// <summary>
/// The active session, or <see langword="null"/> if none is running.
Expand All @@ -43,29 +43,29 @@ public interface IDebuggerService
public event EventHandler? StateChanged;

/// <summary>
/// Registers an adapter. Resolved from the container — the implementation gets constructor
/// Registers an launcher. Resolved from the container — the implementation gets constructor
/// injection like any other service.
/// </summary>
public void RegisterAdapter<T>() where T : IDebugAdapter;
public void RegisterSessionLauncher<T>() where T : IDebugSessionLauncher;

/// <summary>
/// Registers a launch provider. Resolved from the container like adapters.
/// Registers a target preparer. Resolved from the container like session launchers.
/// </summary>
public void RegisterLaunchProvider<T>() where T : IDebugLaunchProvider;
public void RegisterTargetPreparer<T>() where T : IDebugTargetPreparer;

/// <summary>
/// Starts a session, arms the breakpoints currently set in the editor and runs the program.
/// Returns <see langword="false"/> if no adapter accepted the request or the backend did not
/// Returns <see langword="false"/> if no launcher accepted the request or the backend did not
/// come up; nothing is left running in that case.
/// </summary>
public Task<bool> StartAsync(DebugLaunchRequest launchRequest);

/// <summary>
/// Calls <see cref="IDebugLaunchProvider.PrepareAsync"/> first, then starts with the
/// returned request. <see cref="IDebugLaunchProvider.CleanupAsync"/> runs as soon as the
/// Calls <see cref="IDebugTargetPreparer.PrepareAsync"/> first, then starts with the
/// returned request. <see cref="IDebugTargetPreparer.CleanupAsync"/> runs as soon as the
/// session ends, no matter how it ended.
/// </summary>
public Task<bool> StartAsync(IDebugLaunchProvider provider, CancellationToken ct = default);
public Task<bool> StartAsync(IDebugTargetPreparer preparer);

/// <summary>
/// Ends the active session. Does nothing if none is running.
Expand Down
Loading