From 3539ed482637bda02ecfd0ef30e0da85b7dd37a2 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Thu, 1 Jun 2023 20:18:53 -0500 Subject: [PATCH 1/8] Implement new System.Net.Sockets debugger --- Tests/SimpleDebugClient/Program.cs | 7 +- Tests/SimpleIrisApp/Program.cs | 3 +- .../Iris/Debug/Data/DebuggerMessageFrame.cs | 57 ++++++++++ UIX/Microsoft/Iris/Debug/DebugRemoting.cs | 35 +++++- UIX/Microsoft/Iris/Debug/IDebuggerClient.cs | 2 +- .../Iris/Debug/NetMQ/ZmqDebuggerClient.cs | 5 +- .../Iris/Debug/SystemNet/NetDebuggerClient.cs | 89 +++++++++++++++ .../Iris/Debug/SystemNet/NetDebuggerServer.cs | 106 ++++++++++++++++++ 8 files changed, 293 insertions(+), 11 deletions(-) create mode 100644 UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs create mode 100644 UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs create mode 100644 UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs diff --git a/Tests/SimpleDebugClient/Program.cs b/Tests/SimpleDebugClient/Program.cs index b815ec1..fbdc00a 100644 --- a/Tests/SimpleDebugClient/Program.cs +++ b/Tests/SimpleDebugClient/Program.cs @@ -1,5 +1,6 @@ using Microsoft.Iris.Debug; using Microsoft.Iris.Debug.Data; +using Microsoft.Iris.Debug.SystemNet; using System; namespace SimpleDebugClient; @@ -10,12 +11,12 @@ internal class Program static int Main(string[] args) { - string connectionString = args.Length >= 2 - ? args[1] : "tcp://127.0.0.1:5556"; + var connectionString = args.Length >= 2 + ? new Uri(args[1]) : DebugRemoting.DEFAULT_TCP_URI; Console.CancelKeyPress += Console_CancelKeyPress; - Debugger = new ZmqDebuggerClient(connectionString); + Debugger = new NetDebuggerClient(connectionString); Debugger.DispatcherStep += Debugger_DispatcherStep; Debugger.InterpreterStep += Debugger_InterpreterStep; diff --git a/Tests/SimpleIrisApp/Program.cs b/Tests/SimpleIrisApp/Program.cs index 349263a..d14e869 100644 --- a/Tests/SimpleIrisApp/Program.cs +++ b/Tests/SimpleIrisApp/Program.cs @@ -1,4 +1,5 @@ using Microsoft.Iris; +using Microsoft.Iris.Debug; using System; namespace SimpleIrisApp; @@ -19,7 +20,7 @@ internal class Program }; Application.DebugSettings.DebugConnectionUri = args.Length >= 2 - ? args[1] : "tcp://127.0.0.1:5556"; + ? args[1] : DebugRemoting.DEFAULT_TCP_URI.OriginalString; Application.DebugSettings.Breakpoints.Add(new("clr-res://SimpleIrisApp!MainPage.uix", 3, 22)); #endif diff --git a/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs b/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs new file mode 100644 index 0000000..16ca654 --- /dev/null +++ b/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs @@ -0,0 +1,57 @@ +using System; +using System.Text; + +namespace Microsoft.Iris.Debug.Data; + +public class DebuggerMessageFrame +{ + public DebuggerMessageFrame() { } + + public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, byte[] data) + { + TransactionId = transactionId; + Type = type; + Data = data; + } + + public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, string message, Encoding encoding = null) + : this(transactionId, type, (encoding ?? Encoding.UTF8).GetBytes(message)) + { + } + + public DebuggerMessageFrame(byte[] bytes) + { + TransactionId = BitConverter.ToInt64(bytes, 0); + Type = (DebuggerMessageType)BitConverter.ToInt32(bytes, sizeof(long)); + Data = bytes.AsSpan(sizeof(long) + sizeof(DebuggerMessageType)).ToArray(); + } + + public long TransactionId { get; set; } + + public DebuggerMessageType Type { get; set; } + + public byte[] Data { get; set; } + + public byte[] ToBytes() + { + byte[] bytes = new byte[Data.Length + sizeof(DebuggerMessageType) + sizeof(long)]; + +#if NET5_0_OR_GREATER + var span = bytes.AsSpan(); + BitConverter.TryWriteBytes(span, TransactionId); + BitConverter.TryWriteBytes(span[sizeof(long)..], (int)Type); +#else + var idBytes = BitConverter.GetBytes(TransactionId); + idBytes.CopyTo(bytes, 0); + + var typeBytes = BitConverter.GetBytes((int)Type); + typeBytes.CopyTo(bytes, sizeof(long)); +#endif + + Data.CopyTo(bytes, sizeof(long) + sizeof(DebuggerMessageType)); + + return bytes; + } + + public string GetDataAsString(Encoding encoding = null) => (encoding ?? Encoding.UTF8).GetString(Data); +} diff --git a/UIX/Microsoft/Iris/Debug/DebugRemoting.cs b/UIX/Microsoft/Iris/Debug/DebugRemoting.cs index a078cea..4a36bad 100644 --- a/UIX/Microsoft/Iris/Debug/DebugRemoting.cs +++ b/UIX/Microsoft/Iris/Debug/DebugRemoting.cs @@ -1,4 +1,8 @@ -using System.Runtime.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Sockets; +using System.Runtime.Serialization; namespace Microsoft.Iris.Debug; @@ -7,9 +11,34 @@ namespace Microsoft.Iris.Debug; /// public static class DebugRemoting { - public const string DEFAULT_TCP_CLIENT_URI = ">tcp://127.0.0.1:5555,@tcp://127.0.0.1:55556"; + public const string DEFAULT_TCP_CLIENT_URI = ">tcp://127.0.0.1:5555,@tcp://127.0.0.1:5556"; + public const string DEFAULT_TCP_SERVER_URI = "@tcp://127.0.0.1:5555,>tcp://127.0.0.1:5556"; - public const string DEFAULT_TCP_SERVER_URI = "@tcp://127.0.0.1:5555,>tcp://127.0.0.1:55556"; + public static readonly Uri DEFAULT_TCP_URI = new("tcp://127.0.0.1:5555"); internal static IFormatter CreateBsonFormatter() => new BsonFormatter(new StreamingContext(StreamingContextStates.Remoting)); + + internal static Data.DebuggerMessageFrame ReceiveDebuggerMessage(Socket socket) + { + try + { + byte[] sizeBuffer = new byte[sizeof(int)]; + int bytesReceived = socket.Receive(sizeBuffer); + + // Reached end of stream, no bytes to recieve + if (bytesReceived == 0) + return null; + + int frameLength = BitConverter.ToInt32(sizeBuffer, 0); + + byte[] frameBytes = new byte[frameLength]; + socket.Receive(frameBytes, frameLength, SocketFlags.None); + + return new(frameBytes); + } + catch (SocketException) + { + return null; + } + } } diff --git a/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs b/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs index 5015f44..0d5a2ca 100644 --- a/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs +++ b/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs @@ -8,7 +8,7 @@ public interface IDebuggerClient /// /// The URI the client is connected to. /// - string ConnectionUri { get; } + Uri ConnectionUri { get; } /// /// Fired when the UIX interpreter steps forward. diff --git a/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs b/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs index 110baba..ef711a2 100644 --- a/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs +++ b/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs @@ -1,4 +1,3 @@ -using Microsoft.Iris.Debug; using Microsoft.Iris.Debug.Data; using NetMQ; using NetMQ.Sockets; @@ -16,14 +15,14 @@ public class ZmqDebuggerClient : IDebuggerClient, IDisposable private readonly PairSocket _socket; private readonly IFormatter _formatter; - public string ConnectionUri { get; } + public Uri ConnectionUri { get; } public event EventHandler InterpreterStep; public event Action DispatcherStep; public ZmqDebuggerClient(string connectionUri) { - ConnectionUri = connectionUri ?? DebugRemoting.DEFAULT_TCP_CLIENT_URI; + ConnectionUri = new(connectionUri ?? DebugRemoting.DEFAULT_TCP_CLIENT_URI); _socket = new(connectionUri); _formatter = DebugRemoting.CreateBsonFormatter(); diff --git a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs new file mode 100644 index 0000000..4d4a442 --- /dev/null +++ b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs @@ -0,0 +1,89 @@ +using Microsoft.Iris.Debug.Data; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.Serialization; +using System.Text; + +namespace Microsoft.Iris.Debug.SystemNet; + +public class NetDebuggerClient : IDebuggerClient, IDisposable +{ + public Uri ConnectionUri { get; } + + private readonly Socket _socket; + private readonly Queue _queue; + private readonly IFormatter _formatter; + + public event EventHandler InterpreterStep; + public event Action DispatcherStep; + + public NetDebuggerClient(string connectionUri) : this(new Uri(connectionUri)) + { + } + + public NetDebuggerClient(Uri connectionUri) + { + ConnectionUri = connectionUri ?? DebugRemoting.DEFAULT_TCP_URI; + _socket = new(SocketType.Stream, ProtocolType.Tcp); + _formatter = DebugRemoting.CreateBsonFormatter(); + + System.Threading.Thread connectThread = new(ConnectLoop); + connectThread.Start(); + } + + public void Dispose() => _socket.Dispose(); + + private void QueueDebuggerMessage(DebuggerMessageFrame frame) + { + _queue.Enqueue(frame.ToBytes()); + } + + private void MessageRecieveLoop() + { + while (_socket.Connected) + { + DebuggerMessageFrame frame; + while ((frame = DebugRemoting.ReceiveDebuggerMessage(_socket)) == null) + if (!_socket.Connected) return; + + switch (frame.Type) + { + case DebuggerMessageType.InterpreterOpCode: + { + using MemoryStream stream = new(frame.Data); + var entry = (InterpreterEntry)_formatter.Deserialize(stream); + InterpreterStep?.Invoke(this, entry); + } + break; + + case DebuggerMessageType.DispatcherStep: + string message = frame.GetDataAsString(); + DispatcherStep?.Invoke(message); + break; + + default: + Trace.WriteLine(TraceCategory.MarkupDebug, "Recieved unknown debugger message of type '{0}'.", frame.Type); + break; + } + } + } + + private void ConnectLoop() + { + while (!_socket.Connected) + { + try + { + var endpoint = new IPEndPoint(IPAddress.Parse(ConnectionUri.Host), ConnectionUri.Port); + _socket.Connect(endpoint); + } + catch { } + } + + System.Threading.Thread receiveThread = new(MessageRecieveLoop); + receiveThread.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 new file mode 100644 index 0000000..f0899cb --- /dev/null +++ b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs @@ -0,0 +1,106 @@ +using Microsoft.Iris.Debug.Data; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Runtime.Serialization; +using System.Text; + +namespace Microsoft.Iris.Debug.SystemNet; + +internal class NetDebuggerServer : IDebuggerServer, IDisposable +{ + public static IDebuggerServer Current { get; private set; } + + public Uri ConnectionUri { get; } + + private readonly Queue _outQueue = new(); + private readonly TcpListener _listener; + private readonly IFormatter _formatter; + private Socket _socket; + + public NetDebuggerServer(string connectionUri) : this(new Uri(connectionUri)) + { + } + + public NetDebuggerServer(Uri connectionUri) + { + ConnectionUri = connectionUri ?? DebugRemoting.DEFAULT_TCP_URI; + + _listener = TcpListener.Create(connectionUri.Port); + _listener.Start(); + + _formatter = DebugRemoting.CreateBsonFormatter(); + + System.Threading.Thread connectThread = new(ConnectLoop); + connectThread.Start(); + + Current = this; + } + + public void LogInterpreterOpCode(object context, InterpreterEntry entry) + { + using MemoryStream entryStream = new(); + _formatter.Serialize(entryStream, entry); + + QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterOpCode, entryStream.ToArray())); + } + + public void LogDispatcher(string message) + { + QueueDebuggerMessage(new(0, DebuggerMessageType.DispatcherStep, message)); + } + + public void Dispose() + { + _listener.Stop(); + _socket?.Dispose(); + } + + private void QueueDebuggerMessage(DebuggerMessageFrame frame) + { + _outQueue.Enqueue(frame.ToBytes()); + } + + private void MessageRecieveLoop() + { + while (_socket.Connected) + { + DebuggerMessageFrame frame; + while ((frame = DebugRemoting.ReceiveDebuggerMessage(_socket)) == null) + ; + + switch (frame.Type) + { + default: + Trace.WriteLine(TraceCategory.MarkupDebug, "Recieved unknown debugger message of type '{0}'.", frame.Type); + break; + } + } + } + + private void MessageSendLoop() + { + while (_socket.Connected) + { + while (_outQueue.Count == 0) ; + + var frameBytes = _outQueue.Dequeue(); + _socket.Send(BitConverter.GetBytes(frameBytes.Length)); + _socket.Send(frameBytes); + } + } + + private void ConnectLoop() + { + while (!_listener.Pending()) ; + + _socket = _listener.AcceptSocket(); + + System.Threading.Thread receiveThread = new(MessageRecieveLoop); + System.Threading.Thread sendThread = new(MessageSendLoop); + receiveThread.Start(); + sendThread.Start(); + } +} From 4ce9c51d9ce2cbaf4846485877487c6555cbb425 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Thu, 1 Jun 2023 20:36:32 -0500 Subject: [PATCH 2/8] Handle disposing of debugger server --- UIX/Microsoft/Iris/Application.cs | 6 +++++- UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/UIX/Microsoft/Iris/Application.cs b/UIX/Microsoft/Iris/Application.cs index 6f98725..bac127b 100644 --- a/UIX/Microsoft/Iris/Application.cs +++ b/UIX/Microsoft/Iris/Application.cs @@ -195,7 +195,7 @@ namespace Microsoft.Iris if (DebugSettings.DebugConnectionUri != null) { - Debugger = new Debug.NetMQ.ZmqDebuggerServer(DebugSettings.DebugConnectionUri); + Debugger = new Debug.SystemNet.NetDebuggerServer(DebugSettings.DebugConnectionUri); DebuggerServerReady?.Invoke(Debugger, EventArgs.Empty); } @@ -309,7 +309,11 @@ namespace Microsoft.Iris if (s_initializationState == InitializationState.InitializedWithoutUI) RenderApi.ShutdownForToolOnly(); StaticServices.Uninitialize(); + Debug.Trace.Shutdown(); + if (Debugger is IDisposable disposable) + disposable.Dispose(); + ErrorManager.OnErrors -= new NotifyErrorBatch(NotifyErrorBatchHandler); s_initializationState = InitializationState.NotInitialized; } diff --git a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs index f0899cb..7c1cb6c 100644 --- a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs +++ b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs @@ -19,6 +19,7 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable private readonly TcpListener _listener; private readonly IFormatter _formatter; private Socket _socket; + private bool _disposed = false; public NetDebuggerServer(string connectionUri) : this(new Uri(connectionUri)) { @@ -54,6 +55,10 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable public void Dispose() { + if (_disposed) + return; + + _disposed = true; _listener.Stop(); _socket?.Dispose(); } @@ -94,7 +99,8 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable private void ConnectLoop() { - while (!_listener.Pending()) ; + while (!_listener.Pending()) + if (_disposed) return; _socket = _listener.AcceptSocket(); From c2b9f398e07376970bf8cfb64c87411d9130889d Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Thu, 1 Jun 2023 20:36:57 -0500 Subject: [PATCH 3/8] Ensure proper shutdown in sample app --- Tests/SimpleIrisApp/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/SimpleIrisApp/Program.cs b/Tests/SimpleIrisApp/Program.cs index d14e869..62e034d 100644 --- a/Tests/SimpleIrisApp/Program.cs +++ b/Tests/SimpleIrisApp/Program.cs @@ -33,6 +33,7 @@ internal class Program Application.Window.RequestLoad("clr-res://SimpleIrisApp!MainPage.uix#Frame"); Application.Run(OnInitialLoadComplete); + Application.Shutdown(); } static void OnInitialLoadComplete(object arg) From 3bf297d189244407dd65d58398540d9c67177bb1 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Thu, 1 Jun 2023 22:49:47 -0500 Subject: [PATCH 4/8] Use ConcurrentQueue --- .../Iris/Debug/SystemNet/NetDebuggerServer.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs index 7c1cb6c..7f2e734 100644 --- a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs +++ b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs @@ -1,11 +1,9 @@ using Microsoft.Iris.Debug.Data; using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.IO; -using System.Net; using System.Net.Sockets; using System.Runtime.Serialization; -using System.Text; namespace Microsoft.Iris.Debug.SystemNet; @@ -15,7 +13,7 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable public Uri ConnectionUri { get; } - private readonly Queue _outQueue = new(); + private readonly ConcurrentQueue _outQueue = new(); private readonly TcpListener _listener; private readonly IFormatter _formatter; private Socket _socket; @@ -89,9 +87,9 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable { while (_socket.Connected) { - while (_outQueue.Count == 0) ; + byte[] frameBytes; + while (!_outQueue.TryDequeue(out frameBytes)) ; - var frameBytes = _outQueue.Dequeue(); _socket.Send(BitConverter.GetBytes(frameBytes.Length)); _socket.Send(frameBytes); } From a0d214fb2f15338b1d552d7963b98f5169bbc284 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Fri, 2 Jun 2023 00:07:58 -0500 Subject: [PATCH 5/8] Remove ZeroMQ --- .../Iris/Debug/NetMQ/ZmqDebuggerClient.cs | 78 ------------------- .../Iris/Debug/NetMQ/ZmqDebuggerServer.cs | 55 ------------- UIX/UIX.csproj | 1 - 3 files changed, 134 deletions(-) delete mode 100644 UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs delete mode 100644 UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerServer.cs diff --git a/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs b/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs deleted file mode 100644 index ef711a2..0000000 --- a/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerClient.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Microsoft.Iris.Debug.Data; -using NetMQ; -using NetMQ.Sockets; -using System; -using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization; -using System.Text; - -namespace Microsoft.Iris.Debug.NetMQ; - -public class ZmqDebuggerClient : IDebuggerClient, IDisposable -{ - private List _frames = new(2); - private readonly PairSocket _socket; - private readonly IFormatter _formatter; - - public Uri ConnectionUri { get; } - - public event EventHandler InterpreterStep; - public event Action DispatcherStep; - - public ZmqDebuggerClient(string connectionUri) - { - ConnectionUri = new(connectionUri ?? DebugRemoting.DEFAULT_TCP_CLIENT_URI); - - _socket = new(connectionUri); - _formatter = DebugRemoting.CreateBsonFormatter(); - - System.Threading.Thread th = new(MessageRecieveLoop); - th.Start(); - } - - public void Dispose() => _socket.Dispose(); - - private void MessageRecieveLoop() - { - while (!_socket.IsDisposed) - { - DebuggerMessageType type; - byte[] bytes; - while (!TryRecieveDebuggerMessage(out type, out bytes)) - ; - - switch (type) - { - case DebuggerMessageType.InterpreterOpCode: - { - using MemoryStream stream = new(bytes); - var entry = (InterpreterEntry)_formatter.Deserialize(stream); - InterpreterStep?.Invoke(this, entry); - } - break; - - case DebuggerMessageType.DispatcherStep: - string message = Encoding.Unicode.GetString(bytes); - DispatcherStep?.Invoke(message); - break; - } - } - } - - private bool TryRecieveDebuggerMessage(out DebuggerMessageType type, out byte[] bytes) - { - if (!_socket.IsDisposed && _socket.TryReceiveMultipartBytes(ref _frames, 2)) - { - type = (DebuggerMessageType)BitConverter.ToInt32(_frames[0], 0); - bytes = _frames[1]; - return true; - } - - type = default; - bytes = null; - return false; - } - - internal static IFormatter CreateFormatter() => new BsonFormatter(new StreamingContext(StreamingContextStates.Remoting)); -} diff --git a/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerServer.cs b/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerServer.cs deleted file mode 100644 index 6a7fe06..0000000 --- a/UIX/Microsoft/Iris/Debug/NetMQ/ZmqDebuggerServer.cs +++ /dev/null @@ -1,55 +0,0 @@ -using NetMQ; -using NetMQ.Sockets; -using System; -using System.IO; -using System.Runtime.Serialization; -using System.Text; - -namespace Microsoft.Iris.Debug.NetMQ; - -internal class ZmqDebuggerServer : IDebuggerServer, IDisposable -{ - public static IDebuggerServer Current { get; private set; } - - private readonly PairSocket _socket; - private readonly byte[][] _messageFrame = new byte[2][]; - private readonly IFormatter _formatter; - - public ZmqDebuggerServer(string connectionUri) - { - _socket = new(connectionUri ?? DebugRemoting.DEFAULT_TCP_SERVER_URI); - - _formatter = DebugRemoting.CreateBsonFormatter(); - - Current = this; - } - - public void LogInterpreterOpCode(object context, Data.InterpreterEntry entry) - { - using MemoryStream entryStream = new(); - _formatter.Serialize(entryStream, entry); - - SendDebuggerMessage(DebuggerMessageType.InterpreterOpCode, entryStream.ToArray()); - } - - public void LogDispatcher(string message) - { - SendDebuggerMessage(DebuggerMessageType.DispatcherStep, message); - } - - public void Dispose() => _socket.Dispose(); - - private void SendDebuggerMessage(DebuggerMessageType type, string message, Encoding encoding = null) - { - var messageBytes = (encoding ?? Encoding.Unicode).GetBytes(message); - SendDebuggerMessage(type, messageBytes); - } - - private void SendDebuggerMessage(DebuggerMessageType type, byte[] bytes) - { - _messageFrame[0] = BitConverter.GetBytes((int)type); - _messageFrame[1] = bytes; - - _socket.SendMultipartBytes(_messageFrame); - } -} diff --git a/UIX/UIX.csproj b/UIX/UIX.csproj index fd36702..df83dc1 100644 --- a/UIX/UIX.csproj +++ b/UIX/UIX.csproj @@ -10,7 +10,6 @@ - From 1ae6b92cf9154d0c53231d05e3e76f33bc363357 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Fri, 2 Jun 2023 00:08:28 -0500 Subject: [PATCH 6/8] Mark Breakpoint as serializable --- UIX/Microsoft/Iris/Debug/Data/Breakpoint.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/UIX/Microsoft/Iris/Debug/Data/Breakpoint.cs b/UIX/Microsoft/Iris/Debug/Data/Breakpoint.cs index 8a6ded1..e2c69ad 100644 --- a/UIX/Microsoft/Iris/Debug/Data/Breakpoint.cs +++ b/UIX/Microsoft/Iris/Debug/Data/Breakpoint.cs @@ -3,6 +3,7 @@ using System.Text; namespace Microsoft.Iris.Debug.Data; +[Serializable] public struct Breakpoint : IEquatable { public Breakpoint(string uri, int line, int column, bool enabled = true) : this(uri, enabled) @@ -32,7 +33,16 @@ public struct Breakpoint : IEquatable public bool Enabled { get; set; } - public bool Equals(string uri, int line, int column) => uri == Uri && line == Line && column == Column; + public bool Equals(string uri, int line, int column, uint offset = uint.MaxValue) + { + if (!Uri.Equals(uri, StringComparison.OrdinalIgnoreCase)) + return false; + + if (Offset != uint.MaxValue && offset != uint.MaxValue) + return Offset == offset; + else + return line == Line && column == Column; + } public bool Equals(Breakpoint other) => Equals(other.Uri, other.Line, other.Column); From 1dea74d1e59bb9700b0adff097d4b1ecab0aabe4 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Fri, 2 Jun 2023 00:08:56 -0500 Subject: [PATCH 7/8] Add debugger serialization and deserialization helpers --- UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs | 8 ++++++++ UIX/Microsoft/Iris/Debug/DebugRemoting.cs | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs b/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs index 16ca654..2a5f613 100644 --- a/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs +++ b/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs @@ -1,4 +1,6 @@ using System; +using System.IO; +using System.Runtime.Serialization; using System.Text; namespace Microsoft.Iris.Debug.Data; @@ -54,4 +56,10 @@ public class DebuggerMessageFrame } public string GetDataAsString(Encoding encoding = null) => (encoding ?? Encoding.UTF8).GetString(Data); + + public T DeserializeData(IFormatter formatter) + { + using MemoryStream stream = new(Data); + return (T)formatter.Deserialize(stream); + } } diff --git a/UIX/Microsoft/Iris/Debug/DebugRemoting.cs b/UIX/Microsoft/Iris/Debug/DebugRemoting.cs index 4a36bad..287cba0 100644 --- a/UIX/Microsoft/Iris/Debug/DebugRemoting.cs +++ b/UIX/Microsoft/Iris/Debug/DebugRemoting.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Net.Sockets; using System.Runtime.Serialization; @@ -41,4 +40,11 @@ public static class DebugRemoting return null; } } + + internal static byte[] Serialize(this object obj, IFormatter formatter) + { + using MemoryStream dataStream = new(); + formatter.Serialize(dataStream, obj); + return dataStream.ToArray(); + } } From f1a8627057b1ed42fdcee7c245871b06276cde33 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Fri, 2 Jun 2023 00:09:51 -0500 Subject: [PATCH 8/8] Implement basic interpreter execution controls --- Tests/SimpleDebugClient/Program.cs | 38 +++++++--- Tests/SimpleIrisApp/SimpleIrisApp.csproj | 2 +- .../Iris/Debug/Data/InterpreterCommand.cs | 8 +++ .../Iris/Debug/DebuggerMessageType.cs | 3 + UIX/Microsoft/Iris/Debug/IDebuggerClient.cs | 6 ++ UIX/Microsoft/Iris/Debug/IDebuggerServer.cs | 6 +- .../Iris/Debug/SystemNet/NetDebuggerClient.cs | 72 ++++++++++++++----- .../Iris/Debug/SystemNet/NetDebuggerServer.cs | 52 ++++++++++---- UIX/Microsoft/Iris/Markup/Interpreter.cs | 11 +-- 9 files changed, 153 insertions(+), 45 deletions(-) create mode 100644 UIX/Microsoft/Iris/Debug/Data/InterpreterCommand.cs 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: