diff --git a/Tests/SimpleDebugClient/Program.cs b/Tests/SimpleDebugClient/Program.cs index fbdc00a..d7eaa7a 100644 --- a/Tests/SimpleDebugClient/Program.cs +++ b/Tests/SimpleDebugClient/Program.cs @@ -2,6 +2,7 @@ using Microsoft.Iris.Debug.Data; using Microsoft.Iris.Debug.SystemNet; using System; +using System.Threading; namespace SimpleDebugClient; @@ -14,22 +15,41 @@ internal class Program var connectionString = args.Length >= 2 ? new Uri(args[1]) : DebugRemoting.DEFAULT_TCP_URI; - Console.CancelKeyPress += Console_CancelKeyPress; - Debugger = new NetDebuggerClient(connectionString); - Debugger.DispatcherStep += Debugger_DispatcherStep; + //Debugger.DispatcherStep += Debugger_DispatcherStep; Debugger.InterpreterStep += Debugger_InterpreterStep; + Debugger.InterpreterStateChanged += Debugger_InterpreterStateChanged; - Console.WriteLine("Listening for debug messages. Press Ctrl-C to exit."); - Console.ReadLine(); + Console.WriteLine($"Listening for debug messages at '{Debugger.ConnectionUri}'. Press Ctrl-C or 'x' to exit."); + + while (true) + { + var cmd = Console.ReadLine(); + if (cmd[0] == 'x') + { + if (Debugger is IDisposable debugger) + debugger.Dispose(); + break; + } + + switch (cmd[0]) + { + case 's': + Debugger.DebuggerCommand = InterpreterCommand.Step; + break; + + case 'c': + Debugger.DebuggerCommand = InterpreterCommand.Continue; + break; + } + } return 0; } - private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) + private static void Debugger_InterpreterStateChanged(InterpreterCommand state) { - if (Debugger is IDisposable debugger) - debugger.Dispose(); + Console.WriteLine($"Interpreter is in {state} mode"); } private static void Debugger_DispatcherStep(string obj) @@ -39,6 +59,8 @@ internal class Program private static void Debugger_InterpreterStep(object? sender, InterpreterEntry e) { + if (e.LoadUri.EndsWith("TopToolbarSignIn.uix")) + return; Console.WriteLine($"[Interpreter] {e}"); } } \ No newline at end of file diff --git a/Tests/SimpleIrisApp/SimpleIrisApp.csproj b/Tests/SimpleIrisApp/SimpleIrisApp.csproj index 388e4ed..3bc6aa9 100644 --- a/Tests/SimpleIrisApp/SimpleIrisApp.csproj +++ b/Tests/SimpleIrisApp/SimpleIrisApp.csproj @@ -8,7 +8,7 @@ - + diff --git a/UIX/Microsoft/Iris/Debug/Data/InterpreterCommand.cs b/UIX/Microsoft/Iris/Debug/Data/InterpreterCommand.cs new file mode 100644 index 0000000..2dcbf0b --- /dev/null +++ b/UIX/Microsoft/Iris/Debug/Data/InterpreterCommand.cs @@ -0,0 +1,8 @@ +namespace Microsoft.Iris.Debug.Data; + +public enum InterpreterCommand : byte +{ + Continue, + Break, + Step, +} diff --git a/UIX/Microsoft/Iris/Debug/DebuggerMessageType.cs b/UIX/Microsoft/Iris/Debug/DebuggerMessageType.cs index 2401fa8..4ea8af4 100644 --- a/UIX/Microsoft/Iris/Debug/DebuggerMessageType.cs +++ b/UIX/Microsoft/Iris/Debug/DebuggerMessageType.cs @@ -6,4 +6,7 @@ public enum DebuggerMessageType : int InterpreterOpCode, DispatcherStep, + + UpdateBreakpoint, + InterpreterCommand, } diff --git a/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs b/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs index 0d5a2ca..4374f45 100644 --- a/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs +++ b/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs @@ -10,6 +10,10 @@ public interface IDebuggerClient /// Uri ConnectionUri { get; } + InterpreterCommand DebuggerCommand { get; set; } + + event Action InterpreterStateChanged; + /// /// Fired when the UIX interpreter steps forward. /// @@ -19,4 +23,6 @@ public interface IDebuggerClient /// Fired when the UIX dispatcher executes another call from the queue. /// event Action DispatcherStep; + + void UpdateBreakpoint(Breakpoint breakpoint); } diff --git a/UIX/Microsoft/Iris/Debug/IDebuggerServer.cs b/UIX/Microsoft/Iris/Debug/IDebuggerServer.cs index f7cf3c8..265f376 100644 --- a/UIX/Microsoft/Iris/Debug/IDebuggerServer.cs +++ b/UIX/Microsoft/Iris/Debug/IDebuggerServer.cs @@ -4,15 +4,17 @@ namespace Microsoft.Iris.Debug; internal interface IDebuggerServer { + InterpreterCommand DebuggerCommand { get; set; } + /// /// Logs the context, opcode, and arguments of an instruction /// executed by Microsoft.Iris.Markup.Interpreter. /// - public void LogInterpreterOpCode(object context, InterpreterEntry entry); + void LogInterpreterOpCode(object context, InterpreterEntry entry); /// /// Logs the string representation of a dispatcher step. /// /// - public void LogDispatcher(string message); + void LogDispatcher(string message); } diff --git a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs index 4d4a442..1972064 100644 --- a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs +++ b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs @@ -1,24 +1,35 @@ using Microsoft.Iris.Debug.Data; using System; -using System.Collections.Generic; -using System.IO; +using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization; -using System.Text; namespace Microsoft.Iris.Debug.SystemNet; public class NetDebuggerClient : IDebuggerClient, IDisposable { + private readonly Socket _socket; + private readonly ConcurrentQueue _outQueue = new(); + private readonly IFormatter _formatter; + private InterpreterCommand _uibCommand = InterpreterCommand.Continue; + public Uri ConnectionUri { get; } - private readonly Socket _socket; - private readonly Queue _queue; - private readonly IFormatter _formatter; + public InterpreterCommand DebuggerCommand + { + get => _uibCommand; + set + { + var data = new[] { (byte)value }; + QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterCommand, data)); + _uibCommand = value; + } + } public event EventHandler InterpreterStep; public event Action DispatcherStep; + public event Action InterpreterStateChanged; public NetDebuggerClient(string connectionUri) : this(new Uri(connectionUri)) { @@ -30,18 +41,31 @@ public class NetDebuggerClient : IDebuggerClient, IDisposable _socket = new(SocketType.Stream, ProtocolType.Tcp); _formatter = DebugRemoting.CreateBsonFormatter(); - System.Threading.Thread connectThread = new(ConnectLoop); + System.Threading.Thread connectThread = new(ConnectLoop) { IsBackground = true }; connectThread.Start(); } - public void Dispose() => _socket.Dispose(); + public void Dispose() + { + if (_socket != null) + { + if (_socket.Connected) + _socket.Disconnect(false); + _socket.Close(); + } + } + + public void UpdateBreakpoint(Breakpoint breakpoint) + { + QueueDebuggerMessage(new(0, DebuggerMessageType.UpdateBreakpoint, breakpoint.Serialize(_formatter))); + } private void QueueDebuggerMessage(DebuggerMessageFrame frame) { - _queue.Enqueue(frame.ToBytes()); + _outQueue.Enqueue(frame.ToBytes()); } - private void MessageRecieveLoop() + private void MessageReceiveLoop() { while (_socket.Connected) { @@ -52,11 +76,8 @@ public class NetDebuggerClient : IDebuggerClient, IDisposable switch (frame.Type) { case DebuggerMessageType.InterpreterOpCode: - { - using MemoryStream stream = new(frame.Data); - var entry = (InterpreterEntry)_formatter.Deserialize(stream); - InterpreterStep?.Invoke(this, entry); - } + var entry = frame.DeserializeData(_formatter); + InterpreterStep?.Invoke(this, entry); break; case DebuggerMessageType.DispatcherStep: @@ -64,6 +85,11 @@ public class NetDebuggerClient : IDebuggerClient, IDisposable DispatcherStep?.Invoke(message); break; + case DebuggerMessageType.InterpreterCommand: + _uibCommand = (InterpreterCommand)frame.Data[0]; + InterpreterStateChanged?.Invoke(_uibCommand); + break; + default: Trace.WriteLine(TraceCategory.MarkupDebug, "Recieved unknown debugger message of type '{0}'.", frame.Type); break; @@ -71,6 +97,18 @@ public class NetDebuggerClient : IDebuggerClient, IDisposable } } + private void MessageSendLoop() + { + while (_socket.Connected) + { + byte[] frameBytes; + while (!_outQueue.TryDequeue(out frameBytes)) ; + + _socket.Send(BitConverter.GetBytes(frameBytes.Length)); + _socket.Send(frameBytes); + } + } + private void ConnectLoop() { while (!_socket.Connected) @@ -83,7 +121,9 @@ public class NetDebuggerClient : IDebuggerClient, IDisposable catch { } } - System.Threading.Thread receiveThread = new(MessageRecieveLoop); + System.Threading.Thread receiveThread = new(MessageReceiveLoop) { IsBackground = true }; + System.Threading.Thread sendThread = new(MessageSendLoop) { IsBackground = true }; receiveThread.Start(); + sendThread.Start(); } } \ No newline at end of file diff --git a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs index 7f2e734..f0b36ad 100644 --- a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs +++ b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs @@ -1,7 +1,6 @@ using Microsoft.Iris.Debug.Data; using System; using System.Collections.Concurrent; -using System.IO; using System.Net.Sockets; using System.Runtime.Serialization; @@ -11,13 +10,25 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable { public static IDebuggerServer Current { get; private set; } - public Uri ConnectionUri { get; } - private readonly ConcurrentQueue _outQueue = new(); private readonly TcpListener _listener; private readonly IFormatter _formatter; private Socket _socket; private bool _disposed = false; + private InterpreterCommand _uibCommand = InterpreterCommand.Continue; + + public Uri ConnectionUri { get; } + + public InterpreterCommand DebuggerCommand + { + get => _uibCommand; + set + { + var data = new[] { (byte)value }; + QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterCommand, data)); + _uibCommand = value; + } + } public NetDebuggerServer(string connectionUri) : this(new Uri(connectionUri)) { @@ -32,7 +43,7 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable _formatter = DebugRemoting.CreateBsonFormatter(); - System.Threading.Thread connectThread = new(ConnectLoop); + System.Threading.Thread connectThread = new(ConnectLoop) { IsBackground = true }; connectThread.Start(); Current = this; @@ -40,10 +51,7 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable public void LogInterpreterOpCode(object context, InterpreterEntry entry) { - using MemoryStream entryStream = new(); - _formatter.Serialize(entryStream, entry); - - QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterOpCode, entryStream.ToArray())); + QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterOpCode, entry.Serialize(_formatter))); } public void LogDispatcher(string message) @@ -66,16 +74,28 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable _outQueue.Enqueue(frame.ToBytes()); } - private void MessageRecieveLoop() + private void MessageReceiveLoop() { while (_socket.Connected) { DebuggerMessageFrame frame; while ((frame = DebugRemoting.ReceiveDebuggerMessage(_socket)) == null) - ; + if (!_socket.Connected) return; switch (frame.Type) { + case DebuggerMessageType.UpdateBreakpoint: + var breakpoint = frame.DeserializeData(_formatter); + if (breakpoint.Enabled) + Application.DebugSettings.Breakpoints.Add(breakpoint); + else + Application.DebugSettings.Breakpoints.Remove(breakpoint); + break; + + case DebuggerMessageType.InterpreterCommand: + _uibCommand = (InterpreterCommand)frame.Data[0]; + break; + default: Trace.WriteLine(TraceCategory.MarkupDebug, "Recieved unknown debugger message of type '{0}'.", frame.Type); break; @@ -90,8 +110,12 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable byte[] frameBytes; while (!_outQueue.TryDequeue(out frameBytes)) ; - _socket.Send(BitConverter.GetBytes(frameBytes.Length)); - _socket.Send(frameBytes); + try + { + _socket.Send(BitConverter.GetBytes(frameBytes.Length)); + _socket.Send(frameBytes); + } + catch (SocketException) { } } } @@ -102,8 +126,8 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable _socket = _listener.AcceptSocket(); - System.Threading.Thread receiveThread = new(MessageRecieveLoop); - System.Threading.Thread sendThread = new(MessageSendLoop); + System.Threading.Thread receiveThread = new(MessageReceiveLoop) { IsBackground = true }; + System.Threading.Thread sendThread = new(MessageSendLoop) { IsBackground = true }; receiveThread.Start(); sendThread.Start(); } diff --git a/UIX/Microsoft/Iris/Markup/Interpreter.cs b/UIX/Microsoft/Iris/Markup/Interpreter.cs index c2b94ae..0506d2e 100644 --- a/UIX/Microsoft/Iris/Markup/Interpreter.cs +++ b/UIX/Microsoft/Iris/Markup/Interpreter.cs @@ -78,18 +78,21 @@ namespace Microsoft.Iris.Markup if (debugging && context.LoadResult.LineNumberTable.TryLookup(reader.CurrentOffset, out int line, out int column)) { bool ShouldBreak(Breakpoint b) - => b.Enabled - && b.Uri.Equals(loadResult.Uri, StringComparison.OrdinalIgnoreCase) - && (b.Offset == reader.CurrentOffset || (b.Line == line && b.Column == column)); + => b.Enabled && b.Equals(loadResult.Uri, line, column, reader.CurrentOffset); // Check if a breakpoint has been set at this location bool shouldBreakHere = Application.DebugSettings.Breakpoints.Any(ShouldBreak); if (shouldBreakHere) { - System.Diagnostics.Debugger.Break(); + Application.Debugger.DebuggerCommand = InterpreterCommand.Break; + //System.Diagnostics.Debugger.Break(); } } + while (Application.Debugger.DebuggerCommand == InterpreterCommand.Break) ; + if (Application.Debugger.DebuggerCommand == InterpreterCommand.Step) + Application.Debugger.DebuggerCommand = InterpreterCommand.Break; + switch (opCode) { case OpCode.ConstructObject: