Update Mono debug proxy code (#18478)

This commit is contained in:
Steve Sanderson 2020-01-21 21:33:27 +00:00 committed by Artak
parent 0f2e88a334
commit 05f2430bbd
3 changed files with 114 additions and 73 deletions

View File

@ -545,7 +545,7 @@ namespace WsProxy {
return assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase)); return assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase));
} }
/* /*
V8 uses zero based indexing for both line and column. V8 uses zero based indexing for both line and column.
PPDBs uses one 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. PPDBs uses one based indexing for both line and column.
*/ */
static bool Match (SequencePoint sp, int line, int column) static bool Match (SequencePoint sp, int line, int column)
{ {
var bp = (line: line + 1, column: column + 1); var bp = (line: line + 1, column: column + 1);
if (sp.StartLine > bp.line || sp.EndLine < bp.line) if (sp.StartLine > bp.line || sp.EndLine < bp.line)

View File

@ -223,7 +223,7 @@ namespace WsProxy {
Info ("RUNTIME READY, PARTY TIME"); Info ("RUNTIME READY, PARTY TIME");
await RuntimeReady (token); await RuntimeReady (token);
await SendCommand ("Debugger.resume", new JObject (), 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) async Task OnBreakPointHit (JObject args, CancellationToken token)
@ -274,12 +274,18 @@ namespace WsProxy {
var frames = new List<Frame> (); var frames = new List<Frame> ();
int frame_id = 0; int frame_id = 0;
var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values<JObject> (); var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values<JObject> ();
foreach (var mono_frame in the_mono_frames) { foreach (var mono_frame in the_mono_frames) {
var il_pos = mono_frame ["il_pos"].Value<int> (); var il_pos = mono_frame ["il_pos"].Value<int> ();
var method_token = mono_frame ["method_token"].Value<int> (); var method_token = mono_frame ["method_token"].Value<int> ();
var assembly_name = mono_frame ["assembly_name"].Value<string> (); var assembly_name = mono_frame ["assembly_name"].Value<string> ();
var asm = store.GetAssemblyByName (assembly_name); var asm = store.GetAssemblyByName (assembly_name);
if (asm == null) {
Info ($"Unable to find assembly: {assembly_name}");
continue;
}
var method = asm.GetMethodByToken (method_token); var method = asm.GetMethodByToken (method_token);
if (method == null) { if (method == null) {
@ -374,7 +380,7 @@ namespace WsProxy {
//Debug ($"\t{is_ready}"); //Debug ($"\t{is_ready}");
if (is_ready.HasValue && is_ready.Value == true) { if (is_ready.HasValue && is_ready.Value == true) {
Debug ("RUNTIME LOOK READY. GO TIME!"); Debug ("RUNTIME LOOK READY. GO TIME!");
await RuntimeReady (token); await OnRuntimeReady (token);
} }
} }
@ -426,33 +432,38 @@ namespace WsProxy {
return; return;
} }
var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray(); try {
var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
var var_list = new List<JObject>();
var var_list = new List<JObject>(); // 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 var_list.Add(JObject.FromObject(new {
// results in a "Memory access out of bounds", causing 'values' to be null, name = fieldName,
// so skip returning variable values in that case. value
for (int i = 0; i < values.Length; i+=2) }));
{
string fieldName = (string)values[i]["name"]; }
if (fieldName.Contains("k__BackingField")){ o = JObject.FromObject(new
fieldName = fieldName.Replace("k__BackingField", ""); {
fieldName = fieldName.Replace("<", ""); result = var_list
fieldName = fieldName.Replace(">", ""); });
} 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); SendResponse(msg_id, Result.Ok(o), token);
} }
@ -481,41 +492,51 @@ namespace WsProxy {
return; return;
} }
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray (); try {
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
var var_list = new List<JObject> (); var var_list = new List<JObject> ();
int i = 0; int i = 0;
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously // Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
// results in a "Memory access out of bounds", causing 'values' to be null, // results in a "Memory access out of bounds", causing 'values' to be null,
// so skip returning variable values in that case. // so skip returning variable values in that case.
while (values != null && i < vars.Length && i < values.Length) { while (values != null && i < vars.Length && i < values.Length) {
var value = values [i] ["value"]; var value = values [i] ["value"];
if (((string)value ["description"]) == null) if (((string)value ["description"]) == null)
value ["description"] = value ["value"]?.ToString(); value ["description"] = value ["value"]?.ToString ();
var_list.Add (JObject.FromObject (new { var_list.Add (JObject.FromObject (new {
name = vars [i].Name, name = vars [i].Name,
value = values [i] ["value"] value
})); }));
i++; 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 catch (Exception exc) {
//value of "this" comes here either SendResponse (msg_id, res, token);
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;
} }
o = JObject.FromObject (new {
result = var_list
});
SendResponse (msg_id, Result.Ok (o), token);
} }
async Task<Result> EnableBreakPoint (Breakpoint bp, CancellationToken token) async Task<Result> EnableBreakPoint (Breakpoint bp, CancellationToken token)

View File

