mirror of
https://github.com/ZuneDev/ZuneUIXTools.git
synced 2026-07-27 13:11:59 -07:00
175 lines
6.2 KiB
C#
175 lines
6.2 KiB
C#
using Microsoft.Iris.Debug;
|
|
using Microsoft.Iris.Debug.Symbols;
|
|
using Microsoft.Iris.Debug.SystemNet;
|
|
using Microsoft.Iris.DebugAdapter.Client;
|
|
using Spectre.Console;
|
|
using Spectre.Console.Cli;
|
|
using System.ComponentModel;
|
|
using System.Globalization;
|
|
|
|
namespace UIXC.Commands;
|
|
|
|
public class DebugCommand : AsyncCommand<DebugCommand.Settings>
|
|
{
|
|
private readonly TaskCompletionSource _exit = new();
|
|
|
|
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
|
|
{
|
|
if (settings.ConnectionString is null)
|
|
{
|
|
AnsiConsole.MarkupLine("[red]Missing argument, a connection string must be specified.[/]");
|
|
return -1;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
DebugSymbolResolver? symbolResolver = null;
|
|
|
|
if (settings.SymbolDir is not null)
|
|
{
|
|
Directory.CreateDirectory(settings.SymbolDir);
|
|
symbolResolver = new(settings.SymbolDir, settings.SourceDir);
|
|
}
|
|
|
|
AnsiConsole.MarkupLineInterpolated($"Connecting to '{settings.ConnectionString}'...");
|
|
|
|
((IRemoteDebuggerState)c).Connected += (s, e) =>
|
|
{
|
|
AnsiConsole.MarkupLine("[green]Connected[/]");
|
|
Thread consoleThread = new(() => DebugConsole(c, symbolResolver));
|
|
consoleThread.Start();
|
|
};
|
|
|
|
c.Start();
|
|
|
|
await _exit.Task.ConfigureAwait(false);
|
|
|
|
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"))
|
|
{
|
|
breakOffset = Convert.ToUInt32(inputParts[2], 16);
|
|
}
|
|
else
|
|
{
|
|
var line = int.Parse(inputParts[2]);
|
|
var column = int.Parse(inputParts[3]);
|
|
|
|
var fsym = symbolResolver?.GetForFile(fileName);
|
|
if (fsym is null)
|
|
{
|
|
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}.[/]");
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
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()));
|
|
}
|
|
}
|
|
}
|
|
|
|
client.UpdateBreakpoint(new(path, breakOffset));
|
|
AnsiConsole.MarkupLineInterpolated($"[green]Breakpoint set in '{path}' at offset 0x{breakOffset:X4}.[/]");
|
|
|
|
break;
|
|
|
|
case "CONTINUE" or "C":
|
|
client.DebuggerCommand = Microsoft.Iris.Debug.Data.InterpreterCommand.Continue;
|
|
break;
|
|
|
|
case "STEP" or "S":
|
|
client.DebuggerCommand = Microsoft.Iris.Debug.Data.InterpreterCommand.Step;
|
|
break;
|
|
|
|
case "ENABLE":
|
|
|
|
break;
|
|
|
|
case "CLEAR":
|
|
// Not implemented yet, should clear all breakpoints
|
|
break;
|
|
|
|
case "EXIT" or "QUIT":
|
|
_exit.SetResult();
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
public sealed class Settings : CommandSettings
|
|
{
|
|
[Description("The directory containing pre-generated symbols.")]
|
|
[CommandOption("-s|--symbols <symbolDir>")]
|
|
public string? SymbolDir { get; init; }
|
|
|
|
[Description("The directory containing source code.")]
|
|
[CommandOption("--sources <sourceDir>")]
|
|
public string? SourceDir { 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 string used to connect to the debug server.")]
|
|
[CommandOption("-u|--server <serverUri>")]
|
|
public string ConnectionString { get; init; } = DebugRemoting.DEFAULT_TCP_URI.ToString();
|
|
}
|
|
}
|