Update ws-proxy to match Mono commit 4c348d6567f42be (#19600)
This commit is contained in:
parent
f4446f373f
commit
e5cd390a8c
|
|
@ -12,56 +12,76 @@ using System.Text.RegularExpressions;
|
|||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace WebAssembly.Net.Debugging {
|
||||
internal class BreakpointRequest {
|
||||
public string Id { get; private set; }
|
||||
public string Assembly { get; private set; }
|
||||
public string File { get; private set; }
|
||||
public int Line { get; private set; }
|
||||
public int Column { get; private set; }
|
||||
|
||||
JObject request;
|
||||
|
||||
public bool IsResolved => Assembly != null;
|
||||
public List<Breakpoint> Locations { get; } = new List<Breakpoint> ();
|
||||
|
||||
public override string ToString () {
|
||||
return $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}";
|
||||
}
|
||||
|
||||
public static BreakpointRequest Parse (JObject args, DebugStore store)
|
||||
public object AsSetBreakpointByUrlResponse ()
|
||||
=> new { breakpointId = Id, locations = Locations.Select(l => l.Location.AsLocation ()) };
|
||||
|
||||
public static BreakpointRequest Parse (string id, JObject args)
|
||||
{
|
||||
// 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
|
||||
if (args == null || store == null)
|
||||
return null;
|
||||
|
||||
var url = args? ["url"]?.Value<string> ();
|
||||
if (url == null) {
|
||||
var urlRegex = args?["urlRegex"].Value<string>();
|
||||
var sourceFile = store?.GetFileByUrlRegex (urlRegex);
|
||||
|
||||
url = sourceFile?.DotNetUrl;
|
||||
}
|
||||
|
||||
if (url != null && !url.StartsWith ("dotnet://", StringComparison.Ordinal)) {
|
||||
var sourceFile = store.GetFileByUrl (url);
|
||||
url = sourceFile?.DotNetUrl;
|
||||
}
|
||||
|
||||
if (url == null)
|
||||
return null;
|
||||
|
||||
var parts = ParseDocumentUrl (url);
|
||||
if (parts.Assembly == null)
|
||||
return null;
|
||||
|
||||
var line = args? ["lineNumber"]?.Value<int> ();
|
||||
var column = args? ["columnNumber"]?.Value<int> ();
|
||||
if (line == null || column == null)
|
||||
return null;
|
||||
|
||||
return new BreakpointRequest () {
|
||||
Assembly = parts.Assembly,
|
||||
File = parts.DocumentPath,
|
||||
Line = line.Value,
|
||||
Column = column.Value
|
||||
var breakRequest = new BreakpointRequest () {
|
||||
Id = id,
|
||||
request = args
|
||||
};
|
||||
return breakRequest;
|
||||
}
|
||||
|
||||
public BreakpointRequest Clone ()
|
||||
=> new BreakpointRequest { Id = Id, request = request };
|
||||
|
||||
public bool IsMatch (SourceFile sourceFile)
|
||||
{
|
||||
var url = request? ["url"]?.Value<string> ();
|
||||
if (url == null) {
|
||||
var urlRegex = request?["urlRegex"].Value<string>();
|
||||
var regex = new Regex (urlRegex);
|
||||
return regex.IsMatch (sourceFile.Url.ToString ()) || regex.IsMatch (sourceFile.DocUrl);
|
||||
}
|
||||
|
||||
return sourceFile.Url.ToString () == url || sourceFile.DotNetUrl == url;
|
||||
}
|
||||
|
||||
public bool TryResolve (SourceFile sourceFile)
|
||||
{
|
||||
if (!IsMatch (sourceFile))
|
||||
return false;
|
||||
|
||||
var line = request? ["lineNumber"]?.Value<int> ();
|
||||
var column = request? ["columnNumber"]?.Value<int> ();
|
||||
|
||||
if (line == null || column == null)
|
||||
return false;
|
||||
|
||||
Assembly = sourceFile.AssemblyName;
|
||||
File = sourceFile.DebuggerFileName;
|
||||
Line = line.Value;
|
||||
Column = column.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryResolve (DebugStore store)
|
||||
{
|
||||
if (request == null || store == null)
|
||||
return false;
|
||||
|
||||
return store.AllSources().FirstOrDefault (source => TryResolve (source)) != null;
|
||||
}
|
||||
|
||||
static (string Assembly, string DocumentPath) ParseDocumentUrl (string url)
|
||||
|
|
@ -99,7 +119,6 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
internal class CliLocation {
|
||||
public CliLocation (MethodInfo method, int offset)
|
||||
{
|
||||
|
|
@ -111,7 +130,6 @@ namespace WebAssembly.Net.Debugging {
|
|||
public int Offset { get; private set; }
|
||||
}
|
||||
|
||||
|
||||
internal class SourceLocation {
|
||||
SourceId id;
|
||||
int line;
|
||||
|
|
@ -268,11 +286,26 @@ namespace WebAssembly.Net.Debugging {
|
|||
this.source = source;
|
||||
|
||||
var sps = methodDef.DebugInformation.SequencePoints;
|
||||
if (sps != null && sps.Count > 0) {
|
||||
StartLocation = new SourceLocation (this, sps [0]);
|
||||
EndLocation = new SourceLocation (this, sps [sps.Count - 1]);
|
||||
if (sps == null || sps.Count() < 1)
|
||||
return;
|
||||
|
||||
SequencePoint start = sps [0];
|
||||
SequencePoint end = sps [0];
|
||||
|
||||
foreach (var sp in sps) {
|
||||
if (sp.StartLine < start.StartLine)
|
||||
start = sp;
|
||||
else if (sp.StartLine == start.StartLine && sp.StartColumn < start.StartColumn)
|
||||
start = sp;
|
||||
|
||||
if (sp.EndLine > end.EndLine)
|
||||
end = sp;
|
||||
else if (sp.EndLine == end.EndLine && sp.EndColumn > end.EndColumn)
|
||||
end = sp;
|
||||
}
|
||||
|
||||
StartLocation = new SourceLocation (this, start);
|
||||
EndLocation = new SourceLocation (this, end);
|
||||
}
|
||||
|
||||
public SourceLocation GetLocationByIl (int pos)
|
||||
|
|
@ -556,7 +589,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
public Task<byte[][]> Data { get; set; }
|
||||
}
|
||||
|
||||
public async Task Load (SessionId sessionId, string [] loaded_files, CancellationToken token)
|
||||
public async IAsyncEnumerable<SourceFile> Load (SessionId sessionId, string [] loaded_files, [EnumeratorCancellation] CancellationToken token)
|
||||
{
|
||||
static bool MatchPdb (string asm, string pdb)
|
||||
=> Path.ChangeExtension (asm, "pdb") == pdb;
|
||||
|
|
@ -585,12 +618,19 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
|
||||
foreach (var step in steps) {
|
||||
AssemblyInfo assembly = null;
|
||||
try {
|
||||
var bytes = await step.Data;
|
||||
assemblies.Add (new AssemblyInfo (step.Url, bytes[0], bytes[1]));
|
||||
assembly = new AssemblyInfo (step.Url, bytes [0], bytes [1]);
|
||||
} catch (Exception e) {
|
||||
logger.LogDebug ($"Failed to Load {step.Url} ({e.Message})");
|
||||
}
|
||||
if (assembly == null)
|
||||
continue;
|
||||
|
||||
assemblies.Add (assembly);
|
||||
foreach (var source in assembly.Sources)
|
||||
yield return source;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -598,7 +638,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
=> assemblies.SelectMany (a => a.Sources);
|
||||
|
||||
public SourceFile GetFileById (SourceId id)
|
||||
=> AllSources ().FirstOrDefault (f => f.SourceId.Equals (id));
|
||||
=> AllSources ().SingleOrDefault (f => f.SourceId.Equals (id));
|
||||
|
||||
public AssemblyInfo GetAssemblyByName (string name)
|
||||
=> assemblies.FirstOrDefault (a => a.Name.Equals (name, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
|
@ -612,15 +652,16 @@ namespace WebAssembly.Net.Debugging {
|
|||
var spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1);
|
||||
var spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1);
|
||||
|
||||
if (start.Line > spStart.Line)
|
||||
return false;
|
||||
if (start.Column > spStart.Column && start.Line == sp.StartLine)
|
||||
if (start.Line > spEnd.Line)
|
||||
return false;
|
||||
|
||||
if (end.Line < spEnd.Line)
|
||||
if (start.Column > spEnd.Column && start.Line == spEnd.Line)
|
||||
return false;
|
||||
|
||||
if (end.Column < spEnd.Column && end.Line == spEnd.Line)
|
||||
if (end.Line < spStart.Line)
|
||||
return false;
|
||||
|
||||
if (end.Column < spStart.Column && end.Line == spStart.Line)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
|
@ -629,22 +670,25 @@ namespace WebAssembly.Net.Debugging {
|
|||
public List<SourceLocation> FindPossibleBreakpoints (SourceLocation start, SourceLocation end)
|
||||
{
|
||||
//XXX FIXME no idea what todo with locations on different files
|
||||
if (start.Id != end.Id)
|
||||
if (start.Id != end.Id) {
|
||||
logger.LogDebug ($"FindPossibleBreakpoints: documents differ (start: {start.Id}) (end {end.Id}");
|
||||
return null;
|
||||
var src_id = start.Id;
|
||||
}
|
||||
|
||||
var doc = GetFileById (src_id);
|
||||
var sourceId = start.Id;
|
||||
|
||||
var doc = GetFileById (sourceId);
|
||||
|
||||
var res = new List<SourceLocation> ();
|
||||
if (doc == null) {
|
||||
logger.LogDebug ($"Could not find document {src_id}");
|
||||
logger.LogDebug ($"Could not find document {sourceId}");
|
||||
return res;
|
||||
}
|
||||
|
||||
foreach (var m in doc.Methods) {
|
||||
foreach (var sp in m.methodDef.DebugInformation.SequencePoints) {
|
||||
if (Match (sp, start, end))
|
||||
res.Add (new SourceLocation (m, sp));
|
||||
foreach (var method in doc.Methods) {
|
||||
foreach (var sequencePoint in method.methodDef.DebugInformation.SequencePoints) {
|
||||
if (!sequencePoint.IsHidden && Match (sequencePoint, start, end))
|
||||
res.Add (new SourceLocation (method, sequencePoint));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
|
|
@ -674,35 +718,26 @@ namespace WebAssembly.Net.Debugging {
|
|||
return true;
|
||||
}
|
||||
|
||||
public SourceLocation FindBestBreakpoint (BreakpointRequest req)
|
||||
public IEnumerable<SourceLocation> FindBreakpointLocations (BreakpointRequest request)
|
||||
{
|
||||
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));
|
||||
request.TryResolve (this);
|
||||
|
||||
if (src == null)
|
||||
return null;
|
||||
var asm = assemblies.FirstOrDefault (a => a.Name.Equals (request.Assembly, StringComparison.OrdinalIgnoreCase));
|
||||
var sourceFile = asm?.Sources?.SingleOrDefault (s => s.DebuggerFileName.Equals (request.File, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
foreach (var m in src.Methods) {
|
||||
foreach (var sp in m.methodDef.DebugInformation.SequencePoints) {
|
||||
if (sourceFile == null)
|
||||
yield break;
|
||||
|
||||
foreach (var method in sourceFile.Methods) {
|
||||
foreach (var sequencePoint in method.methodDef.DebugInformation.SequencePoints) {
|
||||
//FIXME handle multi doc methods
|
||||
if (Match (sp, req.Line, req.Column))
|
||||
return new SourceLocation (m, sp);
|
||||
if (!sequencePoint.IsHidden && Match (sequencePoint, request.Line, request.Column))
|
||||
yield return new SourceLocation (method, sequencePoint);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public string ToUrl (SourceLocation location)
|
||||
=> location != null ? GetFileById (location.Id).Url : "";
|
||||
|
||||
public SourceFile GetFileByUrlRegex (string urlRegex)
|
||||
{
|
||||
var regex = new Regex (urlRegex);
|
||||
return AllSources ().FirstOrDefault (file => regex.IsMatch (file.Url.ToString()) || regex.IsMatch (file.DocUrl));
|
||||
}
|
||||
|
||||
public SourceFile GetFileByUrl (string url)
|
||||
=> AllSources ().FirstOrDefault (file => file.Url.ToString() == url);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,13 +290,14 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
internal void SendResponse (MessageId id, Result result, CancellationToken token)
|
||||
{
|
||||
//Log ("verbose", $"sending response: {id}: {result.ToJObject (id)}");
|
||||
SendResponseInternal (id, result, token);
|
||||
}
|
||||
|
||||
void SendResponseInternal (MessageId id, Result result, CancellationToken token)
|
||||
{
|
||||
JObject o = result.ToJObject (id);
|
||||
if (result.IsErr)
|
||||
logger.LogError ("sending error response {result}", result);
|
||||
|
||||
Send (this.ide, o, token);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,10 +75,9 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
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 string StackId => $"dotnet:{LocalId}";
|
||||
public string StackId { get; private set; }
|
||||
|
||||
public static bool TryParseId (string stackId, out int id)
|
||||
{
|
||||
|
|
@ -89,10 +88,10 @@ namespace WebAssembly.Net.Debugging {
|
|||
return int.TryParse (stackId.Substring ("dotnet:".Length), out id);
|
||||
}
|
||||
|
||||
public Breakpoint (SourceLocation loc, int localId, BreakpointState state)
|
||||
public Breakpoint (string stackId, SourceLocation loc, BreakpointState state)
|
||||
{
|
||||
this.StackId = stackId;
|
||||
this.Location = loc;
|
||||
this.LocalId = localId;
|
||||
this.State = state;
|
||||
}
|
||||
}
|
||||
|
|
@ -110,18 +109,17 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
|
||||
internal class ExecutionContext {
|
||||
int breakpointIndex = -1;
|
||||
public List<Breakpoint> Breakpoints { get; } = new List<Breakpoint> ();
|
||||
public string DebuggerId { get; set; }
|
||||
public Dictionary<string,BreakpointRequest> BreakpointRequests { get; } = new Dictionary<string,BreakpointRequest> ();
|
||||
|
||||
public TaskCompletionSource<DebugStore> ready = null;
|
||||
public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted;
|
||||
|
||||
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> ();
|
||||
|
||||
|
|
@ -149,6 +147,14 @@ namespace WebAssembly.Net.Debugging {
|
|||
throw new ArgumentException ($"Invalid Session: \"{id}\"", nameof (sessionId));
|
||||
}
|
||||
|
||||
bool UpdateContext (SessionId sessionId, ExecutionContext executionContext, out ExecutionContext previousExecutionContext)
|
||||
{
|
||||
var id = sessionId?.sessionId ?? "default";
|
||||
var previous = contexts.TryGetValue (id, out previousExecutionContext);
|
||||
contexts[id] = executionContext;
|
||||
return previous;
|
||||
}
|
||||
|
||||
internal Task<Result> SendMonoCommand (SessionId id, MonoCommands cmd, CancellationToken token)
|
||||
=> SendCommand (id, "Runtime.evaluate", JObject.FromObject (cmd), token);
|
||||
|
||||
|
|
@ -163,6 +169,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "Runtime.executionContextCreated": {
|
||||
SendEvent (sessionId, method, args, token);
|
||||
var ctx = args? ["context"];
|
||||
|
|
@ -187,27 +194,47 @@ namespace WebAssembly.Net.Debugging {
|
|||
break;
|
||||
}
|
||||
|
||||
case "Debugger.breakpointResolved": {
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.scriptParsed":{
|
||||
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}");
|
||||
Log ("verbose", $"ignoring wasm: Debugger.scriptParsed {url}");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Log ("info", $"proxying Debugger.scriptParsed ({sessionId.sessionId}) {url} {args}");
|
||||
Log ("verbose", $"proxying Debugger.scriptParsed ({sessionId.sessionId}) {url} {args}");
|
||||
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;
|
||||
}
|
||||
|
||||
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> ();
|
||||
|
|
@ -240,19 +267,27 @@ namespace WebAssembly.Net.Debugging {
|
|||
return true;
|
||||
}
|
||||
|
||||
case "Debugger.setBreakpoint": {
|
||||
break;
|
||||
}
|
||||
|
||||
case "Debugger.setBreakpointByUrl": {
|
||||
var context = GetContext (id);
|
||||
var resp = await SendCommand (id, method, args, token);
|
||||
if (resp.IsOk && resp.Value ["locations"].HasValues) {
|
||||
if (!resp.IsOk) {
|
||||
SendResponse (id, resp, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
var bpid = resp.Value["breakpointId"]?.ToString ();
|
||||
var request = BreakpointRequest.Parse (bpid, args);
|
||||
context.BreakpointRequests[bpid] = request;
|
||||
var store = await RuntimeReady (id, token);
|
||||
|
||||
SendResponse (id, resp, token);
|
||||
Log ("verbose", $"BP req {args}");
|
||||
await SetBreakpoint (id, store, request, token);
|
||||
|
||||
SendResponse (id, Result.OkFromObject (request.AsSetBreakpointByUrlResponse()), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -333,10 +368,8 @@ namespace WebAssembly.Net.Debugging {
|
|||
//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 bp = context.BreakpointRequests.Values.SelectMany (v => v.Locations).FirstOrDefault (b => b.RemoteId == bp_id.Value);
|
||||
|
||||
var callFrames = new List<object> ();
|
||||
foreach (var frame in orig_callframes) {
|
||||
|
|
@ -352,6 +385,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
var method_token = mono_frame ["method_token"].Value<int> ();
|
||||
var assembly_name = mono_frame ["assembly_name"].Value<string> ();
|
||||
|
||||
var store = await LoadStore (sessionId, token);
|
||||
var asm = store.GetAssemblyByName (assembly_name);
|
||||
if (asm == null) {
|
||||
Log ("info",$"Unable to find assembly: {assembly_name}");
|
||||
|
|
@ -430,25 +464,19 @@ namespace WebAssembly.Net.Debugging {
|
|||
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 context.Breakpoints){
|
||||
b.State = BreakpointState.Pending;
|
||||
if (UpdateContext (sessionId, context, out var previousContext)) {
|
||||
foreach (var kvp in previousContext.BreakpointRequests) {
|
||||
context.BreakpointRequests[kvp.Key] = kvp.Value.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
Log ("debug", "checking if the runtime is ready");
|
||||
var res = await SendMonoCommand (sessionId, MonoCommands.IsRuntimeReady (), token);
|
||||
var is_ready = res.Value? ["result"]? ["value"]?.Value<bool> ();
|
||||
|
||||
if (is_ready.HasValue && is_ready.Value == true) {
|
||||
if (await IsRuntimeAlreadyReadyAlready (sessionId, token))
|
||||
await RuntimeReady (sessionId, token);
|
||||
}
|
||||
}
|
||||
|
||||
async Task OnResume (MessageId msd_id, CancellationToken token)
|
||||
{
|
||||
//discard frames
|
||||
//discard managed frames
|
||||
GetContext (msd_id).CallStack = null;
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
|
@ -487,8 +515,7 @@ namespace WebAssembly.Net.Debugging {
|
|||
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)
|
||||
{
|
||||
if (res.IsErr) {
|
||||
SendResponse(msg_id, res, token);
|
||||
return;
|
||||
}
|
||||
|
|
@ -544,8 +571,10 @@ namespace WebAssembly.Net.Debugging {
|
|||
|
||||
var values = res.Value? ["result"]? ["value"]?.Values<JObject> ().ToArray ();
|
||||
|
||||
if(values == null)
|
||||
if(values == null) {
|
||||
SendResponse (msg_id, Result.OkFromObject (new {result = Array.Empty<object> ()}), token);
|
||||
return;
|
||||
}
|
||||
|
||||
var var_list = new List<object> ();
|
||||
int i = 0;
|
||||
|
|
@ -589,8 +618,9 @@ namespace WebAssembly.Net.Debugging {
|
|||
}
|
||||
}
|
||||
|
||||
async Task<Result> EnableBreakpoint (SessionId sessionId, Breakpoint bp, CancellationToken token)
|
||||
async Task<Breakpoint> SetMonoBreakpoint (SessionId sessionId, BreakpointRequest req, SourceLocation location, CancellationToken token)
|
||||
{
|
||||
var bp = new Breakpoint (req.Id, location, BreakpointState.Pending);
|
||||
var asm_name = bp.Location.CliLocation.Method.Assembly.Name;
|
||||
var method_token = bp.Location.CliLocation.Method.Token;
|
||||
var il_offset = bp.Location.CliLocation.Offset;
|
||||
|
|
@ -604,147 +634,124 @@ namespace WebAssembly.Net.Debugging {
|
|||
//Log ("verbose", $"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}");
|
||||
}
|
||||
|
||||
return res;
|
||||
return bp;
|
||||
}
|
||||
|
||||
async Task<DebugStore> LoadStore (SessionId sessionId, CancellationToken token)
|
||||
{
|
||||
var context = GetContext (sessionId);
|
||||
|
||||
if (Interlocked.CompareExchange (ref context.store, new DebugStore (logger), null) != null) {
|
||||
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 context.store.Load(sessionId, the_pdbs, token);
|
||||
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 await context.Source.Task;
|
||||
return context.store;
|
||||
}
|
||||
|
||||
async Task RuntimeReady (SessionId sessionId, CancellationToken token)
|
||||
async Task<DebugStore> RuntimeReady (SessionId sessionId, CancellationToken token)
|
||||
{
|
||||
var context = GetContext (sessionId);
|
||||
if (context.RuntimeReady)
|
||||
return;
|
||||
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}");
|
||||
}
|
||||
|
||||
context.RuntimeReady = true;
|
||||
var store = await LoadStore (sessionId, token);
|
||||
|
||||
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 context.Breakpoints) {
|
||||
if (bp.State != BreakpointState.Pending)
|
||||
continue;
|
||||
var res = await EnableBreakpoint (sessionId, bp, token);
|
||||
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
|
||||
|
||||
//if we fail we just buble that to the IDE (and let it panic over it)
|
||||
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;
|
||||
}
|
||||
}
|
||||
context.ready.SetResult (store);
|
||||
SendEvent (sessionId, "Mono.runtimeReady", new JObject (), token);
|
||||
return store;
|
||||
}
|
||||
|
||||
async Task<bool> RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token) {
|
||||
var bpid = args? ["breakpointId"]?.Value<string> ();
|
||||
|
||||
if (!Breakpoint.TryParseId (bpid, out var the_id))
|
||||
return false;
|
||||
|
||||
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}");
|
||||
if (!context.BreakpointRequests.TryGetValue (bpid, out var breakpointRequest))
|
||||
return false;
|
||||
}
|
||||
|
||||
context.Breakpoints.Remove (bp);
|
||||
//FIXME verify result (and log?)
|
||||
var res = await RemoveBreakpoint (msg_id, bp, token);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
async Task<Result> RemoveBreakpoint (SessionId sessionId, Breakpoint bp, CancellationToken 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;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async Task<bool> SetBreakpoint (MessageId msg_id, BreakpointRequest req, CancellationToken token)
|
||||
{
|
||||
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 ("verbose", $"Could not resolve breakpoint request: {req}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Breakpoint bp = null;
|
||||
if (!context.RuntimeReady) {
|
||||
bp = new Breakpoint (bp_loc, context.NextBreakpointId (), BreakpointState.Pending);
|
||||
} else {
|
||||
bp = new Breakpoint (bp_loc, context.NextBreakpointId (), BreakpointState.Disabled);
|
||||
|
||||
var res = await EnableBreakpoint (msg_id, bp, token);
|
||||
foreach (var bp in breakpointRequest.Locations) {
|
||||
var res = await SendMonoCommand (msg_id, MonoCommands.RemoveBreakpoint (bp.RemoteId), token);
|
||||
var ret_code = res.Value? ["result"]? ["value"]?.Value<int> ();
|
||||
|
||||
//if we fail we just buble that to the IDE (and let it panic over it)
|
||||
if (!ret_code.HasValue) {
|
||||
return false;
|
||||
if (ret_code.HasValue) {
|
||||
bp.RemoteId = -1;
|
||||
bp.State = BreakpointState.Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
context.Breakpoints.Add (bp);
|
||||
|
||||
var ok = new {
|
||||
breakpointId = bp.StackId,
|
||||
locations = new [] {
|
||||
bp_loc.AsLocation ()
|
||||
},
|
||||
};
|
||||
|
||||
SendResponse (msg_id, Result.OkFromObject (ok), token);
|
||||
return true;
|
||||
breakpointRequest.Locations.Clear ();
|
||||
return false;
|
||||
}
|
||||
|
||||
async Task<bool> GetPossibleBreakpoints (MessageId msg_id, SourceLocation start, SourceLocation end, CancellationToken token)
|
||||
async Task SetBreakpoint (SessionId sessionId, DebugStore store, BreakpointRequest req, CancellationToken token)
|
||||
{
|
||||
var bps = (await LoadStore (msg_id, token)).FindPossibleBreakpoints (start, end);
|
||||
var context = GetContext (sessionId);
|
||||
if (req.Locations.Any ()) {
|
||||
Log ("debug", $"locations already loaded for {req.Id}");
|
||||
return;
|
||||
}
|
||||
|
||||
var locations = store.FindBreakpointLocations (req).ToList ();
|
||||
logger.LogDebug ("BP request for '{req}' runtime ready {context.RuntimeReady}", req, GetContext (sessionId).IsRuntimeReady);
|
||||
|
||||
var breakpoints = new List<Breakpoint> ();
|
||||
foreach (var loc in locations) {
|
||||
var bp = await SetMonoBreakpoint (sessionId, req, loc, token);
|
||||
|
||||
// If we didn't successfully enable the breakpoint
|
||||
// don't add it to the list of locations for this id
|
||||
if (bp.State != BreakpointState.Active)
|
||||
continue;
|
||||
|
||||
breakpoints.Add (bp);
|
||||
|
||||
var resolvedLocation = new {
|
||||
breakpointId = req.Id,
|
||||
location = loc.AsLocation ()
|
||||
};
|
||||
|
||||
SendEvent (sessionId, "Debugger.breakpointResolved", JObject.FromObject (resolvedLocation), token);
|
||||
}
|
||||
|
||||
req.Locations.AddRange (breakpoints);
|
||||
return;
|
||||
}
|
||||
|
||||
async Task<bool> GetPossibleBreakpoints (MessageId msg, SourceLocation start, SourceLocation end, CancellationToken token)
|
||||
{
|
||||
var bps = (await RuntimeReady (msg, token)).FindPossibleBreakpoints (start, end);
|
||||
|
||||
if (bps == null)
|
||||
return false;
|
||||
|
||||
SendResponse (msg_id, Result.OkFromObject (new { locations = bps.Select (b => b.AsLocation ()) }), token);
|
||||
var response = new { locations = bps.Select (b => b.AsLocation ()) };
|
||||
|
||||
SendResponse (msg, Result.OkFromObject (response), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue