Update DbgProxy to c8cbcaad (#20865)

This commit is contained in:
Pranav K 2020-04-20 11:22:29 -07:00 committed by GitHub
parent 089bb4a072
commit 2bd662fe0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 284 additions and 140 deletions

View File

@ -13,6 +13,7 @@ using System.Threading.Tasks;
using System.Threading; using System.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Security.Cryptography;
namespace WebAssembly.Net.Debugging { namespace WebAssembly.Net.Debugging {
internal class BreakpointRequest { internal class BreakpointRequest {
@ -21,26 +22,35 @@ namespace WebAssembly.Net.Debugging {
public string File { get; private set; } public string File { get; private set; }
public int Line { get; private set; } public int Line { get; private set; }
public int Column { get; private set; } public int Column { get; private set; }
public MethodInfo Method { get; private set; }
JObject request; JObject request;
public bool IsResolved => Assembly != null; public bool IsResolved => Assembly != null;
public List<Breakpoint> Locations { get; } = new List<Breakpoint> (); public List<Breakpoint> Locations { get; } = new List<Breakpoint> ();
public override string ToString () { public override string ToString ()
return $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}"; => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}";
}
public object AsSetBreakpointByUrlResponse () public object AsSetBreakpointByUrlResponse ()
=> new { breakpointId = Id, locations = Locations.Select(l => l.Location.AsLocation ()) }; => 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) public static BreakpointRequest Parse (string id, JObject args)
{ {
var breakRequest = new BreakpointRequest () { return new BreakpointRequest (id, args);
Id = id,
request = args
};
return breakRequest;
} }
public BreakpointRequest Clone () public BreakpointRequest Clone ()
@ -83,18 +93,6 @@ namespace WebAssembly.Net.Debugging {
return store.AllSources().FirstOrDefault (source => TryResolve (source)) != null; 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 { internal class VarInfo {
@ -110,13 +108,11 @@ namespace WebAssembly.Net.Debugging {
this.Index = (p.Index + 1) * -1; this.Index = (p.Index + 1) * -1;
} }
public string Name { get; private set; } public string Name { get; }
public int Index { get; private set; } public int Index { get; }
public override string ToString () public override string ToString ()
{ => $"(var-info [{Index}] '{Name}')";
return $"(var-info [{Index}] '{Name}')";
}
} }
internal class CliLocation { internal class CliLocation {
@ -126,8 +122,8 @@ namespace WebAssembly.Net.Debugging {
Offset = offset; Offset = offset;
} }
public MethodInfo Method { get; private set; } public MethodInfo Method { get; }
public int Offset { get; private set; } public int Offset { get; }
} }
internal class SourceLocation { internal class SourceLocation {
@ -157,9 +153,7 @@ namespace WebAssembly.Net.Debugging {
public CliLocation CliLocation => this.cliLoc; public CliLocation CliLocation => this.cliLoc;
public override string ToString () public override string ToString ()
{ => $"{id}:{Line}:{Column}";
return $"{id}:{Line}:{Column}";
}
public static SourceLocation Parse (JObject obj) public static SourceLocation Parse (JObject obj)
{ {
@ -235,9 +229,7 @@ namespace WebAssembly.Net.Debugging {
} }
public override string ToString () public override string ToString ()
{ => $"{Scheme}{assembly}_{document}";
return $"{Scheme}{assembly}_{document}";
}
public override bool Equals (object obj) public override bool Equals (object obj)
{ {
@ -248,44 +240,36 @@ namespace WebAssembly.Net.Debugging {
} }
public override int GetHashCode () public override int GetHashCode ()
{ => assembly.GetHashCode () ^ document.GetHashCode ();
return this.assembly.GetHashCode () ^ this.document.GetHashCode ();
}
public static bool operator == (SourceId a, SourceId b) public static bool operator == (SourceId a, SourceId b)
{ => ((object)a == null) ? (object)b == null : a.Equals (b);
if ((object)a == null)
return (object)b == null;
return a.Equals (b);
}
public static bool operator != (SourceId a, SourceId b) public static bool operator != (SourceId a, SourceId b)
{ => !a.Equals (b);
return !a.Equals (b);
}
} }
internal class MethodInfo { internal class MethodInfo {
AssemblyInfo assembly; MethodDefinition methodDef;
internal MethodDefinition methodDef;
SourceFile source; SourceFile source;
public SourceId SourceId => source.SourceId; public SourceId SourceId => source.SourceId;
public string Name => methodDef.Name; public string Name => methodDef.Name;
public MethodDebugInformation DebugInformation => methodDef.DebugInformation;
public SourceLocation StartLocation { get; private set; } public SourceLocation StartLocation { get; }
public SourceLocation EndLocation { get; private set; } public SourceLocation EndLocation { get; }
public AssemblyInfo Assembly => assembly; public AssemblyInfo Assembly { get; }
public int Token => (int)methodDef.MetadataToken.RID; public uint Token => methodDef.MetadataToken.RID;
public MethodInfo (AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source) public MethodInfo (AssemblyInfo assembly, MethodDefinition methodDef, SourceFile source)
{ {
this.assembly = assembly; this.Assembly = assembly;
this.methodDef = methodDef; this.methodDef = methodDef;
this.source = source; this.source = source;
var sps = methodDef.DebugInformation.SequencePoints; var sps = DebugInformation.SequencePoints;
if (sps == null || sps.Count() < 1) if (sps == null || sps.Count() < 1)
return; return;
@ -311,7 +295,7 @@ namespace WebAssembly.Net.Debugging {
public SourceLocation GetLocationByIl (int pos) public SourceLocation GetLocationByIl (int pos)
{ {
SequencePoint prev = null; SequencePoint prev = null;
foreach (var sp in methodDef.DebugInformation.SequencePoints) { foreach (var sp in DebugInformation.SequencePoints) {
if (sp.Offset > pos) if (sp.Offset > pos)
break; break;
prev = sp; prev = sp;
@ -328,7 +312,6 @@ namespace WebAssembly.Net.Debugging {
var res = new List<VarInfo> (); var res = new List<VarInfo> ();
res.AddRange (methodDef.Parameters.Select (p => new VarInfo (p))); res.AddRange (methodDef.Parameters.Select (p => new VarInfo (p)));
res.AddRange (methodDef.DebugInformation.GetScopes () res.AddRange (methodDef.DebugInformation.GetScopes ()
.Where (s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset)) .Where (s => s.Start.Offset <= offset && (s.End.IsEndOfMethod || s.End.Offset > offset))
.SelectMany (s => s.Variables) .SelectMany (s => s.Variables)
@ -337,17 +320,38 @@ namespace WebAssembly.Net.Debugging {
return res.ToArray (); return res.ToArray ();
} }
public override string ToString () => "MethodInfo(" + methodDef.FullName + ")";
} }
internal class AssemblyInfo { internal class TypeInfo {
AssemblyInfo assembly;
TypeDefinition type;
List<MethodInfo> methods;
public TypeInfo (AssemblyInfo assembly, TypeDefinition type) {
this.assembly = assembly;
this.type = type;
methods = new List<MethodInfo> ();
}
public string Name => type.Name;
public string FullName => type.FullName;
public List<MethodInfo> Methods => methods;
public override string ToString () => "TypeInfo('" + FullName + "')";
}
class AssemblyInfo {
static int next_id; static int next_id;
ModuleDefinition image; ModuleDefinition image;
readonly int id; readonly int id;
readonly ILogger logger; readonly ILogger logger;
Dictionary<int, MethodInfo> methods = new Dictionary<int, MethodInfo> (); Dictionary<uint, MethodInfo> methods = new Dictionary<uint, MethodInfo> ();
Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>(); Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>();
Dictionary<string, TypeInfo> typesByName = new Dictionary<string, TypeInfo> ();
readonly List<SourceFile> sources = new List<SourceFile>(); readonly List<SourceFile> sources = new List<SourceFile>();
internal string Url { get; private set; } internal string Url { get; }
public AssemblyInfo (string url, byte[] assembly, byte[] pdb) public AssemblyInfo (string url, byte[] assembly, byte[] pdb)
{ {
@ -356,18 +360,23 @@ namespace WebAssembly.Net.Debugging {
try { try {
Url = url; Url = url;
ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/); 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.ReadSymbols = true;
rp.SymbolReaderProvider = new PdbReaderProvider (); rp.SymbolReaderProvider = new PdbReaderProvider ();
if (pdb != null) if (pdb != null)
rp.SymbolStream = new MemoryStream (pdb); rp.SymbolStream = new MemoryStream (pdb);
rp.ReadingMode = ReadingMode.Immediate; rp.ReadingMode = ReadingMode.Immediate;
rp.InMemory = true; rp.InMemory = true;
this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp); this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
} catch (BadImageFormatException ex) { } catch (BadImageFormatException ex) {
logger.LogWarning ($"Failed to read assembly as portable PDB: {ex.Message}"); 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) if (pdb != null)
throw; throw;
} }
@ -396,45 +405,38 @@ namespace WebAssembly.Net.Debugging {
void Populate () void Populate ()
{ {
ProcessSourceLink(); ProcessSourceLink();
var d2s = new Dictionary<Document, SourceFile> (); var d2s = new Dictionary<Document, SourceFile> ();
Func<Document, SourceFile> get_src = (doc) => { SourceFile FindSource (Document doc)
{
if (doc == null) if (doc == null)
return 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)); var src = new SourceFile (this, sources.Count, doc, GetSourceLinkUrl (doc.Url));
sources.Add (src); sources.Add (src);
d2s [doc] = src; d2s [doc] = src;
return src; return src;
}; };
foreach (var m in image.GetTypes().SelectMany(t => t.Methods)) { foreach (var type in image.GetTypes()) {
Document first_doc = null; var typeInfo = new TypeInfo (this, type);
foreach (var sp in m.DebugInformation.SequencePoints) { typesByName [type.FullName] = typeInfo;
if (first_doc == null && !sp.Document.Url.EndsWith (".g.cs", StringComparison.OrdinalIgnoreCase)) {
first_doc = sp.Document; 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) private Uri GetSourceLinkUrl (string document)
{ {
if (sourceLinkMappings.TryGetValue (document, out string url)) { if (sourceLinkMappings.TryGetValue (document, out string url))
return new Uri (url); return new Uri (url);
}
foreach (var sourceLinkDocument in sourceLinkMappings) { foreach (var sourceLinkDocument in sourceLinkMappings) {
string key = sourceLinkDocument.Key; string key = sourceLinkDocument.Key;
@ -477,7 +478,7 @@ namespace WebAssembly.Net.Debugging {
return null; 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 uri = new Uri (relativeTo, UriKind.RelativeOrAbsolute);
var rel = Uri.UnescapeDataString (uri.MakeRelativeUri (new Uri (path, UriKind.RelativeOrAbsolute)).ToString ()).Replace (Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); 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; return rel;
} }
public IEnumerable<SourceFile> Sources { public IEnumerable<SourceFile> Sources
get { return this.sources; } => this.sources;
}
public int Id => id; public int Id => id;
public string Name => image.Name; public string Name => image.Name;
@ -499,22 +499,27 @@ namespace WebAssembly.Net.Debugging {
return sources.FirstOrDefault (s => s.SourceId.Document == document); return sources.FirstOrDefault (s => s.SourceId.Document == document);
} }
public MethodInfo GetMethodByToken (int token) public MethodInfo GetMethodByToken (uint token)
{ {
methods.TryGetValue (token, out var value); methods.TryGetValue (token, out var value);
return value; return value;
} }
public TypeInfo GetTypeByName (string name) {
typesByName.TryGetValue (name, out var res);
return res;
}
} }
internal class SourceFile { internal class SourceFile {
HashSet<MethodInfo> methods; Dictionary<uint, MethodInfo> methods;
AssemblyInfo assembly; AssemblyInfo assembly;
int id; int id;
Document doc; Document doc;
internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri) internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri)
{ {
this.methods = new HashSet<MethodInfo> (); this.methods = new Dictionary<uint, MethodInfo> ();
this.SourceLinkUri = sourceLinkUri; this.SourceLinkUri = sourceLinkUri;
this.assembly = assembly; this.assembly = assembly;
this.id = id; this.id = id;
@ -531,7 +536,8 @@ namespace WebAssembly.Net.Debugging {
internal void AddMethod (MethodInfo mi) internal void AddMethod (MethodInfo mi)
{ {
this.methods.Add (mi); if (!this.methods.ContainsKey (mi.Token))
this.methods [mi.Token] = mi;
} }
public string DebuggerFileName { get; } public string DebuggerFileName { get; }
@ -543,25 +549,93 @@ namespace WebAssembly.Net.Debugging {
public Uri SourceLinkUri { get; } public Uri SourceLinkUri { get; }
public Uri SourceUri { get; } public Uri SourceUri { get; }
public IEnumerable<MethodInfo> Methods => this.methods; public IEnumerable<MethodInfo> Methods => this.methods.Values;
public byte[] EmbeddedSource => doc.EmbeddedSource;
public string DocUrl => doc.Url; public string DocUrl => doc.Url;
public (int startLine, int startColumn, int endLine, int endColumn) GetExtents () 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 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 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); return (start.StartLocation.Line, start.StartLocation.Column, end.EndLocation.Line, end.EndLocation.Column);
} }
public async Task<byte[]> LoadSource () async Task<MemoryStream> GetDataAsync (Uri uri, CancellationToken token)
{ {
if (EmbeddedSource.Length > 0) var mem = new MemoryStream ();
return await Task.FromResult (EmbeddedSource); 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; 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<byte> ();
}
public async Task<Stream> 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) public object ToScriptSource (int executionContextId, object executionContextAuxData)
{ {
return new { return new {
@ -569,7 +643,7 @@ namespace WebAssembly.Net.Debugging {
url = Url, url = Url,
executionContextId, executionContextId,
executionContextAuxData, executionContextAuxData,
//hash = "abcdee" + id, //hash: should be the v8 hash algo, managed implementation is pending
dotNetUrl = DotNetUrl, dotNetUrl = DotNetUrl,
}; };
} }
@ -577,13 +651,18 @@ namespace WebAssembly.Net.Debugging {
internal class DebugStore { internal class DebugStore {
List<AssemblyInfo> assemblies = new List<AssemblyInfo> (); List<AssemblyInfo> assemblies = new List<AssemblyInfo> ();
HttpClient client = new HttpClient (); readonly HttpClient client;
readonly ILogger logger; readonly ILogger logger;
public DebugStore (ILogger logger) { public DebugStore (ILogger logger, HttpClient client) {
this.client = client;
this.logger = logger; this.logger = logger;
} }
public DebugStore (ILogger logger) : this (logger, new HttpClient ())
{
}
class DebugItem { class DebugItem {
public string Url { get; set; } public string Url { get; set; }
public Task<byte[][]> Data { get; set; } public Task<byte[][]> Data { get; set; }
@ -623,7 +702,7 @@ namespace WebAssembly.Net.Debugging {
var bytes = await step.Data; var bytes = await step.Data;
assembly = new AssemblyInfo (step.Url, bytes [0], bytes [1]); assembly = new AssemblyInfo (step.Url, bytes [0], bytes [1]);
} catch (Exception e) { } catch (Exception e) {
logger.LogDebug ($"Failed to Load {step.Url} ({e.Message})"); logger.LogDebug ($"Failed to load {step.Url} ({e.Message})");
} }
if (assembly == null) if (assembly == null)
continue; continue;
@ -686,7 +765,7 @@ namespace WebAssembly.Net.Debugging {
} }
foreach (var method in doc.Methods) { 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)) if (!sequencePoint.IsHidden && Match (sequencePoint, start, end))
res.Add (new SourceLocation (method, sequencePoint)); res.Add (new SourceLocation (method, sequencePoint));
} }
@ -729,8 +808,7 @@ namespace WebAssembly.Net.Debugging {
yield break; yield break;
foreach (var method in sourceFile.Methods) { foreach (var method in sourceFile.Methods) {
foreach (var sequencePoint in method.methodDef.DebugInformation.SequencePoints) { foreach (var sequencePoint in method.DebugInformation.SequencePoints) {
//FIXME handle multi doc methods
if (!sequencePoint.IsHidden && Match (sequencePoint, request.Line, request.Column)) if (!sequencePoint.IsHidden && Match (sequencePoint, request.Line, request.Column))
yield return new SourceLocation (method, sequencePoint); yield return new SourceLocation (method, sequencePoint);
} }

View File

@ -89,6 +89,9 @@ namespace WebAssembly.Net.Debugging {
public static Result Err (JObject err) public static Result Err (JObject err)
=> new Result (null, 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) public static Result Exception (Exception e)
=> new Result (null, JObject.FromObject (new { message = e.Message })); => 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) public static MonoCommands GetScopeVariables (int scopeId, params int[] vars)
=> new MonoCommands ($"MONO.mono_wasm_get_variables({scopeId}, [ {string.Join (",", 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})"); => new MonoCommands ($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})");
public static MonoCommands RemoveBreakpoint (int breakpointId) public static MonoCommands RemoveBreakpoint (int breakpointId)
@ -251,6 +254,5 @@ namespace WebAssembly.Net.Debugging {
} }
public int NextValueTypeId () => Interlocked.Increment (ref nextValueTypeId); public int NextValueTypeId () => Interlocked.Increment (ref nextValueTypeId);
} }
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System.Net.WebSockets; using System.Net.WebSockets;
@ -118,7 +119,10 @@ namespace WebAssembly.Net.Debugging {
void Send (WebSocket to, JObject o, CancellationToken token) void Send (WebSocket to, JObject o, CancellationToken token)
{ {
var sender = browser == to ? "Send-browser" : "Send-ide"; 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 bytes = Encoding.UTF8.GetBytes (o.ToString ());
var queue = GetQueueForSocket (to); var queue = GetQueueForSocket (to);
@ -165,9 +169,12 @@ namespace WebAssembly.Net.Debugging {
void ProcessBrowserMessage (string msg, CancellationToken token) void ProcessBrowserMessage (string msg, CancellationToken token)
{ {
Log ("protocol", $"browser: {msg}");
var res = JObject.Parse (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) if (res ["id"] == null)
pending_ops.Add (OnEvent (new SessionId (res ["sessionId"]?.Value<string> ()), res ["method"].Value<string> (), res ["params"] as JObject, token)); pending_ops.Add (OnEvent (new SessionId (res ["sessionId"]?.Value<string> ()), res ["method"].Value<string> (), res ["params"] as JObject, token));
else else

View File

@ -99,6 +99,8 @@ namespace WebAssembly.Net.Debugging {
return res.Value? ["result"]? ["value"]?.Value<bool> () ?? false; return res.Value? ["result"]? ["value"]?.Value<bool> () ?? false;
} }
static int bpIdGenerator;
protected override async Task<bool> AcceptCommand (MessageId id, string method, JObject args, CancellationToken token) protected override async Task<bool> AcceptCommand (MessageId id, string method, JObject args, CancellationToken token)
{ {
if (!contexts.TryGetValue (id, out var context)) if (!contexts.TryGetValue (id, out var context))
@ -174,7 +176,8 @@ namespace WebAssembly.Net.Debugging {
} }
case "Debugger.removeBreakpoint": { case "Debugger.removeBreakpoint": {
return await RemoveBreakpoint (id, args, token); await RemoveBreakpoint (id, args, token);
break;
} }
case "Debugger.resume": { case "Debugger.resume": {
@ -247,6 +250,68 @@ namespace WebAssembly.Net.Debugging {
} }
break; 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> ();
string typeName = args ["typeName"]?.Value<string> ();
string methodName = args ["methodName"]?.Value<string> ();
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; return false;
@ -318,7 +383,7 @@ namespace WebAssembly.Net.Debugging {
foreach (var mono_frame in the_mono_frames) { foreach (var mono_frame in the_mono_frames) {
++frame_id; ++frame_id;
var il_pos = mono_frame ["il_pos"].Value<int> (); var il_pos = mono_frame ["il_pos"].Value<int> ();
var method_token = mono_frame ["method_token"].Value<int> (); var method_token = mono_frame ["method_token"].Value<uint> ();
var assembly_name = mono_frame ["assembly_name"].Value<string> (); var assembly_name = mono_frame ["assembly_name"].Value<string> ();
var store = await LoadStore (sessionId, token); var store = await LoadStore (sessionId, token);
@ -672,9 +737,9 @@ namespace WebAssembly.Net.Debugging {
} }
} }
async Task<Breakpoint> SetMonoBreakpoint (SessionId sessionId, BreakpointRequest req, SourceLocation location, CancellationToken token) async Task<Breakpoint> 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 asm_name = bp.Location.CliLocation.Method.Assembly.Name;
var method_token = bp.Location.CliLocation.Method.Token; var method_token = bp.Location.CliLocation.Method.Token;
var il_offset = bp.Location.CliLocation.Offset; var il_offset = bp.Location.CliLocation.Offset;
@ -742,12 +807,12 @@ namespace WebAssembly.Net.Debugging {
return store; return store;
} }
async Task<bool> RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token) { async Task RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken token) {
var bpid = args? ["breakpointId"]?.Value<string> (); var bpid = args? ["breakpointId"]?.Value<string> ();
var context = GetContext (msg_id); var context = GetContext (msg_id);
if (!context.BreakpointRequests.TryGetValue (bpid, out var breakpointRequest)) if (!context.BreakpointRequests.TryGetValue (bpid, out var breakpointRequest))
return false; return;
foreach (var bp in breakpointRequest.Locations) { foreach (var bp in breakpointRequest.Locations) {
var res = await SendMonoCommand (msg_id, MonoCommands.RemoveBreakpoint (bp.RemoteId), token); var res = await SendMonoCommand (msg_id, MonoCommands.RemoveBreakpoint (bp.RemoteId), token);
@ -759,7 +824,6 @@ namespace WebAssembly.Net.Debugging {
} }
} }
breakpointRequest.Locations.Clear (); breakpointRequest.Locations.Clear ();
return false;
} }
async Task SetBreakpoint (SessionId sessionId, DebugStore store, BreakpointRequest req, CancellationToken token) 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); logger.LogDebug ("BP request for '{req}' runtime ready {context.RuntimeReady}", req, GetContext (sessionId).IsRuntimeReady);
var breakpoints = new List<Breakpoint> (); var breakpoints = new List<Breakpoint> ();
// 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) { 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 // If we didn't successfully enable the breakpoint
// don't add it to the list of locations for this id // don't add it to the list of locations for this id
@ -820,31 +890,18 @@ namespace WebAssembly.Net.Debugging {
return false; return false;
var src_file = (await LoadStore (msg_id, token)).GetFileById (id); var src_file = (await LoadStore (msg_id, token)).GetFileById (id);
var res = new StringWriter ();
try { try {
var uri = new Uri (src_file.Url); var uri = new Uri (src_file.Url);
string source = $"// Unable to find document {src_file.SourceUri}"; string source = $"// Unable to find document {src_file.SourceUri}";
if (uri.IsFile && File.Exists(uri.LocalPath)) { using (var data = await src_file.GetSourceAsync (checkHash: false, token: token)) {
using (var f = new StreamReader (File.Open (uri.LocalPath, FileMode.Open))) { if (data.Length == 0)
await res.WriteAsync (await f.ReadToEndAsync ()); return false;
}
source = res.ToString (); using (var reader = new StreamReader (data))
} else if (src_file.SourceUri.IsFile && File.Exists(src_file.SourceUri.LocalPath)) { source = await reader.ReadToEndAsync ();
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 ();
} }
SendResponse (msg_id, Result.OkFromObject (new { scriptSource = source }), token); SendResponse (msg_id, Result.OkFromObject (new { scriptSource = source }), token);
} catch (Exception e) { } catch (Exception e) {
var o = new { var o = new {