Files

174 lines
6.2 KiB
C#
Raw Permalink Normal View History

2025-11-28 18:06:42 -06:00
using Microsoft.Iris.Debug;
2025-11-29 01:32:09 -06:00
using Microsoft.Iris.Debug.Symbols;
2025-11-28 18:06:42 -06:00
using Microsoft.Iris.Debug.SystemNet;
2025-11-30 13:37:33 -06:00
using Microsoft.Iris.DebugAdapter.Client;
2025-11-28 18:06:42 -06:00
using Spectre.Console;
using Spectre.Console.Cli;
using System.ComponentModel;
using System.Globalization;
namespace UIXC.Commands;
2025-12-01 00:45:01 -06:00
public class DebugCommand : AsyncCommand<DebugCommand.Settings>
2025-11-28 18:06:42 -06:00
{
2025-12-01 00:45:01 -06:00
private readonly TaskCompletionSource _exit = new();
2025-11-28 18:06:42 -06:00
2025-12-01 00:45:01 -06:00
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
2025-11-28 18:06:42 -06:00
{
2025-11-30 13:37:33 -06:00
if (settings.ConnectionString is null)
2025-11-28 18:06:42 -06:00
{
2025-11-30 13:37:33 -06:00
AnsiConsole.MarkupLine("[red]Missing argument, a connection string must be specified.[/]");
2025-11-28 18:06:42 -06:00
return -1;
}
2025-11-30 13:37:33 -06:00
IDebuggerClient c;
if (Uri.TryCreate(settings.ConnectionString, UriKind.Absolute, out var connectionUri))
{
if (connectionUri.Scheme == "tcp")
{
c = new NetDebuggerClient(connectionUri);
}
else
{
throw new NotSupportedException();
}
}
else
{
c = new IrisDebugAdapterClient(settings.ConnectionString);
}
2025-11-28 18:06:42 -06:00
DebugSymbolResolver? symbolResolver = null;
if (settings.SymbolDir is not null)
{
Directory.CreateDirectory(settings.SymbolDir);
2025-11-29 01:32:09 -06:00
symbolResolver = new(settings.SymbolDir, settings.SourceDir);
2025-11-28 18:06:42 -06:00
}
2025-11-30 13:37:33 -06:00
AnsiConsole.MarkupLineInterpolated($"Connecting to '{settings.ConnectionString}'...");
((IRemoteDebuggerState)c).Connected += (s, e) =>
2025-11-28 18:06:42 -06:00
{
AnsiConsole.MarkupLine("[green]Connected[/]");
Thread consoleThread = new(() => DebugConsole(c, symbolResolver));
consoleThread.Start();
};
c.Start();
2025-12-01 00:45:01 -06:00
await _exit.Task.ConfigureAwait(false);
2025-11-28 18:06:42 -06:00
return 0;
}
private int DebugConsole(IDebuggerClient client, DebugSymbolResolver? symbolResolver)
{
client.InterpreterStateChanged += (cmd) =>
{
AnsiConsole.MarkupLineInterpolated($"[yellow]Application in '{cmd}' state[/]");
};
while (true)
{
var input = AnsiConsole.Prompt(new TextPrompt<string>("> "));
var inputParts = input.Split(' ', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
var command = inputParts[0].ToUpperInvariant();
switch (command)
{
case "BREAK" or "B":
string path = inputParts[1];
var fileName = Path.GetFileName(path).Split('!')[^1];
uint breakOffset;
if (inputParts.Length >= 3 && inputParts[2].StartsWith("0x"))
{
2025-12-03 20:42:58 -06:00
breakOffset = Convert.ToUInt32(inputParts[2], 16);
2025-11-28 18:06:42 -06:00
}
else
{
var line = int.Parse(inputParts[2]);
var column = int.Parse(inputParts[3]);
2025-11-29 01:32:09 -06:00
2025-12-03 20:42:58 -06:00
var fsym = symbolResolver?.GetForFile(fileName);
if (fsym is null)
2025-11-29 01:32:09 -06:00
{
2025-12-03 20:42:58 -06:00
AnsiConsole.MarkupLineInterpolated($"[yellow]No symbols loaded for '{fileName}'.[/]");
client.UpdateBreakpoint(new(path, line, column));
AnsiConsole.MarkupLineInterpolated($"[green]Breakpoint set in '{path}' at line {line}, column {column}.[/]");
2025-11-29 01:32:09 -06:00
break;
}
2025-12-03 20:42:58 -06:00
else
2025-11-29 01:32:09 -06:00
{
2025-12-03 20:42:58 -06:00
if (fsym.OriginalFileName is not null)
path = fsym.OriginalFileName;
var position = new SourcePosition(line, column);
var location = fsym!.SourceMap.GetLocationFromPosition(position);
if (location is null)
{
AnsiConsole.MarkupLineInterpolated($"[red]Line {line}, column {column} has known offset in '{fileName}'.[/]");
break;
}
breakOffset = location.Offset;
if (fsym.HasSourceCode())
{
var sourceCode = fsym.GetSourceSubstring(location.Span);
AnsiConsole.Write(new Panel(sourceCode.EscapeMarkup()));
}
2025-11-29 01:32:09 -06:00
}
2025-11-28 18:06:42 -06:00
}
2025-11-29 01:32:09 -06:00
client.UpdateBreakpoint(new(path, breakOffset));
2025-11-29 18:44:08 -06:00
AnsiConsole.MarkupLineInterpolated($"[green]Breakpoint set in '{path}' at offset 0x{breakOffset:X4}.[/]");
2025-11-28 18:06:42 -06:00
break;
case "CONTINUE" or "C":
client.DebuggerCommand = Microsoft.Iris.Debug.Data.InterpreterCommand.Continue;
break;
2025-11-30 13:37:33 -06:00
case "STEP" or "S":
client.DebuggerCommand = Microsoft.Iris.Debug.Data.InterpreterCommand.Step;
break;
case "ENABLE":
break;
2025-11-28 18:06:42 -06:00
case "CLEAR":
// Not implemented yet, should clear all breakpoints
break;
2025-11-30 13:37:33 -06:00
case "EXIT" or "QUIT":
2025-12-01 00:45:01 -06:00
_exit.SetResult();
2025-11-28 18:06:42 -06:00
return 0;
}
}
}
public sealed class Settings : CommandSettings
{
[Description("The directory containing pre-generated symbols.")]
[CommandOption("-s|--symbols <symbolDir>")]
public string? SymbolDir { get; init; }
2025-11-29 01:32:09 -06:00
[Description("The directory containing source code.")]
[CommandOption("--sources <sourceDir>")]
public string? SourceDir { get; init; }
2025-11-28 18:06:42 -06:00
[Description("Whether to decompile the current file when a breakpoint is hit. Generated symbols will be written to the symbol directory if specified.")]
[CommandOption("-d|--decompile")]
public bool Decompile { get; init; }
2025-11-30 13:37:33 -06:00
[Description("The string used to connect to the debug server.")]
2025-11-28 18:06:42 -06:00
[CommandOption("-u|--server <serverUri>")]
2025-11-30 13:37:33 -06:00
public string ConnectionString { get; init; } = DebugRemoting.DEFAULT_TCP_URI.ToString();
2025-11-28 18:06:42 -06:00
}
}