diff --git a/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs b/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs index b120340362ee2..faaaa627ec1cd 100644 --- a/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs +++ b/src/mono/wasm/debugger/BrowserDebugHost/Startup.cs @@ -17,6 +17,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; namespace Microsoft.WebAssembly.Diagnostics { @@ -154,7 +155,10 @@ async Task ConnectProxy(HttpContext context) { using var loggerFactory = LoggerFactory.Create( builder => builder.AddConsole().AddFilter(null, LogLevel.Information)); - var proxy = new DebuggerProxy(loggerFactory); + + context.Request.Query.TryGetValue("urlSymbolServer", out StringValues urlSymbolServerList); + var proxy = new DebuggerProxy(loggerFactory, urlSymbolServerList.ToList()); + var ideSocket = await context.WebSockets.AcceptWebSocketAsync(); await proxy.Run(endpoint, ideSocket); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index 65a9fd28f1745..2f836e881f8bd 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -389,6 +389,7 @@ class AssemblyInfo Dictionary typesByName = new Dictionary(); readonly List sources = new List(); internal string Url { get; } + public bool TriedToLoadSymbolsOnDemand { get; set; } public AssemblyInfo(IAssemblyResolver resolver, string url, byte[] assembly, byte[] pdb) { @@ -446,7 +447,23 @@ public AssemblyInfo(ILogger logger) this.logger = logger; } - void Populate() + public ModuleDefinition Image => image; + + public void ClearDebugInfo() + { + foreach (var type in image.GetTypes()) + { + var typeInfo = new TypeInfo(this, type); + typesByName[type.FullName] = typeInfo; + + foreach (var method in type.Methods) + { + method.DebugInformation = null; + } + } + } + + public void Populate() { ProcessSourceLink(); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs index 08fe836200dcd..197f4dd6cea5d 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebuggerProxy.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Net.WebSockets; using System.Threading.Tasks; using Microsoft.Extensions.Logging; @@ -16,9 +17,9 @@ public class DebuggerProxy { private readonly MonoProxy proxy; - public DebuggerProxy(ILoggerFactory loggerFactory) + public DebuggerProxy(ILoggerFactory loggerFactory, IList urlSymbolServerList) { - proxy = new MonoProxy(loggerFactory); + proxy = new MonoProxy(loggerFactory, urlSymbolServerList); } public Task Run(Uri browserUri, WebSocket ideSocket) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index 89b6ac1e60ec2..9b2933d6c1ec9 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -12,16 +12,26 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Mono.Cecil.Cil; +using Mono.Cecil.Pdb; +using Mono.Cecil; +using System.Net.Http; namespace Microsoft.WebAssembly.Diagnostics { internal class MonoProxy : DevToolsProxy { + IList urlSymbolServerList; + static HttpClient client = new HttpClient(); HashSet sessions = new HashSet(); Dictionary contexts = new Dictionary(); - public MonoProxy(ILoggerFactory loggerFactory, bool hideWebDriver = true) : base(loggerFactory) { this.hideWebDriver = hideWebDriver; } + public MonoProxy(ILoggerFactory loggerFactory, IList urlSymbolServerList, bool hideWebDriver = true) : base(loggerFactory) + { + this.hideWebDriver = hideWebDriver; + this.urlSymbolServerList = urlSymbolServerList ?? new List(); + } readonly bool hideWebDriver; @@ -360,6 +370,15 @@ protected override async Task AcceptCommand(MessageId id, string method, J } // Protocol extensions + case "DotnetDebugger.addSymbolServerUrl": + { + string url = args["url"]?.Value(); + if (!String.IsNullOrEmpty(url) && !urlSymbolServerList.Contains(url)) + urlSymbolServerList.Add(url); + SendResponse(id, Result.OkFromObject(new { }), token); + return true; + } + case "DotnetDebugger.getMethodLocation": { Console.WriteLine("set-breakpoint-by-method: " + id + " " + args); @@ -562,6 +581,19 @@ async Task OnPause(SessionId sessionId, JObject args, CancellationToken to var method = asm.GetMethodByToken(method_token); + if (method == null && !asm.Image.HasSymbols) + { + try + { + method = await LoadSymbolsOnDemand(asm, method_token, sessionId, token); + } + catch (Exception e) + { + Log("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name} exception: {e.ToString()}"); + continue; + } + } + if (method == null) { Log("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}"); @@ -639,6 +671,81 @@ async Task OnPause(SessionId sessionId, JObject args, CancellationToken to return true; } + async Task LoadSymbolsOnDemand(AssemblyInfo asm, uint method_token, SessionId sessionId, CancellationToken token) + { + var context = GetContext(sessionId); + if (asm.TriedToLoadSymbolsOnDemand) + return null; + asm.TriedToLoadSymbolsOnDemand = true; + ImageDebugHeader header = asm.Image.GetDebugHeader(); + + for (var i = 0; i < header.Entries.Length; i++) + { + var entry = header.Entries[i]; + if (entry.Directory.Type != ImageDebugType.CodeView) + { + continue; + } + + var data = entry.Data; + + if (data.Length < 24) + return null; + + var pdbSignature = (data[0] + | (data[1] << 8) + | (data[2] << 16) + | (data[3] << 24)); + + if (pdbSignature != 0x53445352) // "SDSR" mono/metadata/debug-mono-ppdb.c#L101 + return null; + + var buffer = new byte[16]; + Buffer.BlockCopy(data, 4, buffer, 0, 16); + + var pdbAge = (data[20] + | (data[21] << 8) + | (data[22] << 16) + | (data[23] << 24)); + + var pdbGuid = new Guid(buffer); + var buffer2 = new byte[(data.Length - 24) - 1]; + Buffer.BlockCopy(data, 24, buffer2, 0, (data.Length - 24) - 1); + var pdbName = System.Text.Encoding.UTF8.GetString(buffer2, 0, buffer2.Length); + pdbName = Path.GetFileName(pdbName); + + foreach (var urlSymbolServer in urlSymbolServerList) + { + var downloadURL = $"{urlSymbolServer}/{pdbName}/{pdbGuid.ToString("N").ToUpper() + pdbAge}/{pdbName}"; + + try + { + using HttpResponseMessage response = await client.GetAsync(downloadURL); + using Stream streamToReadFrom = await response.Content.ReadAsStreamAsync(); + var portablePdbReaderProvider = new PdbReaderProvider(); + var symbolReader = portablePdbReaderProvider.GetSymbolReader(asm.Image, streamToReadFrom); + asm.ClearDebugInfo(); //workaround while cecil PR #686 is not merged + asm.Image.ReadSymbols(symbolReader); + asm.Populate(); + foreach (var source in asm.Sources) + { + var scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData)); + SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token); + } + return asm.GetMethodByToken(method_token); + } + catch (Exception e) + { + Log("info", $"Unable to load symbols on demand exception: {e.ToString()} url:{downloadURL} assembly: {asm.Name}"); + } + } + break; + } + + Log("info", "Unable to load symbols on demand assembly: {asm.Name}"); + return null; + } + async Task OnDefaultContext(SessionId sessionId, ExecutionContext context, CancellationToken token) { Log("verbose", "Default context created, clearing state and sending events"); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs index 265f1a3581646..f37b84aee0cac 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs @@ -122,7 +122,7 @@ public async Task LaunchAndServe(ProcessStartInfo psi, HttpContext context, Func using var loggerFactory = LoggerFactory.Create( builder => builder.AddConsole().AddFilter(null, LogLevel.Information)); - var proxy = new DebuggerProxy(loggerFactory); + var proxy = new DebuggerProxy(loggerFactory, null); var browserUri = new Uri(con_str); var ideSocket = await context.WebSockets.AcceptWebSocketAsync();