Skip to content

Commit 05f2430

Browse files
SteveSandersonMSmkArtakMSFT
authored andcommitted
Update Mono debug proxy code (#18478)
1 parent 0f2e88a commit 05f2430

File tree

3 files changed

+116
-75
lines changed

3 files changed

+116
-75
lines changed

src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/DebugStore.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,7 @@ public AssemblyInfo GetAssemblyByName (string name)
545545
return assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase));
546546
}
547547

548-
/*
548+
/*
549549
V8 uses zero based indexing for both line and column.
550550
PPDBs uses one based indexing for both line and column.
551551
*/
@@ -598,7 +598,7 @@ public List<SourceLocation> FindPossibleBreakpoints (SourceLocation start, Sourc
598598
PPDBs uses one based indexing for both line and column.
599599
*/
600600
static bool Match (SequencePoint sp, int line, int column)
601-
{
601+
{
602602
var bp = (line: line + 1, column: column + 1);
603603

604604
if (sp.StartLine > bp.line || sp.EndLine < bp.line)

src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/MonoProxy.cs

Lines changed: 80 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ async Task OnRuntimeReady (CancellationToken token)
223223
Info ("RUNTIME READY, PARTY TIME");
224224
await RuntimeReady (token);
225225
await SendCommand ("Debugger.resume", new JObject (), token);
226-
SendEvent ("Mono.runtimeReady", new JObject (), token);
226+
SendEvent ("Mono.runtimeReady", new JObject (), token);
227227
}
228228

229229
async Task OnBreakPointHit (JObject args, CancellationToken token)
@@ -274,12 +274,18 @@ async Task OnBreakPointHit (JObject args, CancellationToken token)
274274
var frames = new List<Frame> ();
275275
int frame_id = 0;
276276
var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values<JObject> ();
277+
277278
foreach (var mono_frame in the_mono_frames) {
278279
var il_pos = mono_frame ["il_pos"].Value<int> ();
279280
var method_token = mono_frame ["method_token"].Value<int> ();
280281
var assembly_name = mono_frame ["assembly_name"].Value<string> ();
281282

282283
var asm = store.GetAssemblyByName (assembly_name);
284+
if (asm == null) {
285+
Info ($"Unable to find assembly: {assembly_name}");
286+
continue;
287+
}
288+
283289
var method = asm.GetMethodByToken (method_token);
284290

285291
if (method == null) {
@@ -374,7 +380,7 @@ async Task OnDefaultContext (int ctx_id, JObject aux_data, CancellationToken tok
374380
//Debug ($"\t{is_ready}");
375381
if (is_ready.HasValue && is_ready.Value == true) {
376382
Debug ("RUNTIME LOOK READY. GO TIME!");
377-
await RuntimeReady (token);
383+
await OnRuntimeReady (token);
378384
}
379385
}
380386

@@ -426,33 +432,38 @@ async Task GetDetails(int msg_id, int object_id, CancellationToken token, string
426432
return;
427433
}
428434

429-
var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray();
435+
try {
436+
var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray() ?? Array.Empty<JObject>();
437+
var var_list = new List<JObject>();
438+
439+
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
440+
// results in a "Memory access out of bounds", causing 'values' to be null,
441+
// so skip returning variable values in that case.
442+
for (int i = 0; i < values.Length; i+=2)
443+
{
444+
string fieldName = (string)values[i]["name"];
445+
if (fieldName.Contains("k__BackingField")){
446+
fieldName = fieldName.Replace("k__BackingField", "");
447+
fieldName = fieldName.Replace("<", "");
448+
fieldName = fieldName.Replace(">", "");
449+
}
450+
var value = values [i + 1]? ["value"];
451+
if (((string)value ["description"]) == null)
452+
value ["description"] = value ["value"]?.ToString ();
430453

431-
var var_list = new List<JObject>();
432-
433-
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
434-
// results in a "Memory access out of bounds", causing 'values' to be null,
435-
// so skip returning variable values in that case.
436-
for (int i = 0; i < values.Length; i+=2)
437-
{
438-
string fieldName = (string)values[i]["name"];
439-
if (fieldName.Contains("k__BackingField")){
440-
fieldName = fieldName.Replace("k__BackingField", "");
441-
fieldName = fieldName.Replace("<", "");
442-
fieldName = fieldName.Replace(">", "");
443-
}
444-
var_list.Add(JObject.FromObject(new
445-
{
446-
name = fieldName,
447-
value = values[i+1]["value"]
448-
}));
454+
var_list.Add(JObject.FromObject(new {
455+
name = fieldName,
456+
value
457+
}));
449458

459+
}
460+
o = JObject.FromObject(new
461+
{
462+
result = var_list
463+
});
464+
} catch (Exception e) {
465+
Debug ($"failed to parse {res.Value}");
450466
}
451-
o = JObject.FromObject(new
452-
{
453-
result = var_list
454-
});
455-
456467
SendResponse(msg_id, Result.Ok(o), token);
457468
}
458469

@@ -481,41 +492,51 @@ async Task GetScopeProperties (int msg_id, int scope_id, CancellationToken token
481492
return;
482493
}
483494

484-
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
485-
486-
var var_list = new List<JObject> ();
487-
int i = 0;
488-
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
489-
// results in a "Memory access out of bounds", causing 'values' to be null,
490-
// so skip returning variable values in that case.
491-
while (values != null && i < vars.Length && i < values.Length) {
492-
var value = values [i] ["value"];
493-
if (((string)value ["description"]) == null)
494-
value ["description"] = value ["value"]?.ToString();
495-
496-
var_list.Add (JObject.FromObject (new {
497-
name = vars [i].Name,
498-
value = values [i] ["value"]
499-
}));
500-
i++;
495+
try {
496+
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
497+
498+
var var_list = new List<JObject> ();
499+
int i = 0;
500+
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
501+
// results in a "Memory access out of bounds", causing 'values' to be null,
502+
// so skip returning variable values in that case.
503+
while (values != null && i < vars.Length && i < values.Length) {
504+
var value = values [i] ["value"];
505+
if (((string)value ["description"]) == null)
506+
value ["description"] = value ["value"]?.ToString ();
507+
508+
var_list.Add (JObject.FromObject (new {
509+
name = vars [i].Name,
510+
value
511+
}));
512+
i++;
513+
}
514+
//Async methods are special in the way that local variables can be lifted to generated class fields
515+
//value of "this" comes here either
516+
while (i < values.Length) {
517+
String name = values [i] ["name"].ToString ();
518+
519+
if (name.IndexOf (">", StringComparison.Ordinal) > 0)
520+
name = name.Substring (1, name.IndexOf (">", StringComparison.Ordinal) - 1);
521+
522+
var value = values [i + 1] ["value"];
523+
if (((string)value ["description"]) == null)
524+
value ["description"] = value ["value"]?.ToString ();
525+
526+
var_list.Add (JObject.FromObject (new {
527+
name,
528+
value
529+
}));
530+
i = i + 2;
531+
}
532+
o = JObject.FromObject (new {
533+
result = var_list
534+
});
535+
SendResponse (msg_id, Result.Ok (o), token);
501536
}
502-
//Async methods are special in the way that local variables can be lifted to generated class fields
503-
//value of "this" comes here either
504-
while (i < values.Length) {
505-
String name = values [i] ["name"].ToString ();
506-
507-
if (name.IndexOf (">", StringComparison.Ordinal) > 0)
508-
name = name.Substring (1, name.IndexOf (">", StringComparison.Ordinal) - 1);
509-
var_list.Add (JObject.FromObject (new {
510-
name = name,
511-
value = values [i+1] ["value"]
512-
}));
513-
i = i + 2;
537+
catch (Exception exc) {
538+
SendResponse (msg_id, res, token);
514539
}
515-
o = JObject.FromObject (new {
516-
result = var_list
517-
});
518-
SendResponse (msg_id, Result.Ok (o), token);
519540
}
520541

521542
async Task<Result> EnableBreakPoint (Breakpoint bp, CancellationToken token)

src/Components/Blazor/Server/src/MonoDebugProxy/ws-proxy/WsProxy.cs

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public Task Pump (CancellationToken token)
9797

9898
internal class WsProxy {
9999
TaskCompletionSource<bool> side_exception = new TaskCompletionSource<bool> ();
100+
TaskCompletionSource<bool> client_initiated_close = new TaskCompletionSource<bool> ();
100101
List<(int, TaskCompletionSource<Result>)> pending_cmds = new List<(int, TaskCompletionSource<Result>)> ();
101102
ClientWebSocket browser;
102103
WebSocket ide;
@@ -119,8 +120,16 @@ async Task<string> ReadOne (WebSocket socket, CancellationToken token)
119120
byte [] buff = new byte [4000];
120121
var mem = new MemoryStream ();
121122
while (true) {
123+
124+
if (socket.State != WebSocketState.Open) {
125+
Console.WriteLine ($"WSProxy: Socket is no longer open.");
126+
client_initiated_close.TrySetResult (true);
127+
return null;
128+
}
129+
122130
var result = await socket.ReceiveAsync (new ArraySegment<byte> (buff), token);
123131
if (result.MessageType == WebSocketMessageType.Close) {
132+
client_initiated_close.TrySetResult (true);
124133
return null;
125134
}
126135

@@ -144,7 +153,7 @@ WsQueue GetQueueForTask (Task task) {
144153

145154
void Send (WebSocket to, JObject o, CancellationToken token)
146155
{
147-
var bytes = Encoding.UTF8.GetBytes (o.ToString ());
156+
var bytes = Encoding.UTF8.GetBytes (o.ToString ());
148157

149158
var queue = GetQueueForSocket (to);
150159
var task = queue.Send (bytes, token);
@@ -199,9 +208,10 @@ void ProcessBrowserMessage (string msg, CancellationToken token)
199208

200209
void ProcessIdeMessage (string msg, CancellationToken token)
201210
{
202-
var res = JObject.Parse (msg);
203-
204-
pending_ops.Add (OnCommand (res ["id"].Value<int> (), res ["method"].Value<string> (), res ["params"] as JObject, token));
211+
if (!string.IsNullOrEmpty (msg)) {
212+
var res = JObject.Parse (msg);
213+
pending_ops.Add (OnCommand (res ["id"].Value<int> (), res ["method"].Value<string> (), res ["params"] as JObject, token));
214+
}
205215
}
206216

207217
internal async Task<Result> SendCommand (string method, JObject args, CancellationToken token) {
@@ -255,40 +265,49 @@ void SendResponseInternal (int id, Result result, CancellationToken token)
255265
Send (this.ide, o, token);
256266
}
257267

258-
// , HttpContext context)
268+
// , HttpContext context)
259269
public async Task Run (Uri browserUri, WebSocket ideSocket)
260270
{
261-
Debug ("wsproxy start");
271+
Debug ($"WsProxy Starting on {browserUri}");
262272
using (this.ide = ideSocket) {
263-
Debug ("ide connected");
273+
Debug ($"WsProxy: IDE waiting for connection on {browserUri}");
264274
queues.Add (new WsQueue (this.ide));
265275
using (this.browser = new ClientWebSocket ()) {
266276
this.browser.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
267277
await this.browser.ConnectAsync (browserUri, CancellationToken.None);
268278
queues.Add (new WsQueue (this.browser));
269279

270-
Debug ("client connected");
280+
Debug ($"WsProxy: Client connected on {browserUri}");
271281
var x = new CancellationTokenSource ();
272282

273283
pending_ops.Add (ReadOne (browser, x.Token));
274284
pending_ops.Add (ReadOne (ide, x.Token));
275285
pending_ops.Add (side_exception.Task);
286+
pending_ops.Add (client_initiated_close.Task);
276287

277288
try {
278289
while (!x.IsCancellationRequested) {
279290
var task = await Task.WhenAny (pending_ops.ToArray ());
280291
//Console.WriteLine ("pump {0} {1}", task, pending_ops.IndexOf (task));
281292
if (task == pending_ops [0]) {
282293
var msg = ((Task<string>)task).Result;
283-
pending_ops [0] = ReadOne (browser, x.Token); //queue next read
284-
ProcessBrowserMessage (msg, x.Token);
294+
if (msg != null) {
295+
pending_ops [0] = ReadOne (browser, x.Token); //queue next read
296+
ProcessBrowserMessage (msg, x.Token);
297+
}
285298
} else if (task == pending_ops [1]) {
286299
var msg = ((Task<string>)task).Result;
287-
pending_ops [1] = ReadOne (ide, x.Token); //queue next read
288-
ProcessIdeMessage (msg, x.Token);
300+
if (msg != null) {
301+
pending_ops [1] = ReadOne (ide, x.Token); //queue next read
302+
ProcessIdeMessage (msg, x.Token);
303+
}
289304
} else if (task == pending_ops [2]) {
290305
var res = ((Task<bool>)task).Result;
291306
throw new Exception ("side task must always complete with an exception, what's going on???");
307+
} else if (task == pending_ops [3]) {
308+
var res = ((Task<bool>)task).Result;
309+
Debug ($"WsProxy: Client initiated close from {browserUri}");
310+
x.Cancel ();
292311
} else {
293312
//must be a background task
294313
pending_ops.Remove (task);
@@ -301,10 +320,11 @@ public async Task Run (Uri browserUri, WebSocket ideSocket)
301320
}
302321
}
303322
} catch (Exception e) {
304-
Debug ($"got exception {e}");
323+
Debug ($"WsProxy::Run: Exception {e}");
305324
//throw;
306325
} finally {
307-
x.Cancel ();
326+
if (!x.IsCancellationRequested)
327+
x.Cancel ();
308328
}
309329
}
310330
}

0 commit comments

Comments
 (0)