Update Mono WebAssembly for Blazor (https://github.com/aspnet/Blazor/pull/1807)
This commit is contained in:
parent
dbe9ab7dd5
commit
6c5e1690ad
|
|
@ -27,6 +27,11 @@
|
|||
$(RestoreSources);
|
||||
https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json;
|
||||
</RestoreSources>
|
||||
<!-- TODO remove this once we move Microsoft.AspNetCore.Blazor.Mono to a non-myget feed -->
|
||||
<RestoreSources>
|
||||
$(RestoreSources);
|
||||
https://dotnet.myget.org/F/blazor-dev/api/v3/index.json;
|
||||
</RestoreSources>
|
||||
|
||||
<!-- In an orchestrated build, this may be overriden to other Azure feeds. -->
|
||||
<DotNetAssetRootUrl Condition="'$(DotNetAssetRootUrl)'==''">https://dotnetcli.blob.core.windows.net/dotnet/</DotNetAssetRootUrl>
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@
|
|||
<MicrosoftWebXdtPackageVersion>1.4.0</MicrosoftWebXdtPackageVersion>
|
||||
<SystemIdentityModelTokensJwtPackageVersion>5.3.0</SystemIdentityModelTokensJwtPackageVersion>
|
||||
<!-- Dependencies for Blazor. -->
|
||||
<MicrosoftAspNetCoreBlazorMonoPackageVersion>0.10.0-preview-20190325.1</MicrosoftAspNetCoreBlazorMonoPackageVersion>
|
||||
<MicrosoftAspNetCoreBlazorMonoPackageVersion>0.10.0-preview-20190523.1</MicrosoftAspNetCoreBlazorMonoPackageVersion>
|
||||
<!-- Packages from 2.1/2.2 branches used for site extension build -->
|
||||
<MicrosoftAspNetCoreAzureAppServicesSiteExtension21PackageVersion>2.1.1</MicrosoftAspNetCoreAzureAppServicesSiteExtension21PackageVersion>
|
||||
<MicrosoftAspNetCoreAzureAppServicesSiteExtension22PackageVersion>2.2.0</MicrosoftAspNetCoreAzureAppServicesSiteExtension22PackageVersion>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
<PropertyGroup Label="Blazor build outputs">
|
||||
<MonoLinkerI18NAssemblies>none</MonoLinkerI18NAssemblies> <!-- See Mono linker docs - allows comma-separated values from: none,all,cjk,mideast,other,rare,west -->
|
||||
<AdditionalMonoLinkerOptions>--verbose --strip-security true --exclude-feature com --exclude-feature sre -v false -c link -u link -b true</AdditionalMonoLinkerOptions>
|
||||
<AdditionalMonoLinkerOptions>--disable-opt unreachablebodies --verbose --strip-security true --exclude-feature com --exclude-feature sre -v false -c link -u link -b true</AdditionalMonoLinkerOptions>
|
||||
<BaseBlazorDistPath>dist/</BaseBlazorDistPath>
|
||||
<BaseBlazorPackageContentOutputPath>$(BaseBlazorDistPath)_content/</BaseBlazorPackageContentOutputPath>
|
||||
<BaseBlazorRuntimeOutputPath>$(BaseBlazorDistPath)_framework/</BaseBlazorRuntimeOutputPath>
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
@echo off
|
||||
echo |----
|
||||
echo | Copying the ws-proxy sources here is a temporary step until ws-proxy is
|
||||
echo | distributed as a NuGet package.
|
||||
echo | ...
|
||||
echo | Instead of dealing with Git submodules, this script simply fetches the
|
||||
echo | latest sources so they can be built directly inside this project (hence
|
||||
echo | we don't have to publish our own separate package for this).
|
||||
echo | ...
|
||||
echo | When updating, you'll need to re-apply any patches we've made manually.
|
||||
echo |----
|
||||
@echo on
|
||||
|
||||
cd /D "%~dp0"
|
||||
rmdir /s /q ws-proxy
|
||||
git clone https://github.com/kumpera/ws-proxy.git
|
||||
rmdir /s /q ws-proxy\.git
|
||||
del ws-proxy\*.csproj
|
||||
del ws-proxy\*.sln
|
||||
del ws-proxy\Program.cs
|
||||
|
|
@ -6,6 +6,9 @@ using Mono.Cecil.Cil;
|
|||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System.Net.Http;
|
||||
using Mono.Cecil.Pdb;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace WsProxy {
|
||||
internal class BreakPointRequest {
|
||||
|
|
@ -18,17 +21,29 @@ namespace WsProxy {
|
|||
return $"BreakPointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}";
|
||||
}
|
||||
|
||||
public static BreakPointRequest Parse (JObject args)
|
||||
public static BreakPointRequest Parse (JObject args, DebugStore store)
|
||||
{
|
||||
if (args == null)
|
||||
return null;
|
||||
|
||||
var url = args? ["url"]?.Value<string> ();
|
||||
if (!url.StartsWith ("dotnet://", StringComparison.InvariantCulture))
|
||||
if (url == null) {
|
||||
var urlRegex = args?["urlRegex"].Value<string>();
|
||||
var sourceFile = store.GetFileByUrlRegex (urlRegex);
|
||||
|
||||
url = sourceFile?.DotNetUrl;
|
||||
}
|
||||
|
||||
if (url != null && !url.StartsWith ("dotnet://", StringComparison.InvariantCulture)) {
|
||||
var sourceFile = store.GetFileByUrl (url);
|
||||
url = sourceFile?.DotNetUrl;
|
||||
}
|
||||
|
||||
if (url == null)
|
||||
return null;
|
||||
|
||||
var parts = url.Substring ("dotnet://".Length).Split ('/');
|
||||
if (parts.Length != 2)
|
||||
var parts = ParseDocumentUrl (url);
|
||||
if (parts.Assembly == null)
|
||||
return null;
|
||||
|
||||
var line = args? ["lineNumber"]?.Value<int> ();
|
||||
|
|
@ -37,12 +52,24 @@ namespace WsProxy {
|
|||
return null;
|
||||
|
||||
return new BreakPointRequest () {
|
||||
Assembly = parts [0],
|
||||
File = parts [1],
|
||||
Assembly = parts.Assembly,
|
||||
File = parts.DocumentPath,
|
||||
Line = line.Value,
|
||||
Column = column.Value
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -101,7 +128,7 @@ namespace WsProxy {
|
|||
public SourceLocation (MethodInfo mi, SequencePoint sp)
|
||||
{
|
||||
this.id = mi.SourceId;
|
||||
this.line = sp.StartLine;
|
||||
this.line = sp.StartLine - 1;
|
||||
this.column = sp.StartColumn - 1;
|
||||
this.cliLoc = new CliLocation (mi, sp.Offset);
|
||||
}
|
||||
|
|
@ -260,13 +287,13 @@ namespace WsProxy {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
internal class AssemblyInfo {
|
||||
static int next_id;
|
||||
ModuleDefinition image;
|
||||
readonly int id;
|
||||
Dictionary<int, MethodInfo> methods = new Dictionary<int, MethodInfo> ();
|
||||
readonly List<SourceFile> sources = new List<SourceFile> ();
|
||||
Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>();
|
||||
readonly List<SourceFile> sources = new List<SourceFile>();
|
||||
|
||||
public AssemblyInfo (byte[] assembly, byte[] pdb)
|
||||
{
|
||||
|
|
@ -274,16 +301,35 @@ namespace WsProxy {
|
|||
this.id = ++next_id;
|
||||
}
|
||||
|
||||
ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/);
|
||||
if (pdb != null) {
|
||||
rp.ReadSymbols = true;
|
||||
rp.SymbolReaderProvider = new PortablePdbReaderProvider ();
|
||||
rp.SymbolStream = new MemoryStream (pdb);
|
||||
try {
|
||||
ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/);
|
||||
if (pdb != null) {
|
||||
rp.ReadSymbols = true;
|
||||
rp.SymbolReaderProvider = new PortablePdbReaderProvider ();
|
||||
rp.SymbolStream = new MemoryStream (pdb);
|
||||
}
|
||||
|
||||
rp.ReadingMode = ReadingMode.Immediate;
|
||||
rp.InMemory = true;
|
||||
|
||||
this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
|
||||
} catch (BadImageFormatException ex) {
|
||||
Console.WriteLine ($"Failed to read assembly as portable PDB: {ex.Message}");
|
||||
}
|
||||
|
||||
rp.InMemory = true;
|
||||
if (this.image == null) {
|
||||
ReaderParameters rp = new ReaderParameters (/*ReadingMode.Immediate*/);
|
||||
if (pdb != null) {
|
||||
rp.ReadSymbols = true;
|
||||
rp.SymbolReaderProvider = new NativePdbReaderProvider ();
|
||||
rp.SymbolStream = new MemoryStream (pdb);
|
||||
}
|
||||
|
||||
this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
|
||||
rp.ReadingMode = ReadingMode.Immediate;
|
||||
rp.InMemory = true;
|
||||
|
||||
this.image = ModuleDefinition.ReadModule (new MemoryStream (assembly), rp);
|
||||
}
|
||||
|
||||
Populate ();
|
||||
}
|
||||
|
|
@ -294,6 +340,8 @@ namespace WsProxy {
|
|||
|
||||
void Populate ()
|
||||
{
|
||||
ProcessSourceLink();
|
||||
|
||||
var d2s = new Dictionary<Document, SourceFile> ();
|
||||
|
||||
Func<Document, SourceFile> get_src = (doc) => {
|
||||
|
|
@ -301,33 +349,88 @@ namespace WsProxy {
|
|||
return null;
|
||||
if (d2s.ContainsKey (doc))
|
||||
return d2s [doc];
|
||||
var src = new SourceFile (this, sources.Count, doc);
|
||||
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)) {
|
||||
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) {
|
||||
if (first_doc == null && !sp.Document.Url.EndsWith (".g.cs")) {
|
||||
first_doc = sp.Document;
|
||||
} 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}");
|
||||
}
|
||||
// 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}");
|
||||
//}
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessSourceLink ()
|
||||
{
|
||||
var sourceLinkDebugInfo = image.CustomDebugInformations.FirstOrDefault (i => i.Kind == CustomDebugInformationKind.SourceLink);
|
||||
|
||||
if (sourceLinkDebugInfo != null) {
|
||||
var sourceLinkContent = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content;
|
||||
|
||||
if (sourceLinkContent != null) {
|
||||
var jObject = JObject.Parse (sourceLinkContent) ["documents"];
|
||||
sourceLinkMappings = JsonConvert.DeserializeObject<Dictionary<string, string>> (jObject.ToString ());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Uri GetSourceLinkUrl (string document)
|
||||
{
|
||||
if (sourceLinkMappings.TryGetValue (document, out string url)) {
|
||||
return new Uri (url);
|
||||
}
|
||||
|
||||
foreach (var sourceLinkDocument in sourceLinkMappings) {
|
||||
string key = sourceLinkDocument.Key;
|
||||
|
||||
if (Path.GetFileName (key) != "*") {
|
||||
continue;
|
||||
}
|
||||
|
||||
var keyTrim = key.TrimEnd ('*');
|
||||
|
||||
if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase)) {
|
||||
var docUrlPart = document.Replace (keyTrim, "");
|
||||
return new Uri (sourceLinkDocument.Value.TrimEnd ('*') + docUrlPart);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private 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);
|
||||
if (rel.Contains (Path.DirectorySeparatorChar.ToString ()) == false) {
|
||||
rel = $".{ Path.DirectorySeparatorChar }{ rel }";
|
||||
}
|
||||
return rel;
|
||||
}
|
||||
|
||||
public IEnumerable<SourceFile> Sources {
|
||||
get { return this.sources; }
|
||||
}
|
||||
|
|
@ -342,9 +445,9 @@ namespace WsProxy {
|
|||
|
||||
public MethodInfo GetMethodByToken (int token)
|
||||
{
|
||||
return methods [token];
|
||||
methods.TryGetValue (token, out var value);
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class SourceFile {
|
||||
|
|
@ -353,23 +456,36 @@ namespace WsProxy {
|
|||
int id;
|
||||
Document doc;
|
||||
|
||||
internal SourceFile (AssemblyInfo assembly, int id, Document doc)
|
||||
internal SourceFile (AssemblyInfo assembly, int id, Document doc, Uri sourceLinkUri)
|
||||
{
|
||||
this.methods = new HashSet<MethodInfo> ();
|
||||
this.SourceLinkUri = sourceLinkUri;
|
||||
this.assembly = assembly;
|
||||
this.id = id;
|
||||
this.doc = doc;
|
||||
this.DebuggerFileName = doc.Url.Replace ("\\", "/").Replace (":", "");
|
||||
|
||||
this.SourceUri = new Uri ((Path.IsPathRooted (doc.Url) ? "file://" : "") + doc.Url, UriKind.RelativeOrAbsolute);
|
||||
if (SourceUri.IsFile && File.Exists (SourceUri.LocalPath)) {
|
||||
this.Url = this.SourceUri.ToString ();
|
||||
} else {
|
||||
this.Url = DotNetUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal void AddMethod (MethodInfo mi)
|
||||
{
|
||||
this.methods.Add (mi);
|
||||
}
|
||||
public string FileName => Path.GetFileName (doc.Url);
|
||||
public string Url => $"dotnet://{assembly.Name}/{FileName}";
|
||||
public string DebuggerFileName { get; }
|
||||
public string Url { get; }
|
||||
public string AssemblyName => assembly.Name;
|
||||
public string DotNetUrl => $"dotnet://{assembly.Name}/{DebuggerFileName}";
|
||||
public string DocHashCode => "abcdee" + id;
|
||||
public SourceId SourceId => new SourceId (assembly.Id, this.id);
|
||||
public string LocalPath => doc.Url;
|
||||
public Uri SourceLinkUri { get; }
|
||||
public Uri SourceUri { get; }
|
||||
|
||||
public IEnumerable<MethodInfo> Methods => this.methods;
|
||||
}
|
||||
|
|
@ -377,17 +493,18 @@ namespace WsProxy {
|
|||
internal class DebugStore {
|
||||
List<AssemblyInfo> assemblies = new List<AssemblyInfo> ();
|
||||
|
||||
public DebugStore (string[] loaded_files)
|
||||
public DebugStore (string [] loaded_files)
|
||||
{
|
||||
bool MatchPdb (string asm, string pdb) {
|
||||
bool MatchPdb (string asm, string pdb)
|
||||
{
|
||||
return Path.ChangeExtension (asm, "pdb") == pdb;
|
||||
}
|
||||
|
||||
var asm_files = new List<string> ();
|
||||
var pdb_files = new List<string> ();
|
||||
foreach (var f in loaded_files) {
|
||||
var file_name = f.ToLower ();
|
||||
if (file_name.EndsWith (".pdb", StringComparison.Ordinal))
|
||||
var file_name = f;
|
||||
if (file_name.EndsWith (".pdb", StringComparison.OrdinalIgnoreCase))
|
||||
pdb_files.Add (file_name);
|
||||
else
|
||||
asm_files.Add (file_name);
|
||||
|
|
@ -395,14 +512,18 @@ namespace WsProxy {
|
|||
|
||||
//FIXME make this parallel
|
||||
foreach (var p in asm_files) {
|
||||
var pdb = pdb_files.FirstOrDefault (n => MatchPdb (p, n));
|
||||
HttpClient h = new HttpClient ();
|
||||
var assembly_bytes = h.GetByteArrayAsync (p).Result;
|
||||
byte[] pdb_bytes = null;
|
||||
if (pdb != null)
|
||||
pdb_bytes = h.GetByteArrayAsync (pdb).Result;
|
||||
try {
|
||||
var pdb = pdb_files.FirstOrDefault (n => MatchPdb (p, n));
|
||||
HttpClient h = new HttpClient ();
|
||||
var assembly_bytes = h.GetByteArrayAsync (p).Result;
|
||||
byte [] pdb_bytes = null;
|
||||
if (pdb != null)
|
||||
pdb_bytes = h.GetByteArrayAsync (pdb).Result;
|
||||
|
||||
this.assemblies.Add (new AssemblyInfo (assembly_bytes, pdb_bytes));
|
||||
this.assemblies.Add (new AssemblyInfo (assembly_bytes, pdb_bytes));
|
||||
} catch (Exception e) {
|
||||
Console.WriteLine ($"Failed to read {p} ({e.Message})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -412,7 +533,6 @@ namespace WsProxy {
|
|||
foreach (var s in a.Sources)
|
||||
yield return s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SourceFile GetFileById (SourceId id)
|
||||
|
|
@ -426,26 +546,23 @@ namespace WsProxy {
|
|||
}
|
||||
|
||||
/*
|
||||
Matching logic here is hilarious and it goes like this:
|
||||
We inject one line at the top of all sources to make it easy to identify them [1].
|
||||
V8 uses zero based indexing for both line and column.
|
||||
PPDBs uses one based indexing for both line and column.
|
||||
Which means that:
|
||||
- for lines, values are already adjusted (v8 numbers come +1 due to the injected line)
|
||||
- for columns, we need to +1 the v8 numbers
|
||||
[1] It's so we can deal with the Runtime.compileScript ide cmd
|
||||
*/
|
||||
static bool Match (SequencePoint sp, SourceLocation start, SourceLocation end)
|
||||
{
|
||||
if (start.Line > sp.StartLine)
|
||||
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 + 1) > sp.StartColumn && start.Line == sp.StartLine)
|
||||
if (start.Column > spStart.Column && start.Line == sp.StartLine)
|
||||
return false;
|
||||
|
||||
if (end.Line < sp.EndLine)
|
||||
if (end.Line < spEnd.Line)
|
||||
return false;
|
||||
|
||||
if ((end.Column + 1) < sp.EndColumn && end.Line == sp.EndLine)
|
||||
if (end.Column < spEnd.Column && end.Line == spEnd.Line)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
|
@ -477,28 +594,24 @@ namespace WsProxy {
|
|||
}
|
||||
|
||||
/*
|
||||
Matching logic here is hilarious and it goes like this:
|
||||
We inject one line at the top of all sources to make it easy to identify them [1].
|
||||
V8 uses zero based indexing for both line and column.
|
||||
PPDBs uses one based indexing for both line and column.
|
||||
Which means that:
|
||||
- for lines, values are already adjusted (v8 numbers come + 1 due to the injected line)
|
||||
- for columns, we need to +1 the v8 numbers
|
||||
[1] It's so we can deal with the Runtime.compileScript ide cmd
|
||||
*/
|
||||
static bool Match (SequencePoint sp, int line, int column)
|
||||
{
|
||||
if (sp.StartLine > line || sp.EndLine < line)
|
||||
var bp = (line: line + 1, column: column + 1);
|
||||
|
||||
if (sp.StartLine > bp.line || sp.EndLine < bp.line)
|
||||
return false;
|
||||
|
||||
//Chrome sends a zero column even if getPossibleBreakpoints say something else
|
||||
if (column == 0)
|
||||
return true;
|
||||
|
||||
if (sp.StartColumn > (column + 1) && sp.StartLine == line)
|
||||
if (sp.StartColumn > bp.column && sp.StartLine == bp.line)
|
||||
return false;
|
||||
|
||||
if (sp.EndColumn < (column + 1) && sp.EndLine == line)
|
||||
if (sp.EndColumn < bp.column && sp.EndLine == bp.line)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
|
@ -506,8 +619,11 @@ namespace WsProxy {
|
|||
|
||||
public SourceLocation FindBestBreakpoint (BreakPointRequest req)
|
||||
{
|
||||
var asm = this.assemblies.FirstOrDefault (a => a.Name == req.Assembly);
|
||||
var src = asm.Sources.FirstOrDefault (s => s.FileName == req.File);
|
||||
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));
|
||||
|
||||
if (src == null)
|
||||
return null;
|
||||
|
||||
foreach (var m in src.Methods) {
|
||||
foreach (var sp in m.methodDef.DebugInformation.SequencePoints) {
|
||||
|
|
@ -521,8 +637,15 @@ namespace WsProxy {
|
|||
}
|
||||
|
||||
public string ToUrl (SourceLocation location)
|
||||
=> location != null ? GetFileById (location.Id).Url : "";
|
||||
|
||||
public SourceFile GetFileByUrlRegex (string urlRegex)
|
||||
{
|
||||
return GetFileById (location.Id).Url;
|
||||
var regex = new Regex (urlRegex);
|
||||
return AllSources ().FirstOrDefault (file => regex.IsMatch (file.Url.ToString()));
|
||||
}
|
||||
|
||||
public SourceFile GetFileByUrl (string url)
|
||||
=> AllSources ().FirstOrDefault (file => file.Url.ToString() == url);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Threading;
|
|||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
|
||||
namespace WsProxy {
|
||||
|
||||
|
|
@ -20,6 +21,8 @@ namespace WsProxy {
|
|||
public const string REMOVE_BREAK_POINT = "MONO.mono_wasm_remove_breakpoint({0})";
|
||||
public const string GET_LOADED_FILES = "MONO.mono_wasm_get_loaded_files()";
|
||||
public const string CLEAR_ALL_BREAKPOINTS = "MONO.mono_wasm_clear_all_breakpoints()";
|
||||
public const string GET_OBJECT_PROPERTIES = "MONO.mono_wasm_get_object_properties({0})";
|
||||
public const string GET_ARRAY_VALUES = "MONO.mono_wasm_get_array_values({0})";
|
||||
}
|
||||
|
||||
internal enum MonoErrorCodes {
|
||||
|
|
@ -128,7 +131,7 @@ namespace WsProxy {
|
|||
case "Debugger.getScriptSource": {
|
||||
var script_id = args? ["scriptId"]?.Value<string> ();
|
||||
if (script_id.StartsWith ("dotnet://", StringComparison.InvariantCultureIgnoreCase)) {
|
||||
OnGetScriptSource (id, script_id, token);
|
||||
await OnGetScriptSource (id, script_id, token);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +157,7 @@ namespace WsProxy {
|
|||
|
||||
case "Debugger.setBreakpointByUrl": {
|
||||
Info ($"BP req {args}");
|
||||
var bp_req = BreakPointRequest.Parse (args);
|
||||
var bp_req = BreakPointRequest.Parse (args, store);
|
||||
if (bp_req != null) {
|
||||
await SetBreakPoint (id, bp_req, token);
|
||||
return true;
|
||||
|
|
@ -200,7 +203,14 @@ namespace WsProxy {
|
|||
await GetScopeProperties (id, int.Parse (objId.Substring ("dotnet:scope:".Length)), token);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (objId.StartsWith("dotnet:", StringComparison.InvariantCulture))
|
||||
{
|
||||
if (objId.StartsWith("dotnet:object:", StringComparison.InvariantCulture))
|
||||
await GetDetails(id, int.Parse(objId.Substring("dotnet:object:".Length)), token, MonoCommands.GET_OBJECT_PROPERTIES);
|
||||
if (objId.StartsWith("dotnet:array:", StringComparison.InvariantCulture))
|
||||
await GetDetails(id, int.Parse(objId.Substring("dotnet:array:".Length)), token, MonoCommands.GET_ARRAY_VALUES);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -213,6 +223,7 @@ namespace WsProxy {
|
|||
Info ("RUNTIME READY, PARTY TIME");
|
||||
await RuntimeReady (token);
|
||||
await SendCommand ("Debugger.resume", new JObject (), token);
|
||||
SendEvent ("Mono.runtimeReady", new JObject (), token);
|
||||
}
|
||||
|
||||
async Task OnBreakPointHit (JObject args, CancellationToken token)
|
||||
|
|
@ -257,9 +268,9 @@ namespace WsProxy {
|
|||
var src = bp == null ? null : store.GetFileById (bp.Location.Id);
|
||||
|
||||
var callFrames = new List<JObject> ();
|
||||
foreach (var f in orig_callframes) {
|
||||
var function_name = f ["functionName"]?.Value<string> ();
|
||||
var url = f ["url"]?.Value<string> ();
|
||||
foreach (var frame in orig_callframes) {
|
||||
var function_name = frame ["functionName"]?.Value<string> ();
|
||||
var url = frame ["url"]?.Value<string> ();
|
||||
if ("mono_wasm_fire_bp" == function_name || "_mono_wasm_fire_bp" == function_name) {
|
||||
var frames = new List<Frame> ();
|
||||
int frame_id = 0;
|
||||
|
|
@ -271,14 +282,19 @@ namespace WsProxy {
|
|||
|
||||
var asm = store.GetAssemblyByName (assembly_name);
|
||||
var method = asm.GetMethodByToken (method_token);
|
||||
var location = method.GetLocationByIl (il_pos);
|
||||
|
||||
if (method == null) {
|
||||
Info ($"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var location = method?.GetLocationByIl (il_pos);
|
||||
|
||||
// When hitting a breakpoint on the "IncrementCount" method in the standard
|
||||
// Blazor project template, one of the stack frames is inside mscorlib.dll
|
||||
// and we get location==null for it. It will trigger a NullReferenceException
|
||||
// if we don't skip over that stack frame.
|
||||
if (location == null)
|
||||
{
|
||||
if (location == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -288,7 +304,7 @@ namespace WsProxy {
|
|||
|
||||
callFrames.Add (JObject.FromObject (new {
|
||||
functionName = method.Name,
|
||||
|
||||
callFrameId = $"dotnet:scope:{frame_id}",
|
||||
functionLocation = method.StartLocation.ToJObject (),
|
||||
|
||||
location = location.ToJObject (),
|
||||
|
|
@ -300,25 +316,24 @@ namespace WsProxy {
|
|||
type = "local",
|
||||
@object = new {
|
||||
@type = "object",
|
||||
className = "Object",
|
||||
className = "Object",
|
||||
description = "Object",
|
||||
objectId = $"dotnet:scope:{frame_id}"
|
||||
objectId = $"dotnet:scope:{frame_id}",
|
||||
},
|
||||
name = method.Name,
|
||||
startLocation = method.StartLocation.ToJObject (),
|
||||
endLocation = method.EndLocation.ToJObject (),
|
||||
}
|
||||
},
|
||||
|
||||
@this = new {
|
||||
}
|
||||
}},
|
||||
@this = new { }
|
||||
}));
|
||||
|
||||
++frame_id;
|
||||
this.current_callstack = frames;
|
||||
|
||||
}
|
||||
} else if (!url.StartsWith ("wasm://wasm/", StringComparison.InvariantCulture)) {
|
||||
callFrames.Add (f);
|
||||
} else if (!(function_name.StartsWith ("wasm-function", StringComparison.InvariantCulture)
|
||||
|| url.StartsWith ("wasm://wasm/", StringComparison.InvariantCulture))) {
|
||||
callFrames.Add (frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -393,6 +408,57 @@ namespace WsProxy {
|
|||
await SendCommand ("Debugger.resume", new JObject (), token);
|
||||
}
|
||||
|
||||
async Task GetDetails(int msg_id, int object_id, CancellationToken token, string command)
|
||||
{
|
||||
var o = JObject.FromObject(new
|
||||
{
|
||||
expression = string.Format(command, object_id),
|
||||
objectGroup = "mono_debugger",
|
||||
includeCommandLineAPI = false,
|
||||
silent = false,
|
||||
returnByValue = true,
|
||||
});
|
||||
|
||||
var res = await SendCommand("Runtime.evaluate", o, token);
|
||||
|
||||
//if we fail we just buble that to the IDE (and let it panic over it)
|
||||
if (res.IsErr)
|
||||
{
|
||||
SendResponse(msg_id, res, token);
|
||||
return;
|
||||
}
|
||||
|
||||
var values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray();
|
||||
|
||||
var var_list = new List<JObject>();
|
||||
|
||||
// Trying to inspect the stack frame for DotNetDispatcher::InvokeSynchronously
|
||||
// results in a "Memory access out of bounds", causing 'values' to be null,
|
||||
// so skip returning variable values in that case.
|
||||
for (int i = 0; i < values.Length; i+=2)
|
||||
{
|
||||
string fieldName = (string)values[i]["name"];
|
||||
if (fieldName.Contains("k__BackingField")){
|
||||
fieldName = fieldName.Replace("k__BackingField", "");
|
||||
fieldName = fieldName.Replace("<", "");
|
||||
fieldName = fieldName.Replace(">", "");
|
||||
}
|
||||
var_list.Add(JObject.FromObject(new
|
||||
{
|
||||
name = fieldName,
|
||||
value = values[i+1]["value"]
|
||||
}));
|
||||
|
||||
}
|
||||
o = JObject.FromObject(new
|
||||
{
|
||||
result = var_list
|
||||
});
|
||||
|
||||
SendResponse(msg_id, Result.Ok(o), token);
|
||||
}
|
||||
|
||||
|
||||
async Task GetScopeProperties (int msg_id, int scope_id, CancellationToken token)
|
||||
{
|
||||
var scope = this.current_callstack.FirstOrDefault (s => s.Id == scope_id);
|
||||
|
|
@ -425,6 +491,10 @@ namespace WsProxy {
|
|||
// results in a "Memory access out of bounds", causing 'values' to be null,
|
||||
// so skip returning variable values in that case.
|
||||
for (int i = 0; values != null && i < vars.Length; ++i) {
|
||||
var value = values [i] ["value"];
|
||||
if (((string)value ["description"]) == null)
|
||||
value ["description"] = value ["value"]?.ToString();
|
||||
|
||||
var_list.Add (JObject.FromObject (new {
|
||||
name = vars [i].Name,
|
||||
value = values [i] ["value"]
|
||||
|
|
@ -485,7 +555,8 @@ namespace WsProxy {
|
|||
url = s.Url,
|
||||
executionContextId = this.ctx_id,
|
||||
hash = s.DocHashCode,
|
||||
executionContextAuxData = this.aux_ctx_data
|
||||
executionContextAuxData = this.aux_ctx_data,
|
||||
dotNetUrl = s.DotNetUrl
|
||||
});
|
||||
//Debug ($"\tsending {s.Url}");
|
||||
SendEvent ("Debugger.scriptParsed", ok, token);
|
||||
|
|
@ -640,23 +711,51 @@ namespace WsProxy {
|
|||
|
||||
}
|
||||
|
||||
void OnGetScriptSource (int msg_id, string script_id, CancellationToken token)
|
||||
async Task OnGetScriptSource (int msg_id, string script_id, CancellationToken token)
|
||||
{
|
||||
var id = new SourceId (script_id);
|
||||
var src_file = store.GetFileById (id);
|
||||
|
||||
var res = new StringWriter ();
|
||||
res.WriteLine ($"//dotnet:{id}");
|
||||
//res.WriteLine ($"//{id}");
|
||||
|
||||
using (var f = new StreamReader (File.Open (src_file.LocalPath, FileMode.Open))) {
|
||||
res.Write (f.ReadToEnd ());
|
||||
try {
|
||||
var uri = new Uri (src_file.Url);
|
||||
if (uri.IsFile && File.Exists(uri.LocalPath)) {
|
||||
using (var f = new StreamReader (File.Open (src_file.SourceUri.LocalPath, FileMode.Open))) {
|
||||
await res.WriteAsync (await f.ReadToEndAsync ());
|
||||
}
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = res.ToString ()
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
} else if(src_file.SourceLinkUri != null) {
|
||||
var doc = await new WebClient ().DownloadStringTaskAsync (src_file.SourceLinkUri);
|
||||
await res.WriteAsync (doc);
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = res.ToString ()
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
} else {
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = $"// Unable to find document {src_file.SourceUri}"
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = $"// Unable to read document ({e.Message})\n" +
|
||||
$"Local path: {src_file?.SourceUri}\n" +
|
||||
$"SourceLink path: {src_file?.SourceLinkUri}\n"
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
}
|
||||
|
||||
var o = JObject.FromObject (new {
|
||||
scriptSource = res.ToString ()
|
||||
});
|
||||
|
||||
SendResponse (msg_id, Result.Ok (o), token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ namespace WsProxy {
|
|||
|
||||
void Send (WebSocket to, JObject o, CancellationToken token)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes (o.ToString ());
|
||||
var bytes = Encoding.UTF8.GetBytes (o.ToString ());
|
||||
|
||||
var queue = GetQueueForSocket (to);
|
||||
var task = queue.Send (bytes, token);
|
||||
|
|
@ -256,7 +256,7 @@ namespace WsProxy {
|
|||
}
|
||||
|
||||
// , HttpContext context)
|
||||
public async Task Run (Uri browserUri, WebSocket ideSocket)
|
||||
public async Task Run (Uri browserUri, WebSocket ideSocket)
|
||||
{
|
||||
Debug ("wsproxy start");
|
||||
using (this.ide = ideSocket) {
|
||||
|
|
@ -276,7 +276,7 @@ namespace WsProxy {
|
|||
|
||||
try {
|
||||
while (!x.IsCancellationRequested) {
|
||||
var task = await Task.WhenAny (pending_ops);
|
||||
var task = await Task.WhenAny (pending_ops.ToArray ());
|
||||
//Console.WriteLine ("pump {0} {1}", task, pending_ops.IndexOf (task));
|
||||
if (task == pending_ops [0]) {
|
||||
var msg = ((Task<string>)task).Result;
|
||||
|
|
|
|||
Loading…
Reference in New Issue