using Microsoft.Iris.Debug; using Microsoft.Iris.Debug.SystemNet; using Spectre.Console; using Spectre.Console.Cli; using System.ComponentModel; using System.Globalization; namespace UIXC.Commands; public class DebugCommand : Command { private bool isRunning = true; public override int Execute(CommandContext context, Settings settings) { if (settings.ServerUri is null) { AnsiConsole.MarkupLine("[red]Missing argument: must specify a debug server to connect to.[/]"); return -1; } DebugSymbolResolver? symbolResolver = null; if (settings.SymbolDir is not null) { Directory.CreateDirectory(settings.SymbolDir); symbolResolver = new(settings.SymbolDir); } AnsiConsole.MarkupLineInterpolated($"Connecting to '{settings.ServerUri}'..."); var c = new NetDebuggerClient(settings.ServerUri); c.Connected += (s, e) => { AnsiConsole.MarkupLine("[green]Connected[/]"); Thread consoleThread = new(() => DebugConsole(c, symbolResolver)); consoleThread.Start(); }; c.Start(); while (isRunning) ; 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("> ")); 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")) { breakOffset = uint.Parse(inputParts[2][2..], NumberStyles.HexNumber); } else { if (symbolResolver is null) { AnsiConsole.MarkupLine("[red]Setting breakpoints via source code requires debug symbols. Use the --symbols argument to specify a directory.[/]"); break; } var fsym = symbolResolver.GetForFile(fileName); if (fsym is null) { AnsiConsole.MarkupLineInterpolated($"[red]No symbols loaded for '{fileName}'.[/]"); break; } var line = int.Parse(inputParts[2]); var column = int.Parse(inputParts[3]); breakOffset = fsym!.OffsetByLineAndColumn(line, column); } client.UpdateBreakpoint(new(path, breakOffset)); AnsiConsole.MarkupLineInterpolated($"[green]Breakpoint set in '{fileName}' at offset 0x{breakOffset:X4}.[/]"); break; case "CONTINUE" or "C": client.DebuggerCommand = Microsoft.Iris.Debug.Data.InterpreterCommand.Continue; break; case "CLEAR": // Not implemented yet, should clear all breakpoints break; case "EXIT": isRunning = false; return 0; } } } public sealed class Settings : CommandSettings { [Description("The directory containing pre-generated symbols.")] [CommandOption("-s|--symbols ")] public string? SymbolDir { get; init; } [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; } [Description("The URI of the debug server to connect to.")] [CommandOption("-u|--server ")] public Uri ServerUri { get; init; } = DebugRemoting.DEFAULT_TCP_URI; } }