From 05f2430bbd5e7b6929d5bae69dcdba0cb3c06e32 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Tue, 21 Jan 2020 21:33:27 +0000 Subject: [PATCH] Update Mono debug proxy code (#18478) --- .../src/MonoDebugProxy/ws-proxy/DebugStore.cs | 4 +- .../src/MonoDebugProxy/ws-proxy/MonoProxy.cs | 135 ++++++++++-------- .../src/MonoDebugProxy/ws-proxy/WsProxy.cs | 48 +++++-- 3 files changed, 114 insertions(+), 73 deletions(-) diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs index ee28bd94b7..e1e9b7392b 100644 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs +++ b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs @@ -545,7 +545,7 @@ namespace WsProxy { return assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase)); } - /* + /* V8 uses zero based indexing for both line and column. PPDBs uses one based indexing for both line and column. */ @@ -598,7 +598,7 @@ namespace WsProxy { PPDBs uses one based indexing for both line and column. */ static bool Match (SequencePoint sp, int line, int column) - { + { var bp = (line: line + 1, column: column + 1); if (sp.StartLine > bp.line || sp.EndLine < bp.line) diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs index 8c440da1ce..dfcb373afe 100644 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs +++ b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs @@ -223,7 +223,7 @@ namespace WsProxy { Info ("RUNTIME READY, PARTY TIME"); await RuntimeReady (token); await SendCommand ("Debugger.resume", new JObject (), token); - SendEvent ("Mono.runtimeReady", new JObject (), token); + SendEvent ("Mono.runtimeReady", new JObject (), token); } async Task OnBreakPointHit (JObject args, CancellationToken token) @@ -274,12 +274,18 @@ namespace WsProxy { var frames = new List (); int frame_id = 0; var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values (); + foreach (var mono_frame in the_mono_frames) { var il_pos = mono_frame ["il_pos"].Value (); var method_token = mono_frame ["method_token"].Value (); var assembly_name = mono_frame ["assembly_name"].Value (); var asm = store.GetAssemblyByName (assembly_name); + if (asm == null) { + Info ($"Unable to find assembly: {assembly_name}"); + continue; + } + var method = asm.GetMethodByToken (method_token); if (method == null) { @@ -374,7 +380,7 @@ namespace WsProxy { //Debug ($"\t{is_ready}"); if (is_ready.HasValue && is_ready.Value == true) { Debug ("RUNTIME LOOK READY. GO TIME!"); - await RuntimeReady (token); + await OnRuntimeReady (token); } } @@ -426,33 +432,38 @@ namespace WsProxy { return; } - var values = res.Value?["result"]?["value"]?.Values().ToArray(); + try { + var values = res.Value?["result"]?["value"]?.Values().ToArray() ?? Array.Empty(); + var var_list = new List(); - var var_list = new List(); + // Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously + // results in a "Memory access out of bounds", causing 'values' to be null, + // so skip returning variable values in that case. + for (int i = 0; i < values.Length; i+=2) + { + string fieldName = (string)values[i]["name"]; + if (fieldName.Contains("k__BackingField")){ + fieldName = fieldName.Replace("k__BackingField", ""); + fieldName = fieldName.Replace("<", ""); + fieldName = fieldName.Replace(">", ""); + } + var value = values [i + 1]? ["value"]; + if (((string)value ["description"]) == null) + value ["description"] = value ["value"]?.ToString (); - // Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously - // results in a "Memory access out of bounds", causing 'values' to be null, - // so skip returning variable values in that case. - for (int i = 0; i < values.Length; i+=2) - { - string fieldName = (string)values[i]["name"]; - if (fieldName.Contains("k__BackingField")){ - fieldName = fieldName.Replace("k__BackingField", ""); - fieldName = fieldName.Replace("<", ""); - fieldName = fieldName.Replace(">", ""); + var_list.Add(JObject.FromObject(new { + name = fieldName, + value + })); + + } + o = JObject.FromObject(new + { + result = var_list + }); + } catch (Exception e) { + Debug ($"failed to parse {res.Value}"); } - var_list.Add(JObject.FromObject(new - { - name = fieldName, - value = values[i+1]["value"] - })); - - } - o = JObject.FromObject(new - { - result = var_list - }); - SendResponse(msg_id, Result.Ok(o), token); } @@ -481,41 +492,51 @@ namespace WsProxy { return; } - var values = res.Value? ["result"]? ["value"]?.Values ().ToArray (); + try { + var values = res.Value? ["result"]? ["value"]?.Values ().ToArray (); - var var_list = new List (); - int i = 0; - // Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously - // results in a "Memory access out of bounds", causing 'values' to be null, - // so skip returning variable values in that case. - while (values != null && i < vars.Length && i < values.Length) { - var value = values [i] ["value"]; - if (((string)value ["description"]) == null) - value ["description"] = value ["value"]?.ToString(); + var var_list = new List (); + int i = 0; + // Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously + // results in a "Memory access out of bounds", causing 'values' to be null, + // so skip returning variable values in that case. + while (values != null && i < vars.Length && i < values.Length) { + var value = values [i] ["value"]; + if (((string)value ["description"]) == null) + value ["description"] = value ["value"]?.ToString (); - var_list.Add (JObject.FromObject (new { - name = vars [i].Name, - value = values [i] ["value"] - })); - i++; + var_list.Add (JObject.FromObject (new { + name = vars [i].Name, + value + })); + i++; + } + //Async methods are special in the way that local variables can be lifted to generated class fields + //value of "this" comes here either + while (i < values.Length) { + String name = values [i] ["name"].ToString (); + + if (name.IndexOf (">", StringComparison.Ordinal) > 0) + name = name.Substring (1, name.IndexOf (">", StringComparison.Ordinal) - 1); + + var value = values [i + 1] ["value"]; + if (((string)value ["description"]) == null) + value ["description"] = value ["value"]?.ToString (); + + var_list.Add (JObject.FromObject (new { + name, + value + })); + i = i + 2; + } + o = JObject.FromObject (new { + result = var_list + }); + SendResponse (msg_id, Result.Ok (o), token); } - //Async methods are special in the way that local variables can be lifted to generated class fields - //value of "this" comes here either - while (i < values.Length) { - String name = values [i] ["name"].ToString (); - - if (name.IndexOf (">", StringComparison.Ordinal) > 0) - name = name.Substring (1, name.IndexOf (">", StringComparison.Ordinal) - 1); - var_list.Add (JObject.FromObject (new { - name = name, - value = values [i+1] ["value"] - })); - i = i + 2; + catch (Exception exc) { + SendResponse (msg_id, res, token); } - o = JObject.FromObject (new { - result = var_list - }); - SendResponse (msg_id, Result.Ok (o), token); } async Task EnableBreakPoint (Breakpoint bp, CancellationToken token) diff --git a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs index 17c72e9ce7..87ef23027e 100644 --- a/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs +++ b/src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs @@ -97,6 +97,7 @@ namespace WsProxy { internal class WsProxy { TaskCompletionSource side_exception = new TaskCompletionSource (); + TaskCompletionSource client_initiated_close = new TaskCompletionSource (); List<(int, TaskCompletionSource)> pending_cmds = new List<(int, TaskCompletionSource)> (); ClientWebSocket browser; WebSocket ide; @@ -119,8 +120,16 @@ namespace WsProxy { byte [] buff = new byte [4000]; var mem = new MemoryStream (); while (true) { + + if (socket.State != WebSocketState.Open) { + Console.WriteLine ($"WSProxy: Socket is no longer open."); + client_initiated_close.TrySetResult (true); + return null; + } + var result = await socket.ReceiveAsync (new ArraySegment (buff), token); if (result.MessageType == WebSocketMessageType.Close) { + client_initiated_close.TrySetResult (true); return null; } @@ -144,7 +153,7 @@ namespace WsProxy { void Send (WebSocket to, JObject o, CancellationToken token) { - var bytes = Encoding.UTF8.GetBytes (o.ToString ()); + var bytes = Encoding.UTF8.GetBytes (o.ToString ()); var queue = GetQueueForSocket (to); var task = queue.Send (bytes, token); @@ -199,9 +208,10 @@ namespace WsProxy { void ProcessIdeMessage (string msg, CancellationToken token) { - var res = JObject.Parse (msg); - - pending_ops.Add (OnCommand (res ["id"].Value (), res ["method"].Value (), res ["params"] as JObject, token)); + if (!string.IsNullOrEmpty (msg)) { + var res = JObject.Parse (msg); + pending_ops.Add (OnCommand (res ["id"].Value (), res ["method"].Value (), res ["params"] as JObject, token)); + } } internal async Task SendCommand (string method, JObject args, CancellationToken token) { @@ -255,24 +265,25 @@ namespace WsProxy { Send (this.ide, o, token); } - // , HttpContext context) + // , HttpContext context) public async Task Run (Uri browserUri, WebSocket ideSocket) { - Debug ("wsproxy start"); + Debug ($"WsProxy Starting on {browserUri}"); using (this.ide = ideSocket) { - Debug ("ide connected"); + Debug ($"WsProxy: IDE waiting for connection on {browserUri}"); queues.Add (new WsQueue (this.ide)); using (this.browser = new ClientWebSocket ()) { this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan; await this.browser.ConnectAsync (browserUri, CancellationToken.None); queues.Add (new WsQueue (this.browser)); - Debug ("client connected"); + Debug ($"WsProxy: Client connected on {browserUri}"); var x = new CancellationTokenSource (); pending_ops.Add (ReadOne (browser, x.Token)); pending_ops.Add (ReadOne (ide, x.Token)); pending_ops.Add (side_exception.Task); + pending_ops.Add (client_initiated_close.Task); try { while (!x.IsCancellationRequested) { @@ -280,15 +291,23 @@ namespace WsProxy { //Console.WriteLine ("pump {0} {1}", task, pending_ops.IndexOf (task)); if (task == pending_ops [0]) { var msg = ((Task)task).Result; - pending_ops [0] = ReadOne (browser, x.Token); //queue next read - ProcessBrowserMessage (msg, x.Token); + if (msg != null) { + pending_ops [0] = ReadOne (browser, x.Token); //queue next read + ProcessBrowserMessage (msg, x.Token); + } } else if (task == pending_ops [1]) { var msg = ((Task)task).Result; - pending_ops [1] = ReadOne (ide, x.Token); //queue next read - ProcessIdeMessage (msg, x.Token); + if (msg != null) { + pending_ops [1] = ReadOne (ide, x.Token); //queue next read + ProcessIdeMessage (msg, x.Token); + } } else if (task == pending_ops [2]) { var res = ((Task)task).Result; throw new Exception ("side task must always complete with an exception, what's going on???"); + } else if (task == pending_ops [3]) { + var res = ((Task)task).Result; + Debug ($"WsProxy: Client initiated close from {browserUri}"); + x.Cancel (); } else { //must be a background task pending_ops.Remove (task); @@ -301,10 +320,11 @@ namespace WsProxy { } } } catch (Exception e) { - Debug ($"got exception {e}"); + Debug ($"WsProxy::Run: Exception {e}"); //throw; } finally { - x.Cancel (); + if (!x.IsCancellationRequested) + x.Cancel (); } } }