Begin work on UIXC debug command

This commit is contained in:
Joshua "Yoshi" Askharoun
2025-11-28 18:06:49 -06:00
parent 806d697e79
commit c68fb6fc1f
8 changed files with 1474 additions and 533 deletions
+126
View File
@@ -0,0 +1,126 @@
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<DebugCommand.Settings>
{
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<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 = 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 <symbolDir>")]
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 <serverUri>")]
public Uri ServerUri { get; init; } = DebugRemoting.DEFAULT_TCP_URI;
}
}
+21 -2
View File
@@ -1,5 +1,7 @@
using Microsoft.Iris.Asm; using Microsoft.Iris.Asm;
using Microsoft.Iris.Data;
using Microsoft.Iris.Debug; using Microsoft.Iris.Debug;
using Microsoft.Iris.Debug.Symbols;
using Microsoft.Iris.DecompXml; using Microsoft.Iris.DecompXml;
using Microsoft.Iris.Markup; using Microsoft.Iris.Markup;
using Spectre.Console; using Spectre.Console;
@@ -59,7 +61,7 @@ public class DecompileCommand : CompilerCommandBase<DecompileCommand.Settings>
foreach (var redirectOption in settings.ImportRedirects ?? []) foreach (var redirectOption in settings.ImportRedirects ?? [])
{ {
var parts = redirectOption.Split('>'); var parts = redirectOption.Split('>');
MarkupSystem.AddImportRedirect(parts[0], parts[1]); ResourceManager.Instance.AddUriRedirect(parts[0], parts[1]);
} }
MarkupSystem.Startup(true); MarkupSystem.Startup(true);
@@ -90,10 +92,23 @@ public class DecompileCommand : CompilerCommandBase<DecompileCommand.Settings>
} }
else if (settings.Language == SourceLanguage.Xml) else if (settings.Language == SourceLanguage.Xml)
{ {
var saveDebugSymbols = settings.SymbolDir is not null;
decompilerMethod = loadResult => decompilerMethod = loadResult =>
{ {
var decompiler = Decompiler.Load(loadResult); var decompiler = Decompiler.Load(loadResult);
return decompiler.DecompileToSource(); var source = decompiler.DecompileToSource(saveDebugSymbols);
if (settings.SymbolDir is not null && decompiler.DebugSymbols is not null)
{
var fsymJson = DebugSymbolsJsonParser.Serialize(decompiler.DebugSymbols);
var sourceName = Path.GetFileName(decompiler.DebugSymbols.CompiledFileName);
var fsymPath = Path.Combine(settings.SymbolDir, $"{sourceName}.fsym.json");
File.WriteAllText(fsymPath, fsymJson);
}
return source;
}; };
} }
else else
@@ -179,5 +194,9 @@ public class DecompileCommand : CompilerCommandBase<DecompileCommand.Settings>
[Description("The language to decompile to.")] [Description("The language to decompile to.")]
[CommandOption("-l|--lang <lang>")] [CommandOption("-l|--lang <lang>")]
public SourceLanguage Language { get; init; } = SourceLanguage.Asm; public SourceLanguage Language { get; init; } = SourceLanguage.Asm;
[Description("The directory to write file symbols to.")]
[CommandOption("--symbols <symbolOutDir>")]
public string? SymbolDir { get; init; }
} }
} }
+57
View File
@@ -0,0 +1,57 @@
using Microsoft.Iris.Debug.Symbols;
namespace UIXC;
public class DebugSymbolResolver(string symbolDir)
{
private readonly DirectoryInfo _symbolDir = new(symbolDir);
public ApplicationDebugSymbols? GetForApplication(string application)
{
var fileName = $"{application}.asym.json";
var asymFile = _symbolDir.EnumerateFiles()
.FirstOrDefault(f => f.Name == fileName);
if (asymFile is null)
return null;
using var stream = asymFile.OpenRead();
using var reader = new StreamReader(stream);
return DebugSymbolsJsonParser.ParseForApplication(reader.ReadToEnd());
}
public FileDebugSymbols? GetForFile(string file, string? application = null)
{
var fileName = $"{file}.fsym.json";
var fsymFile = FindFile(fileName);
if (fsymFile is not null)
{
using var stream = fsymFile.OpenRead();
using var reader = new StreamReader(stream);
return DebugSymbolsJsonParser.ParseForFile(reader.ReadToEnd());
}
if (application is null)
return null;
var asym = GetForApplication(application);
if (asym is null)
return null;
return asym.Files.FirstOrDefault(f => f.CompiledFileName == file || f.SourceFileName == file);
}
private FileInfo? FindFile(string fileName)
{
return _symbolDir
.EnumerateFiles()
.FirstOrDefault(f => AreEquivalentFileNames(f.Name, fileName));
}
private static bool AreEquivalentFileNames(string fileName1, string fileName2)
{
// NOTE: Assumes Windows. Should be trivial to support other filesystems later.
return fileName1.Equals(fileName2, StringComparison.InvariantCultureIgnoreCase);
}
}
+4
View File
@@ -19,6 +19,10 @@ internal class Program
.WithAlias("d") .WithAlias("d")
.WithDescription("Decompiles the given compiled UIX to a source language."); .WithDescription("Decompiles the given compiled UIX to a source language.");
config.AddCommand<DebugCommand>("debug")
.WithAlias("dbg")
.WithDescription("Starts an Iris debugger client.");
config.AddCommand<ResxCommand>("resx") config.AddCommand<ResxCommand>("resx")
.WithDescription("Generates a RESX file compatible with the CLR DLL resource loader."); .WithDescription("Generates a RESX file compatible with the CLR DLL resource loader.");
+4 -1
View File
@@ -3,8 +3,11 @@
"UIXC": { "UIXC": {
"commandName": "Project", "commandName": "Project",
"commandLineArgs": "debug --symbols E:\\Repos\\ZuneDev\\ZuneUIXTools\\test\\syms",
//"commandLineArgs": "decompile E:\\Documents\\REProj\\Zune\\Resources\\ZuneMarketplaceResources48\\MarketplaceData.schema.xml -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneUIXTools\\test", //"commandLineArgs": "decompile E:\\Documents\\REProj\\Zune\\Resources\\ZuneMarketplaceResources48\\MarketplaceData.schema.xml -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneUIXTools\\test",
"commandLineArgs": "decompile E:\\Documents\\REProj\\Zune\\Resources\\ZuneShellResources48\\NowPlayingLand.uix -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneUIXTools\\test", //"commandLineArgs": "decompile E:\\Documents\\REProj\\Zune\\Resources\\ZuneShellResources48\\DeviceLandElements.uix -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneUIXTools\\test",
//"commandLineArgs": "decompile E:\\Documents\\REProj\\Zune\\Resources\\ZuneShellResources48\\NOWPLAYINGMUSICBACKGROUND.UIX -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneUIXTools\\test --symbols E:\\Repos\\ZuneDev\\ZuneUIXTools\\test\\syms",
//"commandLineArgs": "decompile E:\\Repos\\ZuneDev\\ZuneShell.dll\\libs\\MicrosoftIris\\UIXcontrols\\Resources\\RCDATA\\STYLES.UIX -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneShell.dll\\ZuneShell\\Resources\\RCDATA\\Dark\\Controls", //"commandLineArgs": "decompile E:\\Repos\\ZuneDev\\ZuneShell.dll\\libs\\MicrosoftIris\\UIXcontrols\\Resources\\RCDATA\\STYLES.UIX -l xml -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneShell.dll\\ZuneShell\\Resources\\RCDATA\\Dark\\Controls",
//"commandLineArgs": "compile E:\\Repos\\ZuneDev\\ZuneUIXTools\\test\\ADDTOCOLLECTION.31_48.UIX -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneUIXTools\\test", //"commandLineArgs": "compile E:\\Repos\\ZuneDev\\ZuneUIXTools\\test\\ADDTOCOLLECTION.31_48.UIX -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneUIXTools\\test",
//"commandLineArgs": "compile E:\\Documents\\REProj\\Zune\\Resources\\ZuneMarketplaceResources31\\MARKETPLACEDATA.SCHEMA.31_48.XML -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneUIXTools\\test", //"commandLineArgs": "compile E:\\Documents\\REProj\\Zune\\Resources\\ZuneMarketplaceResources31\\MARKETPLACEDATA.SCHEMA.31_48.XML -A ZuneShell.dll -A UIXControls.dll -A ZuneDBApi.dll -o E:\\Repos\\ZuneDev\\ZuneUIXTools\\test",
+1
View File
@@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Errata" Version="0.13.0" /> <PackageReference Include="Errata" Version="0.13.0" />
<PackageReference Include="OwlCore" Version="0.6.1" />
<PackageReference Include="Spectre.Console" Version="0.49.1" /> <PackageReference Include="Spectre.Console" Version="0.49.1" />
<PackageReference Include="Spectre.Console.Cli" Version="0.49.1" /> <PackageReference Include="Spectre.Console.Cli" Version="0.49.1" />
<ProjectReference Include="..\libs\MicrosoftIris\UIX\UIX.csproj" /> <ProjectReference Include="..\libs\MicrosoftIris\UIX\UIX.csproj" />
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff