From 2bd662fe0b16a92358c62b8204b99ea4143900ae Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 20 Apr 2020 11:22:29 -0700 Subject: [PATCH] Update DbgProxy to c8cbcaad (#20865) --- .../src/MonoDebugProxy/ws-proxy/DebugStore.cs | 298 +++++++++++------- .../MonoDebugProxy/ws-proxy/DevToolsHelper.cs | 6 +- .../MonoDebugProxy/ws-proxy/DevToolsProxy.cs | 11 +- .../src/MonoDebugProxy/ws-proxy/MonoProxy.cs | 109 +++++-- 4 files changed, 284 insertions(+), 140 deletions(-) diff --git a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DebugStore.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DebugStore.cs index c5bc9fdcc6..8cf41de65f 100644 --- a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DebugStore.cs +++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DebugStore.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using System.Threading; using Microsoft.Extensions.Logging; using System.Runtime.CompilerServices; +using System.Security.Cryptography; namespace WebAssembly.Net.Debugging { internal class BreakpointRequest { @@ -21,26 +22,35 @@ namespace WebAssembly.Net.Debugging { public string File { get; private set; } public int Line { get; private set; } public int Column { get; private set; } + public MethodInfo Method { get; private set; } JObject request; public bool IsResolved => Assembly != null; public List Locations { get; } = new List (); - public override string ToString () { - return $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; - } + public override string ToString () + => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; public object AsSetBreakpointByUrlResponse () => new { breakpointId = Id, locations = Locations.Select(l => l.Location.AsLocation ()) }; + public BreakpointRequest () { + } + + public BreakpointRequest (string id, MethodInfo method) { + Id = id; + Method = method; + } + + public BreakpointRequest (string id, JObject request) { + Id = id; + this.request = request; + } + public static BreakpointRequest Parse (string id, JObject args) { - var breakRequest = new BreakpointRequest () { - Id = id, - request = args - }; - return breakRequest; + return new BreakpointRequest (id, args); } public BreakpointRequest Clone () @@ -83,18 +93,6 @@ namespace WebAssembly.Net.Debugging { return store.AllSources().FirstOrDefault (source => TryResolve (source)) != null; } - - static (string Assembly, string DocumentPath) ParseDocumentUrl (string url) - { - if (Uri.TryCreate (url, UriKind.Absolute, out var docUri) && docUri.Scheme == "dotnet") { - return ( - docUri.Host, - docUri.PathAndQuery.Substring (1) - ); - } else { - return (null, null); - } - } } internal class VarInfo { @@ -110,13 +108,11 @@ namespace WebAssembly.Net.Debugging { this.Index = (p.Index + 1) * -1; } - public string Name { get; private set; } - public int Index { get; private set; } + public string Name { get; } + public int Index { get; } public override string ToString () - { - return $"(var-info [{Index}] '{Name}')"; - } + => $"(var-info [{Index}] '{Name}')"; } internal class CliLocation { @@ -126,8 +122,8 @@ namespace WebAssembly.Net.Debugging { Offset = offset; } - public MethodInfo Method { get; private set; } - public int Offset { get; private set; } + public MethodInfo Method { get; } + public int Offset { get; } } internal class SourceLocation { @@ -157,9 +153,7 @@ namespace WebAssembly.Net.Debugging { public CliLocation CliLocation => this.cliLoc; public override string ToString () - { - return $"{id}:{Line}:{Column}"; - } + => $"{id}:{Line}:{Column}"; public static SourceLocation Parse (JObject obj) { @@ -235,9 +229,7 @@ namespace WebAssembly.Net.Debugging { } public override string ToString () - { - return $"{Scheme}{assembly}_{document}"; - } + => $"{Scheme}{assembly}_{document}"; public override bool Equals (object obj) { @@ -248,44 +240,36 @@ namespace WebAssembly.Net.Debugging { } public override int GetHashCode () - { - return this.assembly.GetHashCode () ^ this.document.GetHashCode (); - } + => assembly.GetHashCode () ^ document.GetHashCode (); public static bool operator == (SourceId a, SourceId b) - { - if ((object)a == null) - return (object)b == null; - return a.Equals (b); - } + => ((object)a == null) ? (object)b == null : a.Equals (b); public static bool operator != (SourceId a, SourceId b) - { - return !a.Equals (b); - } + => !a.Equals (b); } internal class MethodInfo { - AssemblyInfo assembly; - internal MethodDefinition methodDef; + MethodDefinition methodDef; SourceFile source; public SourceId SourceId => source.SourceId; public string Name => methodDef.Name; + public MethodDebugInformation DebugInformation => methodDef.DebugInformation; - public SourceLocation StartLocation { get; private set; } - public SourceLocation EndLocation { get; private set; } - public AssemblyInfo Assembly => assembly; - public int Token => (int)methodDef.MetadataToken.RID; + public SourceLocation StartLocation { get; } + public SourceLocation EndLocation { get; } + public AssemblyInfo Assembly { get; } + public uint Token => methodDef.MetadataToken.RID; public MethodInfo (AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source) { - this.assembly = assembly; + this.Assembly = assembly; this.methodDef = methodDef; this.source = source; - var sps = methodDef.DebugInformation.SequencePoints; + var sps = DebugInformation.SequencePoints; if (sps == null || sps.Count() < 1) return; @@ -311,7 +295,7 @@ namespace WebAssembly.Net.Debugging { public SourceLocation GetLocationByIl (int pos) { SequencePoint prev = null; - foreach (var sp in methodDef.DebugInformation.SequencePoints) { + foreach (var sp in DebugInformation.SequencePoints) { if (sp.Offset > pos) break; prev = sp; @@ -328,7 +312,6 @@ namespace WebAssembly.Net.Debugging { var res = new List (); res.AddRange (methodDef.Parameters.Select (p => new VarInfo (p))); - res.AddRange (methodDef.DebugInformation.GetScopes () .Where (s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset)) .SelectMany (s => s.Variables) @@ -337,17 +320,38 @@ namespace WebAssembly.Net.Debugging { return res.ToArray (); } + + public override string ToString () => "MethodInfo(" + methodDef.FullName + ")"; } - internal class AssemblyInfo { + internal class TypeInfo { + AssemblyInfo assembly; + TypeDefinition type; + List methods; + + public TypeInfo (AssemblyInfo assembly, TypeDefinition type) { + this.assembly = assembly; + this.type = type; + methods = new List (); + } + + public string Name => type.Name; + public string FullName => type.FullName; + public List Methods => methods; + + public override string ToString () => "TypeInfo('" + FullName + "')"; + } + + class AssemblyInfo { static int next_id; ModuleDefinition image; readonly int id; readonly ILogger logger; - Dictionary methods = new Dictionary (); + Dictionary methods = new Dictionary (); Dictionary sourceLinkMappings = new Dictionary(); + Dictionary typesByName = new Dictionary (); readonly List sources = new List(); - internal string Url { get; private set; } + internal string Url { get; } public AssemblyInfo (string url, byte[] assembly, byte[] pdb) { @@ -356,18 +360,23 @@ namespace WebAssembly.Net.Debugging { try { Url = url; ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/); + + // set ReadSymbols = true unconditionally in case there + // is an embedded pdb then handle ArgumentException + // and assume that if pdb == null that is the cause rp.ReadSymbols = true; rp.SymbolReaderProvider = new PdbReaderProvider (); if (pdb != null) rp.SymbolStream = new MemoryStream (pdb); - rp.ReadingMode = ReadingMode.Immediate; rp.InMemory = true; this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp); } catch (BadImageFormatException ex) { logger.LogWarning ($"Failed to read assembly as portable PDB: {ex.Message}"); - } catch (ArgumentNullException) { + } catch (ArgumentException) { + // if pdb == null this is expected and we + // read the assembly without symbols below if (pdb != null) throw; } @@ -396,45 +405,38 @@ namespace WebAssembly.Net.Debugging { void Populate () { - ProcessSourceLink(); + ProcessSourceLink(); var d2s = new Dictionary (); - Func get_src = (doc) => { + SourceFile FindSource (Document doc) + { if (doc == null) return null; - if (d2s.ContainsKey (doc)) - return d2s [doc]; + + if (d2s.TryGetValue (doc, out var source)) + return source; + var src = new SourceFile (this, sources.Count, doc, GetSourceLinkUrl (doc.Url)); sources.Add (src); d2s [doc] = src; return src; }; - 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", StringComparison.OrdinalIgnoreCase)) { - first_doc = sp.Document; + foreach (var type in image.GetTypes()) { + var typeInfo = new TypeInfo (this, type); + typesByName [type.FullName] = typeInfo; + + foreach (var method in type.Methods) { + foreach (var sp in method.DebugInformation.SequencePoints) { + var source = FindSource (sp.Document); + var methodInfo = new MethodInfo (this, method, source); + methods [method.MetadataToken.RID] = methodInfo; + if (source != null) + source.AddMethod (methodInfo); + + typeInfo.Methods.Add (methodInfo); } - // else if (first_doc != sp.Document) { - // //FIXME this is needed for (c)ctors in corlib - // throw new Exception ($"Cant handle multi-doc methods in {m}"); - //} - } - - if (first_doc == null) { - // all generated files - first_doc = m.DebugInformation.SequencePoints.FirstOrDefault ()?.Document; - } - - if (first_doc != null) { - var src = get_src (first_doc); - var mi = new MethodInfo (this, m, src); - int mt = (int)m.MetadataToken.RID; - this.methods [mt] = mi; - if (src != null) - src.AddMethod (mi); } } } @@ -455,9 +457,8 @@ namespace WebAssembly.Net.Debugging { private Uri GetSourceLinkUrl (string document) { - if (sourceLinkMappings.TryGetValue (document, out string url)) { + if (sourceLinkMappings.TryGetValue (document, out string url)) return new Uri (url); - } foreach (var sourceLinkDocument in sourceLinkMappings) { string key = sourceLinkDocument.Key; @@ -477,7 +478,7 @@ namespace WebAssembly.Net.Debugging { return null; } - private string GetRelativePath (string relativeTo, string path) + private static string GetRelativePath (string relativeTo, string path) { var uri = new Uri (relativeTo, UriKind.RelativeOrAbsolute); var rel = Uri.UnescapeDataString (uri.MakeRelativeUri (new Uri (path, UriKind.RelativeOrAbsolute)).ToString ()).Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); @@ -487,9 +488,8 @@ namespace WebAssembly.Net.Debugging { return rel; } - public IEnumerable Sources { - get { return this.sources; } - } + public IEnumerable Sources + => this.sources; public int Id => id; public string Name => image.Name; @@ -499,22 +499,27 @@ namespace WebAssembly.Net.Debugging { return sources.FirstOrDefault (s => s.SourceId.Document == document); } - public MethodInfo GetMethodByToken (int token) + public MethodInfo GetMethodByToken (uint token) { methods.TryGetValue (token, out var value); return value; } + + public TypeInfo GetTypeByName (string name) { + typesByName.TryGetValue (name, out var res); + return res; + } } internal class SourceFile { - HashSet methods; + Dictionary methods; AssemblyInfo assembly; int id; Document doc; internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri) { - this.methods = new HashSet (); + this.methods = new Dictionary (); this.SourceLinkUri = sourceLinkUri; this.assembly = assembly; this.id = id; @@ -531,7 +536,8 @@ namespace WebAssembly.Net.Debugging { internal void AddMethod (MethodInfo mi) { - this.methods.Add (mi); + if (!this.methods.ContainsKey (mi.Token)) + this.methods [mi.Token] = mi; } public string DebuggerFileName { get; } @@ -543,25 +549,93 @@ namespace WebAssembly.Net.Debugging { public Uri SourceLinkUri { get; } public Uri SourceUri { get; } - public IEnumerable Methods => this.methods; - public byte[] EmbeddedSource => doc.EmbeddedSource; + public IEnumerable Methods => this.methods.Values; + public string DocUrl => doc.Url; 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 (); + 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 LoadSource () + async Task GetDataAsync (Uri uri, CancellationToken token) { - if (EmbeddedSource.Length > 0) - return await Task.FromResult (EmbeddedSource); + var mem = new MemoryStream (); + try { + if (uri.IsFile && File.Exists (uri.LocalPath)) { + using (var file = File.Open (SourceUri.LocalPath, FileMode.Open)) { + await file.CopyToAsync (mem, token); + mem.Position = 0; + } + } else if (uri.Scheme == "http" || uri.Scheme == "https") { + var client = new HttpClient (); + using (var stream = await client.GetStreamAsync (uri)) { + await stream.CopyToAsync (mem, token); + mem.Position = 0; + } + } + } catch (Exception) { + return null; + } + return mem; + } + static HashAlgorithm GetHashAlgorithm (DocumentHashAlgorithm algorithm) + { + switch (algorithm) { + case DocumentHashAlgorithm.SHA1: return SHA1.Create (); + case DocumentHashAlgorithm.SHA256: return SHA256.Create (); + case DocumentHashAlgorithm.MD5: return MD5.Create (); + } return null; } + bool CheckPdbHash (byte [] computedHash) + { + if (computedHash.Length != doc.Hash.Length) + return false; + + for (var i = 0; i < computedHash.Length; i++) + if (computedHash[i] != doc.Hash[i]) + return false; + + return true; + } + + byte[] ComputePdbHash (Stream sourceStream) + { + var algorithm = GetHashAlgorithm (doc.HashAlgorithm); + if (algorithm != null) + using (algorithm) + return algorithm.ComputeHash (sourceStream); + + return Array.Empty (); + } + + public async Task GetSourceAsync (bool checkHash, CancellationToken token = default(CancellationToken)) + { + if (doc.EmbeddedSource.Length > 0) + return new MemoryStream (doc.EmbeddedSource, false); + + MemoryStream mem; + + mem = await GetDataAsync (SourceUri, token); + if (mem != null && (!checkHash || CheckPdbHash (ComputePdbHash (mem)))) { + mem.Position = 0; + return mem; + } + + mem = await GetDataAsync (SourceLinkUri, token); + if (mem != null && (!checkHash || CheckPdbHash (ComputePdbHash (mem)))) { + mem.Position = 0; + return mem; + } + + return MemoryStream.Null; + } + public object ToScriptSource (int executionContextId, object executionContextAuxData) { return new { @@ -569,7 +643,7 @@ namespace WebAssembly.Net.Debugging { url = Url, executionContextId, executionContextAuxData, - //hash = "abcdee" + id, + //hash: should be the v8 hash algo, managed implementation is pending dotNetUrl = DotNetUrl, }; } @@ -577,13 +651,18 @@ namespace WebAssembly.Net.Debugging { internal class DebugStore { List assemblies = new List (); - HttpClient client = new HttpClient (); + readonly HttpClient client; readonly ILogger logger; - public DebugStore (ILogger logger) { + public DebugStore (ILogger logger, HttpClient client) { + this.client = client; this.logger = logger; } + public DebugStore (ILogger logger) : this (logger, new HttpClient ()) + { + } + class DebugItem { public string Url { get; set; } public Task Data { get; set; } @@ -623,7 +702,7 @@ namespace WebAssembly.Net.Debugging { var bytes = await step.Data; assembly = new AssemblyInfo (step.Url, bytes [0], bytes [1]); } catch (Exception e) { - logger.LogDebug ($"Failed to Load {step.Url} ({e.Message})"); + logger.LogDebug ($"Failed to load {step.Url} ({e.Message})"); } if (assembly == null) continue; @@ -686,7 +765,7 @@ namespace WebAssembly.Net.Debugging { } foreach (var method in doc.Methods) { - foreach (var sequencePoint in method.methodDef.DebugInformation.SequencePoints) { + foreach (var sequencePoint in method.DebugInformation.SequencePoints) { if (!sequencePoint.IsHidden && Match (sequencePoint, start, end)) res.Add (new SourceLocation (method, sequencePoint)); } @@ -729,8 +808,7 @@ namespace WebAssembly.Net.Debugging { yield break; foreach (var method in sourceFile.Methods) { - foreach (var sequencePoint in method.methodDef.DebugInformation.SequencePoints) { - //FIXME handle multi doc methods + foreach (var sequencePoint in method.DebugInformation.SequencePoints) { if (!sequencePoint.IsHidden && Match (sequencePoint, request.Line, request.Column)) yield return new SourceLocation (method, sequencePoint); } diff --git a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsHelper.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsHelper.cs index 441426299d..302a6ae0c3 100644 --- a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsHelper.cs +++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsHelper.cs @@ -89,6 +89,9 @@ namespace WebAssembly.Net.Debugging { public static Result Err (JObject err) => new Result (null, err); + public static Result Err (string msg) + => new Result (null, JObject.FromObject (new { message = msg })); + public static Result Exception (Exception e) => new Result (null, JObject.FromObject (new { message = e.Message })); @@ -151,7 +154,7 @@ namespace WebAssembly.Net.Debugging { 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) + public static MonoCommands SetBreakpoint (string assemblyName, uint methodToken, int ilOffset) => new MonoCommands ($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})"); public static MonoCommands RemoveBreakpoint (int breakpointId) @@ -251,6 +254,5 @@ namespace WebAssembly.Net.Debugging { } public int NextValueTypeId () => Interlocked.Increment (ref nextValueTypeId); - } } diff --git a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsProxy.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsProxy.cs index 22d86b20f8..1632e3a4a1 100644 --- a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsProxy.cs +++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/DevToolsProxy.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Net.WebSockets; @@ -118,7 +119,10 @@ namespace WebAssembly.Net.Debugging { void Send (WebSocket to, JObject o, CancellationToken token) { var sender = browser == to ? "Send-browser" : "Send-ide"; - Log ("protocol", $"{sender}: {o}"); + + var method = o ["method"]?.ToString (); + //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled") + Log ("protocol", $"{sender}: " + JsonConvert.SerializeObject (o)); var bytes = Encoding.UTF8.GetBytes (o.ToString ()); var queue = GetQueueForSocket (to); @@ -165,9 +169,12 @@ namespace WebAssembly.Net.Debugging { void ProcessBrowserMessage (string msg, CancellationToken token) { - Log ("protocol", $"browser: {msg}"); var res = JObject.Parse (msg); + var method = res ["method"]?.ToString (); + //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled") + Log ("protocol", $"browser: {msg}"); + if (res ["id"] == null) pending_ops.Add (OnEvent (new SessionId (res ["sessionId"]?.Value ()), res ["method"].Value (), res ["params"] as JObject, token)); else diff --git a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/MonoProxy.cs b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/MonoProxy.cs index 998e20eeba..78b44f5314 100644 --- a/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/MonoProxy.cs +++ b/src/Components/WebAssembly/DebugProxy/src/MonoDebugProxy/ws-proxy/MonoProxy.cs @@ -99,6 +99,8 @@ namespace WebAssembly.Net.Debugging { return res.Value? ["result"]? ["value"]?.Value () ?? false; } + static int bpIdGenerator; + protected override async Task AcceptCommand (MessageId id, string method, JObject args, CancellationToken token) { if (!contexts.TryGetValue (id, out var context)) @@ -174,7 +176,8 @@ namespace WebAssembly.Net.Debugging { } case "Debugger.removeBreakpoint": { - return await RemoveBreakpoint (id, args, token); + await RemoveBreakpoint (id, args, token); + break; } case "Debugger.resume": { @@ -247,6 +250,68 @@ namespace WebAssembly.Net.Debugging { } break; } + + // Protocol extensions + case "Dotnet-test.setBreakpointByMethod": { + Console.WriteLine ("set-breakpoint-by-method: " + id + " " + args); + + var store = await RuntimeReady (id, token); + string aname = args ["assemblyName"]?.Value (); + string typeName = args ["typeName"]?.Value (); + string methodName = args ["methodName"]?.Value (); + if (aname == null || typeName == null || methodName == null) { + SendResponse (id, Result.Err ("Invalid protocol message '" + args + "'."), token); + return true; + } + + // GetAssemblyByName seems to work on file names + var assembly = store.GetAssemblyByName (aname); + if (assembly == null) + assembly = store.GetAssemblyByName (aname + ".exe"); + if (assembly == null) + assembly = store.GetAssemblyByName (aname + ".dll"); + if (assembly == null) { + SendResponse (id, Result.Err ("Assembly '" + aname + "' not found."), token); + return true; + } + + var type = assembly.GetTypeByName (typeName); + if (type == null) { + SendResponse (id, Result.Err ($"Type '{typeName}' not found."), token); + return true; + } + + var methodInfo = type.Methods.FirstOrDefault (m => m.Name == methodName); + if (methodInfo == null) { + SendResponse (id, Result.Err ($"Method '{typeName}:{methodName}' not found."), token); + return true; + } + + bpIdGenerator ++; + string bpid = "by-method-" + bpIdGenerator.ToString (); + var request = new BreakpointRequest (bpid, methodInfo); + context.BreakpointRequests[bpid] = request; + + var loc = methodInfo.StartLocation; + var bp = await SetMonoBreakpoint (id, bpid, loc, token); + if (bp.State != BreakpointState.Active) { + // FIXME: + throw new NotImplementedException (); + } + + var resolvedLocation = new { + breakpointId = bpid, + location = loc.AsLocation () + }; + + SendEvent (id, "Debugger.breakpointResolved", JObject.FromObject (resolvedLocation), token); + + SendResponse (id, Result.OkFromObject (new { + result = new { breakpointId = bpid, locations = new object [] { loc.AsLocation () }} + }), token); + + return true; + } } return false; @@ -318,7 +383,7 @@ namespace WebAssembly.Net.Debugging { foreach (var mono_frame in the_mono_frames) { ++frame_id; var il_pos = mono_frame ["il_pos"].Value (); - var method_token = mono_frame ["method_token"].Value (); + var method_token = mono_frame ["method_token"].Value (); var assembly_name = mono_frame ["assembly_name"].Value (); var store = await LoadStore (sessionId, token); @@ -672,9 +737,9 @@ namespace WebAssembly.Net.Debugging { } } - async Task SetMonoBreakpoint (SessionId sessionId, BreakpointRequest req, SourceLocation location, CancellationToken token) + async Task SetMonoBreakpoint (SessionId sessionId, string reqId, SourceLocation location, CancellationToken token) { - var bp = new Breakpoint (req.Id, location, BreakpointState.Pending); + var bp = new Breakpoint (reqId, 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; @@ -742,12 +807,12 @@ namespace WebAssembly.Net.Debugging { return store; } - async Task RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token) { + async Task RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token) { var bpid = args? ["breakpointId"]?.Value (); var context = GetContext (msg_id); if (!context.BreakpointRequests.TryGetValue (bpid, out var breakpointRequest)) - return false; + return; foreach (var bp in breakpointRequest.Locations) { var res = await SendMonoCommand (msg_id, MonoCommands.RemoveBreakpoint (bp.RemoteId), token); @@ -759,7 +824,6 @@ namespace WebAssembly.Net.Debugging { } } breakpointRequest.Locations.Clear (); - return false; } async Task SetBreakpoint (SessionId sessionId, DebugStore store, BreakpointRequest req, CancellationToken token) @@ -774,8 +838,14 @@ namespace WebAssembly.Net.Debugging { logger.LogDebug ("BP request for '{req}' runtime ready {context.RuntimeReady}", req, GetContext (sessionId).IsRuntimeReady); var breakpoints = new List (); + + // if column is specified the frontend wants the exact matches + // and will clear the bp if it isn't close enough + if (req.Column != 0) + locations = locations.Where (l => l.Column == req.Column).ToList (); + foreach (var loc in locations) { - var bp = await SetMonoBreakpoint (sessionId, req, loc, token); + var bp = await SetMonoBreakpoint (sessionId, req.Id, loc, token); // If we didn't successfully enable the breakpoint // don't add it to the list of locations for this id @@ -820,31 +890,18 @@ namespace WebAssembly.Net.Debugging { return false; var src_file = (await LoadStore (msg_id, token)).GetFileById (id); - var res = new StringWriter (); 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 ()); - } + using (var data = await src_file.GetSourceAsync (checkHash: false, token: token)) { + if (data.Length == 0) + return false; - 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 ()); - } - - source = res.ToString (); - } else if(src_file.SourceLinkUri != null) { - var doc = await new WebClient ().DownloadStringTaskAsync (src_file.SourceLinkUri); - await res.WriteAsync (doc); - - source = res.ToString (); + using (var reader = new StreamReader (data)) + source = await reader.ReadToEndAsync (); } - SendResponse (msg_id, Result.OkFromObject (new { scriptSource = source }), token); } catch (Exception e) { var o = new {