Update DbgProxy to c8cbcaad (#20865)
This commit is contained in:
parent
089bb4a072
commit
2bd662fe0b
|
|
@ -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<Breakpoint> Locations { get; } = new List<Breakpoint> ();
|
||||
|
||||
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<VarInfo> ();
|
||||
|
||||
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<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;
|
||||
ModuleDefinition image;
|
||||
readonly int id;
|
||||
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, TypeInfo> typesByName = new Dictionary<string, TypeInfo> ();
|
||||
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)
|
||||
{
|
||||
|
|
@ -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<Document, SourceFile> ();
|
||||
|
||||
Func<Document, SourceFile> 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<SourceFile> Sources {
|
||||
get { return this.sources; }
|
||||
}
|
||||
public IEnumerable<SourceFile> 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<MethodInfo> methods;
|
||||
Dictionary<uint, MethodInfo> methods;
|
||||
AssemblyInfo assembly;
|
||||
int id;
|
||||
Document doc;
|
||||
|
||||
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.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<MethodInfo> Methods => this.methods;
|
||||
public byte[] EmbeddedSource => doc.EmbeddedSource;
|
||||
public IEnumerable<MethodInfo> 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<byte[]> LoadSource ()
|
||||
async Task<MemoryStream> 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<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)
|
||||
{
|
||||
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<AssemblyInfo> assemblies = new List<AssemblyInfo> ();
|
||||
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<byte[][]> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string> ()), res ["method"].Value<string> (), res ["params"] as JObject, token));
|
||||
else
|
||||
|
|
|
|||
|
|
@ -99,6 +99,8 @@ namespace WebAssembly.Net.Debugging {
|
|||
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)
|
||||
{
|
||||
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> ();
|
||||
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;
|
||||
|
|
@ -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<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 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 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<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 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<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) {
|
||||
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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue