async Task GetDetails(MessageId msg_id, MonoCommands cmd, CancellationToken token) { var res = await SendMonoCommand(msg_id, cmd, token); //if we fail we just buble that to the IDE (and let it panic over it) if (res.IsErr) { SendResponse(msg_id, res, token); return; } try { var values = res.Value?["result"]?["value"]?.Values <JObject>().ToArray() ?? Array.Empty <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 = FormatFieldName((string)values[i]["name"]); var value = values [i + 1]? ["value"]; if (((string)value ["description"]) == null) { value ["description"] = value ["value"]?.ToString(); } var_list.Add(JObject.FromObject(new { name = fieldName, value })); } var response = JObject.FromObject(new { result = var_list }); SendResponse(msg_id, Result.Ok(response), token); } catch (Exception e) { Log("verbose", $"failed to parse {res.Value} - {e.Message}"); SendResponse(msg_id, Result.Exception(e), token); } }
async Task <Result> EnableBreakPoint(SessionId sessionId, Breakpoint bp, CancellationToken token) { var asm_name = bp.Location.CliLocation.Method.Assembly.Name; var method_token = bp.Location.CliLocation.Method.Token; var il_offset = bp.Location.CliLocation.Offset; var res = await SendMonoCommand(sessionId, MonoCommands.SetBreakpoint(asm_name, method_token, il_offset), token); var ret_code = res.Value? ["result"]? ["value"]?.Value <int> (); if (ret_code.HasValue) { bp.RemoteId = ret_code.Value; bp.State = BreakpointState.Active; //Log ("verbose", $"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}"); } return(res); }
async Task <bool> Step(MessageId msg_id, StepKind kind, CancellationToken token) { var context = GetContext(msg_id); if (context.CallStack == null) { return(false); } var res = await SendMonoCommand(msg_id, MonoCommands.StartSingleStepping(kind), token); SendResponse(msg_id, Result.Ok(new JObject()), token); context.CallStack = null; await SendCommand(msg_id, "Debugger.resume", new JObject(), token); return(true); }
async Task <DebugStore> LoadStore(SessionId sessionId, CancellationToken token) { var context = GetContext(sessionId); if (Interlocked.CompareExchange(ref context.store, new DebugStore(logger), null) != null) { return(await context.Source.Task); } try { var loaded_pdbs = await SendMonoCommand(sessionId, MonoCommands.GetLoadedFiles(), token); var the_value = loaded_pdbs.Value? ["result"]? ["value"]; var the_pdbs = the_value?.ToObject <string[]> (); await foreach (var source in context.store.Load(sessionId, the_pdbs, token).WithCancellation(token)) { var scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData)); Log("verbose", $"\tsending {source.Url} {context.Id} {sessionId.sessionId}"); SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token); foreach (var req in context.BreakpointRequests.Values) { if (req.TryResolve(source)) { await SetBreakpoint(sessionId, context.store, req, token); } } } } catch (Exception e) { context.Source.SetException(e); } if (!context.Source.Task.IsCompleted) { context.Source.SetResult(context.store); } return(context.store); }
async Task <DebugStore> RuntimeReady(SessionId sessionId, CancellationToken token) { var context = GetContext(sessionId); if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource <DebugStore> (), null) != null) { return(await context.ready.Task); } var clear_result = await SendMonoCommand(sessionId, MonoCommands.ClearAllBreakpoints(), token); if (clear_result.IsErr) { Log("verbose", $"Failed to clear breakpoints due to {clear_result}"); } var store = await LoadStore(sessionId, token); context.ready.SetResult(store); SendEvent(sessionId, "Mono.runtimeReady", new JObject(), token); return(store); }
async Task GetScopeProperties(MessageId msg_id, int scope_id, CancellationToken token) { try { var scope = GetContext(msg_id).CallStack.FirstOrDefault(s => s.Id == scope_id); var vars = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset); var var_ids = vars.Select(v => v.Index).ToArray(); var res = await SendMonoCommand(msg_id, MonoCommands.GetScopeVariables(scope.Id, var_ids), token); //if we fail we just buble that to the IDE (and let it panic over it) if (res.IsErr) { SendResponse(msg_id, res, token); return; } var values = res.Value? ["result"]? ["value"]?.Values <JObject> ().ToArray(); if (values == null) { SendResponse(msg_id, Result.OkFromObject(new { result = Array.Empty <object> () }), token); } var var_list = new List <object> (); 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 (i < vars.Length && i < values.Length) { var value = values [i] ["value"]; if (((string)value ["description"]) == null) { value ["description"] = value ["value"]?.ToString(); } var_list.Add(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(new { name, value }); i = i + 2; } SendResponse(msg_id, Result.OkFromObject(new { result = var_list }), token); } catch (Exception exception) { Log("verbose", $"Error resolving scope properties {exception.Message}"); SendResponse(msg_id, Result.Exception(exception), token); } }
//static int frame_id=0; async Task <bool> OnBreakpointHit(SessionId sessionId, JObject args, CancellationToken token) { //FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime var res = await SendMonoCommand(sessionId, MonoCommands.GetCallStack(), token); var orig_callframes = args? ["callFrames"]?.Values <JObject> (); var context = GetContext(sessionId); if (res.IsErr) { //Give up and send the original call stack return(false); } //step one, figure out where did we hit var res_value = res.Value? ["result"]? ["value"]; if (res_value == null || res_value is JValue) { //Give up and send the original call stack return(false); } Log("verbose", $"call stack (err is {res.Error} value is:\n{res.Value}"); var bp_id = res_value? ["breakpoint_id"]?.Value <int> (); Log("verbose", $"We just hit bp {bp_id}"); if (!bp_id.HasValue) { //Give up and send the original call stack return(false); } var bp = context.Breakpoints.FirstOrDefault(b => b.RemoteId == bp_id.Value); var store = context.Store; var src = bp == null ? null : store.GetFileById(bp.Location.Id); var callFrames = new List <object> (); foreach (var frame in orig_callframes) { var function_name = frame ["functionName"]?.Value <string> (); var url = frame ["url"]?.Value <string> (); if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) { var frames = new List <Frame> (); int frame_id = 0; var the_mono_frames = res.Value? ["result"]? ["value"]? ["frames"]?.Values <JObject> (); foreach (var mono_frame in the_mono_frames) { var il_pos = mono_frame ["il_pos"].Value <int> (); var method_token = mono_frame ["method_token"].Value <int> (); var assembly_name = mono_frame ["assembly_name"].Value <string> (); var asm = store.GetAssemblyByName(assembly_name); if (asm == null) { Log("info", $"Unable to find assembly: {assembly_name}"); continue; } var method = asm.GetMethodByToken(method_token); if (method == null) { Log("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}"); continue; } var location = method?.GetLocationByIl(il_pos); // When hitting a breakpoint on the "IncrementCount" method in the standard // Blazor project template, one of the stack frames is inside mscorlib.dll // and we get location==null for it. It will trigger a NullReferenceException // if we don't skip over that stack frame. if (location == null) { continue; } Log("info", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}"); Log("info", $"\tmethod {method.Name} location: {location}"); frames.Add(new Frame(method, location, frame_id)); callFrames.Add(new { functionName = method.Name, callFrameId = $"dotnet:scope:{frame_id}", functionLocation = method.StartLocation.AsLocation(), location = location.AsLocation(), url = store.ToUrl(location), scopeChain = new [] { new { type = "local", @object = new { @type = "object", className = "Object", description = "Object", objectId = $"dotnet:scope:{frame_id}", }, name = method.Name, startLocation = method.StartLocation.AsLocation(), endLocation = method.EndLocation.AsLocation(), } } }); ++frame_id; context.CallStack = frames; } } else if (!(function_name.StartsWith("wasm-function", StringComparison.Ordinal) || url.StartsWith("wasm://wasm/", StringComparison.Ordinal))) { callFrames.Add(frame); } } var bp_list = new string [bp == null ? 0 : 1]; if (bp != null) { bp_list [0] = bp.StackId; } var o = JObject.FromObject(new { callFrames, reason = "other", //other means breakpoint hitBreakpoints = bp_list, }); SendEvent(sessionId, "Debugger.paused", o, token); return(true); }
protected override async Task <bool> AcceptCommand(MessageId id, string method, JObject args, CancellationToken token) { switch (method) { case "Debugger.getScriptSource": { var script = args? ["scriptId"]?.Value <string> (); return(await OnGetScriptSource(id, script, token)); } case "Runtime.compileScript": { var exp = args? ["expression"]?.Value <string> (); if (exp.StartsWith("//dotnet:", StringComparison.Ordinal)) { OnCompileDotnetScript(id, token); return(true); } break; } case "Debugger.getPossibleBreakpoints": { var start = SourceLocation.Parse(args? ["start"] as JObject); //FIXME support variant where restrictToFunction=true and end is omitted var end = SourceLocation.Parse(args? ["end"] as JObject); if (start != null && end != null) { return(GetPossibleBreakpoints(id, start, end, token)); } break; } case "Debugger.setBreakpointByUrl": { Log("info", $"BP req {args}"); var bp_req = BreakpointRequest.Parse(args, GetContext(id).Store); if (bp_req != null) { await SetBreakPoint(id, bp_req, token); return(true); } break; } case "Debugger.removeBreakpoint": { return(await RemoveBreakpoint(id, args, token)); } case "Debugger.resume": { await OnResume(id, token); break; } case "Debugger.stepInto": { return(await Step(id, StepKind.Into, token)); } case "Debugger.stepOut": { return(await Step(id, StepKind.Out, token)); } case "Debugger.stepOver": { return(await Step(id, StepKind.Over, token)); } case "Runtime.getProperties": { var objId = args? ["objectId"]?.Value <string> (); if (objId.StartsWith("dotnet:", StringComparison.Ordinal)) { var parts = objId.Split(new char [] { ':' }); if (parts.Length < 3) { return(true); } switch (parts[1]) { case "scope": { await GetScopeProperties(id, int.Parse(parts[2]), token); break; } case "object": { await GetDetails(id, MonoCommands.GetObjectProperties(int.Parse(parts[2])), token); break; } case "array": { await GetDetails(id, MonoCommands.GetArrayValues(int.Parse(parts [2])), token); break; } } return(true); } break; } } return(false); }
internal Task <Result> SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) => SendCommand(id, "Runtime.evaluate", JObject.FromObject(cmd), token);
protected override async Task <bool> AcceptCommand(MessageId id, string method, JObject args, CancellationToken token) { switch (method) { case "Debugger.enable": { var resp = await SendCommand(id, method, args, token); GetContext(id).DebuggerId = resp.Value ["debuggerId"]?.ToString(); if (await IsRuntimeAlreadyReadyAlready(id, token)) { await RuntimeReady(id, token); } SendResponse(id, resp, token); return(true); } case "Debugger.getScriptSource": { var script = args? ["scriptId"]?.Value <string> (); return(await OnGetScriptSource(id, script, token)); } case "Runtime.compileScript": { var exp = args? ["expression"]?.Value <string> (); if (exp.StartsWith("//dotnet:", StringComparison.Ordinal)) { OnCompileDotnetScript(id, token); return(true); } break; } case "Debugger.getPossibleBreakpoints": { var resp = await SendCommand(id, method, args, token); if (resp.IsOk && resp.Value["locations"].HasValues) { SendResponse(id, resp, token); return(true); } var start = SourceLocation.Parse(args? ["start"] as JObject); //FIXME support variant where restrictToFunction=true and end is omitted var end = SourceLocation.Parse(args? ["end"] as JObject); if (start != null && end != null && await GetPossibleBreakpoints(id, start, end, token)) { return(true); } SendResponse(id, resp, token); return(true); } case "Debugger.setBreakpoint": { break; } case "Debugger.setBreakpointByUrl": { var context = GetContext(id); var resp = await SendCommand(id, method, args, token); if (!resp.IsOk) { SendResponse(id, resp, token); return(true); } var bpid = resp.Value["breakpointId"]?.ToString(); var request = BreakpointRequest.Parse(bpid, args); context.BreakpointRequests[bpid] = request; if (await IsRuntimeAlreadyReadyAlready(id, token)) { var store = await RuntimeReady(id, token); Log("verbose", $"BP req {args}"); await SetBreakpoint(id, store, request, token); } SendResponse(id, Result.OkFromObject(request.AsSetBreakpointByUrlResponse()), token); return(true); } case "Debugger.removeBreakpoint": { return(await RemoveBreakpoint(id, args, token)); } case "Debugger.resume": { await OnResume(id, token); break; } case "Debugger.stepInto": { return(await Step(id, StepKind.Into, token)); } case "Debugger.stepOut": { return(await Step(id, StepKind.Out, token)); } case "Debugger.stepOver": { return(await Step(id, StepKind.Over, token)); } case "Runtime.getProperties": { var objId = args? ["objectId"]?.Value <string> (); if (objId.StartsWith("dotnet:", StringComparison.Ordinal)) { var parts = objId.Split(new char [] { ':' }); if (parts.Length < 3) { return(true); } switch (parts[1]) { case "scope": { await GetScopeProperties(id, int.Parse(parts[2]), token); break; } case "object": { await GetDetails(id, MonoCommands.GetObjectProperties(int.Parse(parts[2])), token); break; } case "array": { await GetDetails(id, MonoCommands.GetArrayValues(int.Parse(parts [2])), token); break; } } return(true); } break; } } return(false); }
async Task <bool> IsRuntimeAlreadyReadyAlready(SessionId sessionId, CancellationToken token) { var res = await SendMonoCommand(sessionId, MonoCommands.IsRuntimeReady(), token); return(res.Value? ["result"]? ["value"]?.Value <bool> () ?? false); }