@ -97,6 +97,7 @@ namespace WsProxy {
internal class WsProxy { internal class WsProxy {
TaskCompletionSource<bool> side_exception = new TaskCompletionSource<bool> (); TaskCompletionSource<bool> side_exception = new TaskCompletionSource<bool> ();
TaskCompletionSource<bool> client_initiated_close = new TaskCompletionSource<bool> ();
List<(int, TaskCompletionSource<Result>)> pending_cmds = new List<(int, TaskCompletionSource<Result>)> (); List<(int, TaskCompletionSource<Result>)> pending_cmds = new List<(int, TaskCompletionSource<Result>)> ();
ClientWebSocket browser; ClientWebSocket browser;
WebSocket ide; WebSocket ide;
@ -119,8 +120,16 @@ namespace WsProxy {
byte [] buff = new byte [4000]; byte [] buff = new byte [4000];
var mem = new MemoryStream (); var mem = new MemoryStream ();
while (true) { 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<byte> (buff), token); var result = await socket.ReceiveAsync (new ArraySegment<byte> (buff), token);
if (result.MessageType == WebSocketMessageType.Close) { if (result.MessageType == WebSocketMessageType.Close) {
client_initiated_close.TrySetResult (true);
return null; return null;
} }
@ -144,7 +153,7 @@ namespace WsProxy {
void Send (WebSocket to, JObject o, CancellationToken token) 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 queue = GetQueueForSocket (to);
var task = queue.Send (bytes, token); var task = queue.Send (bytes, token);
@ -199,9 +208,10 @@ namespace WsProxy {
void ProcessIdeMessage (string msg, CancellationToken token) void ProcessIdeMessage (string msg, CancellationToken token)
{ {
var res = JObject.Parse (msg); if (!string.IsNullOrEmpty (msg)) {
var res = JObject.Parse (msg);
pending_ops.Add (OnCommand (res ["id"].Value<int> (), res ["method"].Value<string> (), res ["params"] as JObject, token)); pending_ops.Add (OnCommand (res ["id"].Value<int> (), res ["method"].Value<string> (), res ["params"] as JObject, token));
}
} }
internal async Task<Result> SendCommand (string method, JObject args, CancellationToken token) { internal async Task<Result> SendCommand (string method, JObject args, CancellationToken token) {
@ -255,24 +265,25 @@ namespace WsProxy {
Send (this.ide, o, token); Send (this.ide, o, token);
} }
// , HttpContext context) // , HttpContext context)
public async Task Run (Uri browserUri, WebSocket ideSocket) public async Task Run (Uri browserUri, WebSocket ideSocket)
{ {
Debug ("wsproxy start"); Debug ($"WsProxy Starting on {browserUri}");
using (this.ide = ideSocket) { using (this.ide = ideSocket) {
Debug ("ide connected"); Debug ($"WsProxy: IDE waiting for connection on {browserUri}");
queues.Add (new WsQueue (this.ide)); queues.Add (new WsQueue (this.ide));
using (this.browser = new ClientWebSocket ()) { using (this.browser = new ClientWebSocket ()) {
this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan; this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
await this.browser.ConnectAsync (browserUri, CancellationToken.None); await this.browser.ConnectAsync (browserUri, CancellationToken.None);
queues.Add (new WsQueue (this.browser)); queues.Add (new WsQueue (this.browser));
Debug ("client connected"); Debug ($"WsProxy: Client connected on {browserUri}");
var x = new CancellationTokenSource (); var x = new CancellationTokenSource ();
pending_ops.Add (ReadOne (browser, x.Token)); pending_ops.Add (ReadOne (browser, x.Token));
pending_ops.Add (ReadOne (ide, x.Token)); pending_ops.Add (ReadOne (ide, x.Token));
pending_ops.Add (side_exception.Task); pending_ops.Add (side_exception.Task);
pending_ops.Add (client_initiated_close.Task);
try { try {
while (!x.IsCancellationRequested) { while (!x.IsCancellationRequested) {
@ -280,15 +291,23 @@ namespace WsProxy {
//Console.WriteLine ("pump {0} {1}", task, pending_ops.IndexOf (task)); //Console.WriteLine ("pump {0} {1}", task, pending_ops.IndexOf (task));
if (task == pending_ops [0]) { if (task == pending_ops [0]) {
var msg = ((Task<string>)task).Result; var msg = ((Task<string>)task).Result;
pending_ops [0] = ReadOne (browser, x.Token); //queue next read if (msg != null) {
ProcessBrowserMessage (msg, x.Token); pending_ops [0] = ReadOne (browser, x.Token); //queue next read
ProcessBrowserMessage (msg, x.Token);
}
} else if (task == pending_ops [1]) { } else if (task == pending_ops [1]) {
var msg = ((Task<string>)task).Result; var msg = ((Task<string>)task).Result;
pending_ops [1] = ReadOne (ide, x.Token); //queue next read if (msg != null) {
ProcessIdeMessage (msg, x.Token); pending_ops [1] = ReadOne (ide, x.Token); //queue next read
ProcessIdeMessage (msg, x.Token);
}
} else if (task == pending_ops [2]) { } else if (task == pending_ops [2]) {
var res = ((Task<bool>)task).Result; var res = ((Task<bool>)task).Result;
throw new Exception ("side task must always complete with an exception, what's going on???"); throw new Exception ("side task must always complete with an exception, what's going on???");
} else if (task == pending_ops [3]) {
var res = ((Task<bool>)task).Result;
Debug ($"WsProxy: Client initiated close from {browserUri}");
x.Cancel ();
} else { } else {
//must be a background task //must be a background task
pending_ops.Remove (task); pending_ops.Remove (task);
@ -301,10 +320,11 @@ namespace WsProxy {
} }
} }
} catch (Exception e) { } catch (Exception e) {
Debug ($"got exception {e}"); Debug ($"WsProxy::Run: Exception {e}");
//throw; //throw;
} finally { } finally {
x.Cancel (); if (!x.IsCancellationRequested)
x.Cancel ();
} }
} }
} }