Update Mono debug proxy (#19335)
* Update to match Mono sources at a8d34b2 * Update to match Mono sources at fafa41f * Update to match Mono sources at ed012b6 * Change class/struct/enum types to internal * Use ILogger in proxy code * Update to match https://github.com/mono/mono/pull/19026
This commit is contained in:
parent
bfcf72dfdf
commit
34b165a0ef
|
|
@ -11,19 +11,20 @@ using Newtonsoft.Json;
|
|||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WebAssembly.Net.Debugging {
|
||||
internal class BreakPointRequest {
|
||||
internal class BreakpointRequest {
|
||||
public string Assembly { get; private set; }
|
||||
public string File { get; private set; }
|
||||
public int Line { get; private set; }
|
||||
public int Column { get; private set; }
|
||||
|
||||
public override string ToString () {
|
||||
return $"BreakPointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}";
|
||||
return $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}";
|
||||
}
|
||||
|
||||
public static BreakPointRequest Parse (JObject args, DebugStore store)
|
||||
public static BreakpointRequest Parse (JObject args, DebugStore store)
|
||||
{
|
||||
// Events can potentially come out of order, so DebugStore may not be initialized
|
||||
// The BP being set in these cases are JS ones, which we can safely ignore
|
||||
|
|
@ -38,7 +39,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
url = sourceFile?.DotNetUrl;
|
||||
}
|
||||
|
||||
if (url != null && !url.StartsWith ("dotnet://", StringComparison.InvariantCulture)) {
|
||||
if (url != null && !url.StartsWith ("dotnet://", StringComparison.Ordinal)) {
|
||||
var sourceFile = store.GetFileByUrl (url);
|
||||
url = sourceFile?.DotNetUrl;
|
||||
}
|
||||
|
|
@ -55,7 +56,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
if (line == null || column == null)
|
||||
return null;
|
||||
|
||||
return new BreakPointRequest () {
|
||||
return new BreakpointRequest () {
|
||||
Assembly = parts.Assembly,
|
||||
File = parts.DocumentPath,
|
||||
Line = line.Value,
|
||||
|
|
@ -147,7 +148,9 @@ namespace WebAssembly.Net.Debugging {
|
|||
if (obj == null)
|
||||
return null;
|
||||
|
||||
var id = SourceId.TryParse (obj ["scriptId"]?.Value<string> ());
|
||||
if (!SourceId.TryParse (obj ["scriptId"]?.Value<string> (), out var id))
|
||||
return null;
|
||||
|
||||
var line = obj ["lineNumber"]?.Value<int> ();
|
||||
var column = obj ["columnNumber"]?.Value<int> ();
|
||||
if (id == null || line == null || column == null)
|
||||
|
|
@ -156,18 +159,17 @@ namespace WebAssembly.Net.Debugging {
|
|||
return new SourceLocation (id, line.Value, column.Value);
|
||||
}
|
||||
|
||||
internal JObject ToJObject ()
|
||||
{
|
||||
return JObject.FromObject (new {
|
||||
internal object AsLocation ()
|
||||
=> new {
|
||||
scriptId = id.ToString (),
|
||||
lineNumber = line,
|
||||
columnNumber = column
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
internal class SourceId {
|
||||
const string Scheme = "dotnet://";
|
||||
|
||||
readonly int assembly, document;
|
||||
|
||||
public int Assembly => assembly;
|
||||
|
|
@ -179,25 +181,27 @@ namespace WebAssembly.Net.Debugging {
|
|||
this.document = document;
|
||||
}
|
||||
|
||||
|
||||
public SourceId (string id)
|
||||
{
|
||||
id = id.Substring ("dotnet://".Length);
|
||||
id = id.Substring (Scheme.Length);
|
||||
var sp = id.Split ('_');
|
||||
this.assembly = int.Parse (sp [0]);
|
||||
this.document = int.Parse (sp [1]);
|
||||
}
|
||||
|
||||
public static SourceId TryParse (string id)
|
||||
public static bool TryParse (string id, out SourceId script)
|
||||
{
|
||||
if (!id.StartsWith ("dotnet://", StringComparison.InvariantCulture))
|
||||
return null;
|
||||
return new SourceId (id);
|
||||
script = null;
|
||||
if (id == null || !id.StartsWith (Scheme, StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
script = new SourceId (id);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString ()
|
||||
{
|
||||
return $"dotnet://{assembly}_{document}";
|
||||
return $"{Scheme}{assembly}_{document}";
|
||||
}
|
||||
|
||||
public override bool Equals (object obj)
|
||||
|
|
@ -289,6 +293,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
static int next_id;
|
||||
ModuleDefinition image;
|
||||
readonly int id;
|
||||
readonly ILogger logger;
|
||||
Dictionary<int, MethodInfo> methods = new Dictionary<int, MethodInfo> ();
|
||||
Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>();
|
||||
readonly List<SourceFile> sources = new List<SourceFile>();
|
||||
|
|
@ -296,9 +301,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
public AssemblyInfo (string url, byte[] assembly, byte[] pdb)
|
||||
{
|
||||
lock (typeof (AssemblyInfo)) {
|
||||
this.id = ++next_id;
|
||||
}
|
||||
this.id = Interlocked.Increment (ref next_id);
|
||||
|
||||
try {
|
||||
Url = url;
|
||||
|
|
@ -313,7 +316,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
|
||||
} catch (BadImageFormatException ex) {
|
||||
Console.WriteLine ($"Failed to read assembly as portable PDB: {ex.Message}");
|
||||
logger.LogWarning ($"Failed to read assembly as portable PDB: {ex.Message}");
|
||||
} catch (ArgumentNullException) {
|
||||
if (pdb != null)
|
||||
throw;
|
||||
|
|
@ -336,8 +339,9 @@ namespace WebAssembly.Net.Debugging {
|
|||
Populate ();
|
||||
}
|
||||
|
||||
public AssemblyInfo ()
|
||||
public AssemblyInfo (ILogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
void Populate ()
|
||||
|
|
@ -360,7 +364,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
foreach (var m in image.GetTypes().SelectMany(t => t.Methods)) {
|
||||
Document first_doc = null;
|
||||
foreach (var sp in m.DebugInformation.SequencePoints) {
|
||||
if (first_doc == null && !sp.Document.Url.EndsWith (".g.cs")) {
|
||||
if (first_doc == null && !sp.Document.Url.EndsWith (".g.cs", StringComparison.OrdinalIgnoreCase)) {
|
||||
first_doc = sp.Document;
|
||||
}
|
||||
// else if (first_doc != sp.Document) {
|
||||
|
|
@ -473,29 +477,61 @@ namespace WebAssembly.Net.Debugging {
|
|||
} else {
|
||||
this.Url = DotNetUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal void AddMethod (MethodInfo mi)
|
||||
{
|
||||
this.methods.Add (mi);
|
||||
}
|
||||
|
||||
public string DebuggerFileName { get; }
|
||||
public string Url { get; }
|
||||
public string AssemblyName => assembly.Name;
|
||||
public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}";
|
||||
public string DocHashCode => "abcdee" + id;
|
||||
|
||||
public SourceId SourceId => new SourceId (assembly.Id, this.id);
|
||||
public Uri SourceLinkUri { get; }
|
||||
public Uri SourceUri { get; }
|
||||
|
||||
public IEnumerable<MethodInfo> Methods => this.methods;
|
||||
public byte[] EmbeddedSource => doc.EmbeddedSource;
|
||||
|
||||
public (int startLine, int startColumn, int endLine, int endColumn) GetExtents ()
|
||||
{
|
||||
var start = methods.OrderBy (m => m.StartLocation.Line).ThenBy (m => m.StartLocation.Column).First ();
|
||||
var end = methods.OrderByDescending (m => m.EndLocation.Line).ThenByDescending (m => m.EndLocation.Column).First ();
|
||||
return (start.StartLocation.Line, start.StartLocation.Column, end.EndLocation.Line, end.EndLocation.Column);
|
||||
}
|
||||
|
||||
public async Task<byte[]> LoadSource ()
|
||||
{
|
||||
if (EmbeddedSource.Length > 0)
|
||||
return await Task.FromResult (EmbeddedSource);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ToScriptSource (int executionContextId, object executionContextAuxData)
|
||||
{
|
||||
return new {
|
||||
scriptId = SourceId.ToString (),
|
||||
url = Url,
|
||||
executionContextId,
|
||||
executionContextAuxData,
|
||||
//hash = "abcdee" + id,
|
||||
dotNetUrl = DotNetUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
internal class DebugStore {
|
||||
// MonoProxy proxy; - commenting out because never gets assigned
|
||||
List<AssemblyInfo> assemblies = new List<AssemblyInfo> ();
|
||||
HttpClient client = new HttpClient ();
|
||||
readonly ILogger logger;
|
||||
|
||||
public DebugStore (ILogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
class DebugItem {
|
||||
public string Url { get; set; }
|
||||
|
|
@ -526,16 +562,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
Data = Task.WhenAll (client.GetByteArrayAsync (url), pdb != null ? client.GetByteArrayAsync (pdb) : Task.FromResult<byte []> (null))
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine ($"Failed to read {url} ({e.Message})");
|
||||
var o = JObject.FromObject (new {
|
||||
entry = new {
|
||||
source = "other",
|
||||
level = "warning",
|
||||
text = $"Failed to read {url} ({e.Message})"
|
||||
}
|
||||
});
|
||||
// proxy.SendEvent (sessionId, "Log.entryAdded", o, token); - commenting out because `proxy` would always be null
|
||||
|
||||
logger.LogDebug ($"Failed to read {url} ({e.Message})");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -544,7 +571,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
var bytes = await step.Data;
|
||||
assemblies.Add (new AssemblyInfo (step.Url, bytes[0], bytes[1]));
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine ($"Failed to Load {step.Url} ({e.Message})");
|
||||
logger.LogDebug ($"Failed to Load {step.Url} ({e.Message})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -592,8 +619,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
var res = new List<SourceLocation> ();
|
||||
if (doc == null) {
|
||||
//FIXME we need to write up logging here
|
||||
Console.WriteLine ($"Could not find document {src_id}");
|
||||
logger.LogDebug ($"Could not find document {src_id}");
|
||||
return res;
|
||||
}
|
||||
|
||||
|
|
@ -630,7 +656,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
return true;
|
||||
}
|
||||
|
||||
public SourceLocation FindBestBreakpoint (BreakPointRequest req)
|
||||
public SourceLocation FindBestBreakpoint (BreakpointRequest req)
|
||||
{
|
||||
var asm = assemblies.FirstOrDefault (a => a.Name.Equals (req.Assembly, StringComparison.OrdinalIgnoreCase));
|
||||
var src = asm?.Sources?.FirstOrDefault (s => s.DebuggerFileName.Equals (req.File, StringComparison.OrdinalIgnoreCase));
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Threading;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace WebAssembly.Net.Debugging {
|
||||
internal class DevToolsClient: IDisposable {
|
||||
|
|
@ -14,8 +15,10 @@ namespace WebAssembly.Net.Debugging {
|
|||
TaskCompletionSource<bool> side_exit = new TaskCompletionSource<bool> ();
|
||||
List<byte []> pending_writes = new List<byte []> ();
|
||||
Task current_write;
|
||||
readonly ILogger logger;
|
||||
|
||||
public DevToolsClient () {
|
||||
public DevToolsClient (ILogger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
~DevToolsClient() {
|
||||
|
|
@ -99,7 +102,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
Func<CancellationToken, Task> send,
|
||||
CancellationToken token) {
|
||||
|
||||
Console.WriteLine ("connecting to {0}", uri);
|
||||
logger.LogDebug ("connecting to {0}", uri);
|
||||
this.socket = new ClientWebSocket ();
|
||||
this.socket.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
|
@ -28,8 +28,17 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
Result (JObject result, JObject error)
|
||||
{
|
||||
this.Value = result;
|
||||
this.Error = error;
|
||||
if (result != null && error != null)
|
||||
throw new ArgumentException ($"Both {nameof(result)} and {nameof(error)} arguments cannot be non-null.");
|
||||
|
||||
bool resultHasError = String.Compare ((result? ["result"] as JObject)? ["subtype"]?. Value<string> (), "error") == 0;
|
||||
if (result != null && resultHasError) {
|
||||
this.Value = null;
|
||||
this.Error = result;
|
||||
} else {
|
||||
this.Value = result;
|
||||
this.Error = error;
|
||||
}
|
||||
}
|
||||
|
||||
public static Result FromJson (JObject obj)
|
||||
|
|
@ -39,14 +48,16 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
|
||||
public static Result Ok (JObject ok)
|
||||
{
|
||||
return new Result (ok, null);
|
||||
}
|
||||
=> new Result (ok, null);
|
||||
|
||||
public static Result OkFromObject (object ok)
|
||||
=> Ok (JObject.FromObject(ok));
|
||||
|
||||
public static Result Err (JObject err)
|
||||
{
|
||||
return new Result (null, err);
|
||||
}
|
||||
=> new Result (null, err);
|
||||
|
||||
public static Result Exception (Exception e)
|
||||
=> new Result (null, JObject.FromObject (new { message = e.Message }));
|
||||
|
||||
public JObject ToJObject (MessageId target) {
|
||||
if (IsOk) {
|
||||
|
|
@ -83,7 +94,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
if (pending.Count == 1) {
|
||||
if (current_send != null)
|
||||
throw new Exception ("current_send MUST BE NULL IF THERE'S no pending send");
|
||||
//Console.WriteLine ("sending {0} bytes", bytes.Length);
|
||||
//logger.LogTrace ("sending {0} bytes", bytes.Length);
|
||||
current_send = Ws.SendAsync (new ArraySegment<byte> (bytes), WebSocketMessageType.Text, true, token);
|
||||
return current_send;
|
||||
}
|
||||
|
|
@ -98,7 +109,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
if (pending.Count > 0) {
|
||||
if (current_send != null)
|
||||
throw new Exception ("current_send MUST BE NULL IF THERE'S no pending send");
|
||||
//Console.WriteLine ("sending more {0} bytes", pending[0].Length);
|
||||
|
||||
current_send = Ws.SendAsync (new ArraySegment<byte> (pending [0]), WebSocketMessageType.Text, true, token);
|
||||
return current_send;
|
||||
}
|
||||
|
|
@ -115,12 +126,12 @@ namespace WebAssembly.Net.Debugging {
|
|||
int next_cmd_id;
|
||||
List<Task> pending_ops = new List<Task> ();
|
||||
List<DevToolsQueue> queues = new List<DevToolsQueue> ();
|
||||
readonly ILogger logger;
|
||||
protected readonly ILogger logger;
|
||||
|
||||
public DevToolsProxy(ILoggerFactory loggerFactory)
|
||||
{
|
||||
logger = loggerFactory.CreateLogger<DevToolsProxy>();
|
||||
}
|
||||
public DevToolsProxy (ILoggerFactory loggerFactory)
|
||||
{
|
||||
logger = loggerFactory.CreateLogger<DevToolsProxy>();
|
||||
}
|
||||
|
||||
protected virtual Task<bool> AcceptEvent (SessionId sessionId, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
|
|
@ -184,7 +195,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
{
|
||||
try {
|
||||
if (!await AcceptEvent (sessionId, method, args, token)) {
|
||||
//Console.WriteLine ("proxy browser: {0}::{1}",method, args);
|
||||
//logger.LogDebug ("proxy browser: {0}::{1}",method, args);
|
||||
SendEventInternal (sessionId, method, args, token);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
|
@ -206,7 +217,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
void OnResponse (MessageId id, Result result)
|
||||
{
|
||||
//Console.WriteLine ("got id {0} res {1}", id, result);
|
||||
//logger.LogTrace ("got id {0} res {1}", id, result);
|
||||
// Fixme
|
||||
var idx = pending_cmds.FindIndex (e => e.Item1.id == id.id && e.Item1.sessionId == id.sessionId);
|
||||
var item = pending_cmds [idx];
|
||||
|
|
@ -242,7 +253,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
Task<Result> SendCommandInternal (SessionId sessionId, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
int id = ++next_cmd_id;
|
||||
int id = Interlocked.Increment (ref next_cmd_id);
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
sessionId.sessionId,
|
||||
|
|
@ -252,7 +263,6 @@ namespace WebAssembly.Net.Debugging {
|
|||
});
|
||||
var tcs = new TaskCompletionSource<Result> ();
|
||||
|
||||
|
||||
var msgId = new MessageId { id = id, sessionId = sessionId.sessionId };
|
||||
//Log ("verbose", $"add cmd id {sessionId}-{id}");
|
||||
pending_cmds.Add ((msgId , tcs));
|
||||
|
|
@ -314,7 +324,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
try {
|
||||
while (!x.IsCancellationRequested) {
|
||||
var task = await Task.WhenAny (pending_ops.ToArray ());
|
||||
//Console.WriteLine ("pump {0} {1}", task, pending_ops.IndexOf (task));
|
||||
//logger.LogTrace ("pump {0} {1}", task, pending_ops.IndexOf (task));
|
||||
if (task == pending_ops [0]) {
|
||||
var msg = ((Task<string>)task).Result;
|
||||
if (msg != null) {
|
||||
|
|
@ -360,23 +370,17 @@ namespace WebAssembly.Net.Debugging {
|
|||
{
|
||||
switch (priority) {
|
||||
case "protocol":
|
||||
logger.LogTrace (msg);
|
||||
//logger.LogTrace (msg);
|
||||
break;
|
||||
case "verbose":
|
||||
logger.LogDebug (msg);
|
||||
//logger.LogDebug (msg);
|
||||
break;
|
||||
case "info":
|
||||
logger.LogInformation(msg);
|
||||
break;
|
||||
case "warning":
|
||||
logger.LogWarning(msg);
|
||||
break;
|
||||
case "error":
|
||||
logger.LogError (msg);
|
||||
break;
|
||||
case "error":
|
||||
default:
|
||||
logger.LogError(msg);
|
||||
break;
|
||||
logger.LogDebug (msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,16 +12,44 @@ using Microsoft.Extensions.Logging;
|
|||
namespace WebAssembly.Net.Debugging {
|
||||
|
||||
internal class MonoCommands {
|
||||
public const string GET_CALL_STACK = "MONO.mono_wasm_get_call_stack()";
|
||||
public const string IS_RUNTIME_READY_VAR = "MONO.mono_wasm_runtime_is_ready";
|
||||
public const string START_SINGLE_STEPPING = "MONO.mono_wasm_start_single_stepping({0})";
|
||||
public const string GET_SCOPE_VARIABLES = "MONO.mono_wasm_get_variables({0}, [ {1} ])";
|
||||
public const string SET_BREAK_POINT = "MONO.mono_wasm_set_breakpoint(\"{0}\", {1}, {2})";
|
||||
public const string REMOVE_BREAK_POINT = "MONO.mono_wasm_remove_breakpoint({0})";
|
||||
public const string GET_LOADED_FILES = "MONO.mono_wasm_get_loaded_files()";
|
||||
public const string CLEAR_ALL_BREAKPOINTS = "MONO.mono_wasm_clear_all_breakpoints()";
|
||||
public const string GET_OBJECT_PROPERTIES = "MONO.mono_wasm_get_object_properties({0})";
|
||||
public const string GET_ARRAY_VALUES = "MONO.mono_wasm_get_array_values({0})";
|
||||
public string expression { get; set; }
|
||||
public string objectGroup { get; set; } = "mono-debugger";
|
||||
public bool includeCommandLineAPI { get; set; } = false;
|
||||
public bool silent { get; set; } = false;
|
||||
public bool returnByValue { get; set; } = true;
|
||||
|
||||
public MonoCommands (string expression)
|
||||
=> this.expression = expression;
|
||||
|
||||
public static MonoCommands GetCallStack ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_get_call_stack()");
|
||||
|
||||
public static MonoCommands IsRuntimeReady ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_runtime_is_ready");
|
||||
|
||||
public static MonoCommands StartSingleStepping (StepKind kind)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_start_single_stepping ({(int)kind})");
|
||||
|
||||
public static MonoCommands GetLoadedFiles ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_get_loaded_files()");
|
||||
|
||||
public static MonoCommands ClearAllBreakpoints ()
|
||||
=> new MonoCommands ("MONO.mono_wasm_clear_all_breakpoints()");
|
||||
|
||||
public static MonoCommands GetObjectProperties (int objectId)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_get_object_properties({objectId})");
|
||||
|
||||
public static MonoCommands GetArrayValues (int objectId)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_get_array_values({objectId})");
|
||||
|
||||
public static MonoCommands GetScopeVariables (int scopeId, params int[] vars)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_get_variables({scopeId}, [ {string.Join (",", vars)} ])");
|
||||
|
||||
public static MonoCommands SetBreakpoint (string assemblyName, int methodToken, int ilOffset)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})");
|
||||
|
||||
public static MonoCommands RemoveBreakpoint (int breakpointId)
|
||||
=> new MonoCommands ($"MONO.mono_wasm_remove_breakpoint({breakpointId})");
|
||||
}
|
||||
|
||||
internal enum MonoErrorCodes {
|
||||
|
|
@ -45,14 +73,23 @@ namespace WebAssembly.Net.Debugging {
|
|||
public int Id { get; private set; }
|
||||
}
|
||||
|
||||
|
||||
class Breakpoint {
|
||||
public SourceLocation Location { get; private set; }
|
||||
public int LocalId { get; private set; }
|
||||
public int RemoteId { get; set; }
|
||||
public BreakPointState State { get; set; }
|
||||
public BreakpointState State { get; set; }
|
||||
public string StackId => $"dotnet:{LocalId}";
|
||||
|
||||
public Breakpoint (SourceLocation loc, int localId, BreakPointState state)
|
||||
public static bool TryParseId (string stackId, out int id)
|
||||
{
|
||||
id = -1;
|
||||
if (stackId?.StartsWith ("dotnet:", StringComparison.Ordinal) != true)
|
||||
return false;
|
||||
|
||||
return int.TryParse (stackId.Substring ("dotnet:".Length), out id);
|
||||
}
|
||||
|
||||
public Breakpoint (SourceLocation loc, int localId, BreakpointState state)
|
||||
{
|
||||
this.Location = loc;
|
||||
this.LocalId = localId;
|
||||
|
|
@ -60,7 +97,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
}
|
||||
|
||||
enum BreakPointState {
|
||||
enum BreakpointState {
|
||||
Active,
|
||||
Disabled,
|
||||
Pending
|
||||
|
|
@ -72,54 +109,92 @@ namespace WebAssembly.Net.Debugging {
|
|||
Over
|
||||
}
|
||||
|
||||
internal class ExecutionContext {
|
||||
int breakpointIndex = -1;
|
||||
public List<Breakpoint> Breakpoints { get; } = new List<Breakpoint> ();
|
||||
|
||||
public bool RuntimeReady { get; set; }
|
||||
public int Id { get; set; }
|
||||
public object AuxData { get; set; }
|
||||
|
||||
public List<Frame> CallStack { get; set; }
|
||||
|
||||
public int NextBreakpointId ()
|
||||
=> Interlocked.Increment (ref breakpointIndex);
|
||||
|
||||
internal DebugStore store;
|
||||
public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore> ();
|
||||
|
||||
public DebugStore Store {
|
||||
get {
|
||||
if (store == null || !Source.Task.IsCompleted)
|
||||
return null;
|
||||
|
||||
return store;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class MonoProxy : DevToolsProxy {
|
||||
DebugStore store;
|
||||
List<Breakpoint> breakpoints = new List<Breakpoint> ();
|
||||
List<Frame> current_callstack;
|
||||
bool runtime_ready;
|
||||
int local_breakpoint_id;
|
||||
int ctx_id;
|
||||
JObject aux_ctx_data;
|
||||
Dictionary<string, ExecutionContext> contexts = new Dictionary<string, ExecutionContext> ();
|
||||
|
||||
public MonoProxy (ILoggerFactory loggerFactory) : base(loggerFactory) { }
|
||||
|
||||
ExecutionContext GetContext (SessionId sessionId)
|
||||
{
|
||||
var id = sessionId?.sessionId ?? "default";
|
||||
if (contexts.TryGetValue (id, out var context))
|
||||
return context;
|
||||
|
||||
throw new ArgumentException ($"Invalid Session: \"{id}\"", nameof (sessionId));
|
||||
}
|
||||
|
||||
internal Task<Result> SendMonoCommand (SessionId id, MonoCommands cmd, CancellationToken token)
|
||||
=> SendCommand (id, "Runtime.evaluate", JObject.FromObject (cmd), token);
|
||||
|
||||
protected override async Task<bool> AcceptEvent (SessionId sessionId, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
switch (method) {
|
||||
case "Runtime.executionContextCreated": {
|
||||
SendEvent (sessionId, method, args, token);
|
||||
var ctx = args? ["context"];
|
||||
var aux_data = ctx? ["auxData"] as JObject;
|
||||
var id = ctx ["id"].Value<int> ();
|
||||
if (aux_data != null) {
|
||||
var is_default = aux_data ["isDefault"]?.Value<bool> ();
|
||||
if (is_default == true) {
|
||||
var id = new MessageId { id = ctx ["id"].Value<int> (), sessionId = sessionId.sessionId };
|
||||
await OnDefaultContext (id, aux_data, token);
|
||||
await OnDefaultContext (sessionId, new ExecutionContext { Id = id, AuxData = aux_data }, token);
|
||||
}
|
||||
}
|
||||
break;
|
||||
return true;
|
||||
//break;
|
||||
}
|
||||
|
||||
case "Debugger.paused": {
|
||||
//TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack
|
||||
var top_func = args? ["callFrames"]? [0]? ["functionName"]?.Value<string> ();
|
||||
|
||||
if (top_func == "mono_wasm_fire_bp" || top_func == "_mono_wasm_fire_bp") {
|
||||
await OnBreakPointHit (sessionId, args, token);
|
||||
return true;
|
||||
return await OnBreakpointHit (sessionId, args, token);
|
||||
}
|
||||
if (top_func == MonoConstants.RUNTIME_IS_READY) {
|
||||
await OnRuntimeReady (new SessionId { sessionId = sessionId.sessionId }, token);
|
||||
await OnRuntimeReady (sessionId, token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.scriptParsed":{
|
||||
if (args?["url"]?.Value<string> ()?.StartsWith ("wasm://") == true) {
|
||||
// Console.WriteLine ("ignoring wasm event");
|
||||
return true;
|
||||
var url = args? ["url"]?.Value<string> () ?? "";
|
||||
|
||||
switch (url) {
|
||||
case var _ when url == "":
|
||||
case var _ when url.StartsWith ("wasm://", StringComparison.Ordinal): {
|
||||
Log ("info", $"ignoring wasm: Debugger.scriptParsed {url}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Debugger.enabled": {
|
||||
await LoadStore (new SessionId { sessionId = args? ["sessionId"]?.Value<string> () }, token);
|
||||
Log ("info", $"proxying Debugger.scriptParsed ({sessionId.sessionId}) {url} {args}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -130,24 +205,15 @@ namespace WebAssembly.Net.Debugging {
|
|||
protected override async Task<bool> AcceptCommand (MessageId id, string method, JObject args, CancellationToken token)
|
||||
{
|
||||
switch (method) {
|
||||
case "Target.attachToTarget": {
|
||||
break;
|
||||
}
|
||||
case "Target.attachToBrowserTarget": {
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.getScriptSource": {
|
||||
var script_id = args? ["scriptId"]?.Value<string> ();
|
||||
if (script_id.StartsWith ("dotnet://", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
await OnGetScriptSource (id, script_id, token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
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.InvariantCultureIgnoreCase)) {
|
||||
if (exp.StartsWith ("//dotnet:", StringComparison.Ordinal)) {
|
||||
OnCompileDotnetScript (id, token);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -165,7 +231,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
case "Debugger.setBreakpointByUrl": {
|
||||
Log ("info", $"BP req {args}");
|
||||
var bp_req = BreakPointRequest.Parse (args, store);
|
||||
var bp_req = BreakpointRequest.Parse (args, GetContext (id).Store);
|
||||
if (bp_req != null) {
|
||||
await SetBreakPoint (id, bp_req, token);
|
||||
return true;
|
||||
|
|
@ -178,37 +244,25 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
|
||||
case "Debugger.resume": {
|
||||
await OnResume (token);
|
||||
await OnResume (id, token);
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.stepInto": {
|
||||
if (this.current_callstack != null) {
|
||||
await Step (id, StepKind.Into, token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
return await Step (id, StepKind.Into, token);
|
||||
}
|
||||
|
||||
case "Debugger.stepOut": {
|
||||
if (this.current_callstack != null) {
|
||||
await Step (id, StepKind.Out, token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
return await Step (id, StepKind.Out, token);
|
||||
}
|
||||
|
||||
case "Debugger.stepOver": {
|
||||
if (this.current_callstack != null) {
|
||||
await Step (id, StepKind.Over, token);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
return await Step (id, StepKind.Over, token);
|
||||
}
|
||||
|
||||
case "Runtime.getProperties": {
|
||||
var objId = args? ["objectId"]?.Value<string> ();
|
||||
if (objId.StartsWith ("dotnet:")) {
|
||||
if (objId.StartsWith ("dotnet:", StringComparison.Ordinal)) {
|
||||
var parts = objId.Split (new char [] { ':' });
|
||||
if (parts.Length < 3)
|
||||
return true;
|
||||
|
|
@ -218,11 +272,11 @@ namespace WebAssembly.Net.Debugging {
|
|||
break;
|
||||
}
|
||||
case "object": {
|
||||
await GetDetails (id, int.Parse (parts[2]), token, MonoCommands.GET_OBJECT_PROPERTIES);
|
||||
await GetDetails (id, MonoCommands.GetObjectProperties (int.Parse (parts[2])), token);
|
||||
break;
|
||||
}
|
||||
case "array": {
|
||||
await GetDetails (id, int.Parse (parts[2]), token, MonoCommands.GET_ARRAY_VALUES);
|
||||
await GetDetails (id, MonoCommands.GetArrayValues (int.Parse (parts [2])), token);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -237,39 +291,30 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
async Task OnRuntimeReady (SessionId sessionId, CancellationToken token)
|
||||
{
|
||||
Log ("info", "RUNTIME READY, PARTY TIME");
|
||||
Log ("info", "Runtime ready");
|
||||
await RuntimeReady (sessionId, token);
|
||||
await SendCommand (sessionId, "Debugger.resume", new JObject (), token);
|
||||
SendEvent (sessionId, "Mono.runtimeReady", new JObject (), token);
|
||||
}
|
||||
|
||||
//static int frame_id=0;
|
||||
async Task OnBreakPointHit (SessionId sessionId, JObject args, CancellationToken token)
|
||||
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 o = JObject.FromObject (new {
|
||||
expression = MonoCommands.GET_CALL_STACK,
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true
|
||||
});
|
||||
|
||||
var res = await SendMonoCommand (sessionId, MonoCommands.GetCallStack(), token);
|
||||
var orig_callframes = args? ["callFrames"]?.Values<JObject> ();
|
||||
var res = await SendCommand (sessionId, "Runtime.evaluate", o, token);
|
||||
var context = GetContext (sessionId);
|
||||
|
||||
if (res.IsErr) {
|
||||
//Give up and send the original call stack
|
||||
SendEvent (sessionId, "Debugger.paused", args, token);
|
||||
return;
|
||||
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
|
||||
SendEvent (sessionId, "Debugger.paused", args, token);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
Log ("verbose", $"call stack (err is {res.Error} value is:\n{res.Value}");
|
||||
|
|
@ -277,14 +322,14 @@ namespace WebAssembly.Net.Debugging {
|
|||
Log ("verbose", $"We just hit bp {bp_id}");
|
||||
if (!bp_id.HasValue) {
|
||||
//Give up and send the original call stack
|
||||
SendEvent (sessionId, "Debugger.paused", args, token);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
var bp = this.breakpoints.FirstOrDefault (b => b.RemoteId == bp_id.Value);
|
||||
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<JObject> ();
|
||||
var callFrames = new List<object> ();
|
||||
foreach (var frame in orig_callframes) {
|
||||
var function_name = frame ["functionName"]?.Value<string> ();
|
||||
var url = frame ["url"]?.Value<string> ();
|
||||
|
|
@ -325,12 +370,12 @@ namespace WebAssembly.Net.Debugging {
|
|||
Log ("info", $"\tmethod {method.Name} location: {location}");
|
||||
frames.Add (new Frame (method, location, frame_id));
|
||||
|
||||
callFrames.Add (JObject.FromObject (new {
|
||||
callFrames.Add (new {
|
||||
functionName = method.Name,
|
||||
callFrameId = $"dotnet:scope:{frame_id}",
|
||||
functionLocation = method.StartLocation.ToJObject (),
|
||||
functionLocation = method.StartLocation.AsLocation (),
|
||||
|
||||
location = location.ToJObject (),
|
||||
location = location.AsLocation (),
|
||||
|
||||
url = store.ToUrl (location),
|
||||
|
||||
|
|
@ -344,104 +389,92 @@ namespace WebAssembly.Net.Debugging {
|
|||
objectId = $"dotnet:scope:{frame_id}",
|
||||
},
|
||||
name = method.Name,
|
||||
startLocation = method.StartLocation.ToJObject (),
|
||||
endLocation = method.EndLocation.ToJObject (),
|
||||
startLocation = method.StartLocation.AsLocation (),
|
||||
endLocation = method.EndLocation.AsLocation (),
|
||||
}}
|
||||
}));
|
||||
});
|
||||
|
||||
++frame_id;
|
||||
this.current_callstack = frames;
|
||||
context.CallStack = frames;
|
||||
|
||||
}
|
||||
} else if (!(function_name.StartsWith ("wasm-function", StringComparison.InvariantCulture)
|
||||
|| url.StartsWith ("wasm://wasm/", StringComparison.InvariantCulture))) {
|
||||
} 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] = $"dotnet:{bp.LocalId}";
|
||||
bp_list [0] = bp.StackId;
|
||||
|
||||
o = JObject.FromObject (new {
|
||||
callFrames = callFrames,
|
||||
var o = JObject.FromObject (new {
|
||||
callFrames,
|
||||
reason = "other", //other means breakpoint
|
||||
hitBreakpoints = bp_list,
|
||||
});
|
||||
|
||||
SendEvent (sessionId, "Debugger.paused", o, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
async Task OnDefaultContext (MessageId ctx_id, JObject aux_data, CancellationToken token)
|
||||
async Task OnDefaultContext (SessionId sessionId, ExecutionContext context, CancellationToken token)
|
||||
{
|
||||
Log ("verbose", "Default context created, clearing state and sending events");
|
||||
|
||||
contexts[sessionId.sessionId ?? "default"] = context;
|
||||
//reset all bps
|
||||
foreach (var b in this.breakpoints){
|
||||
b.State = BreakPointState.Pending;
|
||||
foreach (var b in context.Breakpoints){
|
||||
b.State = BreakpointState.Pending;
|
||||
}
|
||||
this.runtime_ready = false;
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
expression = MonoCommands.IS_RUNTIME_READY_VAR,
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true
|
||||
});
|
||||
this.ctx_id = ctx_id.id;
|
||||
this.aux_ctx_data = aux_data;
|
||||
|
||||
Log ("verbose", "checking if the runtime is ready");
|
||||
var res = await SendCommand (ctx_id, "Runtime.evaluate", o, token);
|
||||
Log ("info", "checking if the runtime is ready");
|
||||
var res = await SendMonoCommand (sessionId, MonoCommands.IsRuntimeReady (), token);
|
||||
var is_ready = res.Value? ["result"]? ["value"]?.Value<bool> ();
|
||||
//Log ("verbose", $"\t{is_ready}");
|
||||
if (is_ready.HasValue && is_ready.Value == true) {
|
||||
Log ("verbose", "RUNTIME LOOK READY. GO TIME!");
|
||||
await OnRuntimeReady (ctx_id, token);
|
||||
Log ("info", "RUNTIME LOOK READY. GO TIME!");
|
||||
await RuntimeReady (sessionId, token);
|
||||
SendEvent (sessionId, "Mono.runtimeReady", new JObject (), token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async Task OnResume (CancellationToken token)
|
||||
async Task OnResume (MessageId msd_id, CancellationToken token)
|
||||
{
|
||||
//discard frames
|
||||
this.current_callstack = null;
|
||||
GetContext (msd_id).CallStack = null;
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
async Task Step (MessageId msg_id, StepKind kind, CancellationToken token)
|
||||
async Task<bool> Step (MessageId msg_id, StepKind kind, CancellationToken token)
|
||||
{
|
||||
var context = GetContext (msg_id);
|
||||
if (context.CallStack == null)
|
||||
return false;
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
expression = string.Format (MonoCommands.START_SINGLE_STEPPING, (int)kind),
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var res = await SendCommand (msg_id, "Runtime.evaluate", o, token);
|
||||
var res = await SendMonoCommand (msg_id, MonoCommands.StartSingleStepping (kind), token);
|
||||
|
||||
SendResponse (msg_id, Result.Ok (new JObject ()), token);
|
||||
|
||||
this.current_callstack = null;
|
||||
context.CallStack = null;
|
||||
|
||||
await SendCommand (msg_id, "Debugger.resume", new JObject (), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
async Task GetDetails(MessageId msg_id, int object_id, CancellationToken token, string command)
|
||||
static string FormatFieldName (string name)
|
||||
{
|
||||
var o = JObject.FromObject(new
|
||||
{
|
||||
expression = string.Format(command, object_id),
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
if (name.Contains("k__BackingField", StringComparison.Ordinal)) {
|
||||
return name.Replace("k__BackingField", "", StringComparison.Ordinal)
|
||||
.Replace("<", "", StringComparison.Ordinal)
|
||||
.Replace(">", "", StringComparison.Ordinal);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
var res = await SendCommand(msg_id, "Runtime.evaluate", o, token);
|
||||
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)
|
||||
|
|
@ -459,12 +492,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
// 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(">", "");
|
||||
}
|
||||
string fieldName = FormatFieldName ((string)values[i]["name"]);
|
||||
var value = values [i + 1]? ["value"];
|
||||
if (((string)value ["description"]) == null)
|
||||
value ["description"] = value ["value"]?.ToString ();
|
||||
|
|
@ -475,57 +503,54 @@ namespace WebAssembly.Net.Debugging {
|
|||
}));
|
||||
|
||||
}
|
||||
o = JObject.FromObject(new
|
||||
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);
|
||||
}
|
||||
SendResponse(msg_id, Result.Ok(o), token);
|
||||
|
||||
}
|
||||
|
||||
async Task GetScopeProperties (MessageId msg_id, int scope_id, CancellationToken token)
|
||||
{
|
||||
var scope = this.current_callstack.FirstOrDefault (s => s.Id == scope_id);
|
||||
var vars = scope.Method.GetLiveVarsAt (scope.Location.CliLocation.Offset);
|
||||
|
||||
|
||||
var var_ids = string.Join (",", vars.Select (v => v.Index));
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
expression = string.Format (MonoCommands.GET_SCOPE_VARIABLES, scope.Id, var_ids),
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var res = await SendCommand (msg_id, "Runtime.evaluate", o, 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 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 ();
|
||||
|
||||
var var_list = new List<JObject> ();
|
||||
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 (values != null && i < vars.Length && i < values.Length) {
|
||||
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 (JObject.FromObject (new {
|
||||
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
|
||||
|
|
@ -540,19 +565,17 @@ namespace WebAssembly.Net.Debugging {
|
|||
if (((string)value ["description"]) == null)
|
||||
value ["description"] = value ["value"]?.ToString ();
|
||||
|
||||
var_list.Add (JObject.FromObject (new {
|
||||
var_list.Add (new {
|
||||
name,
|
||||
value
|
||||
}));
|
||||
});
|
||||
i = i + 2;
|
||||
}
|
||||
o = JObject.FromObject (new {
|
||||
result = var_list
|
||||
});
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
|
||||
SendResponse (msg_id, Result.OkFromObject (new { result = var_list }), token);
|
||||
} catch (Exception exception) {
|
||||
Log ("verbose", $"Error resolving scope properties {exception.Message}");
|
||||
SendResponse (msg_id, res, token);
|
||||
SendResponse (msg_id, Result.Exception (exception), token);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -562,80 +585,63 @@ namespace WebAssembly.Net.Debugging {
|
|||
var method_token = bp.Location.CliLocation.Method.Token;
|
||||
var il_offset = bp.Location.CliLocation.Offset;
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
expression = string.Format (MonoCommands.SET_BREAK_POINT, asm_name, method_token, il_offset),
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var res = await SendCommand (sessionId, "Runtime.evaluate", o, token);
|
||||
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;
|
||||
bp.State = BreakpointState.Active;
|
||||
//Log ("verbose", $"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}");
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async Task LoadStore (SessionId sessionId, CancellationToken token)
|
||||
async Task<DebugStore> LoadStore (SessionId sessionId, CancellationToken token)
|
||||
{
|
||||
var o = JObject.FromObject (new {
|
||||
expression = MonoCommands.GET_LOADED_FILES,
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
var context = GetContext (sessionId);
|
||||
|
||||
var loaded_pdbs = await SendCommand (sessionId, "Runtime.evaluate", o, token);
|
||||
var the_value = loaded_pdbs.Value? ["result"]? ["value"];
|
||||
var the_pdbs = the_value?.ToObject<string[]> ();
|
||||
if (Interlocked.CompareExchange (ref context.store, new DebugStore (logger), null) != null) {
|
||||
return await context.Source.Task;
|
||||
}
|
||||
|
||||
store = new DebugStore ();
|
||||
await store.Load(sessionId, the_pdbs, token);
|
||||
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 context.store.Load(sessionId, the_pdbs, token);
|
||||
} catch (Exception e) {
|
||||
context.Source.SetException (e);
|
||||
}
|
||||
|
||||
if (!context.Source.Task.IsCompleted)
|
||||
context.Source.SetResult (context.store);
|
||||
return await context.Source.Task;
|
||||
}
|
||||
|
||||
async Task RuntimeReady (SessionId sessionId, CancellationToken token)
|
||||
{
|
||||
if (store == null)
|
||||
await LoadStore (sessionId, token);
|
||||
var context = GetContext (sessionId);
|
||||
if (context.RuntimeReady)
|
||||
return;
|
||||
|
||||
foreach (var s in store.AllSources ()) {
|
||||
var ok = JObject.FromObject (new {
|
||||
scriptId = s.SourceId.ToString (),
|
||||
url = s.Url,
|
||||
executionContextId = this.ctx_id,
|
||||
hash = s.DocHashCode,
|
||||
executionContextAuxData = this.aux_ctx_data,
|
||||
dotNetUrl = s.DotNetUrl,
|
||||
});
|
||||
//Log ("verbose", $"\tsending {s.Url}");
|
||||
SendEvent (sessionId, "Debugger.scriptParsed", ok, token);
|
||||
}
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
expression = MonoCommands.CLEAR_ALL_BREAKPOINTS,
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var clear_result = await SendCommand (sessionId, "Runtime.evaluate", o, token);
|
||||
var clear_result = await SendMonoCommand (sessionId, MonoCommands.ClearAllBreakpoints (), token);
|
||||
if (clear_result.IsErr) {
|
||||
Log ("verbose", $"Failed to clear breakpoints due to {clear_result}");
|
||||
}
|
||||
|
||||
context.RuntimeReady = true;
|
||||
var store = await LoadStore (sessionId, token);
|
||||
|
||||
runtime_ready = true;
|
||||
foreach (var s in store.AllSources ()) {
|
||||
var scriptSource = JObject.FromObject (s.ToScriptSource (context.Id, context.AuxData));
|
||||
Log ("verbose", $"\tsending {s.Url} {context.Id} {sessionId.sessionId}");
|
||||
SendEvent (sessionId, "Debugger.scriptParsed", scriptSource, token);
|
||||
}
|
||||
|
||||
foreach (var bp in breakpoints) {
|
||||
if (bp.State != BreakPointState.Pending)
|
||||
foreach (var bp in context.Breakpoints) {
|
||||
if (bp.State != BreakpointState.Pending)
|
||||
continue;
|
||||
var res = await EnableBreakPoint (sessionId, bp, token);
|
||||
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
|
||||
|
|
@ -644,60 +650,53 @@ namespace WebAssembly.Net.Debugging {
|
|||
if (!ret_code.HasValue) {
|
||||
//FIXME figure out how to inform the IDE of that.
|
||||
Log ("info", $"FAILED TO ENABLE BP {bp.LocalId}");
|
||||
bp.State = BreakPointState.Disabled;
|
||||
bp.State = BreakpointState.Disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task<bool> RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token) {
|
||||
var bpid = args? ["breakpointId"]?.Value<string> ();
|
||||
if (bpid?.StartsWith ("dotnet:") != true)
|
||||
|
||||
if (!Breakpoint.TryParseId (bpid, out var the_id))
|
||||
return false;
|
||||
|
||||
var the_id = int.Parse (bpid.Substring ("dotnet:".Length));
|
||||
|
||||
var bp = breakpoints.FirstOrDefault (b => b.LocalId == the_id);
|
||||
var context = GetContext (msg_id);
|
||||
var bp = context.Breakpoints.FirstOrDefault (b => b.LocalId == the_id);
|
||||
if (bp == null) {
|
||||
Log ("info", $"Could not find dotnet bp with id {the_id}");
|
||||
return false;
|
||||
}
|
||||
|
||||
breakpoints.Remove (bp);
|
||||
context.Breakpoints.Remove (bp);
|
||||
//FIXME verify result (and log?)
|
||||
var res = await RemoveBreakPoint (msg_id, bp, token);
|
||||
var res = await RemoveBreakpoint (msg_id, bp, token);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
async Task<Result> RemoveBreakPoint (SessionId sessionId, Breakpoint bp, CancellationToken token)
|
||||
async Task<Result> RemoveBreakpoint (SessionId sessionId, Breakpoint bp, CancellationToken token)
|
||||
{
|
||||
var o = JObject.FromObject (new {
|
||||
expression = string.Format (MonoCommands.REMOVE_BREAK_POINT, bp.RemoteId),
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var res = await SendCommand (sessionId, "Runtime.evaluate", o, token);
|
||||
var res = await SendMonoCommand (sessionId, MonoCommands.RemoveBreakpoint (bp.RemoteId), token);
|
||||
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
|
||||
|
||||
if (ret_code.HasValue) {
|
||||
bp.RemoteId = -1;
|
||||
bp.State = BreakPointState.Disabled;
|
||||
bp.State = BreakpointState.Disabled;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async Task SetBreakPoint (MessageId msg_id, BreakPointRequest req, CancellationToken token)
|
||||
async Task SetBreakPoint (MessageId msg_id, BreakpointRequest req, CancellationToken token)
|
||||
{
|
||||
var bp_loc = store?.FindBestBreakpoint (req);
|
||||
Log ("info", $"BP request for '{req}' runtime ready {runtime_ready} location '{bp_loc}'");
|
||||
var context = GetContext (msg_id);
|
||||
var bp_loc = context.Store.FindBestBreakpoint (req);
|
||||
Log ("info", $"BP request for '{req}' runtime ready {context.RuntimeReady} location '{bp_loc}'");
|
||||
if (bp_loc == null) {
|
||||
|
||||
Log ("info", $"Could not resolve breakpoint request: {req}");
|
||||
Log ("verbose", $"Could not resolve breakpoint request: {req}");
|
||||
SendResponse (msg_id, Result.Err(JObject.FromObject (new {
|
||||
code = (int)MonoErrorCodes.BpNotFound,
|
||||
message = $"C# Breakpoint at {req} not found."
|
||||
|
|
@ -706,10 +705,10 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
|
||||
Breakpoint bp = null;
|
||||
if (!runtime_ready) {
|
||||
bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Pending);
|
||||
if (!context.RuntimeReady) {
|
||||
bp = new Breakpoint (bp_loc, context.NextBreakpointId (), BreakpointState.Pending);
|
||||
} else {
|
||||
bp = new Breakpoint (bp_loc, local_breakpoint_id++, BreakPointState.Disabled);
|
||||
bp = new Breakpoint (bp_loc, context.NextBreakpointId (), BreakpointState.Disabled);
|
||||
|
||||
var res = await EnableBreakPoint (msg_id, bp, token);
|
||||
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
|
||||
|
|
@ -721,106 +720,75 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
}
|
||||
|
||||
var locations = new List<JObject> ();
|
||||
context.Breakpoints.Add (bp);
|
||||
|
||||
locations.Add (JObject.FromObject (new {
|
||||
scriptId = bp_loc.Id.ToString (),
|
||||
lineNumber = bp_loc.Line,
|
||||
columnNumber = bp_loc.Column
|
||||
}));
|
||||
var ok = new {
|
||||
breakpointId = bp.StackId,
|
||||
locations = new [] {
|
||||
bp_loc.AsLocation ()
|
||||
},
|
||||
};
|
||||
|
||||
breakpoints.Add (bp);
|
||||
|
||||
var ok = JObject.FromObject (new {
|
||||
breakpointId = $"dotnet:{bp.LocalId}",
|
||||
locations = locations,
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (ok), token);
|
||||
SendResponse (msg_id, Result.OkFromObject (ok), token);
|
||||
}
|
||||
|
||||
bool GetPossibleBreakpoints (MessageId msg_id, SourceLocation start, SourceLocation end, CancellationToken token)
|
||||
{
|
||||
var bps = store.FindPossibleBreakpoints (start, end);
|
||||
var bps = GetContext (msg_id).Store.FindPossibleBreakpoints (start, end);
|
||||
if (bps == null)
|
||||
return false;
|
||||
|
||||
var loc = new List<JObject> ();
|
||||
foreach (var b in bps) {
|
||||
loc.Add (b.ToJObject ());
|
||||
}
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
locations = loc
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
|
||||
SendResponse (msg_id, Result.OkFromObject (new { locations = bps.Select (b => b.AsLocation ()) }), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnCompileDotnetScript (MessageId msg_id, CancellationToken token)
|
||||
{
|
||||
var o = JObject.FromObject (new { });
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
SendResponse (msg_id, Result.OkFromObject (new { }), token);
|
||||
}
|
||||
|
||||
async Task OnGetScriptSource (MessageId msg_id, string script_id, CancellationToken token)
|
||||
async Task<bool> OnGetScriptSource (MessageId msg_id, string script_id, CancellationToken token)
|
||||
{
|
||||
var id = new SourceId (script_id);
|
||||
var src_file = store.GetFileById (id);
|
||||
if (!SourceId.TryParse (script_id, out var id))
|
||||
return false;
|
||||
|
||||
var src_file = GetContext (msg_id).Store.GetFileById (id);
|
||||
var res = new StringWriter ();
|
||||
//res.WriteLine ($"//{id}");
|
||||
|
||||
try {
|
||||
var uri = new Uri (src_file.Url);
|
||||
string source = $"// Unable to find document {src_file.SourceUri}";
|
||||
|
||||
if (uri.IsFile && File.Exists(uri.LocalPath)) {
|
||||
using (var f = new StreamReader (File.Open (uri.LocalPath, FileMode.Open))) {
|
||||
await res.WriteAsync (await f.ReadToEndAsync ());
|
||||
}
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = res.ToString ()
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
source = res.ToString ();
|
||||
} else if (src_file.SourceUri.IsFile && File.Exists(src_file.SourceUri.LocalPath)) {
|
||||
using (var f = new StreamReader (File.Open (src_file.SourceUri.LocalPath, FileMode.Open))) {
|
||||
await res.WriteAsync (await f.ReadToEndAsync ());
|
||||
}
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = res.ToString ()
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
source = res.ToString ();
|
||||
} else if(src_file.SourceLinkUri != null) {
|
||||
var doc = await new WebClient ().DownloadStringTaskAsync (src_file.SourceLinkUri);
|
||||
await res.WriteAsync (doc);
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = res.ToString ()
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
} else {
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = $"// Unable to find document {src_file.SourceUri}"
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
source = res.ToString ();
|
||||
}
|
||||
|
||||
SendResponse (msg_id, Result.OkFromObject (new { scriptSource = source }), token);
|
||||
} catch (Exception e) {
|
||||
var o = JObject.FromObject (new {
|
||||
var o = new {
|
||||
scriptSource = $"// Unable to read document ({e.Message})\n" +
|
||||
$"Local path: {src_file?.SourceUri}\n" +
|
||||
$"SourceLink path: {src_file?.SourceLinkUri}\n"
|
||||
});
|
||||
};
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
SendResponse (msg_id, Result.OkFromObject (o), token);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue