diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 207b98137132d7..4b24a10ca844cd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -73,7 +73,6 @@ /src/mono/browser @lewing @pavelsavara /src/mono/wasi @lewing @pavelsavara /src/mono/wasm @lewing @pavelsavara -/src/mono/browser/debugger @thaystg @ilonatommy /src/mono/wasm/build @maraf @akoeplinger /src/mono/wasi/build @maraf @akoeplinger /src/mono/browser/build @maraf @akoeplinger @@ -83,7 +82,6 @@ /src/mono/nuget/*WebAssembly*/ @lewing @akoeplinger /src/mono/nuget/*MonoTargets*/ @lewing @akoeplinger -/src/mono/nuget/*BrowserDebugHost*/ @lewing @akoeplinger /src/mono/nuget/*Workload.Mono.Toolchain*/ @lewing @akoeplinger /src/mono/nuget/*MonoAOTCompiler*/ @lewing @akoeplinger diff --git a/docs/workflow/debugging/mono/wasm-debugging.md b/docs/workflow/debugging/mono/wasm-debugging.md index f6d29a2c176c7f..a8fa59756a3da5 100644 --- a/docs/workflow/debugging/mono/wasm-debugging.md +++ b/docs/workflow/debugging/mono/wasm-debugging.md @@ -2,147 +2,14 @@ This document provides debugging instructions for WebAssembly. -## Debug with VS Code -To debug WebAssembly with Visual Studio Code: +## Debug native code with Chrome DevTools (DWARF) -### 1. Configuration - -Add the appropriate configuration to your `.vscode/launch.json` depending on your debugging scenario: - -**For WebAssembly applications, library tests, and general debugging:** -```json -{ - "name": "WASM Attach", - "request": "attach", - "type": "chrome", - "address": "localhost", - "port": -} -``` - -**For WASI applications:** -```json -{ - "name": "WASI Attach", - "type": "mono", - "request": "attach", - "address": "localhost", - "port": -} -``` - -Replace `` with the proxy port shown in your application's output. - -### 2. Setup Steps - -1. **Set initial breakpoint**: Place a breakpoint in `WasmTestRunner.cs` or your main entry point to prevent execution before you're ready -2. **Run the configuration**: Launch the VS Code debug configuration -3. **Set additional breakpoints**: Once stopped, set breakpoints in the code you want to debug -4. **Continue execution**: Click Resume or F5 to continue - -## Debug with Chrome DevTools - -### 1. Basic Setup - -1. **Open Chrome Inspector**: Navigate to `chrome://inspect/#devices` in a new Chrome tab -2. **Configure proxy**: Click "Configure": - -![image](https://user-images.githubusercontent.com/32700855/201867874-7f707eb1-e859-441c-8205-abb70a7a0d0b.png) - -and paste the address of proxy that was provided in the program output: - -![image](https://user-images.githubusercontent.com/32700855/201862487-df76a06c-b24d-41a0-bf06-6959bba59a58.png) - -3. **Select target**: New remote targets will be displayed, select the address you opened in the other tab by clicking `Inspect`: - -![image](https://user-images.githubusercontent.com/32700855/201863048-6a4fe20b-a215-435d-b594-47750fcb2872.png) - -### 2. Using DevTools - -1. **Sources tab**: A new window with Chrome DevTools will be opened. In the tab `sources` you should look for `file://` directory to browse source files -2. **Wait for files to load**: It may take time for all source files to appear. You cannot set breakpoints in Chrome DevTools before the files get loaded -3. **Set breakpoints**: Click on line numbers to set breakpoints -4. **Initial run strategy**: Consider using the first run to set an initial breakpoint in `WasmTestRunner.cs`, then restart the application. DevTools will stop on the previously set breakpoint and you will have time to set breakpoints in the libs you want to debug and click Resume - -### 3. For Native/C Code Debugging +Native C/C++ code compiled to WebAssembly can be debugged directly in Chrome DevTools using DWARF debug info: 1. **Install DWARF extension**: Install the "C/C++ DevTools Support (DWARF)" Chrome extension 2. **Enable symbols**: Build with `WasmNativeDebugSymbols=true` and `WasmNativeStrip=false` -3. **Debug native code**: Step through C/C++ code, set breakpoints, and inspect WebAssembly linear memory - -## Starting Chrome with Remote Debugging - -To enable remote debugging for WebAssembly applications: - -```bash -# Close all Chrome instances first -chrome --remote-debugging-port=9222 -``` - -Replace `` with the URL shown in your application's output. - -## Common Debugging Workflow - -### For Library Tests - -For building libraries or testing them without debugging, read: -- [Building libraries](https://github.com/dotnet/runtime/blob/main/docs/workflow/building/libraries/README.md) -- [Testing libraries](https://github.com/dotnet/runtime/blob/main/docs/workflow/testing/libraries/testing.md) - -**Run the selected library tests with debugger support:** - -Run the selected library tests in the browser, e.g. `System.Collections.Concurrent.Tests` this way: -```bash -dotnet run -r browser-wasm -c Debug --project src/libraries/System.Collections/tests/System.Collections.Tests.csproj --debug --host browser -p:DebuggerSupport=true -``` - -Where we choose `browser-wasm` as the runtime and by setting `DebuggerSupport=true` we ensure that tests won't start execution before the debugger will get attached. In the output, among others you should see: - -``` -Debug proxy for chrome now listening on http://127.0.0.1:58346/. And expecting chrome at http://localhost:9222/ -App url: http://127.0.0.1:9000/index.html?arg=--debug&arg=--run&arg=WasmTestRunner.dll&arg=System.Collections.Concurrent.Tests.dll -``` - -The proxy's url/port will be used in the next step. - -You may need to close all Chrome instances. Then, start the browser with debugging mode enabled: - -```bash -chrome --remote-debugging-port=9222 -``` - -Now you can choose an IDE to start debugging. Remember that the tests wait only till the debugger gets attached. Once it does, they start running. You may want to set breakpoints first, before attaching the debugger, e.g. setting one in `src\libraries\Common\tests\WasmTestRunner\WasmTestRunner.cs` on the first line of `Main()` will prevent any test to be run before you get prepared. - -Use either Chrome DevTools or VS Code as described above to attach the debugger - -### For WASI Applications - -1. **Build with debug**: - ```bash - cd sample/console - make debug - ``` - -2. **Set up VS Code**: Use the Mono Debug extension configuration above -3. **Set breakpoints**: Place breakpoints in your Program.cs or other C# files -4. **Start debugging**: Launch the VS Code configuration - -## Troubleshooting - -### Files Not Loading in DevTools -- Wait patiently - source files can take time to load initially -- Try refreshing the DevTools window -- Ensure your build includes debug symbols - -### Breakpoints Not Hit -- Verify the proxy port matches your configuration -- Check that Chrome is started with remote debugging enabled -- Ensure your breakpoints are set in code that will actually execute - -### Connection Issues -- Verify no firewall is blocking the proxy port -- Check that the proxy is still running (visible in application output) -- Try restarting both the application and Chrome +3. **Open DevTools**: Open Chrome DevTools (F12) and use the `Sources` tab to browse the source files +4. **Debug native code**: Set breakpoints, step through C/C++ code, and inspect WebAssembly linear memory ## Advanced Debugging diff --git a/docs/workflow/wasm-documentation.md b/docs/workflow/wasm-documentation.md index 942a5d5820cf43..92ba2f0e777f6e 100644 --- a/docs/workflow/wasm-documentation.md +++ b/docs/workflow/wasm-documentation.md @@ -89,7 +89,7 @@ Located in `src/mono/sample/wasm/`: ### How do I debug a library test failure seen on CI? -See the [WebAssembly Debugging Reference](debugging/mono/wasm-debugging.md#common-debugging-workflow) for detailed instructions on debugging library tests locally. +See the [WebAssembly Debugging Reference](debugging/mono/wasm-debugging.md) for detailed instructions on debugging locally. ### How do I build for different WebAssembly targets? diff --git a/eng/Signing.props b/eng/Signing.props index c3fd63545484ff..f19014472e2527 100644 --- a/eng/Signing.props +++ b/eng/Signing.props @@ -132,7 +132,6 @@ - diff --git a/src/mono/browser/Makefile b/src/mono/browser/Makefile index 4bb2240a0c4385..78e70921c234b0 100644 --- a/src/mono/browser/Makefile +++ b/src/mono/browser/Makefile @@ -135,8 +135,6 @@ submit-tests-helix: $(_MSBUILD_WASM_BUILD_ARGS) \ $(MSBUILD_ARGS) -build-dbg-proxy: - $(DOTNET) build $(TOP)/src/mono/browser/debugger/BrowserDebugHost $(MSBUILD_ARGS) build-app-host: $(DOTNET) build $(TOP)/src/mono/wasm/host $(_MSBUILD_WASM_BUILD_ARGS) $(MSBUILD_ARGS) diff --git a/src/mono/browser/README.md b/src/mono/browser/README.md index 2fccdb1233c6de..6173010b986313 100644 --- a/src/mono/browser/README.md +++ b/src/mono/browser/README.md @@ -149,22 +149,6 @@ src/mono/wasm/symbolicator$ dotnet run /path/to/dotnet.native.js.symbols /path/t When not relinking, or not building with AOT, you can find `dotnet.native.js.symbols` in the runtime pack. -## Debugger tests on macOS - -Debugger tests need `Google Chrome` to be installed. - -`make run-debugger-tests` - -To run a test with `FooBar` in the name: - -`make run-debugger-tests TEST_FILTER=FooBar` - -(See https://learn.microsoft.com/dotnet/core/testing/selective-unit-tests?pivots=xunit for filter options) - -Additional arguments for `dotnet test` can be passed via `MSBUILD_ARGS` or `TEST_ARGS`. For example `MSBUILD_ARGS="/p:WasmDebugLevel=5"`. Though only one of `TEST_ARGS`, or `TEST_FILTER` can be used at a time. - -Chrome can be installed for testing by setting `InstallChromeForDebuggerTests=true` when building the tests. - ## Run samples The samples in `src/mono/sample/wasm` can be build and run like this: @@ -333,7 +317,6 @@ npm update --lockfile-version=1 | libtests aot | linux+windows: smoke, only-pc | | high resource aot | none | | Wasm.Build.Tests | linux+windows: only-pc | -| Debugger tests | linux+windows: only-pc | | Runtime tests | linux+windows: only-pc | ### Run manually with `/azp run ..` @@ -348,15 +331,12 @@ npm update --lockfile-version=1 | libtests aot | linux+windows: all | linux+windows: all | none | | high resource aot | linux+windows: all | linux+windows: all | none | | Wasm.Build.Tests | linux+windows | none | linux+windows | -| Debugger tests | linux+windows | none | linux+windows | | Runtime tests | linux | none | linux | | Multi-thread | linux: all tests | linux: all tests | none | * `runtime-extra-platforms` does not run any wasm jobs on PRs * `high resource aot` runs a few specific library tests with AOT, that require more memory to AOT. -* `runtime-wasm-dbgtests` runs all the debugger test jobs - ## Rolling build (twice a day): * `runtime` runs all the wasm jobs, but `AOT` still only runs smoke tests. @@ -370,7 +350,6 @@ npm update --lockfile-version=1 | high resource aot | none | linux+windows: all | | | | | | Wasm.Build.Tests | linux+windows | none | -| Debugger tests | linux+windows | none | | Runtime tests | linux | none | | Multi-thread | linux: build only | none | diff --git a/src/mono/browser/debugger/BrowserDebugHost/BrowserDebugHost.csproj b/src/mono/browser/debugger/BrowserDebugHost/BrowserDebugHost.csproj deleted file mode 100644 index 49c81579953224..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugHost/BrowserDebugHost.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - $(AspNetCoreAppCurrent) - true - $(NoWarn),CA2007,CA1873 - false - Major - - - - - - - - - diff --git a/src/mono/browser/debugger/BrowserDebugHost/DebugProxyHost.cs b/src/mono/browser/debugger/BrowserDebugHost/DebugProxyHost.cs deleted file mode 100644 index ec1143218a99ba..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugHost/DebugProxyHost.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using System.IO; -using System.Runtime.ExceptionServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -#nullable enable - -namespace Microsoft.WebAssembly.Diagnostics; - -public static class DebugProxyHost -{ - public static async Task RunDebugProxyAsync(ProxyOptions options, string[] args, ILoggerFactory loggerFactory, CancellationToken token) - { - List tasks = new(capacity: 2) - { - RunDevToolsProxyAsync(options, args, loggerFactory, token) - }; - if (!options.RunningForBlazor || options.IsFirefoxDebugging) - tasks.Add(RunFirefoxServerLoopAsync(options, args, loggerFactory, token)); - - Task completedTask = await Task.WhenAny(tasks); - if (completedTask.IsFaulted) - ExceptionDispatchInfo.Capture(completedTask.Exception!).Throw(); - } - - public static Task RunFirefoxServerLoopAsync(ProxyOptions options, string[] args, ILoggerFactory loggerFactory, CancellationToken token) - => FirefoxDebuggerProxy.RunServerLoopAsync(browserPort: options.FirefoxDebugPort, - proxyPort: options.FirefoxProxyPort, - loggerFactory, - loggerFactory.CreateLogger("FirefoxMonoProxy"), - token, - options); - - public static async Task RunDevToolsProxyAsync(ProxyOptions options, string[] args, ILoggerFactory loggerFactory, CancellationToken token) - { - string proxyUrl = $"http://127.0.0.1:{options.DevToolsProxyPort}"; - IHost host = new HostBuilder().ConfigureWebHost(webHostBuilder => - webHostBuilder - .UseSetting("UseIISIntegration", false.ToString()) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .ConfigureServices(services => - { - services.AddSingleton(loggerFactory); - services.AddSingleton(Options.Create(options)); - services.AddRouting(); - }) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.AddCommandLine(args); - }) - .UseUrls(proxyUrl) - ).Build(); - - if (token.CanBeCanceled) - token.Register(async () => await host.StopAsync()); - - await host.RunAsync(token); - } -} diff --git a/src/mono/browser/debugger/BrowserDebugHost/Program.cs b/src/mono/browser/debugger/BrowserDebugHost/Program.cs deleted file mode 100644 index 281bae1600edee..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugHost/Program.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Logging.Console; - -#nullable enable - -namespace Microsoft.WebAssembly.Diagnostics -{ - public class Program - { - public static async Task Main(string[] args) - { - IConfigurationRoot config = new ConfigurationBuilder().AddCommandLine(args).Build(); - ProxyOptions options = new(); - config.Bind(options); - options.RunningForBlazor = true; - - using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => - { - builder - .AddConsole(options => options.FormatterName = "messageOnly") // Emit messages as expected by DebugProxyLauncher.cs - .AddConsoleFormatter() - .AddFilter("Microsoft.Hosting.Lifetime", LogLevel.Information) - .AddFilter("DevToolsProxy", LogLevel.Information) - .AddFilter("FirefoxMonoProxy", LogLevel.Information) - .AddFilter(null, LogLevel.Warning); - }); - - await DebugProxyHost.RunDebugProxyAsync(options, args, loggerFactory, CancellationToken.None); - } - } - - public class MessageOnlyFormatter : ConsoleFormatter - { - public MessageOnlyFormatter() : base("messageOnly") { } - - public override void Write(in LogEntry logEntry, IExternalScopeProvider? scopeProvider, TextWriter textWriter) - => textWriter.WriteLine(logEntry.Formatter(logEntry.State, logEntry.Exception)); - } -} diff --git a/src/mono/browser/debugger/BrowserDebugHost/Startup.cs b/src/mono/browser/debugger/BrowserDebugHost/Startup.cs deleted file mode 100644 index a6856867b3ef5c..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugHost/Startup.cs +++ /dev/null @@ -1,244 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Net.Http; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting.Server.Features; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; - -namespace Microsoft.WebAssembly.Diagnostics -{ - internal sealed class Startup - { - public Startup(IConfiguration configuration) => - Configuration = configuration; - - public IConfiguration Configuration { get; } - -#pragma warning disable CA1822 - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IOptions optionsContainer, ILogger logger, IHostApplicationLifetime applicationLifetime) - { - ProxyOptions options = optionsContainer.Value; - - if (options.OwnerPid.HasValue) - { - Process ownerProcess = Process.GetProcessById(options.OwnerPid.Value); - if (ownerProcess != null) - { - ownerProcess.EnableRaisingEvents = true; - ownerProcess.Exited += (sender, eventArgs) => - { - applicationLifetime.StopApplication(); - }; - } - } - - applicationLifetime.ApplicationStarted.Register(() => - { - string ipAddress = app.ServerFeatures - .Get()? - .Addresses? - .Where(a => a.StartsWith("http:", StringComparison.InvariantCultureIgnoreCase)) - .Select(a => new Uri(a)) - .Select(uri => uri.ToString()) - .FirstOrDefault(); - - if (!options.RunningForBlazor) - Console.WriteLine($"Debug proxy for chrome now listening on {ipAddress}. And expecting chrome at {options.DevToolsUrl}"); - }); - - app.UseDeveloperExceptionPage() - .UseWebSockets() - .UseDebugProxy(logger, options); - } -#pragma warning restore CA1822 - } - - internal static class DebugExtensions - { - private static readonly HttpClient s_httpClient = new(); - - public static Dictionary MapValues(Dictionary response, HttpContext context, Uri debuggerHost) - { - var filtered = new Dictionary(); - HttpRequest request = context.Request; - var isNode = response.TryGetValue("type", out string type) && type == "node"; - - foreach (string key in response.Keys) - { - switch (key) - { - case "devtoolsFrontendUrl": - string front = response[key]; - if (!isNode) - filtered[key] = $"{debuggerHost.Scheme}://{debuggerHost.Authority}{front.Replace($"ws={debuggerHost.Authority}", $"ws={request.Host}")}"; - else - filtered[key] = $"{front.Replace($"ws={debuggerHost.Authority}", $"ws={request.Host}")}"; - break; - case "webSocketDebuggerUrl": - var page = new Uri(response[key]); - filtered[key] = $"{page.Scheme}://{request.Host}{page.PathAndQuery}"; - break; - default: - filtered[key] = response[key]; - break; - } - } - return filtered; - } - - public static IApplicationBuilder UseDebugProxy(this IApplicationBuilder app, ILogger logger, ProxyOptions options) => - UseDebugProxy(app, logger, options, MapValues); - - public static IApplicationBuilder UseDebugProxy( - this IApplicationBuilder app, - ILogger logger, - ProxyOptions options, - Func, HttpContext, Uri, Dictionary> mapFunc) - { - Uri devToolsHost = options.DevToolsUrl; - app.UseRouter(router => - { - router.MapGet("/", Copy); - router.MapGet("/favicon.ico", Copy); - router.MapGet("json", RewriteArray); - router.MapGet("json/list", RewriteArray); - router.MapGet("json/version", RewriteSingle); - router.MapGet("json/new", RewriteSingle); - router.MapGet("devtools/page/{pageId}", ConnectProxy); - router.MapGet("devtools/browser/{pageId}", ConnectProxy); - router.MapGet("{pageId}", ConnectProxy); //for node this is the URL format: ws://localhost:54693/dbad4979-2d2e-4ada-b449-d583a83b0545 - - string GetEndpoint(HttpContext context) - { - HttpRequest request = context.Request; - PathString requestPath = request.Path; - return $"{devToolsHost.Scheme}://{devToolsHost.Authority}{request.Path}{request.QueryString}"; - } - - async Task Copy(HttpContext context) - { - try - { - HttpResponseMessage response = await s_httpClient.GetAsync(GetEndpoint(context)); - context.Response.ContentType = response.Content.Headers.ContentType.ToString(); - if ((response.Content.Headers.ContentLength ?? 0) > 0) - context.Response.ContentLength = response.Content.Headers.ContentLength; - byte[] bytes = await response.Content.ReadAsByteArrayAsync(); - await context.Response.Body.WriteAsync(bytes); - } - catch (HostConnectionException hce) - { - logger.LogWarning(hce.Message); - context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; - } - } - - async Task RewriteSingle(HttpContext context) - { - try - { - Dictionary version = await ProxyGetJsonAsync>(GetEndpoint(context)); - context.Response.ContentType = "application/json"; - await context.Response.WriteAsync( - JsonSerializer.Serialize(mapFunc(version, context, devToolsHost))); - } - catch (HostConnectionException hce) - { - logger.LogWarning(hce.Message); - context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; - } - } - - async Task RewriteArray(HttpContext context) - { - try - { - Dictionary[] tabs = await ProxyGetJsonAsync[]>(GetEndpoint(context)); - Dictionary[] alteredTabs = tabs.Select(t => mapFunc(t, context, devToolsHost)).ToArray(); - context.Response.ContentType = "application/json"; - string text = JsonSerializer.Serialize(alteredTabs); - context.Response.ContentLength = text.Length; - await context.Response.WriteAsync(text); - } - catch (HostConnectionException hce) - { - logger.LogWarning(hce.Message); - context.Response.StatusCode = StatusCodes.Status503ServiceUnavailable; - } - } - - async Task ConnectProxy(HttpContext context) - { - if (!context.WebSockets.IsWebSocketRequest) - { - context.Response.StatusCode = 400; - return; - } - - var endpoint = new Uri($"ws://{devToolsHost.Authority}{context.Request.Path}"); - int runtimeId = 0; - if (context.Request.Query.TryGetValue("RuntimeId", out StringValues runtimeIdValue) && - int.TryParse(runtimeIdValue.FirstOrDefault(), out int parsedId)) - { - runtimeId = parsedId; - } - - CancellationTokenSource cts = new(); - try - { - var loggerFactory = context.RequestServices.GetService(); - var proxy = new DebuggerProxy(loggerFactory, runtimeId, options: options); - - System.Net.WebSockets.WebSocket ideSocket = await context.WebSockets.AcceptWebSocketAsync(); - - logger.LogInformation("Connection accepted from IDE. Starting debug proxy..."); - await proxy.Run(endpoint, ideSocket, cts); - } - catch (Exception e) - { - logger.LogError($"Failed to start proxy: {e}"); - context.Response.StatusCode = StatusCodes.Status500InternalServerError; - cts.Cancel(); - } - } - }); - return app; - } - - private static async Task ProxyGetJsonAsync(string url) - { - try - { - HttpResponseMessage response = await s_httpClient.GetAsync(url); - return await JsonSerializer.DeserializeAsync(await response.Content.ReadAsStreamAsync()); - } - catch (HttpRequestException hre) - { - throw new HostConnectionException($"Failed to read from the host at {url}. Make sure the host is running. error: {hre.Message}", hre); - } - } - } - - internal sealed class HostConnectionException : Exception - { - public HostConnectionException(string message, Exception innerException) : base(message, innerException) - { - } - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/BrowserDebugProxy.csproj b/src/mono/browser/debugger/BrowserDebugProxy/BrowserDebugProxy.csproj deleted file mode 100644 index d27fe41d00f58c..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/BrowserDebugProxy.csproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - $(AspNetCoreAppCurrent) - $(NoWarn),CA2007,CA1873 - true - true - - - - - - - - - - - - - - - diff --git a/src/mono/browser/debugger/BrowserDebugProxy/BrowserDebugProxy.slnx b/src/mono/browser/debugger/BrowserDebugProxy/BrowserDebugProxy.slnx deleted file mode 100644 index 2bb7f62d470b02..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/BrowserDebugProxy.slnx +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/mono/browser/debugger/BrowserDebugProxy/Common/DevToolsDebuggerConnection.cs b/src/mono/browser/debugger/BrowserDebugProxy/Common/DevToolsDebuggerConnection.cs deleted file mode 100644 index d6a658af441d68..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/Common/DevToolsDebuggerConnection.cs +++ /dev/null @@ -1,80 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.IO; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -#nullable enable - -namespace Microsoft.WebAssembly.Diagnostics; - -internal sealed class DevToolsDebuggerConnection : WasmDebuggerConnection -{ - public WebSocket WebSocket { get; init; } - private readonly ILogger _logger; - - public DevToolsDebuggerConnection(WebSocket webSocket, string id, ILogger logger) - : base(id) - { - ArgumentNullException.ThrowIfNull(webSocket); - ArgumentNullException.ThrowIfNull(logger); - WebSocket = webSocket; - _logger = logger; - } - - public override bool IsConnected => WebSocket.State == WebSocketState.Open; - - public override async Task ReadOneAsync(CancellationToken token) - { - byte[] buff = new byte[4000]; - var mem = new MemoryStream(); - - while (true) - { - if (WebSocket.State != WebSocketState.Open) - throw new Exception($"WebSocket is no longer open, state: {WebSocket.State}"); - - ArraySegment buffAsSeg = new(buff); - WebSocketReceiveResult result = await WebSocket.ReceiveAsync(buffAsSeg, token); - if (result.MessageType == WebSocketMessageType.Close) - throw new Exception($"WebSocket close message received, state: {WebSocket.State}"); - - await mem.WriteAsync(new ReadOnlyMemory(buff, 0, result.Count), token); - - if (result.EndOfMessage) - return Encoding.UTF8.GetString(mem.GetBuffer(), 0, (int)mem.Length); - } - } - - public override Task SendAsync(byte[] bytes, CancellationToken token) - => WebSocket.SendAsync(new ArraySegment(bytes), - WebSocketMessageType.Text, - true, - token); - - public override async Task ShutdownAsync(CancellationToken cancellationToken) - { - try - { - if (!cancellationToken.IsCancellationRequested && WebSocket.State == WebSocketState.Open) - await WebSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Closing", cancellationToken); - } - catch (Exception ex) when (ex is IOException || ex is WebSocketException || ex is OperationCanceledException) - { - _logger.LogDebug($"Shutdown: Close failed, but ignoring: {ex}"); - } - } - - public override void Dispose() - { - WebSocket.Dispose(); - base.Dispose(); - } - - public override string ToString() => $"[ {Id} connection: state: {WebSocket?.State} ]"; -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/Common/DevToolsQueue.cs b/src/mono/browser/debugger/BrowserDebugProxy/Common/DevToolsQueue.cs deleted file mode 100644 index 029fda3e92fba2..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/Common/DevToolsQueue.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Concurrent; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; - -#nullable enable - -namespace Microsoft.WebAssembly.Diagnostics -{ - internal sealed class DevToolsQueue - { - private Task? current_send; - private readonly ConcurrentQueue pending; - - public Task? CurrentSend { get { return current_send; } } - - public WasmDebuggerConnection Connection { get; init; } - public string Id => Connection.Id; - - public DevToolsQueue(WasmDebuggerConnection conn) - { - Connection = conn; - pending = new ConcurrentQueue(); - } - - public Task? Send(byte[] bytes, CancellationToken token) - { - ArgumentNullException.ThrowIfNull(bytes); - - pending.Enqueue(bytes); - TryPumpIfCurrentCompleted(token, out Task? sendTask); - return sendTask; - } - - public bool TryPumpIfCurrentCompleted(CancellationToken token, [NotNullWhen(true)] out Task? sendTask) - { - sendTask = null; - - if (current_send?.IsCompleted == false) - return false; - - current_send = null; - if (pending.TryDequeue(out byte[]? bytes)) - { - current_send = Connection.SendAsync(bytes, token); - sendTask = current_send; - } - - return sendTask != null; - } - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/Common/FirefoxDebuggerConnection.cs b/src/mono/browser/debugger/BrowserDebugProxy/Common/FirefoxDebuggerConnection.cs deleted file mode 100644 index 2664cadb0cb3e2..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/Common/FirefoxDebuggerConnection.cs +++ /dev/null @@ -1,116 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.IO; -using System.Linq; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -#nullable enable -namespace Microsoft.WebAssembly.Diagnostics; - -internal sealed class FirefoxDebuggerConnection : WasmDebuggerConnection -{ - public TcpClient TcpClient { get; init; } - private readonly ILogger _logger; - private bool _isDisposed; - private readonly byte[] _lengthBuffer; - - public FirefoxDebuggerConnection(TcpClient tcpClient, string id, ILogger logger) - : base(id) - { - ArgumentNullException.ThrowIfNull(tcpClient); - ArgumentNullException.ThrowIfNull(logger); - TcpClient = tcpClient; - _logger = logger; - _lengthBuffer = new byte[10]; - } - - public override bool IsConnected => TcpClient.Connected; - - public override async Task ReadOneAsync(CancellationToken token) - { -#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' - NetworkStream? stream = TcpClient.GetStream(); - int bytesRead = 0; - while (bytesRead == 0 || Convert.ToChar(_lengthBuffer[bytesRead - 1]) != ':') - { - if (CheckFail()) - return null; - - if (bytesRead + 1 > _lengthBuffer.Length) - throw new IOException($"Protocol error: did not get the expected length preceding a message, " + - $"after reading {bytesRead} bytes. Instead got: {Encoding.UTF8.GetString(_lengthBuffer)}"); - - int readLen = await stream.ReadAsync(_lengthBuffer, bytesRead, 1, token); - bytesRead += readLen; - } - - string str = Encoding.UTF8.GetString(_lengthBuffer, 0, bytesRead - 1); - if (!int.TryParse(str, out int messageLen)) - throw new Exception($"Protocol error: Could not parse length prefix: '{str}'"); - - if (CheckFail()) - return null; - - byte[] buffer = new byte[messageLen]; - bytesRead = await stream.ReadAsync(buffer, 0, messageLen, token); - while (bytesRead != messageLen) - { - if (CheckFail()) - return null; - bytesRead += await stream.ReadAsync(buffer, bytesRead, messageLen - bytesRead, token); - } - - return Encoding.UTF8.GetString(buffer, 0, messageLen); - - bool CheckFail() - { - if (token.IsCancellationRequested) - return true; - - if (!TcpClient.Connected) - throw new Exception($"{this} Connection closed"); - - return false; - } - } - - public override Task SendAsync(byte[] bytes, CancellationToken token) - { - byte[]? bytesWithHeader = Encoding.UTF8.GetBytes($"{bytes.Length}:").Concat(bytes).ToArray(); - NetworkStream toStream = TcpClient.GetStream(); - return toStream.WriteAsync(bytesWithHeader, token).AsTask(); - } - - public override Task ShutdownAsync(CancellationToken cancellationToken) - { - TcpClient.Close(); - return Task.CompletedTask; - } - - public override void Dispose() - { - if (_isDisposed) - return; - - try - { - TcpClient.Close(); - base.Dispose(); - - _isDisposed = true; - } - catch (Exception ex) - { - _logger.LogWarning($"Failed to dispose {this}: {ex}"); - throw; - } - } - - public override string ToString() => $"[ {Id} connection ]"; -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/Common/HelperExtensions.cs b/src/mono/browser/debugger/BrowserDebugProxy/Common/HelperExtensions.cs deleted file mode 100644 index 869ba40d09207d..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/Common/HelperExtensions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; -using Newtonsoft.Json.Linq; - -namespace Microsoft.WebAssembly.Diagnostics; - -internal static class HelperExtensions -{ - private const int MaxLogMessageLineLength = 65536; - private static readonly bool TruncateLogMessages = string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WASM_DONT_TRUNCATE_LOG_MESSAGES")); - - public static string Truncate(this string message, int maxLen, string suffix = "") - - => string.Concat(message.Substring(0, Math.Min(message.Length, maxLen)).AsSpan(), - message.Length > maxLen ? suffix : ""); - - public static string TruncateLogMessage(this string message) - => TruncateLogMessages - ? message.Truncate(MaxLogMessageLineLength, ".. truncated") - : message; - - public static void AddRange(this JArray arr, JArray addedArr) - { - foreach (var item in addedArr) - arr.Add(item); - } - - public static bool IsNullValuedObject(this JObject obj) - => obj != null && obj["type"]?.Value() == "object" && obj["subtype"]?.Value() == "null"; -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/Common/InternalUseFieldName.cs b/src/mono/browser/debugger/BrowserDebugProxy/Common/InternalUseFieldName.cs deleted file mode 100644 index 1a591bd16f32df..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/Common/InternalUseFieldName.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System.Collections.Generic; - -namespace Microsoft.WebAssembly.Diagnostics; - -internal sealed class InternalUseFieldName -{ - public static InternalUseFieldName Hidden = new(nameof(Hidden)); - public static InternalUseFieldName State = new(nameof(State)); - public static InternalUseFieldName Section = new(nameof(Section)); - public static InternalUseFieldName Owner = new(nameof(Owner)); - public static InternalUseFieldName IsStatic = new(nameof(IsStatic)); - public static InternalUseFieldName IsNewSlot = new(nameof(IsNewSlot)); - public static InternalUseFieldName IsBackingField = new(nameof(IsBackingField)); - public static InternalUseFieldName ParentTypeId = new(nameof(ParentTypeId)); - - private static readonly HashSet s_names = new() - { - Hidden.Name, - State.Name, - Section.Name, - Owner.Name, - IsStatic.Name, - IsNewSlot.Name, - IsBackingField.Name, - ParentTypeId.Name - }; - - private InternalUseFieldName(string fieldName) => Name = $"__{fieldName}__"; - - public static int Count => s_names.Count; - public static bool IsKnown(string name) => !string.IsNullOrEmpty(name) && s_names.Contains(name); - public string Name { get; init; } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/Common/RunLoop.cs b/src/mono/browser/debugger/BrowserDebugProxy/Common/RunLoop.cs deleted file mode 100644 index e0948f7defaf3e..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/Common/RunLoop.cs +++ /dev/null @@ -1,226 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Channels; -using System.Collections.Generic; -using Microsoft.Extensions.Logging; - -#nullable enable - -namespace Microsoft.WebAssembly.Diagnostics; - -internal sealed class RunLoop : IDisposable -{ - public event EventHandler? RunLoopStopped; - public bool IsRunning => StoppedState is null; - public RunLoopExitState? StoppedState { get; private set; } - - private TaskCompletionSource _failRequested { get; } = new(); - private TaskCompletionSource _shutdownRequested { get; } = new(); - private readonly ChannelWriter _channelWriter; - private readonly ChannelReader _channelReader; - private readonly DevToolsQueue[] _queues; - private readonly ILogger _logger; - - public RunLoop(DevToolsQueue[] queues, ILogger logger) - { - if (queues.Length == 0) - throw new ArgumentException($"Minimum of one queue need to run", nameof(queues)); - - foreach (DevToolsQueue q in queues) - { - if (q.Connection.OnReadAsync is null) - throw new ArgumentException($"Queue's({q.Id}) connection doesn't have a OnReadAsync handler set"); - } - - _logger = logger; - _queues = queues; - - var channel = Channel.CreateUnbounded(new UnboundedChannelOptions { SingleReader = true }); - _channelWriter = channel.Writer; - _channelReader = channel.Reader; - } - - public Task RunAsync(CancellationTokenSource cts) - => Task.Run(async () => - { - RunLoopExitState exitState; - - try - { - exitState = await RunActualAsync(cts); - StoppedState = exitState; - } - catch (Exception ex) - { - _channelWriter.Complete(ex); - _logger.LogDebug($"RunLoop threw an exception: {ex}"); - StoppedState = new(RunLoopStopReason.Exception, ex); - RunLoopStopped?.Invoke(this, StoppedState); - return; - } - finally - { - if (!cts.IsCancellationRequested) - cts.Cancel(); - } - - try - { - _logger.LogDebug($"RunLoop stopped, reason: {exitState}"); - RunLoopStopped?.Invoke(this, exitState); - } - catch (Exception ex) - { - _logger.LogError(ex, $"Invoking RunLoopStopped event ({exitState}) failed with {ex}"); - } - }); - - private async Task RunActualAsync(CancellationTokenSource x) - { - List pending_ops; - List tmp_ops = new(); - int numFixed; - - // Fixed index tasks - { - pending_ops = new(); - - for (int i = 0; i < _queues.Length; i++) - pending_ops.Add(_queues[i].Connection.ReadOneAsync(x.Token)); - pending_ops.Add(_failRequested.Task); - pending_ops.Add(_shutdownRequested.Task); - - numFixed = pending_ops.Count; - } - - Task readerTask = _channelReader.WaitToReadAsync(x.Token).AsTask(); - pending_ops.Add(readerTask); - - int numQueues = _queues.Length; - while (!x.IsCancellationRequested) - { - Task completedTask = await Task.WhenAny(pending_ops.ToArray()).ConfigureAwait(false); - - if (_shutdownRequested.Task.IsCompleted) - return new(RunLoopStopReason.Shutdown, null); - if (_failRequested.Task.IsCompleted) - return new(RunLoopStopReason.Exception, await _failRequested.Task); - - int completedIdx = pending_ops.IndexOf(completedTask); - if (completedTask.IsFaulted) - { - return (completedIdx < numQueues && !_queues[completedIdx].Connection.IsConnected) - ? new(RunLoopStopReason.ConnectionClosed, new Exception($"Connection id: {_queues[completedIdx].Id}", completedTask.Exception)) - : new(RunLoopStopReason.Exception, completedTask.Exception); - } - - if (x.IsCancellationRequested) - return new(RunLoopStopReason.Cancelled, null); - - // Ensure the fixed slots are filled - for (int i = 0; i < numFixed; i++) - tmp_ops.Add(pending_ops[i]); - - for (int queueIdx = 0; queueIdx < numQueues; queueIdx++) - { - DevToolsQueue curQueue = _queues[queueIdx]; - if (curQueue.TryPumpIfCurrentCompleted(x.Token, out Task? tsk)) - tmp_ops.Add(tsk); - - Task queueReadTask = pending_ops[queueIdx]; - if (!queueReadTask.IsCompleted) - continue; - - string msg = await (Task)queueReadTask; - tmp_ops[queueIdx] = curQueue.Connection.ReadOneAsync(x.Token); - if (msg != null) - { - Task? readHandlerTask = curQueue.Connection.OnReadAsync?.Invoke(msg, x.Token); - if (readHandlerTask != null) - tmp_ops.Add(readHandlerTask); - } - } - - // Remaining tasks *after* the fixed ones - for (int pendingOpsIdx = numFixed; pendingOpsIdx < pending_ops.Count; pendingOpsIdx++) - { - Task t = pending_ops[pendingOpsIdx]; - if (t.IsFaulted) - return new(RunLoopStopReason.Exception, t.Exception); - if (t.IsCanceled) - return new(RunLoopStopReason.Cancelled, null); - - if (!t.IsCompleted) - { - tmp_ops.Add(t); - continue; - } - } - - // Add any tasks that were received over the channel - if (readerTask.IsCompleted) - { - while (_channelReader.TryRead(out Task? newTask)) - tmp_ops.Add(newTask); - - readerTask = _channelReader.WaitToReadAsync(x.Token).AsTask(); - tmp_ops.Add(readerTask); - } - - pending_ops = tmp_ops; - tmp_ops = new(capacity: pending_ops.Count + 10); - } - - _channelWriter.Complete(); - if (_shutdownRequested.Task.IsCompleted) - return new(RunLoopStopReason.Shutdown, null); - return x.IsCancellationRequested - ? new(RunLoopStopReason.Cancelled, null) - : new(RunLoopStopReason.Exception, - new InvalidOperationException($"This shouldn't ever get thrown. Unsure why the loop stopped")); - } - - public Task Send(byte[] payload, CancellationToken token, DevToolsQueue? queue = null) - { - queue ??= _queues[0]; - Task? task = queue.Send(payload, token); - return task is null - ? Task.CompletedTask - : _channelWriter.WriteAsync(task, token).AsTask(); - } - - public void Fail(Exception exception) - { - if (_failRequested.Task.IsCompleted) - _logger.LogError($"Fail requested again with {exception}"); - else - _failRequested.TrySetResult(exception); - } - - // FIXME: Continue with to catch any errors in shutting down - public void Shutdown() => Task.Run(async () => await ShutdownAsync(CancellationToken.None)); - - public async Task ShutdownAsync(CancellationToken cancellationToken) - { - if (_shutdownRequested.Task.IsCompleted) - { - _logger.LogDebug($"Shutdown was already requested once. Ignoring"); - return; - } - - foreach (DevToolsQueue q in _queues) - await q.Connection.ShutdownAsync(cancellationToken); - - _shutdownRequested.TrySetResult(); - } - - public void Dispose() - { - foreach (DevToolsQueue q in _queues) - q.Connection.Dispose(); - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/Common/WasmDebuggerConnection.cs b/src/mono/browser/debugger/BrowserDebugProxy/Common/WasmDebuggerConnection.cs deleted file mode 100644 index 532816839377ff..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/Common/WasmDebuggerConnection.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Threading; -using System.Threading.Tasks; - -#nullable enable - -namespace Microsoft.WebAssembly.Diagnostics; - -internal abstract class WasmDebuggerConnection : IDisposable -{ - public string Id { get; init; } - - protected WasmDebuggerConnection(string id) => Id = id; - - public abstract bool IsConnected { get; } - public Func? OnReadAsync { get; set; } - - public abstract Task ReadOneAsync(CancellationToken token); - public abstract Task SendAsync(byte[] bytes, CancellationToken token); - public abstract Task ShutdownAsync(CancellationToken cancellationToken); - public virtual void Dispose() - {} -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/browser/debugger/BrowserDebugProxy/DebugStore.cs deleted file mode 100644 index 2569b7d5ee3982..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/DebugStore.cs +++ /dev/null @@ -1,2045 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Runtime.CompilerServices; -using System.Security.Cryptography; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Reflection.PortableExecutable; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.IO.Compression; -using System.Reflection; -using System.Diagnostics; -using System.Text; -using Microsoft.SymbolStore; -using Microsoft.SymbolStore.SymbolStores; -using Microsoft.FileFormats.PE; -using Microsoft.Extensions.Primitives; -using Microsoft.NET.WebAssembly.Webcil; -using System.Net.Security; -using Microsoft.FileFormats.PDB; - -namespace Microsoft.WebAssembly.Diagnostics -{ - internal static class PortableCustomDebugInfoKinds - { - public static readonly Guid AsyncMethodSteppingInformationBlob = new Guid("54FD2AC5-E925-401A-9C2A-F94F171072F8"); - - public static readonly Guid StateMachineHoistedLocalScopes = new Guid("6DA9A61E-F8C7-4874-BE62-68BC5630DF71"); - - public static readonly Guid DynamicLocalVariables = new Guid("83C563C4-B4F3-47D5-B824-BA5441477EA8"); - - public static readonly Guid TupleElementNames = new Guid("ED9FDF71-8879-4747-8ED3-FE5EDE3CE710"); - - public static readonly Guid DefaultNamespace = new Guid("58b2eab6-209f-4e4e-a22c-b2d0f910c782"); - - public static readonly Guid EncLocalSlotMap = new Guid("755F52A8-91C5-45BE-B4B8-209571E552BD"); - - public static readonly Guid EncLambdaAndClosureMap = new Guid("A643004C-0240-496F-A783-30D64F4979DE"); - - public static readonly Guid SourceLink = new Guid("CC110556-A091-4D38-9FEC-25AB9A351A6A"); - - public static readonly Guid EmbeddedSource = new Guid("0E8A571B-6926-466E-B4AD-8AB04611F5FE"); - - public static readonly Guid CompilationMetadataReferences = new Guid("7E4D4708-096E-4C5C-AEDA-CB10BA6A740D"); - - public static readonly Guid CompilationOptions = new Guid("B5FEEC05-8CD0-4A83-96DA-466284BB4BD8"); - } - - internal static class HashKinds - { - public static readonly Guid SHA1 = new Guid("ff1816ec-aa5e-4d10-87f7-6f4963833460"); - public static readonly Guid SHA256 = new Guid("8829d00f-11b8-4213-878b-770e8597ac16"); - } - - internal sealed class BreakpointRequest - { - public string Id { get; private set; } - public string Assembly { get; private set; } - public string File { get; private set; } - public int Line { get; set; } - public int Column { get; set; } - public string Condition { get; set; } - public MethodInfo Method { get; set; } - - private JObject request; - - public bool IsResolved => Assembly != null; - public List Locations { get; set; } = new List(); - - public override string ToString() => $"BreakpointRequest Assembly: {Assembly} File: {File} Line: {Line} Column: {Column}, Id: {Id}"; - - public object AsSetBreakpointByUrlResponse(IEnumerable jsloc) => new { breakpointId = Id, locations = Locations.Select(l => l.Location.AsLocation()).Concat(jsloc) }; - - public BreakpointRequest() - { } - - - public BreakpointRequest(string id, JObject request) - { - Id = id; - this.request = request; - Condition = request?["condition"]?.Value(); - } - - public static BreakpointRequest Parse(string id, JObject args) - { - return new BreakpointRequest(id, args); - } - - public BreakpointRequest Clone() => new BreakpointRequest { Id = Id, request = request }; - - public bool IsMatch(SourceFile sourceFile) - { - string url = request?["url"]?.Value(); - if (url == null) - { - string urlRegex = request?["urlRegex"].Value(); - var regex = new Regex(urlRegex); - return regex.IsMatch(sourceFile.Url.ToString()) || regex.IsMatch(sourceFile.FilePath); - } - - return sourceFile.Url.ToString() == url || sourceFile.DotNetUrlEscaped == url; - } - - public bool TryResolve(SourceFile sourceFile) - { - if (!IsMatch(sourceFile)) - return false; - - int? line = request?["lineNumber"]?.Value(); - int column = request?["columnNumber"]?.Value() ?? 0; - - if (line == null) - return false; - - Assembly = sourceFile.AssemblyName; - File = sourceFile.FilePath; - Line = line.Value; - Column = column; - return true; - } - - public bool TryResolve(DebugStore store) - { - if (request == null || store == null) - return false; - - return store.AllSources().FirstOrDefault(TryResolve) != null; - } - - public bool CompareRequest(JObject req) - => this.request["url"].Value() == req["url"].Value() && - this.request["lineNumber"].Value() == req["lineNumber"].Value() && - this.request["columnNumber"].Value() == req["columnNumber"].Value(); - - public void UpdateCondition(string condition) - { - Condition = condition; - foreach (var loc in Locations) - { - loc.Condition = condition; - } - } - - } - - internal sealed class VarInfo - { - public VarInfo(LocalVariable v, MetadataReader pdbReader) - { - this.Name = pdbReader.GetString(v.Name); - this.Index = v.Index; - } - - public VarInfo(Parameter p, MetadataReader pdbReader) - { - this.Name = pdbReader.GetString(p.Name); - this.Index = (p.SequenceNumber) * -1; - } - - public string Name { get; } - public int Index { get; } - - public override string ToString() => $"(var-info [{Index}] '{Name}')"; - } - - internal sealed class IlLocation - { - public IlLocation(MethodInfo method, int offset) - { - Method = method; - Offset = offset; - } - - public MethodInfo Method { get; } - public int Offset { get; } - } - - internal sealed class SourceLocation - { - private readonly SourceId id; - private readonly int line; - private readonly int column; - private readonly IlLocation ilLocation; - - public SourceLocation(SourceId id, int line, int column) - { - this.id = id; - this.line = line; - this.column = column; - } - - public SourceLocation(MethodInfo mi, SequencePoint sp) - { - this.id = mi.SourceId; - this.line = sp.StartLine - 1; - this.column = sp.StartColumn - 1; - this.ilLocation = new IlLocation(mi, sp.Offset); - } - - public SourceId Id { get => id; } - public int Line { get => line; } - public int Column { get => column; } - public IlLocation IlLocation => this.ilLocation; - - public override string ToString() => $"{id}:{Line}:{Column}"; - - public static SourceLocation Parse(JObject obj) - { - if (obj == null) - return null; - - if (!SourceId.TryParse(obj["scriptId"]?.Value(), out SourceId id)) - return null; - - int? line = obj["lineNumber"]?.Value(); - int? column = obj["columnNumber"]?.Value(); - if (id == null || line == null || column == null) - return null; - - return new SourceLocation(id, line.Value, column.Value); - } - - internal sealed class LocationComparer : EqualityComparer - { - public override bool Equals(SourceLocation l1, SourceLocation l2) - { - if (l1 == null && l2 == null) - return true; - else if (l1 == null || l2 == null) - return false; - - return (l1.Line == l2.Line && - l1.Column == l2.Column && - l1.Id == l2.Id); - } - - public override int GetHashCode(SourceLocation loc) - { - int hCode = loc.Line ^ loc.Column; - return loc.Id.GetHashCode() ^ hCode.GetHashCode(); - } - } - - internal object AsLocation() => new - { - scriptId = id.ToString(), - lineNumber = line, - columnNumber = column - }; - } - - internal sealed class SourceId - { - private const string Scheme = "dotnet://"; - - private readonly int assembly, document; - - public int Assembly => assembly; - public int Document => document; - - internal SourceId(int assembly, int document) - { - this.assembly = assembly; - this.document = document; - } - - public SourceId(string id) - { - if (!TryParse(id, out assembly, out document)) - throw new ArgumentException("invalid source identifier", nameof(id)); - } - - public static bool TryParse(string id, out SourceId source) - { - source = null; - if (!TryParse(id, out int assembly, out int document)) - return false; - - source = new SourceId(assembly, document); - return true; - } - - private static bool TryParse(string id, out int assembly, out int document) - { - assembly = document = 0; - if (id == null || !id.StartsWith(Scheme, StringComparison.Ordinal)) - return false; - - string[] sp = id.Substring(Scheme.Length).Split('_'); - if (sp.Length != 2) - return false; - - if (!int.TryParse(sp[0], out assembly)) - return false; - - if (!int.TryParse(sp[1], out document)) - return false; - - return true; - } - - public override string ToString() => $"{Scheme}{assembly}_{document}"; - - public override bool Equals(object obj) - { - if (obj == null) - return false; - SourceId that = obj as SourceId; - return that.assembly == this.assembly && that.document == this.document; - } - - public override int GetHashCode() => assembly.GetHashCode() ^ document.GetHashCode(); - - public static bool operator ==(SourceId a, SourceId b) => a is null ? b is null : a.Equals(b); - - public static bool operator !=(SourceId a, SourceId b) => !a.Equals(b); - } - - internal sealed class MethodInfo - { - private readonly MethodDefinition methodDef; - internal SourceFile Source { get; set; } - - public SourceId SourceId => Source.SourceId; - - public string SourceName => Source.FilePath; - - public string Name { get; } - public MethodDebugInformation DebugInformation; - public MethodDefinitionHandle methodDefHandle; - internal MetadataReader pdbMetadataReader; - internal bool hasDebugInformation; - - public SourceLocation StartLocation { get; set; } - public SourceLocation EndLocation { get; set; } - public AssemblyInfo Assembly { get; } - public int Token { get; } - internal bool IsEnCMethod; - internal LocalScopeHandleCollection localScopes; - public bool IsStatic() => (Attributes & MethodAttributes.Static) != 0; - public MethodAttributes Attributes { get; } - public int IsAsync { get; set; } - public DebuggerAttributesInfo DebuggerAttrInfo { get; set; } - public TypeInfo TypeInfo { get; } - public bool HasSequencePoints { get => hasDebugInformation && !DebugInformation.SequencePointsBlob.IsNil; } - private ParameterInfo[] _parametersInfo; - public int KickOffMethod { get; } - internal bool IsCompilerGenerated { get; } - private AsyncScopeDebugInformation[] _asyncScopes { get; set; } - private static readonly SignatureTypeProvider _signatureTypeProvider = new(); - - public MethodInfo(AssemblyInfo assembly, string methodName, int methodToken, TypeInfo type, MethodAttributes attrs) - { - this.IsAsync = -1; - this.Assembly = assembly; - this.Attributes = attrs; - this.Name = methodName; - this.Token = methodToken; - this.TypeInfo = type; - TypeInfo.Methods.Add(this); - assembly.Methods[methodToken] = this; - _asyncScopes = Array.Empty(); - } - - public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle, int token, SourceFile source, TypeInfo type, MetadataReader asmMetadataReader, MetadataReader pdbMetadataReader, bool fromEnC) - { - this.IsAsync = -1; - this.Assembly = assembly; - this.methodDef = asmMetadataReader.GetMethodDefinition(methodDefHandle); - this.Attributes = methodDef.Attributes; - this.Source = source; - this.Token = token; - this.methodDefHandle = methodDefHandle; - this.Name = assembly.EnCGetString(methodDef.Name); - this.pdbMetadataReader = pdbMetadataReader; - UpdatePdbInformation(methodDefHandle); - if (hasDebugInformation && !DebugInformation.GetStateMachineKickoffMethod().IsNil) - this.KickOffMethod = asmMetadataReader.GetRowNumber(DebugInformation.GetStateMachineKickoffMethod()); - else - this.KickOffMethod = -1; - this.IsEnCMethod = false; - this.TypeInfo = type; - DebuggerAttrInfo = new DebuggerAttributesInfo(); - //we need to loop in all the CustomAttributes from asmMetadataReader because methodDef.GetCustomAttributes() does not work correctly on EnC metadata - var customAttributes = fromEnC ? asmMetadataReader.CustomAttributes : methodDef.GetCustomAttributes(); - foreach (CustomAttributeHandle cattr in customAttributes) - { - var ca = asmMetadataReader.GetCustomAttribute(cattr); - if (fromEnC && (ca.Parent.Kind != HandleKind.MethodDefinition || ca.Parent.GetHashCode() != (token | (int)TokenType.MdtMethodDef))) - continue; - if (!assembly.TryGetCustomAttributeName(cattr, asmMetadataReader, out string name)) - continue; - switch (name) - { - case "DebuggerHiddenAttribute": - DebuggerAttrInfo.HasDebuggerHidden = true; - break; - case "DebuggerStepThroughAttribute": - DebuggerAttrInfo.HasStepThrough = true; - break; - case "DebuggerNonUserCodeAttribute": - DebuggerAttrInfo.HasNonUserCode = true; - break; - case "DebuggerStepperBoundaryAttribute": - DebuggerAttrInfo.HasStepperBoundary = true; - break; - case nameof(CompilerGeneratedAttribute): - IsCompilerGenerated = true; - break; - } - } - if (!hasDebugInformation) - DebuggerAttrInfo.HasNonUserCode = true; - DebuggerAttrInfo.ClearInsignificantAttrFlags(); - } - - public bool ContainsAsyncScope(int oneBasedIdx, int offset) - { - int arrIdx = oneBasedIdx - 1; - return arrIdx >= 0 && arrIdx < _asyncScopes.Length && - offset >= _asyncScopes[arrIdx].StartOffset && offset <= _asyncScopes[arrIdx].EndOffset; - } - - public ParameterInfo[] GetParametersInfo() - { - if (_parametersInfo != null) - return _parametersInfo; - - var signature = methodDef.Signature; - var sigReader = Assembly.asmMetadataReader.GetBlobReader(signature); - var decoder = new SignatureDecoder(_signatureTypeProvider, Assembly.asmMetadataReader, genericContext: null); - MethodSignature methodSignature = decoder.DecodeMethodSignature(ref sigReader); - - var paramsHandles = methodDef.GetParameters().ToArray(); - var paramsCnt = paramsHandles.Length; - var paramsInfo = new ParameterInfo[paramsCnt]; - - int paramInx = 0; - foreach (var paramHandle in paramsHandles) - { - var parameter = Assembly.asmMetadataReader.GetParameter(paramHandle); - var paramName = Assembly.EnCGetString(parameter.Name); - if (string.IsNullOrEmpty(paramName)) - { - continue; - } - var isOptional = parameter.Attributes.HasFlag(ParameterAttributes.Optional) && parameter.Attributes.HasFlag(ParameterAttributes.HasDefault); - if (isOptional) - { - var constantHandle = parameter.GetDefaultValue(); - var blobHandle = Assembly.asmMetadataReader.GetConstant(constantHandle); - var paramBytes = Assembly.asmMetadataReader.GetBlobBytes(blobHandle.Value); - paramsInfo[paramInx] = new ParameterInfo( - paramName, - blobHandle.TypeCode, - paramBytes - ); - } - else - { - paramsInfo[paramInx] = new ParameterInfo( - paramName, - methodSignature.ParameterTypes[paramInx] - ); - } - paramInx++; - } - _parametersInfo = paramsInfo; - return paramsInfo; - } - - public void UpdatePdbInformation(MethodDefinitionHandle methodDefHandleParm) - { - if (pdbMetadataReader == null || methodDefHandleParm.ToDebugInformationHandle().IsNil) - return; - DebugInformation = pdbMetadataReader.GetMethodDebugInformation(methodDefHandleParm.ToDebugInformationHandle()); - if (Source == null && !DebugInformation.Document.IsNil) - { - var document = pdbMetadataReader.GetDocument(DebugInformation.Document); - var documentName = pdbMetadataReader.GetString(document.Name); - Source = Assembly.GetOrAddSourceFile(DebugInformation.Document, documentName); - Source.AddMethod(this); - } - hasDebugInformation = true; - if (HasSequencePoints && Source != null) - { - var sps = DebugInformation.GetSequencePoints(); - SequencePoint start = sps.First(); - SequencePoint end = sps.First(); - Source.BreakableLines.Add(start.StartLine); - - foreach (SequencePoint sp in sps) - { - if (Source.BreakableLines.Last() != sp.StartLine) - Source.BreakableLines.Add(sp.StartLine); - - if (sp.IsHidden) - continue; - - if (sp.StartLine < start.StartLine) - start = sp; - else if (sp.StartLine == start.StartLine && sp.StartColumn < start.StartColumn) - start = sp; - - if (end.EndLine == SequencePoint.HiddenLine) - end = sp; - if (sp.EndLine > end.EndLine) - end = sp; - else if (sp.EndLine == end.EndLine && sp.EndColumn > end.EndColumn) - end = sp; - } - - StartLocation = new SourceLocation(this, start); - EndLocation = new SourceLocation(this, end); - } - localScopes = pdbMetadataReader.GetLocalScopes(methodDefHandleParm); - - byte[] scopeDebugInformation = - (from cdiHandle in pdbMetadataReader.GetCustomDebugInformation(methodDefHandleParm) - let cdi = pdbMetadataReader.GetCustomDebugInformation(cdiHandle) - where pdbMetadataReader.GetGuid(cdi.Kind) == PortableCustomDebugInfoKinds.StateMachineHoistedLocalScopes - select pdbMetadataReader.GetBlobBytes(cdi.Value)).FirstOrDefault(); - - if (scopeDebugInformation != null) - { - _asyncScopes = new AsyncScopeDebugInformation[scopeDebugInformation.Length / 8]; - for (int i = 0; i < _asyncScopes.Length; i++) - { - int scopeOffset = BitConverter.ToInt32(scopeDebugInformation, i * 8); - int scopeLen = BitConverter.ToInt32(scopeDebugInformation, (i * 8) + 4); - _asyncScopes[i] = new AsyncScopeDebugInformation(scopeOffset, scopeOffset + scopeLen); - } - } - - _asyncScopes ??= Array.Empty(); - } - - public void UpdateEnC(MetadataReader pdbMetadataReaderParm, int methodIdx) - { - this.DebugInformation = pdbMetadataReaderParm.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(methodIdx)); - this.pdbMetadataReader = pdbMetadataReaderParm; - this.IsEnCMethod = true; - UpdatePdbInformation(MetadataTokens.MethodDefinitionHandle(methodIdx)); - } - - public SourceLocation GetLocationByIl(int pos) - { - SequencePoint? prev = null; - if (HasSequencePoints) { - foreach (SequencePoint sp in DebugInformation.GetSequencePoints()) - { - if (sp.Offset > pos) - { - //get the earlier line number if the offset is in a hidden sequence point and has a earlier line number available - // if is doesn't continue and get the next line number that is not in a hidden sequence point - if (sp.IsHidden && prev == null) - continue; - break; - } - - if (!sp.IsHidden) - prev = sp; - } - - if (prev.HasValue) - return new SourceLocation(this, prev.Value); - } - return null; - } - - public VarInfo[] GetLiveVarsAt(int offset) - { - var res = new List(); - foreach (var parameterHandle in methodDef.GetParameters()) - { - var parameter = Assembly.asmMetadataReader.GetParameter(parameterHandle); - res.Add(new VarInfo(parameter, Assembly.asmMetadataReader)); - } - - - foreach (var localScopeHandle in localScopes) - { - var localScope = pdbMetadataReader.GetLocalScope(localScopeHandle); - if (localScope.StartOffset <= offset && localScope.EndOffset > offset) - { - var localVariables = localScope.GetLocalVariables(); - foreach (var localVariableHandle in localVariables) - { - var localVariable = pdbMetadataReader.GetLocalVariable(localVariableHandle); - if (localVariable.Attributes != LocalVariableAttributes.DebuggerHidden) - res.Add(new VarInfo(localVariable, pdbMetadataReader)); - } - } - } - return res.ToArray(); - } - - public override string ToString() => "MethodInfo(" + Name + ")"; - - public sealed class DebuggerAttributesInfo - { - internal bool HasDebuggerHidden { get; set; } - internal bool HasStepThrough { get; set; } - internal bool HasNonUserCode { get; set; } - public bool HasStepperBoundary { get; internal set; } - - internal void ClearInsignificantAttrFlags() - { - // hierarchy: hidden > stepThrough > nonUserCode > boundary - if (HasDebuggerHidden) - HasStepThrough = HasNonUserCode = HasStepperBoundary = false; - else if (HasStepThrough) - HasNonUserCode = HasStepperBoundary = false; - else if (HasNonUserCode) - HasStepperBoundary = false; - } - - public bool DoAttributesAffectCallStack(bool justMyCodeEnabled) - { - return HasStepThrough || - HasDebuggerHidden || - HasStepperBoundary || - (HasNonUserCode && justMyCodeEnabled); - } - - public bool ShouldStepOut(EventKind eventKind) - { - return HasDebuggerHidden || (HasStepperBoundary && eventKind == EventKind.Step); - } - } - public bool IsLexicallyContainedInMethod(MethodInfo containerMethod) - => (StartLocation.Line > containerMethod.StartLocation.Line || - (StartLocation.Line == containerMethod.StartLocation.Line && StartLocation.Column > containerMethod.StartLocation.Column)) && - (EndLocation.Line < containerMethod.EndLocation.Line || - (EndLocation.Line == containerMethod.EndLocation.Line && EndLocation.Column < containerMethod.EndLocation.Column)); - - internal sealed class SourceComparer : EqualityComparer - { - public override bool Equals(MethodInfo l1, MethodInfo l2) - { - if (l1.Source.Id == l2.Source.Id) - return true; - return false; - } - - public override int GetHashCode(MethodInfo loc) - { - return loc.Source.Id; - } - } - - private record struct AsyncScopeDebugInformation(int StartOffset, int EndOffset); - } - - internal sealed class ParameterInfo - { - public string Name { get; init; } - - public ElementType? TypeCode { get; init; } - - public object Value { get; init; } - public ParameterInfo(string name, ElementType type) - { - Name = name; - TypeCode = type; - } - public ParameterInfo(string name, ConstantTypeCode? typeCode = null, byte[] value = null) - { - Name = name; - if (value == null) - return; - switch (typeCode) - { - case ConstantTypeCode.Boolean: - Value = BitConverter.ToBoolean(value) ? 1 : 0; - TypeCode = ElementType.Boolean; - break; - case ConstantTypeCode.Char: - Value = (int)BitConverter.ToChar(value); - TypeCode = ElementType.Char; - break; - case ConstantTypeCode.Byte: - Value = (uint)value[0]; - TypeCode = ElementType.U1; - break; - case ConstantTypeCode.SByte: - Value = (int)value[0]; - TypeCode = ElementType.I1; - break; - case ConstantTypeCode.Int16: - Value = (int)BitConverter.ToUInt16(value, 0); - TypeCode = ElementType.I2; - break; - case ConstantTypeCode.UInt16: - Value = (uint)BitConverter.ToUInt16(value, 0); - TypeCode = ElementType.U2; - break; - case ConstantTypeCode.Int32: - Value = BitConverter.ToInt32(value, 0); - TypeCode = ElementType.I4; - break; - case ConstantTypeCode.UInt32: - Value = BitConverter.ToUInt32(value, 0); - TypeCode = ElementType.U4; - break; - case ConstantTypeCode.Int64: - Value = BitConverter.ToInt64(value, 0); - TypeCode = ElementType.I8; - break; - case ConstantTypeCode.UInt64: - Value = BitConverter.ToUInt64(value, 0); - TypeCode = ElementType.U8; - break; - case ConstantTypeCode.Single: - Value = BitConverter.ToSingle(value, 0); - TypeCode = ElementType.R4; - break; - case ConstantTypeCode.Double: - Value = BitConverter.ToDouble(value, 0); - TypeCode = ElementType.R8; - break; - case ConstantTypeCode.String: - Value = Encoding.Unicode.GetString(value); - TypeCode = ElementType.String; - break; - case ConstantTypeCode.NullReference: - Value = (byte)ValueTypeId.Null; - TypeCode = null; - break; - } - } - } - - internal sealed class TypeInfo - { - private readonly ILogger logger; - internal AssemblyInfo assembly; - internal int Token { get; } - internal string Namespace { get; } - internal bool IsCompilerGenerated { get; } - private bool NonUserCode { get; } - public string FullName { get; } - internal bool IsNonUserCode => assembly.pdbMetadataReader == null || NonUserCode; - public List Methods { get; } = new(); - public Dictionary DebuggerBrowsableFields = new(); - public Dictionary DebuggerBrowsableProperties = new(); - - internal TypeInfo(AssemblyInfo assembly, string typeName, int typeToken, ILogger logger) - { - this.logger = logger; - this.assembly = assembly; - FullName = typeName; - Token = typeToken; - } - - internal TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefinition type, MetadataReader metadataReader, ILogger logger) - { - this.logger = logger; - this.assembly = assembly; - Token = MetadataTokens.GetToken(metadataReader, typeHandle); - string name = assembly.EnCGetString(type.Name); - var declaringType = type; - while (declaringType.IsNested) - { - declaringType = metadataReader.GetTypeDefinition(declaringType.GetDeclaringType()); - name = metadataReader.GetString(declaringType.Name) + "." + name; - } - Namespace = assembly.EnCGetString(declaringType.Namespace); - if (Namespace.Length > 0) - FullName = Namespace + "." + name; - else - FullName = name; - - foreach (var field in type.GetFields()) - { - try - { - var fieldDefinition = metadataReader.GetFieldDefinition(field); - var fieldName = assembly.EnCGetString(fieldDefinition.Name); - AppendToBrowsable(DebuggerBrowsableFields, fieldDefinition.GetCustomAttributes(), fieldName); - } - catch (Exception ex) - { - logger.LogDebug($"Failed to read browsable attributes of a field. ({ex.Message})"); - continue; - } - } - - foreach (var prop in type.GetProperties()) - { - try - { - var propDefinition = metadataReader.GetPropertyDefinition(prop); - var propName = assembly.EnCGetString(propDefinition.Name); - AppendToBrowsable(DebuggerBrowsableProperties, propDefinition.GetCustomAttributes(), propName); - } - catch (Exception ex) - { - logger.LogDebug($"Failed to read browsable attributes of a property. ({ex.Message})"); - continue; - } - } - - foreach (CustomAttributeHandle cattr in type.GetCustomAttributes()) - { - if (!assembly.TryGetCustomAttributeName(cattr, metadataReader, out string attributeName)) - continue; - switch (attributeName) - { - case nameof(CompilerGeneratedAttribute): - IsCompilerGenerated = true; - break; - case nameof(DebuggerNonUserCodeAttribute): - NonUserCode = true; - break; - } - } - - void AppendToBrowsable(Dictionary dict, CustomAttributeHandleCollection customAttrs, string fieldName) - { - foreach (var cattr in customAttrs) - { - try - { - var ctorHandle = metadataReader.GetCustomAttribute(cattr).Constructor; - if (ctorHandle.Kind != HandleKind.MemberReference) - continue; - var container = metadataReader.GetMemberReference((MemberReferenceHandle)ctorHandle).Parent; - var valueBytes = metadataReader.GetBlobBytes(metadataReader.GetCustomAttribute(cattr).Value); - var attributeName = assembly.EnCGetString(metadataReader.GetTypeReference((TypeReferenceHandle)container).Name); - if (attributeName != "DebuggerBrowsableAttribute") - continue; - var state = (DebuggerBrowsableState)valueBytes[2]; - if (!Enum.IsDefined(typeof(DebuggerBrowsableState), state)) - continue; - dict.Add(fieldName, state); - break; - } - catch - { - continue; - } - } - } - } - - public override string ToString() => "TypeInfo('" + FullName + "')"; - } - - internal sealed class AssemblyInfo - { - private static int next_id; - private readonly int id; - private readonly ILogger logger; - private readonly Dictionary methods = new Dictionary(); - private Dictionary sourceLinkMappings = new Dictionary(); - private readonly List sources = new List(); - internal string Url { get; } - //The caller must keep the PEReader alive and undisposed throughout the lifetime of the metadata reader - private IDisposable peReaderOrWebcilReader; - internal MetadataReader asmMetadataReader { get; set; } - internal MetadataReader pdbMetadataReader { get; set; } - - internal List<(MetadataReader asm, MetadataReader pdb)> enCMetadataReader = new(); - private int debugId; - internal int PdbAge { get; private set; } - internal System.Guid PdbGuid { get; private set; } - internal bool IsPortableCodeView { get; set; } - internal string PdbName { get; set; } - public bool TriedToLoadSymbolsOnDemand { get; set; } - - private readonly Dictionary _documentIdToSourceFileTable = new Dictionary(); - public PdbChecksum[] PdbChecksums { get; set; } - - public void LoadInfoFromBytes(MonoProxy monoProxy, SessionId sessionId, AssemblyAndPdbData assemblyAndPdbData, CancellationToken token) - { - using var asmStream = new MemoryStream(assemblyAndPdbData.AsmBytes); - if (assemblyAndPdbData.IsAsmMetadataOnly) - { - FromAssemblyAndPdbData(asmStream, assemblyAndPdbData); - } - else - { - try - { - // First try to read it as a PE file, otherwise try it as a Webcil file - var peReader = new PEReader(asmStream); - if (!peReader.HasMetadata) - throw new BadImageFormatException(); - FromPEReader(monoProxy, sessionId, peReader, assemblyAndPdbData.PdbBytes, logger, token); - } - catch (BadImageFormatException) - { - // This is a WebAssembly file - asmStream.Seek(0, SeekOrigin.Begin); - var webcilReader = new WebcilReader(asmStream); - FromWebcilReader(monoProxy, sessionId, webcilReader, assemblyAndPdbData.PdbBytes, logger, token); - } - } - } - - public static AssemblyInfo FromBytes(MonoProxy monoProxy, SessionId sessionId, AssemblyAndPdbData assemblyAndPdbData, ILogger logger, CancellationToken token) - { - var assemblyInfo = new AssemblyInfo(logger); - assemblyInfo.LoadInfoFromBytes(monoProxy, sessionId, assemblyAndPdbData, token); - return assemblyInfo; - } - - public static AssemblyInfo WithoutDebugInfo(ILogger logger) - { - return new AssemblyInfo(logger); - } - - public static AssemblyInfo WithoutDebugInfo(string name, ILogger logger) => new AssemblyInfo(logger) { Name = name }; - - private AssemblyInfo(ILogger logger) - { - debugId = -1; - this.id = Interlocked.Increment(ref next_id); - this.logger = logger; - } - private void FromAssemblyAndPdbData(Stream _stream, AssemblyAndPdbData assemblyAndPdbData) - { - var asmMetadataReader = MetadataReaderProvider.FromMetadataStream(_stream, MetadataStreamOptions.LeaveOpen).GetMetadataReader(); - Name = ReadAssemblyName(asmMetadataReader); - if (assemblyAndPdbData.PdbBytes != null) - { - if (assemblyAndPdbData.PdbUncompressedSize > 0) - { - byte[] decompressedBuffer; - using var compressedStream = new MemoryStream(assemblyAndPdbData.PdbBytes, writable: false); - using var deflateStream = new System.IO.Compression.DeflateStream(compressedStream, System.IO.Compression.CompressionMode.Decompress, leaveOpen: true); - decompressedBuffer = GC.AllocateUninitializedArray(assemblyAndPdbData.PdbUncompressedSize); - using var decompressedStream = new MemoryStream(decompressedBuffer, writable: true); - deflateStream.CopyTo(decompressedStream); - this.pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(new MemoryStream(decompressedBuffer, writable: false)).GetMetadataReader(); - } - else - { - this.pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(new MemoryStream(assemblyAndPdbData.PdbBytes, writable: false)).GetMetadataReader(); - } - } - this.asmMetadataReader = asmMetadataReader; - PdbAge = assemblyAndPdbData.PdbAge; - PdbGuid = assemblyAndPdbData.PdbGuid; - PdbName = assemblyAndPdbData.PdbPath; - IsPortableCodeView = assemblyAndPdbData.IsPortableCodeView; - PdbChecksums = assemblyAndPdbData.PdbChecksums.ToArray(); - Populate(); - } - - private void FromPEReader(MonoProxy monoProxy, SessionId sessionId, PEReader peReader, byte[] pdb, ILogger logger, CancellationToken token) - { - var debugProvider = new PortableExecutableDebugMetadataProvider(peReader); - - var asmMetadataReader = PEReaderExtensions.GetMetadataReader(peReader); - string name = ReadAssemblyName(asmMetadataReader); - var summary = MetadataDebugSummary.Create(monoProxy, sessionId, name, debugProvider, pdb, token); - - LoadAssemblyInfo(peReader, name, asmMetadataReader, summary, logger); - } - - private void FromWebcilReader(MonoProxy monoProxy, SessionId sessionId, WebcilReader wcReader, byte[] pdb, ILogger logger, CancellationToken token) - { - var debugProvider = new WebcilDebugMetadataProvider(wcReader); - var asmMetadataReader = wcReader.GetMetadataReader(); - string name = ReadAssemblyName(asmMetadataReader); - - var summary = MetadataDebugSummary.Create(monoProxy, sessionId, name, debugProvider, pdb, token); - - LoadAssemblyInfo(wcReader, name, asmMetadataReader, summary, logger); - } - - private static string ReadAssemblyName(MetadataReader asmMetadataReader) - { - var asmDef = asmMetadataReader.GetAssemblyDefinition(); - return asmDef.GetAssemblyName().Name + ".dll"; - } - - private unsafe void LoadAssemblyInfo(IDisposable owningReader, string name, MetadataReader asmMetadataReader, MetadataDebugSummary summary, ILogger logger) - { - peReaderOrWebcilReader = owningReader; - var codeViewData = summary.CodeViewData; - if (codeViewData != null) - { - PdbAge = codeViewData.Value.Age; - PdbGuid = codeViewData.Value.Guid; - PdbName = codeViewData.Value.Path; - } - IsPortableCodeView = summary.IsPortableCodeView; - PdbChecksums = summary.PdbChecksums; - this.asmMetadataReader = asmMetadataReader; - Name = name; - logger.LogTrace($"Info: loading AssemblyInfo with name {Name}"); - this.pdbMetadataReader = summary.PdbMetadataReader; - Populate(); - } - public bool TryGetCustomAttributeName(CustomAttributeHandle customAttribute, MetadataReader metadataReader, out string name) - { - name = ""; - try - { - EntityHandle ctorHandle = metadataReader.GetCustomAttribute(customAttribute).Constructor; - if (ctorHandle.Kind != HandleKind.MemberReference) - return false; - EntityHandle? container = ctorHandle.Kind switch - { - HandleKind.MethodDefinition => EnCGetMethodDefinition((MethodDefinitionHandle)ctorHandle).GetDeclaringType(), - HandleKind.MemberReference => EnCGetMemberReference((MemberReferenceHandle)ctorHandle).Parent, - _ => null, - }; - if (container == null) - return false; - StringHandle? attributeTypeNameHandle = container.Value.Kind switch - { - HandleKind.TypeDefinition => EnCGetTypeDefinition((TypeDefinitionHandle)container.Value).Name, - HandleKind.TypeReference => EnCGetTypeReference((TypeReferenceHandle)container.Value).Name, - HandleKind.TypeSpecification => null, // custom generic attributes, TypeSpecification does not keep the attribute name for them - _ => null, - }; - if (attributeTypeNameHandle == null) - return false; - name = EnCGetString(attributeTypeNameHandle.Value); - return true; - } - catch (Exception e) - { - logger.LogError($"Not able to get CustomAttributeName {e}"); - } - return false; - } - - public async Task GetDebugId(MonoSDBHelper sdbAgent, CancellationToken token) - { - if (debugId > 0) - return debugId; - debugId = await sdbAgent.GetAssemblyId(Name, token); - return debugId; - } - - public void SetDebugId(int id) - { - if (debugId <= 0 && debugId != id) - debugId = id; - } - - public bool EnC(MonoSDBHelper sdbAgent, byte[] meta, byte[] pdb) - { - var asmStream = new MemoryStream(meta); - MetadataReader asmMetadataReader = MetadataReaderProvider.FromMetadataStream(asmStream).GetMetadataReader(); - var pdbStream = new MemoryStream(pdb); - MetadataReader pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); - enCMetadataReader.Add(new(asmMetadataReader, pdbMetadataReader)); - PopulateEnC(sdbAgent, asmMetadataReader, pdbMetadataReader); - return true; - } - private static int GetTypeDefIdx(MetadataReader asmMetadataReaderParm, int number) - { - int i = 1; - foreach (var encMapHandle in asmMetadataReaderParm.GetEditAndContinueMapEntries()) - { - if (encMapHandle.Kind == HandleKind.TypeDefinition) - { - if (asmMetadataReaderParm.GetRowNumber(encMapHandle) == number) - return i; - i++; - } - } - return -1; - } - - private static int GetMethodDebugInformationIdx(MetadataReader pdbMetadataReaderParm, int number) - { - int i = 1; - foreach (var encMapHandle in pdbMetadataReaderParm.GetEditAndContinueMapEntries()) - { - if (encMapHandle.Kind == HandleKind.MethodDebugInformation) - { - if (pdbMetadataReaderParm.GetRowNumber(encMapHandle) == number) - return i; - i++; - } - } - return -1; - } - - public TypeReference EnCGetTypeReference(TypeReferenceHandle methodDefHandle) - { - var asmMetadataReaderLocal = asmMetadataReader; - var typeIdx = methodDefHandle.GetHashCode(); - int i = 0; - while (typeIdx > asmMetadataReaderLocal.TypeReferences.Count) - { - typeIdx -= asmMetadataReaderLocal.TypeReferences.Count; - asmMetadataReaderLocal = enCMetadataReader[i].asm; - i += 1; - } - return asmMetadataReaderLocal.GetTypeReference(MetadataTokens.TypeReferenceHandle(typeIdx)); - } - - public TypeDefinition EnCGetTypeDefinition(TypeDefinitionHandle methodDefHandle) - { - var asmMetadataReaderLocal = asmMetadataReader; - var typeIdx = methodDefHandle.GetHashCode(); - int i = 0; - while (typeIdx > asmMetadataReaderLocal.TypeDefinitions.Count) - { - typeIdx -= asmMetadataReaderLocal.TypeDefinitions.Count; - asmMetadataReaderLocal = enCMetadataReader[i].asm; - i += 1; - } - return asmMetadataReaderLocal.GetTypeDefinition(MetadataTokens.TypeDefinitionHandle(typeIdx)); - } - public MethodDefinition EnCGetMethodDefinition(MethodDefinitionHandle methodDefHandle) - { - var asmMetadataReaderLocal = asmMetadataReader; - var methodIdx = methodDefHandle.GetHashCode(); - int i = 0; - while (methodIdx > asmMetadataReaderLocal.MethodDefinitions.Count) - { - methodIdx -= asmMetadataReaderLocal.MethodDefinitions.Count; - asmMetadataReaderLocal = enCMetadataReader[i].asm; - i += 1; - } - return asmMetadataReaderLocal.GetMethodDefinition(MetadataTokens.MethodDefinitionHandle(methodIdx)); - } - - public MemberReference EnCGetMemberReference(MemberReferenceHandle memberHandle) - { - var asmMetadataReaderLocal = asmMetadataReader; - var memberIdx = memberHandle.GetHashCode(); - int i = 0; - while (memberIdx > asmMetadataReaderLocal.MemberReferences.Count) - { - memberIdx -= asmMetadataReaderLocal.MemberReferences.Count; - asmMetadataReaderLocal = enCMetadataReader[i].asm; - i += 1; - } - return asmMetadataReaderLocal.GetMemberReference(MetadataTokens.MemberReferenceHandle(memberIdx)); - } - - public string EnCGetString(StringHandle strHandle) - { - var asmMetadataReaderLocal = asmMetadataReader; - var strIdx = strHandle.GetHashCode(); - int i = 0; - while (strIdx > asmMetadataReaderLocal.GetHeapSize(HeapIndex.String)) - { - strIdx -= asmMetadataReaderLocal.GetHeapSize(HeapIndex.String); - asmMetadataReaderLocal = enCMetadataReader[i].asm; - i+=1; - } - return asmMetadataReaderLocal.GetString(MetadataTokens.StringHandle(strIdx)); - } - - private void PopulateEnC(MonoSDBHelper sdbAgent, MetadataReader asmMetadataReaderParm, MetadataReader pdbMetadataReaderParm) - { - TypeInfo typeInfo = null; - int methodIdxAsm = 1; - sdbAgent.ResetTypes(); // FIXME: only remove the cache for the affected type if fields or methods are added - - foreach (var entry in asmMetadataReaderParm.GetEditAndContinueLogEntries()) - { - if (entry.Operation == EditAndContinueOperation.AddMethod || - entry.Operation == EditAndContinueOperation.AddField) - { - var typeHandle = (TypeDefinitionHandle)entry.Handle; - if (!TypesByToken.TryGetValue(MetadataTokens.GetToken(asmMetadataReaderParm, typeHandle), out typeInfo)) - { - int typeDefIdx = GetTypeDefIdx(asmMetadataReaderParm, asmMetadataReaderParm.GetRowNumber(entry.Handle)); - var typeDefinition = asmMetadataReaderParm.GetTypeDefinition(MetadataTokens.TypeDefinitionHandle(typeDefIdx)); - StringHandle name = MetadataTokens.StringHandle(typeDefinition.Name.GetHashCode() & 127); - - typeInfo = CreateTypeInfo(typeHandle, typeDefinition); - } - } - else if (entry.Operation == EditAndContinueOperation.Default) - { - var entryRow = asmMetadataReader.GetRowNumber(entry.Handle); - if (entry.Handle.Kind == HandleKind.MethodDefinition) - { - int methodIdx = GetMethodDebugInformationIdx(pdbMetadataReaderParm, entryRow); - if (methods.TryGetValue(entryRow, out MethodInfo method)) - { - method.UpdateEnC(pdbMetadataReaderParm, methodIdx); - } - else if (typeInfo != null) - { - var methodDebugInformation = pdbMetadataReaderParm.GetMethodDebugInformation(MetadataTokens.MethodDebugInformationHandle(methodIdx)); - SourceFile source = null; - if (!methodDebugInformation.Document.IsNil) - { - var document = pdbMetadataReaderParm.GetDocument(methodDebugInformation.Document); - var documentName = pdbMetadataReaderParm.GetString(document.Name); - source = GetOrAddSourceFile(methodDebugInformation.Document, documentName); - } - var methodInfo = new MethodInfo(this, MetadataTokens.MethodDefinitionHandle(methodIdxAsm), entryRow, source, typeInfo, asmMetadataReaderParm, pdbMetadataReaderParm, fromEnC: true); - methods[entryRow] = methodInfo; - - source?.AddMethod(methodInfo); - - typeInfo.Methods.Add(methodInfo); - } - methodIdxAsm++; - } - else if (entry.Handle.Kind == HandleKind.FieldDefinition) - { - //Implement new instance field when it's supported on runtime - } - } - else - { - logger.LogError($"Not supported EnC operation {entry.Operation}"); - } - } - } - public SourceFile GetOrAddSourceFile(DocumentHandle doc, string documentName) - { - if (_documentIdToSourceFileTable.TryGetValue(documentName.GetHashCode(), out SourceFile source)) - return source; - - var src = new SourceFile(this, _documentIdToSourceFileTable.Count, doc, documentName, sourceLinkMappings); - _documentIdToSourceFileTable[documentName.GetHashCode()] = src; - return src; - } - - private void Populate() - { - if (pdbMetadataReader != null) - ProcessSourceLink(); - - foreach (TypeDefinitionHandle type in asmMetadataReader.TypeDefinitions) - { - var typeDefinition = asmMetadataReader.GetTypeDefinition(type); - var typeInfo = CreateTypeInfo(type, typeDefinition); - - foreach (MethodDefinitionHandle method in typeDefinition.GetMethods()) - { - SourceFile source = null; - if (pdbMetadataReader != null) - { - MethodDebugInformation methodDebugInformation = pdbMetadataReader.GetMethodDebugInformation(method.ToDebugInformationHandle()); - if (!methodDebugInformation.Document.IsNil) - { - var document = pdbMetadataReader.GetDocument(methodDebugInformation.Document); - var documentName = pdbMetadataReader.GetString(document.Name); - source = GetOrAddSourceFile(methodDebugInformation.Document, documentName); - } - } - var methodInfo = new MethodInfo(this, method, asmMetadataReader.GetRowNumber(method), source, typeInfo, asmMetadataReader, pdbMetadataReader, fromEnC: false); - methods[asmMetadataReader.GetRowNumber(method)] = methodInfo; - - source?.AddMethod(methodInfo); - - typeInfo.Methods.Add(methodInfo); - } - } - } - - private void ProcessSourceLink() - { - var sourceLinkDebugInfo = - (from cdiHandle in pdbMetadataReader.GetCustomDebugInformation(EntityHandle.ModuleDefinition) - let cdi = pdbMetadataReader.GetCustomDebugInformation(cdiHandle) - where pdbMetadataReader.GetGuid(cdi.Kind) == PortableCustomDebugInfoKinds.SourceLink - select pdbMetadataReader.GetBlobBytes(cdi.Value)).SingleOrDefault(); - - if (sourceLinkDebugInfo != null) - { - var sourceLinkContent = System.Text.Encoding.UTF8.GetString(sourceLinkDebugInfo, 0, sourceLinkDebugInfo.Length); - - if (sourceLinkContent != null) - { - JToken jObject = JObject.Parse(sourceLinkContent)["documents"]; - sourceLinkMappings = JsonConvert.DeserializeObject>(jObject.ToString()); - } - } - } - - public TypeInfo CreateTypeInfo(TypeDefinitionHandle typeHandle, TypeDefinition type) - { - var typeInfo = new TypeInfo(this, typeHandle, type, asmMetadataReader, logger); - TypesByName[typeInfo.FullName] = typeInfo; - TypesByToken[typeInfo.Token] = typeInfo; - return typeInfo; - } - - public TypeInfo CreateTypeInfo(string typeName, int typeToken) - { - var typeInfo = new TypeInfo(this, typeName, typeToken, logger); - TypesByName[typeInfo.FullName] = typeInfo; - TypesByToken[typeInfo.Token] = typeInfo; - return typeInfo; - } - - public IEnumerable Sources => this._documentIdToSourceFileTable.Values; - public Dictionary Methods => this.methods; - - public Dictionary TypesByName { get; } = new(); - public Dictionary TypesByToken { get; } = new(); - public int Id => id; - public string Name { get; set; } - public bool HasSymbols => pdbMetadataReader != null; - - // "System.Threading", instead of "System.Threading, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" - public string AssemblyNameUnqualified { get; } - - public SourceFile GetDocById(int document) - { - return sources.FirstOrDefault(s => s.SourceId.Document == document); - } - - public MethodInfo GetMethodByToken(int token) - { - methods.TryGetValue(token, out MethodInfo value); - return value; - } - - public TypeInfo GetTypeByName(string name) - { - TypesByName.TryGetValue(name, out TypeInfo res); - return res; - } - - internal async Task LoadPDBFromSymbolServer(MonoProxy proxy, MonoSDBHelper sdbHelper, SessionId id, DebugStore debugStore, CancellationToken token) - { - try - { - if (TriedToLoadSymbolsOnDemand) - return; - if (asmMetadataReader is null) //it means that the assembly was not loaded before because JMC was enabled - { - var ret = await sdbHelper.GetDataFromAssemblyAndPdbAsync(Name, false, token); - LoadInfoFromBytes(proxy, id, ret, token); - } - var pdbName = Path.GetFileName(PdbName); - var pdbGuid = PdbGuid.ToString("N").ToUpperInvariant() + (IsPortableCodeView ? "FFFFFFFF" : PdbAge); - var key = $"{pdbName}/{pdbGuid}/{pdbName}"; - SymbolStoreFile file = await debugStore.symbolStore.GetFile(new SymbolStoreKey(key, PdbName, false, PdbChecksums), token); - TriedToLoadSymbolsOnDemand = true; - if (file == null) - return; - var pdbStream = new MemoryStream(); - file.Stream.Position = 0; - await file.Stream.CopyToAsync(pdbStream, token); - pdbStream.Position = 0; - pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); - if (pdbMetadataReader == null) - return; - ProcessSourceLink(); - foreach (var method in this.Methods) - { - method.Value.pdbMetadataReader = pdbMetadataReader; - method.Value.UpdatePdbInformation(method.Value.methodDefHandle); - } - } - catch (Exception ex) - { - logger.LogError($"Failed to load symbols from symbol server. ({ex.Message})"); - } - } - } - - internal sealed partial class SourceFile - { - [GeneratedRegex(@"([:/])")] - private static partial Regex RegexForEscapeFileName { get; } - - private readonly Dictionary methods; - private readonly AssemblyInfo assembly; - private readonly Document doc; - private readonly DocumentHandle docHandle; - internal List BreakableLines { get; } - - public string FilePath { get; init; } - public string FileUriEscaped { get; init; } - public string DotNetUrlEscaped { get; init; } - - public Uri Url { get; init; } - public Uri SourceLinkUri { get; set; } - - public int Id { get; } - public string AssemblyName => assembly.Name; - public SourceId SourceId => new SourceId(assembly.Id, this.Id); - public IEnumerable Methods => this.methods.Values; - private static readonly SHA256 _sha256 = System.Security.Cryptography.SHA256.Create(); - private string _relativePath; - - internal SourceFile(AssemblyInfo assembly, int id, DocumentHandle docHandle, string documentName, Dictionary sourceLinkMappings) - { - this.methods = new Dictionary(); - GetSourceLinkUrl(documentName, sourceLinkMappings); - this.assembly = assembly; - this.Id = id; - this.doc = assembly.pdbMetadataReader.GetDocument(docHandle); - this.docHandle = docHandle; - this.BreakableLines = new List(); - - this.FilePath = documentName; - - string escapedDocumentName = EscapePathForUri(documentName.Replace("\\", "/")); - this.FileUriEscaped = $"file://{(OperatingSystem.IsWindows() ? "/" : "")}{escapedDocumentName}"; - this.DotNetUrlEscaped = $"dotnet://{assembly.Name}/{escapedDocumentName}"; - if (!File.Exists(documentName) && SourceLinkUri != null) - { - string sourceLinkCachedPathPartial = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SourceServer", GetHashOfString(SourceLinkUri.AbsoluteUri)); - string sourceLinkCachedPath = Path.Combine(sourceLinkCachedPathPartial, _relativePath); - if (File.Exists(sourceLinkCachedPath)) //first try to find on cache using relativePath as it's done by VS while debugging - { - this.FilePath = sourceLinkCachedPath; - escapedDocumentName = EscapePathForUri(this.FilePath.Replace("\\", "/")); - } - else - { - sourceLinkCachedPath = Path.Combine(sourceLinkCachedPathPartial, Path.GetFileName(_relativePath)); - if (File.Exists(sourceLinkCachedPath)) //second try to find on cache without relativePath as it's done by VS when using "Go To Definition (F12)" - { - this.FilePath = sourceLinkCachedPath; - escapedDocumentName = EscapePathForUri(this.FilePath.Replace("\\", "/")); - } - } - this.FileUriEscaped = $"file://{(OperatingSystem.IsWindows() ? "/" : "")}{escapedDocumentName}"; - } - this.Url = new Uri(File.Exists(this.FilePath) ? FileUriEscaped : DotNetUrlEscaped, UriKind.Absolute); - } - - private void GetSourceLinkUrl(string document, Dictionary sourceLinkMappings) - { - if (sourceLinkMappings.TryGetValue(document, out string url)) - { - SourceLinkUri = new Uri(url); - return; - } - - foreach (KeyValuePair sourceLinkDocument in sourceLinkMappings) - { - string key = sourceLinkDocument.Key; - - if (!key.EndsWith('*')) - { - continue; - } - - string keyTrim = key.TrimEnd('*'); - - if (document.StartsWith(keyTrim, StringComparison.OrdinalIgnoreCase)) - { - _relativePath = document.Replace(keyTrim, ""); - SourceLinkUri = new Uri(sourceLinkDocument.Value.TrimEnd('*') + _relativePath); - return; - } - } - } - - private static string GetHashOfString(string str) - { - byte[] bytes = _sha256.ComputeHash(UnicodeEncoding.Unicode.GetBytes(str)); - StringBuilder builder = new StringBuilder(bytes.Length*2); - foreach (byte b in bytes) - { - builder.Append(b.ToString("x2")); - } - return builder.ToString(); - } - - private static string EscapePathForUri(string path) - { - var builder = new StringBuilder(); - foreach (var part in RegexForEscapeFileName.Split(path)) - { - if (part == ":" || part == "/") - builder.Append(part); - else - builder.Append(Uri.EscapeDataString(part)); - } - return builder.ToString(); - } - - internal void AddMethod(MethodInfo mi) - { - if (!this.methods.ContainsKey(mi.Token)) - { - this.methods[mi.Token] = mi; - } - } - - public (int startLine, int startColumn, int endLine, int endColumn) GetExtents() - { - MethodInfo start = Methods.OrderBy(m => m.StartLocation.Line).ThenBy(m => m.StartLocation.Column).First(); - MethodInfo 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); - } - - private static async Task GetDataAsync(Uri uri, CancellationToken token) - { - var mem = new MemoryStream(); - try - { - if (uri.IsFile && File.Exists(uri.LocalPath)) - { - using (FileStream file = File.Open(uri.LocalPath, FileMode.Open)) - { - await file.CopyToAsync(mem, token).ConfigureAwait(false); - mem.Position = 0; - } - } - else if (uri.Scheme == "http" || uri.Scheme == "https") - { - using (Stream stream = await MonoProxy.HttpClient.GetStreamAsync(uri, token)) - { - await stream.CopyToAsync(mem, token).ConfigureAwait(false); - mem.Position = 0; - } - } - } - catch (Exception) - { - return null; - } - return mem; - } - - private static HashAlgorithm GetHashAlgorithm(Guid algorithm) - { - if (algorithm.Equals(HashKinds.SHA1)) -#pragma warning disable CA5350 // Do Not Use Weak Cryptographic Algorithms - return SHA1.Create(); -#pragma warning restore CA5350 // Do Not Use Weak Cryptographic Algorithms - if (algorithm.Equals(HashKinds.SHA256)) - return SHA256.Create(); - return null; - } - - private bool CheckPdbHash(byte[] computedHash) - { - var hash = assembly.pdbMetadataReader.GetBlobBytes(doc.Hash); - if (computedHash.Length != hash.Length) - return false; - - for (int i = 0; i < computedHash.Length; i++) - if (computedHash[i] != hash[i]) - return false; - - return true; - } - - private byte[] ComputePdbHash(Stream sourceStream) - { - HashAlgorithm algorithm = GetHashAlgorithm(assembly.pdbMetadataReader.GetGuid(doc.HashAlgorithm)); - if (algorithm != null) - using (algorithm) - return algorithm.ComputeHash(sourceStream); - return Array.Empty(); - } - - public async Task GetSourceAsync(bool checkHash, CancellationToken token = default(CancellationToken)) - { - var reader = assembly.pdbMetadataReader; - byte[] bytes = (from handle in reader.GetCustomDebugInformation(docHandle) - let cdi = reader.GetCustomDebugInformation(handle) - where reader.GetGuid(cdi.Kind) == PortableCustomDebugInfoKinds.EmbeddedSource - select reader.GetBlobBytes(cdi.Value)).SingleOrDefault(); - - if (bytes != null) - { - int uncompressedSize = BitConverter.ToInt32(bytes, 0); - var stream = new MemoryStream(bytes, sizeof(int), bytes.Length - sizeof(int)); - - if (uncompressedSize != 0) - { - return new DeflateStream(stream, CompressionMode.Decompress); - } - } - - foreach (Uri url in new[] { new Uri(FileUriEscaped), SourceLinkUri }) - { - MemoryStream mem = await GetDataAsync(url, token).ConfigureAwait(false); - if (mem != null && mem.Length > 0 && (!checkHash || CheckPdbHash(ComputePdbHash(mem)))) - { - mem.Position = 0; - return mem; - } - } - - return MemoryStream.Null; - } - - public object ToScriptSource(int executionContextId, object executionContextAuxData) - { - return new - { - scriptId = SourceId.ToString(), - url = Url.OriginalString, - executionContextId, - executionContextAuxData, - //hash: should be the v8 hash algo, managed implementation is pending - dotNetUrl = DotNetUrlEscaped - }; - } - } - - internal sealed class DebugStore - { - internal List assemblies = new List(); - private readonly ILogger logger; - internal readonly MonoProxy monoProxy; - private readonly ITracer _tracer; - internal Microsoft.SymbolStore.SymbolStores.SymbolStore symbolStore; - - // The constructor can get invoked multiple times, but only *one* of - // the instances will be used. - // So, keep this light, and repeatable - public DebugStore(MonoProxy monoProxy, ILogger logger) - { - this.logger = logger; - this.monoProxy = monoProxy; - this._tracer = new Tracer(logger); - } - - private sealed class DebugItem - { - public string Url { get; set; } - public Task DataTask { get; set; } - public Task ByteArrayTask { get; set; } - } - - public static IEnumerable EnC(MonoSDBHelper sdbAgent, AssemblyInfo asm, byte[] meta_data, byte[] pdb_data) - { - asm.EnC(sdbAgent, meta_data, pdb_data); - return GetEnCMethods(asm); - } - - public static IEnumerable GetEnCMethods(AssemblyInfo asm) - { - foreach (var method in asm.Methods) - { - if (method.Value.IsEnCMethod) - yield return method.Value; - } - } - - public IEnumerable Add(SessionId id, AssemblyAndPdbData assemblyAndPdbData, CancellationToken token) - { - AssemblyInfo assembly; - try - { - assembly = AssemblyInfo.FromBytes(monoProxy, id, assemblyAndPdbData, logger, token); - } - catch (Exception e) - { - logger.LogError($"Failed to load assembly: ({e.Message})"); - yield break; - } - - if (assembly == null) - yield break; - - if (GetAssemblyByName(assembly.Name) != null) - { - logger.LogDebug($"Skipping adding {assembly.Name} into the debug store, as it already exists"); - yield break; - } - - assemblies.Add(assembly); - foreach (var source in assembly.Sources) - { - yield return source; - } - } - - public async IAsyncEnumerable Load(SessionId id, string[] loaded_files, ExecutionContext context, bool useDebuggerProtocol, [EnumeratorCancellation] CancellationToken token) - { - var asm_files = new List(); - List steps = new List(); - - // Use System.Private.CoreLib to determine if we have a fingerprinted assemblies or not. - bool isFingerprinted = Path.GetFileNameWithoutExtension(loaded_files.FirstOrDefault(f => f.Contains("System.Private.CoreLib"))) != "System.Private.CoreLib"; - - if (!useDebuggerProtocol) - { - var pdb_files = new List(); - foreach (string file_name in loaded_files) - { - if (file_name.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase)) - pdb_files.Add(file_name); - else - asm_files.Add(file_name); - } - - foreach (string url in asm_files) - { - try - { - string pdb; - if (isFingerprinted) - { - string noFingerprintPdbFileName = string.Concat(Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(url)), ".pdb"); - pdb = pdb_files.FirstOrDefault(n => string.Concat(Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(n)), Path.GetExtension(n)) == noFingerprintPdbFileName); - } - else - { - string candidate_pdb = Path.ChangeExtension(url, "pdb"); - pdb = pdb_files.FirstOrDefault(n => n == candidate_pdb); - } - - steps.Add( - new DebugItem - { - Url = url, - ByteArrayTask = Task.WhenAll(MonoProxy.HttpClient.GetByteArrayAsync(url, token), pdb != null ? MonoProxy.HttpClient.GetByteArrayAsync(pdb, token) : Task.FromResult(null)), - }); - } - catch (Exception e) - { - logger.LogDebug($"Failed to read {url} ({e.Message})"); - } - } - } - else - { - foreach (string file_name in loaded_files) - { - if (file_name.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase)) - continue; - try - { - string unescapedFileName = Path.GetFileName(Uri.UnescapeDataString(file_name)); - if (isFingerprinted) - unescapedFileName = string.Concat(Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(unescapedFileName)), Path.GetExtension(unescapedFileName)); - steps.Add( - new DebugItem - { - Url = file_name, - DataTask = context.SdbAgent.GetDataFromAssemblyAndPdbAsync(unescapedFileName, false, token) - }); - } - catch (Exception e) - { - logger.LogDebug($"Failed to read {file_name} ({e.Message})"); - } - } - } - - foreach (DebugItem step in steps) - { - AssemblyInfo assembly = null; - try - { - AssemblyAndPdbData assemblyAndPdbData; - if (step.ByteArrayTask != null) - { - byte[][] byteArray = await step.ByteArrayTask.ConfigureAwait(false); - assemblyAndPdbData = new AssemblyAndPdbData(byteArray[0], byteArray[1]); - } - else - { - assemblyAndPdbData = await step.DataTask.ConfigureAwait(false); - } - if (assemblyAndPdbData == null || assemblyAndPdbData.AsmBytes == null) - { - var unescapedFileName = Uri.UnescapeDataString(step.Url); - if (isFingerprinted) - unescapedFileName = string.Concat(Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(unescapedFileName)), Path.GetExtension(unescapedFileName)); - assemblies.Add(AssemblyInfo.WithoutDebugInfo(Path.GetFileName(unescapedFileName), logger)); - logger.LogDebug($"Bytes from assembly {step.Url} is NULL"); - continue; - } - assembly = AssemblyInfo.FromBytes(monoProxy, id, assemblyAndPdbData, logger, token); - } - catch (Exception e) - { - logger.LogError($"Failed to load {step.Url} ({e.Message}) (stack={e.StackTrace})"); - } - if (assembly == null) - continue; - - if (GetAssemblyByName(assembly.Name) != null) - { - logger.LogDebug($"Skipping loading {assembly.Name} into the debug store, as it already exists"); - continue; - } - - assemblies.Add(assembly); - foreach (SourceFile source in assembly.Sources) - yield return source; - } - } - - public IEnumerable AllSources() => assemblies.SelectMany(a => a.Sources); - - public SourceFile GetFileById(SourceId id) => AllSources().SingleOrDefault(f => f.SourceId.Equals(id)); - - public AssemblyInfo GetAssemblyByName(string name) - { - var nameOnly = Path.GetFileNameWithoutExtension(name.AsSpan()); - foreach (var asm in assemblies) - { - if (MemoryExtensions.Equals(nameOnly, Path.GetFileNameWithoutExtension(asm.Name.AsSpan()), StringComparison.InvariantCultureIgnoreCase)) - return asm; - } - return null; - } - - /* - V8 uses zero based indexing for both line and column. - PPDBs uses one based indexing for both line and column. - */ - private static bool Match(SequencePoint sp, SourceLocation start, SourceLocation end) - { - (int Line, int Column) spStart = (Line: sp.StartLine - 1, Column: sp.StartColumn - 1); - (int Line, int Column) spEnd = (Line: sp.EndLine - 1, Column: sp.EndColumn - 1); - - if (start.Line > spEnd.Line) - return false; - - if (start.Column > spEnd.Column && start.Line == spEnd.Line) - return false; - - if (end.Line < spStart.Line) - return false; - - if (end.Column < spStart.Column && end.Line == spStart.Line && end.Column != -1) - return false; - - return true; - } - - public List FindPossibleBreakpoints(SourceLocation start, SourceLocation end) - { - //XXX FIXME no idea what todo with locations on different files - if (start.Id != end.Id) - { - logger.LogDebug($"FindPossibleBreakpoints: documents differ (start: {start.Id}) (end {end.Id}"); - return null; - } - - SourceId sourceId = start.Id; - - SourceFile doc = GetFileById(sourceId); - - var res = new List(); - if (doc == null) - { - logger.LogDebug($"Could not find document {sourceId}"); - return res; - } - - foreach (MethodInfo method in doc.Methods) - res.AddRange(FindBreakpointLocations(start, end, method)); - return res; - } - - public static IEnumerable FindBreakpointLocations(SourceLocation start, SourceLocation end, MethodInfo method) - { - if (!method.HasSequencePoints) - yield break; - foreach (SequencePoint sequencePoint in method.DebugInformation.GetSequencePoints()) - { - if (!sequencePoint.IsHidden && Match(sequencePoint, start, end)) - yield return new SourceLocation(method, sequencePoint); - } - } - - /* - V8 uses zero based indexing for both line and column. - PPDBs uses one based indexing for both line and column. - */ - private static bool Match(SequencePoint sp, int line, int column) - { - (int line, int column) 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 > bp.column && sp.StartLine == bp.line) - return false; - - if (sp.EndColumn < bp.column && sp.EndLine == bp.line) - return false; - - return true; - } - - public IEnumerable FindBreakpointLocations(BreakpointRequest request, bool ifNoneFoundThenFindNext = false) - { - request.TryResolve(this); - - AssemblyInfo asm = assemblies.FirstOrDefault(a => a.Name.Equals(request.Assembly, StringComparison.OrdinalIgnoreCase)); - SourceFile sourceFile = asm?.Sources?.SingleOrDefault(s => s.FilePath.Equals(request.File, StringComparison.OrdinalIgnoreCase)); - - if (sourceFile == null) - yield break; - - List methodList = FindMethodsContainingLine(sourceFile, request.Line); - if (methodList.Count == 0) - yield break; - - List locations = new List(); - foreach (var method in methodList) - { - foreach (SequencePoint sequencePoint in method.DebugInformation.GetSequencePoints()) - { - if (!sequencePoint.IsHidden && - Match(sequencePoint, request.Line, request.Column) && - sequencePoint.StartLine - 1 == request.Line && - (request.Column == 0 || sequencePoint.StartColumn - 1 == request.Column)) - { - // Found an exact match - locations.Add(new SourceLocation(method, sequencePoint)); - } - } - } - if (locations.Count == 0 && ifNoneFoundThenFindNext) - { - (MethodInfo method, SequencePoint seqPoint)? closest = null; - foreach (var method in methodList) - { - foreach (SequencePoint sequencePoint in method.DebugInformation.GetSequencePoints()) - { - if (!sequencePoint.IsHidden && - sequencePoint.StartLine > request.Line && - (closest is null || closest.Value.seqPoint.StartLine > sequencePoint.StartLine)) - { - // sequence points in a method are ordered, - // and we found the one right after request.Line - closest = (method, sequencePoint); - // .. and now we can look for it in other methods - break; - } - } - } - - if (closest is not null) - locations.Add(new SourceLocation(closest.Value.method, closest.Value.seqPoint)); - } - - foreach (SourceLocation loc in locations) - yield return loc; - - static List FindMethodsContainingLine(SourceFile sourceFile, int line) - { - List ret = new(); - foreach (MethodInfo method in sourceFile.Methods) - { - if (method.DebugInformation.SequencePointsBlob.IsNil) - continue; - if (!(method.StartLocation.Line <= line && line <= method.EndLocation.Line)) - continue; - ret.Add(method); - } - return ret; - } - } - - public string ToUrl(SourceLocation location) => location != null ? GetFileById(location.Id).Url.OriginalString : ""; - - internal async Task ReloadAllPDBsFromSymbolServersAndSendSources(MonoProxy monoProxy, SessionId id, ExecutionContext context, CancellationToken token) - { - if (symbolStore == null) - return; - monoProxy.SendLog(id, "Loading symbols from symbol servers.", token); - foreach (var asm in assemblies.Where(asm => asm.pdbMetadataReader == null)) - { - asm.TriedToLoadSymbolsOnDemand = false; //force to load again because added another symbol server - await asm.LoadPDBFromSymbolServer(monoProxy, context.SdbAgent, id, this, token); - foreach (var source in asm.Sources) - await monoProxy.OnSourceFileAdded(id, source, context, token); - } - monoProxy.SendLog(id, "Symbols from symbol servers completely loaded.", token); - } - - internal void UpdateSymbolStore(List urlSymbolServerList, string cachePathSymbolServer) - { - symbolStore = null; - foreach (var urlServer in urlSymbolServerList) - { - if (string.IsNullOrEmpty(urlServer)) - continue; - try - { - symbolStore = new HttpSymbolStore(_tracer, symbolStore, new Uri($"{urlServer}/"), null); - } - catch (Exception ex) - { - logger.LogError($"Failed to create HttpSymbolStore for this URL - {urlServer} - {ex.Message}"); - } - } - if (!string.IsNullOrEmpty(cachePathSymbolServer)) - { - try - { - symbolStore = new CacheSymbolStore(_tracer, symbolStore, cachePathSymbolServer); - } - catch (Exception ex) - { - logger.LogError($"Failed to create CacheSymbolStore for this path - {cachePathSymbolServer} - {ex.Message}"); - } - } - } - public sealed class Tracer : ITracer - { - private readonly ILogger _logger; - - public Tracer(ILogger logger) - { - this._logger = logger; - } - - public void WriteLine(string message) => _logger.LogTrace(message); - - public void WriteLine(string format, params object[] arguments) => _logger.LogTrace(format, arguments); - - public void Information(string message) => _logger.LogTrace(message); - - public void Information(string format, params object[] arguments) => _logger.LogTrace(format, arguments); - - public void Warning(string message) => _logger.LogTrace(message); - - public void Warning(string format, params object[] arguments) => _logger.LogTrace(format, arguments); - - public void Error(string message) => _logger.LogTrace(message); - - public void Error(string format, params object[] arguments) => _logger.LogTrace(format, arguments); - - public void Verbose(string message) => _logger.LogTrace(message); - - public void Verbose(string format, params object[] arguments) => _logger.LogTrace(format, arguments); - } - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/DebuggerAgentException.cs b/src/mono/browser/debugger/BrowserDebugProxy/DebuggerAgentException.cs deleted file mode 100644 index b4ca2bde98927f..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/DebuggerAgentException.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; - -namespace Microsoft.WebAssembly.Diagnostics; - -public class DebuggerAgentException : Exception -{ - public DebuggerAgentException(string message) : base(message) - { - } - - public DebuggerAgentException(string? message, Exception? innerException) : base(message, innerException) - { - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/DebuggerProxy.cs b/src/mono/browser/debugger/BrowserDebugProxy/DebuggerProxy.cs deleted file mode 100644 index 5bac4e3d919b73..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/DebuggerProxy.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// 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; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -namespace Microsoft.WebAssembly.Diagnostics -{ - - // This type is the public entrypoint that allows external code to attach the debugger proxy - // to a given websocket listener. Everything else in this package can be internal. - - public class DebuggerProxy : DebuggerProxyBase - { - internal MonoProxy MonoProxy { get; } - - public DebuggerProxy(ILoggerFactory loggerFactory, int runtimeId = 0, string loggerId = "", ProxyOptions options = null) - { - string suffix = loggerId.Length > 0 ? $"-{loggerId}" : string.Empty; - MonoProxy = new MonoProxy(loggerFactory.CreateLogger($"DevToolsProxy{suffix}"), runtimeId, loggerId, options); - } - - public Task Run(Uri browserUri, WebSocket ideSocket, CancellationTokenSource cts) - { - return MonoProxy.RunForDevTools(browserUri, ideSocket, cts); - } - - public override void Shutdown() => MonoProxy.Shutdown(); - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/DebuggerProxyBase.cs b/src/mono/browser/debugger/BrowserDebugProxy/DebuggerProxyBase.cs deleted file mode 100644 index 1f4f330ed6273d..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/DebuggerProxyBase.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; - -namespace Microsoft.WebAssembly.Diagnostics; - -public abstract class DebuggerProxyBase -{ - public RunLoopExitState? ExitState { get; set; } - - public virtual void Shutdown() - { - } - - public virtual void Fail(Exception ex) - { - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/DevToolsHelper.cs b/src/mono/browser/debugger/BrowserDebugProxy/DevToolsHelper.cs deleted file mode 100644 index 4ca517d5b82314..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/DevToolsHelper.cs +++ /dev/null @@ -1,598 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Microsoft.WebAssembly.Diagnostics -{ - public struct SessionId : IEquatable - { - public readonly string sessionId; - - public SessionId(string sessionId) - { - this.sessionId = sessionId; - } - - // hashset treats 0 as unset - public override int GetHashCode() => sessionId?.GetHashCode() ?? -1; - - public override bool Equals(object obj) => obj is SessionId other && Equals(other); - - public bool Equals(SessionId other) => other.sessionId == sessionId; - - public static bool operator ==(SessionId a, SessionId b) => a.sessionId == b.sessionId; - - public static bool operator !=(SessionId a, SessionId b) => a.sessionId != b.sessionId; - - public static SessionId Null { get; } - - public override string ToString() => $"session-{sessionId}"; - } - - public class MessageId : IEquatable - { - public readonly string sessionId; - public readonly int id; - - public MessageId(string sessionId, int id) - { - this.sessionId = sessionId; - this.id = id; - } - - public static implicit operator SessionId(MessageId id) => new SessionId(id.sessionId); - - public override string ToString() => $"msg-{sessionId}:::{id}"; - - public override int GetHashCode() => id; - - public override bool Equals(object obj) => obj is MessageId other && Equals(other); - - public bool Equals(MessageId other) => other.id == id; - } - - internal sealed class DotnetObjectId - { - private readonly int? _intValue; - - public string Scheme { get; } - public int Value - { - get - { - if (_intValue == null) - throw new ArgumentException($"DotnetObjectId (scheme: {Scheme}, ValueAsJson: {ValueAsJson}) does not have an int value"); - return _intValue.Value; - } - } - public int SubValue { get; set; } - public bool IsValueType => Scheme == "valuetype"; - - public JObject ValueAsJson { get; init; } - - public static bool TryParse(JToken jToken, out DotnetObjectId objectId) => TryParse(jToken?.Value(), out objectId); - - public static bool TryParse(string id, out DotnetObjectId objectId) - { - objectId = null; - if (id == null) - return false; - - if (!id.StartsWith("dotnet:")) - return false; - - string[] parts = id.Split(":", 3); - - if (parts.Length < 3) - return false; - - objectId = new DotnetObjectId(parts[1], parts[2]); - return true; - } - - public DotnetObjectId(string scheme, int value) - : this(scheme, value.ToString()) { } - - public DotnetObjectId(string scheme, string value) - { - Scheme = scheme; - if (int.TryParse(value, out int ival)) - { - _intValue = ival; - } - else - { - try - { - ValueAsJson = JObject.Parse(value); - } - catch (JsonReaderException) { } - } - } - - public override string ToString() - => _intValue != null - ? $"dotnet:{Scheme}:{_intValue}" - : $"dotnet:{Scheme}:{ValueAsJson}"; - } - - public struct Result - { - public JObject Value { get; private set; } - public JObject Error { get; private set; } - public JObject FullContent { get; private set; } - - public bool IsOk => Error == null; - - private Result(JObject resultOrError, bool isError, JObject fullContent = null) - { - ArgumentNullException.ThrowIfNull(resultOrError); - - bool resultHasError = isError || string.Equals((resultOrError["result"] as JObject)?["subtype"]?.Value(), "error"); - resultHasError |= resultOrError["exceptionDetails"] != null; - if (resultHasError) - { - Value = null; - Error = resultOrError; - } - else - { - Value = resultOrError; - Error = null; - } - FullContent = fullContent; - } - public static Result FromJson(JObject obj) - { - var error = obj["error"] as JObject; - if (error != null) - return new Result(error, true); - var result = (obj["result"] as JObject) ?? new JObject(); - return new Result(result, false); - } - public static Result FromJsonFirefox(JObject obj) - { - //Log ("protocol", $"from result: {obj}"); - JObject o; - if (obj["ownProperties"] != null && obj["prototype"]?["class"]?.Value() == "Array") - { - var ret = new JArray(); - var arrayItems = obj["ownProperties"]; - foreach (JProperty arrayItem in arrayItems) - { - if (arrayItem.Name != "length") - ret.Add(arrayItem.Value["value"]); - } - o = JObject.FromObject(new - { - result = new - { - value = ret - } - }); - } - else if (obj["result"] is JObject && obj["result"]?["type"]?.Value() == "object") - { - if (obj["result"]["class"].Value() == "Array") - { - o = JObject.FromObject(new - { - result = new - { - value = obj["result"]["preview"]["items"] - } - }); - } - else if (obj["result"]?["preview"] != null) - { - o = JObject.FromObject(new - { - result = new - { - value = obj["result"]?["preview"]?["ownProperties"]?["value"] - } - }); - } - else - { - o = JObject.FromObject(new - { - result = new - { - value = obj["result"] - } - }); - } - } - else if (obj["result"] != null) - { - o = JObject.FromObject(new - { - result = new - { - value = obj["result"], - type = obj["resultType"], - description = obj["resultDescription"] - } - }); - } - else - { - o = JObject.FromObject(new - { - result = new - { - value = obj - } - }); - } - bool resultHasError = obj["hasException"] != null && obj["hasException"].Value(); - if (resultHasError) - { - return new Result(obj["exception"] as JObject, resultHasError, obj); - } - return new Result(o, false, obj); - } - - public static Result Ok(JObject ok) => new Result(ok, false); - - public static Result OkFromObject(object ok) => Ok(JObject.FromObject(ok)); - - public static Result Err(JObject err) => new Result(err, true); - - public static Result Err(string msg) => new Result(JObject.FromObject(new { message = msg }), true); - - public static Result UserVisibleErr(JObject result) => new Result { Value = result }; - - public static Result Exception(Exception e) => new Result(JObject.FromObject(new { message = e.Message }), true); - - public JObject ToJObject(MessageId target) - { - if (IsOk) - { - return JObject.FromObject(new - { - target.id, - target.sessionId, - result = Value - }); - } - else - { - return JObject.FromObject(new - { - target.id, - target.sessionId, - error = Error - }); - } - } - - public override string ToString() - { - return $"[Result: IsOk: {IsOk}, IsErr: {!IsOk}, Value: {Value?.ToString()}, Error: {Error?.ToString()} ]"; - } - } - - internal sealed class MonoCommands - { - public string expression { get; set; } - public string objectGroup { get; set; } = "mono-debugger"; - public bool includeCommandLineAPI { get; set; } - public bool silent { get; set; } - public bool returnByValue { get; set; } = true; - - public MonoCommands(string expression) - { - this.expression = $"{expression} //# sourceURL=cdp://debug/eval.cdp"; - } - - public static MonoCommands GetDebuggerAgentBufferReceived(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_get_dbg_command_info()"); - - public static MonoCommands IsRuntimeReady(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_runtime_is_ready"); - - public static MonoCommands GetLoadedFiles(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_get_loaded_files()"); - - public static MonoCommands SetDebuggerAttached(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_debugger_attached()"); - - public static MonoCommands SendDebuggerAgentCommand(int runtimeId, int id, int command_set, int command, string command_parameters) - { - return new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_send_dbg_command ({id}, {command_set}, {command},'{command_parameters}')"); - } - - public static MonoCommands SendDebuggerAgentCommandWithParms(int runtimeId, int id, int command_set, int command, string command_parameters, int len, int type, string parm) - { - return new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_send_dbg_command_with_parms ({id}, {command_set}, {command},'{command_parameters}', {len}, {type}, '{parm}')"); - } - - public static MonoCommands CallFunctionOn(int runtimeId, JToken args) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_call_function_on ({args})"); - - public static MonoCommands GetDetails(int runtimeId, int objectId, JToken args = null) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_get_details ({objectId}, {(args ?? "{ }")})"); - - public static MonoCommands Resume(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_debugger_resume ()"); - - public static MonoCommands DetachDebugger(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_detach_debugger()"); - - public static MonoCommands ReleaseObject(int runtimeId, DotnetObjectId objectId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_release_object('{objectId}')"); - - public static MonoCommands GetWasmFunctionIds(int runtimeId) => new MonoCommands($"getDotnetRuntime({runtimeId}).INTERNAL.mono_wasm_get_func_id_to_name_mappings()"); - } - - internal enum MonoErrorCodes - { - BpNotFound = 100000, - } - - internal static class MonoConstants - { - public const string EVENT_RAISED = "mono_wasm_debug_event_raised:aef14bca-5519-4dfe-b35a-f867abc123ae"; - } - - internal sealed class Frame - { - public Frame(MethodInfoWithDebugInformation method, SourceLocation location, int id) - { - this.Method = method; - this.Location = location; - this.Id = id; - } - - public MethodInfoWithDebugInformation Method { get; private set; } - public SourceLocation Location { get; private set; } - public int Id { get; private set; } - } - - internal sealed class Breakpoint - { - public SourceLocation Location { get; private set; } - public int RemoteId { get; set; } - public BreakpointState State { get; set; } - public string StackId { get; private set; } - public string Condition { get; set; } - public bool ConditionAlreadyEvaluatedWithError { get; set; } - public static bool TryParseId(string stackId, out int id) - { - id = -1; - if (stackId?.StartsWith("dotnet:", StringComparison.Ordinal) != true) - return false; - - return int.TryParse(stackId.AsSpan("dotnet:".Length), out id); - } - - public Breakpoint(string stackId, SourceLocation loc, string condition, BreakpointState state) - { - this.StackId = stackId; - this.Location = loc; - this.State = state; - this.Condition = condition; - this.ConditionAlreadyEvaluatedWithError = false; - } - } - - internal enum BreakpointState - { - Active, - Disabled, - Pending - } - - internal enum StepKind - { - Into, - Over, - Out - } - - internal enum PauseOnExceptionsKind - { - Unset, - None, - Uncaught, - All - } - - internal class ExecutionContext - { - public ExecutionContext(MonoSDBHelper sdbAgent, int id, object auxData, PauseOnExceptionsKind pauseOnExceptions) - { - Id = id; - AuxData = auxData; - SdbAgent = sdbAgent; - PauseOnExceptions = pauseOnExceptions; - Destroyed = false; - FrameworkScriptList = new(); - } - public ExecutionContext CreateChildAsyncExecutionContext(SessionId sessionId) - => new ExecutionContext(null, Id, AuxData, PauseOnExceptions) - { - ParentContext = this, - SessionId = sessionId - }; - public bool CopyDataFromParentContext() - { - if (SdbAgent != null) - return false; - ready = ParentContext.ready; - store = ParentContext.store; - Source = ParentContext.Source; - SdbAgent = ParentContext.SdbAgent.Clone(SessionId); - return true; - } - public string DebugId { get; set; } - public Dictionary BreakpointRequests { get; } = new Dictionary(); - public int breakpointId; - public TaskCompletionSource ready; - public bool IsRuntimeReady => ready != null && ready.Task.IsCompleted; - public bool IsSkippingHiddenMethod { get; set; } - public bool IsSteppingThroughMethod { get; set; } - public bool IsResumedAfterBp { get; set; } - public int ThreadId { get; set; } - public int Id { get; set; } - public ExecutionContext ParentContext { get; private set; } - - public List FrameworkScriptList { get; init; } - public SessionId SessionId { get; private set; } - - public bool PausedOnWasm { get; set; } - - public string PauseKind { get; set; } - - public object AuxData { get; set; } - - public bool AutoEvaluateProperties { get; set; } - - public PauseOnExceptionsKind PauseOnExceptions { get; set; } - - public List CallStack { get; set; } - - public string[] LoadedFiles { get; set; } - internal DebugStore store; - internal MonoSDBHelper SdbAgent { get; private set; } - public TaskCompletionSource Source { get; private set; } = new TaskCompletionSource(); - - private Dictionary perScopeCaches { get; } = new Dictionary(); - - internal int TempBreakpointForSetNextIP { get; set; } - internal bool FirstBreakpoint { get; set; } - - internal bool Destroyed { get; set; } - - public DebugStore Store - { - get - { - if (store == null || !Source.Task.IsCompleted) - return null; - - return store; - } - } - public string[] WasmFunctionIds { get; internal set; } - - public PerScopeCache GetCacheForScope(int scopeId) - { - if (perScopeCaches.TryGetValue(scopeId, out PerScopeCache cache)) - return cache; - - cache = new PerScopeCache(); - perScopeCaches[scopeId] = cache; - return cache; - } - - public void ClearState() - { - CallStack = null; - SdbAgent.ClearCache(); - perScopeCaches.Clear(); - } - } - - internal sealed class PerScopeCache - { - public Dictionary Locals { get; } = new Dictionary(); - public Dictionary MemberReferences { get; } = new Dictionary(); - public Dictionary ObjectFields { get; } = new Dictionary(); - public Dictionary EvaluationResults { get; } = new(); - public PerScopeCache(JArray objectValues) - { - foreach (var objectValue in objectValues) - { - ObjectFields[objectValue["name"].Value()] = objectValue.Value(); - } - } - public PerScopeCache() - { - } - } - - internal sealed class ConcurrentExecutionContextDictionary - { - private ConcurrentDictionary> contexts = new(); - public ExecutionContext GetCurrentContext(SessionId sessionId) - => TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context) - ? context - : throw new KeyNotFoundException($"No execution context found for session {sessionId}"); - - public bool TryGetCurrentExecutionContextValue(SessionId id, out ExecutionContext executionContext, bool ignoreDestroyedContext = true) - { - executionContext = null; - if (!contexts.TryGetValue(id, out ConcurrentBag contextBag)) - return false; - if (contextBag.IsEmpty) - return false; - IEnumerable validContexts = null; - if (ignoreDestroyedContext) - validContexts = contextBag.Where(context => !context.Destroyed); - else - validContexts = contextBag; - if (!validContexts.Any()) - return false; - int maxId = validContexts.Max(context => context.Id); - executionContext = contextBag.FirstOrDefault(context => context.Id == maxId); - return executionContext != null; - } - - public void OnDefaultContextUpdate(SessionId sessionId, ExecutionContext newContext) - { - if (TryGetAndAddContext(sessionId, newContext, out ExecutionContext previousContext)) - { - foreach (KeyValuePair kvp in previousContext.BreakpointRequests) - { - newContext.BreakpointRequests[kvp.Key] = kvp.Value.Clone(); - } - newContext.PauseOnExceptions = previousContext.PauseOnExceptions; - } - } - - public bool TryGetAndAddContext(SessionId sessionId, ExecutionContext newExecutionContext, out ExecutionContext previousExecutionContext) - { - bool hasExisting = TryGetCurrentExecutionContextValue(sessionId, out previousExecutionContext, ignoreDestroyedContext: false); - ConcurrentBag bag = contexts.GetOrAdd(sessionId, _ => new ConcurrentBag()); - bag.Add(newExecutionContext); - return hasExisting; - } - - public void CreateWorkerExecutionContext(SessionId workerSessionId, SessionId originSessionId, ILogger logger) - { - if (!TryGetCurrentExecutionContextValue(originSessionId, out ExecutionContext context)) - { - logger.LogDebug($"Origin sessionId does not exist - {originSessionId}"); - return; - } - if (contexts.ContainsKey(workerSessionId)) - { - logger.LogDebug($"Worker sessionId already exists - {originSessionId}"); - return; - } - contexts[workerSessionId] = new(); - contexts[workerSessionId].Add(context.CreateChildAsyncExecutionContext(workerSessionId)); - } - - public void DestroyContext(SessionId sessionId, int id) - { - if (!contexts.TryGetValue(sessionId, out ConcurrentBag contextBag)) - return; - foreach (ExecutionContext context in contextBag.Where(x => x.Id == id).ToList()) - context.Destroyed = true; - } - public void ClearContexts(SessionId sessionId) - { - if (!contexts.TryGetValue(sessionId, out ConcurrentBag contextBag)) - return; - foreach (ExecutionContext context in contextBag) - context.Destroyed = true; - } - public bool ContainsKey(SessionId sessionId) => contexts.ContainsKey(sessionId); - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/DevToolsProxy.cs b/src/mono/browser/debugger/BrowserDebugProxy/DevToolsProxy.cs deleted file mode 100644 index 0b5c83d8e8480e..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/DevToolsProxy.cs +++ /dev/null @@ -1,310 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Channels; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Microsoft.WebAssembly.Diagnostics -{ - internal class DevToolsProxy - { - protected Dictionary> pending_cmds = new Dictionary>(); - protected DevToolsQueue browser; - protected DevToolsQueue ide; - private int next_cmd_id; - protected readonly ILogger logger; - protected RunLoop _runLoop; - private readonly string _loggerId; - - public event EventHandler RunLoopStopped; - public bool IsRunning => _runLoop?.IsRunning == true; - public RunLoopExitState Stopped => _runLoop?.StoppedState; - - protected readonly ProxyOptions _options; - public DevToolsProxy(ProxyOptions options, ILogger logger, string loggerId) - { - _loggerId = loggerId; - _options = options; - this.logger = logger; - } - - protected int GetNewCmdId() => Interlocked.Increment(ref next_cmd_id); - protected int ResetCmdId() => next_cmd_id = 0; - protected virtual Task AcceptEvent(SessionId sessionId, JObject args, CancellationToken token) - { - return Task.FromResult(false); - } - - protected virtual Task AcceptCommand(MessageId id, JObject args, CancellationToken token) - { - return Task.FromResult(false); - } - - protected Task Send(DevToolsQueue queue, JObject o, CancellationToken token) - { - Log("protocol", $"to-{queue.Id}: {GetFromOrTo(o)} {o}"); - var msg = o.ToString(Formatting.None); - var bytes = Encoding.UTF8.GetBytes(msg); - - return _runLoop.Send(bytes, token, queue); - } - - protected virtual async Task OnEvent(SessionId sessionId, JObject parms, CancellationToken token) - { - try - { - if (!await AcceptEvent(sessionId, parms, token)) - { - var method = parms["method"].Value(); - var args = parms["params"] as JObject; - //logger.LogDebug ("proxy browser: {0}::{1}",method, args); - await SendEventInternal(sessionId, method, args, token); - } - } - catch (Exception e) - { - _runLoop.Fail(e); - } - } - - protected virtual async Task OnCommand(MessageId id, JObject parms, CancellationToken token) - { - try - { - if (!await AcceptCommand(id, parms, token)) - { - var method = parms["method"].Value(); - var args = parms["params"] as JObject; - Result res = await SendCommandInternal(id, method, args, token); - await SendResponseInternal(id, res, token); - } - } - catch (Exception e) - { - _runLoop.Fail(e); - } - } - - protected virtual void OnResponse(MessageId id, Result result) - { - //logger.LogTrace ("got id {0} res {1}", id, result); - // Fixme - if (pending_cmds.Remove(id, out TaskCompletionSource task)) - { - task.SetResult(result); - return; - } - logger.LogError($"Cannot respond to command: {id} with result: {result} - command is not pending"); - } - - protected virtual Task ProcessBrowserMessage(string msg, CancellationToken token) - { - try - { - var res = JObject.Parse(msg); - - //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled") - Log("protocol", $"browser: {msg}"); - - if (res["id"] == null) - { - return OnEvent(res.ToObject(), res, token); - } - else - { - OnResponse(res.ToObject(), Result.FromJson(res)); - return null; - } - } - catch (Exception ex) - { - _runLoop.Fail(ex); - throw; - } - } - - protected virtual Task ProcessIdeMessage(string msg, CancellationToken token) - { - try - { - Log("protocol", $"ide: {msg}"); - if (!string.IsNullOrEmpty(msg)) - { - var res = JObject.Parse(msg); - var id = res.ToObject(); - return OnCommand( - id, - res, - token); - } - - return null; - } - catch (Exception ex) - { - _runLoop.Fail(ex); - throw; - } - } - - public virtual async Task SendCommand(SessionId id, string method, JObject args, CancellationToken token) - { - // Log ("protocol", $"sending command {method}: {args}"); - return await SendCommandInternal(id, method, args, token); - } - - protected virtual async Task SendCommandInternal(SessionId sessionId, string method, JObject args, CancellationToken token) - { - int id = GetNewCmdId(); - - var o = JObject.FromObject(new - { - id, - method, - @params = args - }); - if (sessionId.sessionId != null) - o["sessionId"] = sessionId.sessionId; - var tcs = new TaskCompletionSource(); - - var msgId = new MessageId(sessionId.sessionId, id); - pending_cmds[msgId] = tcs; - - await Send(browser, o, token); - return await tcs.Task; - } - - public virtual Task SendEvent(SessionId sessionId, string method, JObject args, CancellationToken token) - { - // logger.LogTrace($"sending event {method}: {args}"); - return SendEventInternal(sessionId, method, args, token); - } - - protected virtual Task SendEventInternal(SessionId sessionId, string method, JObject args, CancellationToken token) - { - var o = JObject.FromObject(new - { - method, - @params = args - }); - if (sessionId.sessionId != null) - o["sessionId"] = sessionId.sessionId; - - return Send(ide, o, token); - } - - public virtual void SendResponse(MessageId id, Result result, CancellationToken token) - { - SendResponseInternal(id, result, token); - } - - protected virtual Task SendResponseInternal(MessageId id, Result result, CancellationToken token) - { - JObject o = result.ToJObject(id); - if (!result.IsOk) - logger.LogDebug($"sending error response for id: {id} -> {result}"); - - return Send(this.ide, o, token); - } - - public virtual Task ForwardMessageToIde(JObject msg, CancellationToken token) - { - // logger.LogTrace($"to-ide: forwarding {GetFromOrTo(msg)} {msg}"); - return Send(ide, msg, token); - } - - public virtual Task ForwardMessageToBrowser(JObject msg, CancellationToken token) - { - // logger.LogTrace($"to-browser: forwarding {GetFromOrTo(msg)} {msg}"); - return Send(this.browser, msg, token); - } - - public async Task RunForDevTools(Uri browserUri, WebSocket ideSocket, CancellationTokenSource cts) - { - try - { - logger.LogDebug($"DevToolsProxy: Starting for browser at {browserUri}"); - logger.LogDebug($"DevToolsProxy: Proxy waiting for connection to the browser at {browserUri}"); - - ClientWebSocket browserSocket = new(); - browserSocket.Options.KeepAliveInterval = Timeout.InfiniteTimeSpan; - var proxy = WebRequest.DefaultWebProxy; - if (_options is not null && _options.IgnoreProxyForLocalAddress && proxy is not null && !proxy.IsBypassed(browserUri)) //only bypass the proxy for local addresses if it is not already an exception in the OS settings - browserSocket.Options.Proxy = new WebProxy(proxy.GetProxy(browserUri), true); - await browserSocket.ConnectAsync(browserUri, cts.Token); - - using var ideConn = new DevToolsDebuggerConnection(ideSocket, "ide", logger); - using var browserConn = new DevToolsDebuggerConnection(browserSocket, "browser", logger); - - await RunLoopAsync(ideConn: ideConn, browserConn: browserConn, cts); - } - catch (Exception ex) - { - logger.LogError($"DevToolsProxy.Run: {ex}"); - throw; - } - } - - protected async Task RunLoopAsync(WasmDebuggerConnection ideConn, WasmDebuggerConnection browserConn, CancellationTokenSource cts) - { - try - { - this.ide = new DevToolsQueue(ideConn); - this.browser = new DevToolsQueue(browserConn); - ideConn.OnReadAsync = ProcessIdeMessage; - browserConn.OnReadAsync = ProcessBrowserMessage; - _runLoop = new(new[] { ide, browser }, logger); - _runLoop.RunLoopStopped += RunLoopStopped; - await _runLoop.RunAsync(cts); - } - finally - { - _runLoop?.Dispose(); - _runLoop = null; - } - } - - public virtual void Shutdown() => _runLoop?.Shutdown(); - public void Fail(Exception exception) => _runLoop?.Fail(exception); - - protected virtual string GetFromOrTo(JObject o) => string.Empty; - - protected void Log(string priority, string msg) - { - if (priority == "protocol") - msg = msg.TruncateLogMessage(); - - switch (priority) - { - case "protocol": - logger.LogTrace(msg); - break; - case "verbose": - logger.LogDebug(msg); - break; - case "error": - logger.LogError(msg); - break; - case "info": - logger.LogInformation(msg); - break; - case "warning": - logger.LogWarning(msg); - break; - default: - logger.LogDebug(msg); - break; - } - } - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/browser/debugger/BrowserDebugProxy/EvaluateExpression.cs deleted file mode 100644 index bbbd5759f70cb2..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ /dev/null @@ -1,567 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Scripting; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Emit; -using Microsoft.CodeAnalysis.Scripting; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Text.RegularExpressions; -using System.Globalization; -using Microsoft.Extensions.Logging; - -namespace Microsoft.WebAssembly.Diagnostics -{ - internal sealed record VariableDefinition( - string IdName, - JObject Obj, - string Definition); - - internal static partial class ExpressionEvaluator - { - internal static Script script = CSharpScript.Create( - "", - ScriptOptions.Default.WithReferences( - typeof(object).Assembly, - typeof(Enumerable).Assembly, - typeof(JObject).Assembly - )); - private sealed partial class ExpressionSyntaxReplacer : CSharpSyntaxWalker - { - [GeneratedRegex(@"[^A-Za-z0-9_]", RegexOptions.Singleline)] - private static partial Regex RegexForReplaceVarName { get; } - - public List identifiers = new List(); - public List methodCalls = new List(); - public List memberAccesses = new List(); - public List elementAccess = new List(); - public List argValues = new List(); - public Dictionary memberAccessValues = new Dictionary(); - private int visitCount; - public bool hasMethodCalls; - public bool hasElementAccesses; - public bool hasStringExpressionStatement; - internal List variableDefinitions = new(); - - public void VisitInternal(SyntaxNode node) - { - Visit(node); - visitCount++; - } - public override void Visit(SyntaxNode node) - { - // TODO: PointerMemberAccessExpression - if (visitCount == 0) - { - if (node is MemberAccessExpressionSyntax maes - && node.IsKind(SyntaxKind.SimpleMemberAccessExpression) - && !(node.Parent is MemberAccessExpressionSyntax) - && !(node.Parent is InvocationExpressionSyntax) - && !(node.Parent is ElementAccessExpressionSyntax)) - { - memberAccesses.Add(maes); - } - - if (node is IdentifierNameSyntax identifier - && !(identifier.Parent is MemberAccessExpressionSyntax) - && !(identifier.Parent is InvocationExpressionSyntax) - && !(node.Parent is ElementAccessExpressionSyntax) - && !identifiers.Any(x => x.Identifier.Text == identifier.Identifier.Text)) - { - identifiers.Add(identifier); - } - } - - if (node is InvocationExpressionSyntax) - { - if (visitCount == 1) - methodCalls.Add(node as InvocationExpressionSyntax); - hasMethodCalls = true; - } - - if (node is ElementAccessExpressionSyntax) - { - if (visitCount == 1) - elementAccess.Add(node as ElementAccessExpressionSyntax); - hasElementAccesses = true; - } - - if (node is BinaryExpressionSyntax) - { - var binaryExpression = node as BinaryExpressionSyntax; - if (binaryExpression.Left.Kind() == SyntaxKind.StringLiteralExpression || binaryExpression.Right.Kind() == SyntaxKind.StringLiteralExpression) - hasStringExpressionStatement = true; - } - - if (node is AssignmentExpressionSyntax) - throw new Exception("Assignment is not implemented yet"); - base.Visit(node); - } - - public SyntaxTree ReplaceVars(SyntaxTree syntaxTree, IEnumerable ma_values, IEnumerable id_values, IEnumerable method_values, IEnumerable ea_values) - { - var memberAccessToParamName = new Dictionary(); - var methodCallToParamName = new Dictionary(); - var elementAccessToParamName = new Dictionary(); - - CompilationUnitSyntax root = syntaxTree.GetCompilationUnitRoot(); - - // 1. Replace all this.a occurrences with this_a_ABDE - root = root.ReplaceNodes(memberAccesses, (maes, _) => - { - string ma_str = maes.ToString(); - if (!memberAccessToParamName.TryGetValue(ma_str, out string id_name)) - { - // Generate a random suffix - string suffix = Guid.NewGuid().ToString().Substring(0, 5); - string prefix = RegexForReplaceVarName.Replace(ma_str, "_"); - id_name = $"{prefix}_{suffix}"; - - memberAccessToParamName[ma_str] = id_name; - } - - return SyntaxFactory.IdentifierName(id_name); - }); - - // 1.1 Replace all this.a() occurrences with this_a_ABDE - root = root.ReplaceNodes(methodCalls, (m, _) => - { - string iesStr = m.ToString(); - if (!methodCallToParamName.TryGetValue(iesStr, out string id_name)) - { - // Generate a random suffix - string suffix = Guid.NewGuid().ToString().Substring(0, 5); - string prefix = RegexForReplaceVarName.Replace(iesStr, "_"); - id_name = $"{prefix}_{suffix}"; - methodCallToParamName[iesStr] = id_name; - } - - return SyntaxFactory.IdentifierName(id_name); - }); - - // 1.2 Replace all this.a[x] occurrences with this_a_ABDE - root = root.ReplaceNodes(elementAccess, (ea, _) => - { - string eaStr = ea.ToString(); - if (!elementAccessToParamName.TryGetValue(eaStr, out string id_name)) - { - // Generate a random suffix - string suffix = Guid.NewGuid().ToString().Substring(0, 5); - string prefix = RegexForReplaceVarName.Replace(eaStr, "_"); - id_name = $"{prefix}_{suffix}"; - elementAccessToParamName[eaStr] = id_name; - } - - return SyntaxFactory.IdentifierName(id_name); - }); - - var localsSet = new HashSet(); - - // 2. For every unique member ref, add a corresponding method param - if (ma_values != null) - { - foreach ((MemberAccessExpressionSyntax maes, JObject value) in memberAccesses.Zip(ma_values)) - { - string node_str = maes.ToString(); - if (!memberAccessToParamName.TryGetValue(node_str, out string id_name)) - { - throw new Exception($"BUG: Expected to find an id name for the member access string: {node_str}"); - } - memberAccessValues[id_name] = value; - AddLocalVariableWithValue(id_name, value); - } - // do not replace memberAccesses that were already replaced - memberAccesses = new List(); - } - - if (id_values != null) - { - foreach ((IdentifierNameSyntax idns, JObject value) in identifiers.Zip(id_values)) - { - AddLocalVariableWithValue(idns.Identifier.Text, value); - } - } - - if (method_values != null) - { - foreach ((InvocationExpressionSyntax ies, JObject value) in methodCalls.Zip(method_values)) - { - string node_str = ies.ToString(); - if (!methodCallToParamName.TryGetValue(node_str, out string id_name)) - { - throw new Exception($"BUG: Expected to find an id name for the invokation expression string: {node_str}"); - } - AddLocalVariableWithValue(id_name, value); - } - } - - if (ea_values != null) - { - foreach ((ElementAccessExpressionSyntax eas, JObject value) in elementAccess.Zip(ea_values)) - { - string node_str = eas.ToString(); - if (!elementAccessToParamName.TryGetValue(node_str, out string id_name)) - { - throw new Exception($"BUG: Expected to find an id name for the element access string: {node_str}"); - } - AddLocalVariableWithValue(id_name, value); - } - } - - return syntaxTree.WithRootAndOptions(root, syntaxTree.Options); - - void AddLocalVariableWithValue(string idName, JObject value) - { - if (localsSet.Contains(idName)) - return; - localsSet.Add(idName); - variableDefinitions.Add(new(idName, value, ConvertJSToCSharpLocalVariableAssignment(idName, value))); - } - } - } - - public static string ConvertJSToCSharpLocalVariableAssignment(string idName, JToken variable) - { - string typeRet; - object valueRet; - JToken value = variable["value"]; - string type = variable["type"].Value(); - string subType = variable["subtype"]?.Value(); - switch (type) - { - case "string": - { - var str = value?.Value(); - str = str.Replace("\"", "\\\""); - valueRet = $"\"{str}\""; - typeRet = "string"; - break; - } - case "symbol": - { - valueRet = $"'{value?.Value()}'"; - typeRet = "char"; - break; - } - case "number": - //casting to double and back to string would loose precision; so casting straight to string - valueRet = value?.Value(); - typeRet = "double"; - break; - case "boolean": - valueRet = value?.Value().ToLowerInvariant(); - typeRet = "bool"; - break; - case "object": - if (variable["subtype"]?.Value() == "null") - { - (valueRet, typeRet) = GetNullObject(variable["className"]?.Value()); - } - else - { - if (!DotnetObjectId.TryParse(variable["objectId"], out DotnetObjectId objectId)) - throw new Exception($"Internal error: Cannot parse objectId for var {idName}, with value: {variable}"); - - switch (objectId?.Scheme) - { - case "valuetype" when variable["isEnum"]?.Value() == true: - typeRet = variable["className"]?.Value(); - valueRet = $"({typeRet}) {value["value"].Value()}"; - break; - case "object": - default: - valueRet = "Newtonsoft.Json.Linq.JObject.FromObject(new {" - + $"type = \"{type}\"" - + $", description = \"{variable["description"].Value()}\"" - + $", className = \"{variable["className"].Value()}\"" - + (subType != null ? $", subtype = \"{subType}\"" : "") - + (objectId != null ? $", objectId = \"{objectId}\"" : "") - + "})"; - typeRet = "object"; - break; - } - } - break; - case "void": - (valueRet, typeRet) = GetNullObject("object"); - break; - default: - throw new Exception($"Evaluate of this datatype {type} not implemented yet");//, "Unsupported"); - } - return $"{typeRet} {idName} = {valueRet};"; - - static (string, string) GetNullObject(string className = "object") - => ("Newtonsoft.Json.Linq.JObject.FromObject(new {" - + $"type = \"object\"," - + $"description = \"object\"," - + $"className = \"{className}\"," - + $"subtype = \"null\"" - + "})", - "object"); - } - - private static async Task> Resolve(IList collectionToResolve, MemberReferenceResolver resolver, - Func> resolutionFunc, CancellationToken token) - { - var values = new List(); - foreach (T element in collectionToResolve) - values.Add(await resolutionFunc(element, resolver, token)); - return values; - } - - private static async Task ResolveMemberAccessExpression(MemberAccessExpressionSyntax memberAccess, - MemberReferenceResolver resolver, CancellationToken token) - { - string memberAccessString = memberAccess.ToString(); - JObject value = await resolver.Resolve(memberAccessString, token); - return value ?? throw new ReturnAsErrorException($"Failed to resolve member access for {memberAccessString}", "ReferenceError"); - } - - private static async Task ResolveIdentifier(IdentifierNameSyntax identifier, - MemberReferenceResolver resolver, CancellationToken token) - { - JObject value = await resolver.Resolve(identifier.Identifier.Text, token); - return value ?? throw new ReturnAsErrorException($"The name {identifier.Identifier.Text} does not exist in the current context", "ReferenceError"); - } - - private static async Task<(IList, IList, IList)> ResolveMethodCalls(ExpressionSyntaxReplacer replacer, MemberReferenceResolver resolver, CancellationToken token) - { - var methodCallValues = new List(capacity: replacer.methodCalls.Count); - // used for replacing method call on primitive: - var maesValues = new List(capacity: replacer.methodCalls.Count); - var identifierValues = new List(capacity: replacer.methodCalls.Count); - InvocationExpressionSyntax[] methodCallsCopy = replacer.methodCalls.ToArray(); - foreach (InvocationExpressionSyntax methodCall in methodCallsCopy) - { - JObject value = await resolver.Resolve(methodCall, replacer.memberAccessValues, token); - if (value == null) - { - await ReplaceMethodCall(methodCall); - continue; - } - methodCallValues.Add(value); - } - return (methodCallValues, maesValues, identifierValues); - - async Task ReplaceMethodCall(InvocationExpressionSyntax method) - { - /* - Instead of invoking the method on the primitive type in the runtime, - we emit a local for the primitive, and emit the method call itself - in the script. For example: - double test_propUlong_2c64c = 12; - return (test_propUlong_2c64c.ToString()); - */ - replacer.methodCalls.Remove(method); - if (method.Expression is MemberAccessExpressionSyntax mses) - { - // primitive is a member field: - if (mses.Expression is MemberAccessExpressionSyntax msesExpr) - { - replacer.memberAccesses.Add(msesExpr); - maesValues.Add(await ResolveMemberAccessExpression(msesExpr, resolver, token)); - } - // primitive is a local value: - else if (mses.Expression is IdentifierNameSyntax identifierExpr) - { - replacer.identifiers.Add(identifierExpr); - identifierValues.Add(await ResolveIdentifier(identifierExpr, resolver, token)); - } - } - } - } - - private static async Task> ResolveElementAccess(ExpressionSyntaxReplacer replacer, MemberReferenceResolver resolver, CancellationToken token) - { - var values = new List(); - JObject index = null; - List nestedIndexers = new(); - IEnumerable elementAccesses = replacer.elementAccess; - foreach (ElementAccessExpressionSyntax elementAccess in elementAccesses.Reverse()) - { - index = await resolver.Resolve(elementAccess, replacer.memberAccessValues, nestedIndexers, replacer.variableDefinitions, token); - if (index == null) - throw new ReturnAsErrorException($"Failed to resolve element access for {elementAccess}", "ReferenceError"); - nestedIndexers.Add(index); - } - values.Add(index); - return values; - } - - internal static async Task CompileAndRunTheExpression( - string expression, MemberReferenceResolver resolver, ILogger logger, CancellationToken token) - { - expression = expression.Trim(); - if (!expression.StartsWith('(')) - { - expression = "(" + expression + "\n)"; - } - SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(expression + @";", cancellationToken: token); - - CompilationUnitSyntax expressionTree = syntaxTree.GetCompilationUnitRoot(token); - if (expressionTree == null) - throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree"); - ExpressionSyntaxReplacer replacer = new ExpressionSyntaxReplacer(); - replacer.VisitInternal(expressionTree); - // this fails with `"a)"` - // because the code becomes: return (a)); - // and the returned expression from GetExpressionFromSyntaxTree is `a`! - if (expressionTree.IsKind(SyntaxKind.IdentifierName) || expressionTree.IsKind(SyntaxKind.ThisExpression)) - { - string varName = expressionTree.ToString(); - JObject value = await resolver.Resolve(varName, token); - if (value == null) - throw new ReturnAsErrorException($"Cannot find member named '{varName}'.", "ReferenceError"); - - return value; - } - - IList memberAccessValues = await Resolve(replacer.memberAccesses, resolver, ResolveMemberAccessExpression, token); - IList identifierValues = await Resolve(replacer.identifiers, resolver, ResolveIdentifier, token); - syntaxTree = replacer.ReplaceVars(syntaxTree, memberAccessValues, identifierValues, null, null); - - // eg. "this.dateTime", " dateTime.TimeOfDay" - if (expressionTree.IsKind(SyntaxKind.SimpleMemberAccessExpression) && replacer.memberAccesses.Count == 1) - { - return memberAccessValues[0]; - } - - if (replacer.hasMethodCalls) - { - expressionTree = syntaxTree.GetCompilationUnitRoot(token); - - replacer.VisitInternal(expressionTree); - - (IList methodValues, IList newMemberAccessValues, IList newIdentifierValues) = - await ResolveMethodCalls(replacer, resolver, token); - syntaxTree = replacer.ReplaceVars(syntaxTree, newMemberAccessValues, newIdentifierValues, methodValues, null); - } - - // eg. "elements[0]" - if (replacer.hasElementAccesses) - { - expressionTree = syntaxTree.GetCompilationUnitRoot(token); - - replacer.VisitInternal(expressionTree); - - IList elementAccessValues = await ResolveElementAccess(replacer, resolver, token); - - syntaxTree = replacer.ReplaceVars(syntaxTree, null, null, null, elementAccessValues); - } - expressionTree = syntaxTree.GetCompilationUnitRoot(token); - if (expressionTree == null) - throw new Exception($"BUG: Unable to evaluate {expression}, could not get expression from the syntax tree"); - var variableDef = await GetVariableDefinitions(resolver, replacer.variableDefinitions, invokeToStringInObject: replacer.hasStringExpressionStatement, token); - return await EvaluateSimpleExpression(resolver, syntaxTree.ToString(), expression, variableDef, logger, token); - } - - internal static async Task> GetVariableDefinitions(MemberReferenceResolver resolver, List variableDefinitions, bool invokeToStringInObject, CancellationToken token) - { - var variableDefStrings = new List(); - foreach (var definition in variableDefinitions) - { - if (!invokeToStringInObject || definition.Obj?["type"]?.Value() != "object") - { - variableDefStrings.Add(definition.Definition); - continue; - } - - if (definition.Obj["subtype"]?.Value()?.Equals("null") == true) - { - variableDefStrings.Add($"string {definition.IdName} = \"\";"); - continue; - } - - if (DotnetObjectId.TryParse(definition.Obj?["objectId"]?.Value(), out DotnetObjectId objectId)) - { - if (objectId.IsValueType) - { - variableDefStrings.Add($"string {definition.IdName} = \"{definition.Obj["description"].Value()}\";"); - } - else - { - var typeIds = await resolver.GetContext().SdbAgent.GetTypeIdsForObject(objectId.Value, withParents: true, token); - var toString = await resolver.GetContext().SdbAgent.InvokeToStringAsync(typeIds, isValueType: false, isEnum: false, objectId.Value, BindingFlags.DeclaredOnly, invokeToStringInObject: true, token); - variableDefStrings.Add($"string {definition.IdName} = \"{toString}\";"); - } - } - else - { - variableDefStrings.Add(definition.Definition); - } - } - return variableDefStrings; - } - - internal static async Task EvaluateSimpleExpression( - MemberReferenceResolver resolver, string compiledExpression, string originalExpression, List variableDefinitions, ILogger logger, CancellationToken token) - { - Script newScript = script; - try - { - newScript = script.ContinueWith(string.Join("\n", variableDefinitions) + "\nreturn " + compiledExpression + ";"); - var state = await newScript.RunAsync(cancellationToken: token); - return JObject.FromObject(resolver.ConvertCSharpToJSType(state.ReturnValue, state.ReturnValue.GetType())); - } - catch (CompilationErrorException cee) - { - logger.LogDebug($"Cannot evaluate '{originalExpression}'. Script used to compile it: {newScript.Code}{Environment.NewLine}{cee.Message}"); - throw new ReturnAsErrorException($"Cannot evaluate '{originalExpression}': {cee.Message}", "CompilationError"); - } - catch (Exception ex) - { - throw new Exception($"Internal Error: Unable to run {originalExpression}, error: {ex.Message}.", ex); - } - } - } - - internal sealed class ReturnAsErrorException : Exception - { - private Result _error; - public Result Error - { - get - { - return _error; - } - set { } - } - public ReturnAsErrorException(JObject error) : base(error.ToString()) - => Error = Result.Err(error); - - public ReturnAsErrorException(string message, string className) - : base($"[{className}] {message}") - { - var result = new - { - type = "object", - subtype = "error", - description = message, - className - }; - _error = Result.UserVisibleErr(JObject.FromObject( - new - { - result = result, - exceptionDetails = new - { - exception = result - } - })); - } - - public override string ToString() => $"Error object: {Error}. {base.ToString()}"; - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxDebuggerProxy.cs b/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxDebuggerProxy.cs deleted file mode 100644 index 57a590af6badb4..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxDebuggerProxy.cs +++ /dev/null @@ -1,86 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; - -#nullable enable - -namespace Microsoft.WebAssembly.Diagnostics; - -public class FirefoxDebuggerProxy : DebuggerProxyBase -{ - private static TcpListener? s_tcpListener; - private static int s_nextId; - internal FirefoxMonoProxy? FirefoxMonoProxy { get; private set; } - - [MemberNotNull(nameof(s_tcpListener))] - public static void StartListener(int proxyPort, ILogger logger, int browserPort = -1) - { - if (s_tcpListener is null) - { - // If there is an existing listener on @proxyPort, then use a new dynamic port. - // Blazor always tries to open the same port (specified in @proxyPort) to avoid - // creating a lot of remote debugging connections on firefox - if (proxyPort != 0 && IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpListeners().Any(x => x.Port == proxyPort)) - { - proxyPort = 0; - } - s_tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), proxyPort); - s_tcpListener.Start(); - Console.WriteLine($"Debug proxy for firefox now listening on tcp://{s_tcpListener.LocalEndpoint}." + - (browserPort >= 0 ? $" And expecting firefox at port {browserPort}." : string.Empty)); - } - } - - public static async Task RunServerLoopAsync(int browserPort, int proxyPort, ILoggerFactory loggerFactory, ILogger logger, CancellationToken token, ProxyOptions? options = null) - { - StartListener(proxyPort, logger, browserPort); - while (!token.IsCancellationRequested) - { - TcpClient ideClient = await s_tcpListener.AcceptTcpClientAsync(token); - _ = Task.Run(async () => - { - CancellationTokenSource cts = new(); - try - { - int id = Interlocked.Increment(ref s_nextId); - logger.LogInformation($"IDE connected to the proxy, id: {id}"); - var monoProxy = new FirefoxMonoProxy(loggerFactory.CreateLogger($"{nameof(FirefoxMonoProxy)}-{id}"), id.ToString(), options); - await monoProxy.RunForFirefox(ideClient: ideClient, browserPort, cts); - } - catch (Exception ex) - { - logger.LogError($"{nameof(FirefoxMonoProxy)} crashed with {ex}"); - } - finally - { - cts.Cancel(); - } - }, token) - .ConfigureAwait(false); - } - } - - public async Task RunForTests(int browserPort, int proxyPort, string testId, ILoggerFactory loggerFactory, ILogger logger, CancellationTokenSource cts) - { - StartListener(proxyPort, logger, browserPort); - - TcpClient ideClient = await s_tcpListener.AcceptTcpClientAsync(cts.Token); - FirefoxMonoProxy = new FirefoxMonoProxy(loggerFactory.CreateLogger($"FirefoxMonoProxy-{testId}"), testId); - FirefoxMonoProxy.RunLoopStopped += (_, args) => ExitState = args; - await FirefoxMonoProxy.RunForFirefox(ideClient: ideClient, browserPort, cts); - } - - public override void Shutdown() => FirefoxMonoProxy?.Shutdown(); - public override void Fail(Exception ex) => FirefoxMonoProxy?.Fail(ex); -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxExecutionContext.cs b/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxExecutionContext.cs deleted file mode 100644 index 095970f4c799cb..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxExecutionContext.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Threading; -using System.Threading.Tasks; - -#nullable enable - -namespace Microsoft.WebAssembly.Diagnostics; - -internal sealed class FirefoxExecutionContext : ExecutionContext -{ - public string? ActorName { get; set; } - public string? ThreadName { get; set; } - public string? GlobalName { get; set; } - public Task? LastDebuggerAgentBufferReceived { get; set; } - - public FirefoxExecutionContext(MonoSDBHelper sdbAgent, int id, string actorName) : base(sdbAgent, id, actorName, PauseOnExceptionsKind.Unset) - { - ActorName = actorName; - } - - private int evaluateExpressionResultId; - - public int GetResultID() - { - return Interlocked.Increment(ref evaluateExpressionResultId); - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxMessageId.cs b/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxMessageId.cs deleted file mode 100644 index cc7108dfbdea8c..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxMessageId.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -namespace Microsoft.WebAssembly.Diagnostics; - -public class FirefoxMessageId : MessageId -{ - public readonly string toId; - - public FirefoxMessageId(string? sessionId, int id, string toId) : base(sessionId, id) - { - this.toId = toId; - } - - public static implicit operator SessionId(FirefoxMessageId id) => new SessionId(id.sessionId); - - public override string ToString() => $"msg-{sessionId}:::{id}:::{toId}"; - - public override int GetHashCode() => (sessionId?.GetHashCode() ?? 0) ^ (toId?.GetHashCode() ?? 0) ^ id.GetHashCode(); - - public override bool Equals(object obj) => (obj is FirefoxMessageId) ? ((FirefoxMessageId)obj).sessionId == sessionId && ((FirefoxMessageId)obj).id == id && ((FirefoxMessageId)obj).toId == toId : false; -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs b/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs deleted file mode 100644 index aa53f23afa4986..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs +++ /dev/null @@ -1,1052 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Sockets; -using System.Runtime.ExceptionServices; -using System.Threading; -using System.Threading.Tasks; -using BrowserDebugProxy; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; - -namespace Microsoft.WebAssembly.Diagnostics; - -internal sealed class FirefoxMonoProxy : MonoProxy -{ - public FirefoxMonoProxy(ILogger logger, string loggerId = null, ProxyOptions options = null) : base(logger, loggerId: loggerId, options: options) - { - } - - public FirefoxExecutionContext GetContextFixefox(SessionId sessionId) - { - if (Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) - return context as FirefoxExecutionContext; - throw new ArgumentException($"Invalid Session: \"{sessionId}\"", nameof(sessionId)); - } - - public async Task RunForFirefox(TcpClient ideClient, int portBrowser, CancellationTokenSource cts) - { - TcpClient browserClient = null; - try - { - using var ideConn = new FirefoxDebuggerConnection(ideClient, "ide", logger); - browserClient = new TcpClient(); - using var browserConn = new FirefoxDebuggerConnection(browserClient, "browser", logger); - - logger.LogDebug($"Connecting to the browser at tcp://127.0.0.1:{portBrowser} .."); - await browserClient.ConnectAsync("127.0.0.1", portBrowser); - logger.LogTrace($".. connected to the browser!"); - - await RunLoopAsync(ideConn, browserConn, cts); - if (Stopped?.reason == RunLoopStopReason.Exception) - ExceptionDispatchInfo.Capture(Stopped.exception).Throw(); - } - finally - { - browserClient?.Close(); - ideClient?.Close(); - } - } - - protected override async Task OnEvent(SessionId sessionId, JObject parms, CancellationToken token) - { - try - { - // logger.LogTrace($"OnEvent: {parms}"); - if (!await AcceptEvent(sessionId, parms, token)) - { - await ForwardMessageToIde(parms, token); - } - } - catch (Exception e) - { - _runLoop.Fail(e); - } - } - - protected override async Task OnCommand(MessageId id, JObject parms, CancellationToken token) - { - try - { - // logger.LogDebug($"OnCommand: id: {id}, {parms}"); - if (!await AcceptCommand(id, parms, token)) - { - await ForwardMessageToBrowser(parms, token); - } - } - catch (Exception e) - { - logger.LogError($"OnCommand for id: {id}, {parms} failed: {e}"); - _runLoop.Fail(e); - } - } - - protected override void OnResponse(MessageId id, Result result) - { - if (pending_cmds.Remove(id, out TaskCompletionSource task)) - { - task.SetResult(result); - return; - } - logger.LogError($"Cannot respond to command: {id} with result: {result} - command is not pending"); - } - - protected override Task ProcessBrowserMessage(string msg, CancellationToken token) - { - try - { - logger.LogTrace($"from-browser: {msg}"); - var res = JObject.Parse(msg); - if (res["error"] is not null) - logger.LogDebug($"from-browser: {res}"); - - //if (method != "Debugger.scriptParsed" && method != "Runtime.consoleAPICalled") - - if (res["prototype"] != null || res["frames"] != null) - { - var msgId = new FirefoxMessageId(null, 0, res["from"].Value()); - // if (pending_cmds.ContainsKey(msgId)) - { - // HACK for now, as we don't correctly handle responses yet - OnResponse(msgId, Result.FromJsonFirefox(res)); - } - } - else if (res["resultID"] == null) - { - return OnEvent(res.ToObject(), res, token); - } - else if (res["type"] == null || res["type"].Value() != "evaluationResult") - { - var o = JObject.FromObject(new - { - type = "evaluationResult", - resultID = res["resultID"].Value() - }); - var id = int.Parse(res["resultID"].Value().Split('-')[1]); - var msgId = new MessageId(null, id + 1); - - return SendCommandInternal(msgId, "", o, token); - } - else if (res["result"] is JObject && res["result"]["type"].Value() == "object" && res["result"]["class"].Value() == "Array") - { - var msgIdNew = new FirefoxMessageId(null, 0, res["result"]["actor"].Value()); - var id = int.Parse(res["resultID"].Value().Split('-')[1]); - - var msgId = new FirefoxMessageId(null, id + 1, ""); - var pendingTask = pending_cmds[msgId]; - pending_cmds.Remove(msgId); - pending_cmds.Add(msgIdNew, pendingTask); - return SendCommandInternal(msgIdNew, "", JObject.FromObject(new - { - type = "prototypeAndProperties", - to = res["result"]["actor"].Value() - }), token); - } - else - { - var id = int.Parse(res["resultID"].Value().Split('-')[1]); - var msgId = new FirefoxMessageId(null, id + 1, ""); - if (pending_cmds.ContainsKey(msgId)) - OnResponse(msgId, Result.FromJsonFirefox(res)); - else - return SendCommandInternal(msgId, "", res, token); - return null; - } - return null; - } - catch (Exception ex) - { - logger.LogError(ex.ToString()); - _runLoop.Fail(ex); - throw; - } - } - - protected override Task ProcessIdeMessage(string msg, CancellationToken token) - { - try - { - if (!string.IsNullOrEmpty(msg)) - { - var res = JObject.Parse(msg); - Log("protocol", $"from-ide: {GetFromOrTo(res)} {msg}"); - var id = res.ToObject(); - return OnCommand( - id, - res, - token); - } - return null; - } - catch (Exception ex) - { - logger.LogError(ex.ToString()); - _runLoop.Fail(ex); - throw; - } - } - - protected override string GetFromOrTo(JObject o) - { - if (o?["to"]?.Value() is string to) - return $"[ to: {to} ]"; - if (o?["from"]?.Value() is string from) - return $"[ from: {from} ]"; - return string.Empty; - } - - protected override async Task SendCommandInternal(SessionId sessionId, string method, JObject args, CancellationToken token) - { - // logger.LogTrace($"SendCommandInternal: to-browser: {method}, {args}"); - if (method != null && method != "") - { - var tcs = new TaskCompletionSource(); - MessageId msgId; - if (method == "evaluateJSAsync") - { - int id = GetNewCmdId(); - msgId = new FirefoxMessageId(sessionId.sessionId, id, ""); - } - else - { - msgId = new FirefoxMessageId(sessionId.sessionId, 0, args["to"].Value()); - } - pending_cmds.Add(msgId, tcs); - await Send(browser, args, token); - - return await tcs.Task; - } - await Send(browser, args, token); - return await Task.FromResult(Result.OkFromObject(new { })); - } - - protected override Task SendEventInternal(SessionId sessionId, string method, JObject args, CancellationToken token) - { - logger.LogTrace($"to-ide {method}: {args}"); - return method != "" - ? Send(ide, new JObject(JObject.FromObject(new { type = method })), token) - : Send(ide, args, token); - } - - protected override async Task AcceptEvent(SessionId sessionId, JObject args, CancellationToken token) - { - if (args["frame"] != null && args["type"] == null) - { - Contexts.OnDefaultContextUpdate(sessionId, new FirefoxExecutionContext(new MonoSDBHelper (this, logger, sessionId), 0, args["frame"]["consoleActor"].Value())); - return false; - } - - if (args["resultID"] != null) - return true; - - if (args["type"] == null) - return false; - - switch (args["type"].Value()) - { - case "paused": - { - var ctx = GetContextFixefox(sessionId); - var topFunc = args["frame"]["displayName"].Value(); - switch (topFunc) - { - case "mono_wasm_fire_debugger_agent_message_with_data_to_pause": - case "_mono_wasm_fire_debugger_agent_message_with_data_to_pause": - { - ctx.PausedOnWasm = true; - return await OnReceiveDebuggerAgentEvent(sessionId, args, GetLastDebuggerAgentBuffer(args), token); - } - default: - ctx.PausedOnWasm = false; - return false; - } - } - //when debugging from firefox - case "resource-available-form": - { - var messages = args["resources"].Value(); - foreach (var message in messages) - { - if (message["resourceType"].Value() == "thread-state" && message["state"].Value() == "paused") - { - var context = GetContextFixefox(sessionId); - if (context.PausedOnWasm) - { - await SendPauseToBrowser(sessionId, args, token); - return true; - } - } - if (message["resourceType"].Value() != "console-message") - continue; - var ctx = GetContextFixefox(sessionId); - ctx.GlobalName = args["from"].Value(); - } - break; - } - case "target-available-form": - { - Contexts.OnDefaultContextUpdate(sessionId, new FirefoxExecutionContext(new MonoSDBHelper (this, logger, sessionId), 0, args["target"]["consoleActor"].Value())); - var ctx = GetContextFixefox(sessionId); - ctx.GlobalName = args["target"]["actor"].Value(); - ctx.ThreadName = args["target"]["threadActor"].Value(); - ResetCmdId(); - if (await IsRuntimeAlreadyReadyAlready(sessionId, token)) - { - await ForwardMessageToIde(args, token); - await RuntimeReady(sessionId, token); - return true; - } - break; - } - } - return false; - } - - //from ide - protected override async Task AcceptCommand(MessageId sessionId, JObject args, CancellationToken token) - { - if (args["type"] == null) - return false; - - switch (args["type"].Value()) - { - case "resume": - { - if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) - return false; - context.PausedOnWasm = false; - if (context.CallStack == null) - return false; - if (args["resumeLimit"] == null || args["resumeLimit"].Type == JTokenType.Null) - { - await OnResume(sessionId, token); - return false; - } - switch (args["resumeLimit"]["type"].Value()) - { - case "next": - await context.SdbAgent.Step(context.ThreadId, StepKind.Over, token); - break; - case "finish": - await context.SdbAgent.Step(context.ThreadId, StepKind.Out, token); - break; - case "step": - await context.SdbAgent.Step(context.ThreadId, StepKind.Into, token); - break; - } - await SendResume(sessionId, token); - return true; - } - case "isAttached": - case "attach": - { - var ctx = GetContextFixefox(sessionId); - ctx.ThreadName = args["to"].Value(); - if (await IsRuntimeAlreadyReadyAlready(sessionId, token)) - await RuntimeReady(sessionId, token); - break; - } - case "source": - { - return await OnGetScriptSource(sessionId, args["to"].Value(), token); - } - case "getBreakableLines": - { - return await OnGetBreakableLines(sessionId, args["to"].Value(), token); - } - case "getBreakpointPositionsCompressed": - { - //{"positions":{"39":[20,28]},"from":"server1.conn2.child10/source27"} - if (args["to"].Value().StartsWith("dotnet://")) - { - var line = new JObject(); - var offsets = new JArray(); - offsets.Add(0); - line.Add(args["query"]["start"]["line"].Value(), offsets); - var o = JObject.FromObject(new - { - positions = line, - from = args["to"].Value() - }); - - await SendEventInternal(sessionId, "", o, token); - return true; - } - break; - } - case "setBreakpoint": - { - if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) - return false; - var req = JObject.FromObject(new - { - url = args["location"]["sourceUrl"].Value(), - lineNumber = args["location"]["line"].Value() - 1, - columnNumber = args["location"]["column"].Value() - }); - - var bp = context.BreakpointRequests.Where(request => request.Value.CompareRequest(req)).FirstOrDefault(); - - if (bp.Value != null) - { - bp.Value.UpdateCondition(args["options"]?["condition"]?.Value()); - await SendCommand(sessionId, "", args, token); - return true; - } - - string bpid = Interlocked.Increment(ref context.breakpointId).ToString(); - - if (args["options"]?["condition"]?.Value() != null) - req["condition"] = args["options"]?["condition"]?.Value(); - - var request = BreakpointRequest.Parse(bpid, req); - bool loaded = context.Source.Task.IsCompleted; - - context.BreakpointRequests[bpid] = request; - - if (await IsRuntimeAlreadyReadyAlready(sessionId, token)) - { - DebugStore store = await RuntimeReady(sessionId, token); - - Log("verbose", $"BP req {args}"); - await SetBreakpoint(sessionId, store, request, !loaded, false, token); - } - await SendCommand(sessionId, "", args, token); - return true; - } - case "removeBreakpoint": - { - if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) - return false; - Result resp = await SendCommand(sessionId, "", args, token); - - var reqToRemove = JObject.FromObject(new - { - url = args["location"]["sourceUrl"].Value(), - lineNumber = args["location"]["line"].Value() - 1, - columnNumber = args["location"]["column"].Value() - }); - - foreach (var req in context.BreakpointRequests.Values) - { - if (req.CompareRequest(reqToRemove)) - { - foreach (var bp in req.Locations) - { - var breakpoint_removed = await context.SdbAgent.RemoveBreakpoint(bp.RemoteId, token); - if (breakpoint_removed) - { - bp.RemoteId = -1; - bp.State = BreakpointState.Disabled; - } - } - } - } - return true; - } - case "prototypeAndProperties": - case "slice": - { - var to = args?["to"].Value().Replace("propertyIterator", ""); - if (!DotnetObjectId.TryParse(to, out DotnetObjectId objectId)) - return false; - var res = await RuntimeGetObjectMembers(sessionId, objectId, args, token); - var variables = ConvertToFirefoxContent(res); - var o = JObject.FromObject(new - { - ownProperties = variables, - from = args["to"].Value() - }); - if (args["type"].Value() == "prototypeAndProperties") - o.Add("prototype", GetPrototype(args)); - await SendEvent(sessionId, "", o, token); - return true; - } - case "prototype": - { - if (!DotnetObjectId.TryParse(args?["to"], out DotnetObjectId objectId)) - return false; - var o = JObject.FromObject(new - { - prototype = GetPrototype(args), - from = args["to"].Value() - }); - await SendEvent(sessionId, "", o, token); - return true; - } - case "enumSymbols": - { - if (!DotnetObjectId.TryParse(args?["to"], out DotnetObjectId objectId)) - return false; - var o = JObject.FromObject(new - { - type = "symbolIterator", - count = 0, - actor = args["to"].Value() + "symbolIterator" - }); - - var iterator = JObject.FromObject(new - { - iterator = o, - from = args["to"].Value() - }); - - await SendEvent(sessionId, "", iterator, token); - return true; - } - case "enumProperties": - { - //{"iterator":{"type":"propertyIterator","actor":"server1.conn19.child63/propertyIterator73","count":3},"from":"server1.conn19.child63/obj71"} - if (!DotnetObjectId.TryParse(args?["to"], out DotnetObjectId objectId)) - return false; - var res = await RuntimeGetObjectMembers(sessionId, objectId, args, token); - var variables = ConvertToFirefoxContent(res); - var o = JObject.FromObject(new - { - type = "propertyIterator", - count = variables.Count, - actor = args["to"].Value() + "propertyIterator" - }); - - var iterator = JObject.FromObject(new - { - iterator = o, - from = args["to"].Value() - }); - - await SendEvent(sessionId, "", iterator, token); - return true; - } - case "getEnvironment": - { - if (!DotnetObjectId.TryParse(args?["to"], out DotnetObjectId objectId)) - return false; - var ctx = GetContextFixefox(sessionId); - if (ctx.CallStack == null) - return false; - Frame scope = ctx.CallStack.FirstOrDefault(s => s.Id == objectId.Value); - var res = await RuntimeGetObjectMembers(sessionId, objectId, args, token); - var variables = ConvertToFirefoxContent(res); - var o = JObject.FromObject(new - { - actor = args["to"].Value() + "_0", - type = "function", - scopeKind = "function", - function = new - { - displayName = scope.Method.Name - }, - bindings = new - { - arguments = new JArray(), - variables - }, - from = args["to"].Value() - }); - - await SendEvent(sessionId, "", o, token); - return true; - } - case "frames": - { - ExecutionContext ctx = GetContextFixefox(sessionId); - if (ctx.PausedOnWasm) - { - try - { - await GetFrames(sessionId, ctx, args, token); - return true; - } - catch (Exception) //if the page is refreshed maybe it stops here. - { - await SendResume(sessionId, token); - return true; - } - } - //var ret = await SendCommand(sessionId, "frames", args, token); - //await SendEvent(sessionId, "", ret.Value["result"]["fullContent"] as JObject, token); - return false; - } - case "evaluateJSAsync": - { - var context = GetContextFixefox(sessionId); - if (context.CallStack != null) - { - var resultID = $"runtimeResult-{context.GetResultID()}"; - var o = JObject.FromObject(new - { - resultID, - from = args["to"].Value() - }); - await SendEvent(sessionId, "", o, token); - - Frame scope = context.CallStack.First(); - string expression = args?["text"]?.Value(); - var osend = JObject.FromObject(new - { - type = "evaluationResult", - resultID, - hasException = false, - input = args?["text"], - from = args["to"].Value() - }); - try - { - var resolver = new MemberReferenceResolver(this, context, sessionId, scope.Id, logger); - JObject retValue = await resolver.Resolve(expression, token); - retValue ??= await ExpressionEvaluator.CompileAndRunTheExpression(expression, resolver, logger, token); - if (retValue["type"].Value() == "object") - { - osend["result"] = JObject.FromObject(new - { - type = retValue["type"], - @class = retValue["className"], - description = retValue["description"], - actor = retValue["objectId"], - }); - } - else - { - osend["result"] = retValue["value"]; - osend["resultType"] = retValue["type"]; - osend["resultDescription"] = retValue["description"]; - } - await SendEvent(sessionId, "", osend, token); - } - catch (ReturnAsErrorException ree) - { - osend["hasException"] = true; - osend.Add("exception", JObject.FromObject(new - { - type = "object", - @class = ree.Error.Value["result"]["className"], - isError = true, - preview = JObject.FromObject(new - { - kind = "Error", - name = ree.Error.Value["result"]["className"], - message = ree.Error.Value["result"]["description"], - isError = true - }) - })); - await SendEvent(sessionId, "", osend, token); - } - catch (Exception e) - { - logger.LogDebug($"Error in EvaluateOnCallFrame for expression '{expression}' with '{e}."); - osend["hasException"] = true; - osend.Add("exception", JObject.FromObject(new - { - type = "object", - @class = "InternalError", - isError = true, - preview = JObject.FromObject(new - { - kind = "Error", - name = "InternalError", - message = e.Message, - isError = true - }) - })); - await SendEvent(sessionId, "", osend, token); - } - } - else - { - var ret = await SendCommand(sessionId, "evaluateJSAsync", args, token); - var o = JObject.FromObject(new - { - resultID = ret.FullContent["resultID"], - from = args["to"].Value() - }); - await SendEvent(sessionId, "", o, token); - await SendEvent(sessionId, "", ret.FullContent, token); - } - return true; - } - case "DotnetDebugger.getMethodLocation": - { - var ret = await GetMethodLocation(sessionId, args, token); - ret.Value["from"] = "internal"; - await SendEvent(sessionId, "", ret.Value, token); - return true; - } - case "DotnetDebugger.runTests": - { - await RuntimeReady(sessionId, token); - return true; - } - default: - return false; - } - return false; - } - - internal override void SaveLastDebuggerAgentBufferReceivedToContext(SessionId sessionId, Task debuggerAgentBufferTask) - { - var context = GetContextFixefox(sessionId); - if (context.LastDebuggerAgentBufferReceived != null) - logger.LogTrace($"Trying to reset debugger agent buffer before use it."); - - context.LastDebuggerAgentBufferReceived = debuggerAgentBufferTask; - } - internal static Result GetLastDebuggerAgentBuffer(JObject args) - { - var result = new JArray(); - result.Add(JObject.FromObject(new { value = new {value = args?["frame"]?["arguments"]?[0].Value()}})); - Result res = Result.OkFromObject(new - { - result - }); - return res; - } - - private async Task SendPauseToBrowser(SessionId sessionId, JObject args, CancellationToken token) - { - var context = GetContextFixefox(sessionId); - Result res = await context.LastDebuggerAgentBufferReceived; - if (!res.IsOk || res.Value?["result"].Value().Count == 0) - { - logger.LogTrace($"Unexpected DebuggerAgentBufferReceived {res}"); - return false; - } - context.LastDebuggerAgentBufferReceived = null; - byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?[0]?["value"]?["value"]?.Value()); - using var retDebuggerCmdReader = new MonoBinaryReader(newBytes); - retDebuggerCmdReader.ReadBytes(11); - retDebuggerCmdReader.ReadByte(); - var number_of_events = retDebuggerCmdReader.ReadInt32(); - var event_kind = (EventKind)retDebuggerCmdReader.ReadByte(); - if (event_kind == EventKind.Step) - context.PauseKind = "resumeLimit"; - else if (event_kind == EventKind.Breakpoint) - context.PauseKind = "breakpoint"; - - args["resources"][0]["why"]["type"] = context.PauseKind; - await SendEvent(sessionId, "", args, token); - return true; - } - - private static JObject GetPrototype(JObject args) - { - var o = JObject.FromObject(new - { - type = "object", - @class = "Object", - actor = args?["to"], - from = args?["to"] - }); - return o; - } - - private static JObject ConvertToFirefoxContent(ValueOrError res) - { - JObject variables = new JObject(); - //TODO check if res.Error and do something - var resVars = res.Value.Flatten(); - foreach (var variable in resVars) - { - JObject variableDesc; - if (variable["get"] != null) - { - variableDesc = JObject.FromObject(new - { - value = JObject.FromObject(new - { - @class = variable["value"]?["className"]?.Value(), - value = variable["value"]?["description"]?.Value(), - actor = variable["get"]["objectId"].Value(), - type = "function" - }), - enumerable = true, - configurable = false, - actor = variable["get"]["objectId"].Value() - }); - } - else if (variable["value"]["objectId"] != null) - { - variableDesc = JObject.FromObject(new - { - value = JObject.FromObject(new - { - @class = variable["value"]?["className"]?.Value(), - value = variable["value"]?["description"]?.Value(), - actor = variable["value"]["objectId"].Value(), - type = variable["value"]?["type"]?.Value() ?? "object" - }), - enumerable = true, - configurable = false, - actor = variable["value"]["objectId"].Value() - }); - } - else - { - variableDesc = JObject.FromObject(new - { - writable = variable["writable"], - enumerable = true, - configurable = false, - type = variable["value"]?["type"]?.Value() - }); - if (variable["value"]["value"].Type != JTokenType.Null) - variableDesc.Add("value", variable["value"]["value"]); - else //{"type":"null"} - { - variableDesc.Add("value", JObject.FromObject(new { - type = "null", - @class = variable["value"]["className"] - })); - } - } - variables.Add(variable["name"].Value(), variableDesc); - } - return variables; - } - - protected override async Task SendResume(SessionId id, CancellationToken token) - { - var ctx = GetContextFixefox(id); - await SendCommand(id, "", JObject.FromObject(new - { - to = ctx.ThreadName, - type = "resume" - }), token); - } - - internal override Task SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) - { - var ctx = GetContextFixefox(id); - var o = JObject.FromObject(new - { - to = ctx.ActorName, - type = "evaluateJSAsync", - text = cmd.expression, - options = new { eager = true, mapped = new { await = true } } - }); - return SendCommand(id, "evaluateJSAsync", o, token); - } - - internal override async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, ExecutionContext context, CancellationToken token, bool resolveBreakpoints = true) - { - //different behavior when debugging from VSCode and from Firefox - var ctx = context as FirefoxExecutionContext; - logger.LogTrace($"sending {source.Url} {context.Id} {sessionId.sessionId}"); - var obj = JObject.FromObject(new - { - actor = source.SourceId.ToString(), - extensionName = (string)null, - url = source.Url, - isBlackBoxed = false, - introductionType = "scriptElement", - resourceType = "source", - dotNetUrl = source.DotNetUrlEscaped - }); - JObject sourcesJObj; - if (!string.IsNullOrEmpty(ctx.GlobalName)) - { - sourcesJObj = JObject.FromObject(new - { - type = "resource-available-form", - resources = new JArray(obj), - from = ctx.GlobalName - }); - } - else - { - sourcesJObj = JObject.FromObject(new - { - type = "newSource", - source = obj, - from = ctx.ThreadName - }); - } - await SendEvent(sessionId, "", sourcesJObj, token); - if (!resolveBreakpoints) - return; - foreach (var req in context.BreakpointRequests.Values) - { - if (req.TryResolve(source)) - { - await SetBreakpoint(sessionId, context.store, req, true, false, token); - } - } - } - - protected override async Task SendCallStack(SessionId sessionId, ExecutionContext context, string reason, int thread_id, Breakpoint bp, JObject data, JObject args, EventKind event_kind, CancellationToken token) - { - Frame frame = null; - var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(thread_id); - commandParamsWriter.Write(0); - commandParamsWriter.Write(1); - var retDebuggerCmdReader = await context.SdbAgent.SendDebuggerAgentCommand(CmdThread.GetFrameInfo, commandParamsWriter, token); - var frame_count = retDebuggerCmdReader.ReadInt32(); - if (frame_count > 0) - { - var frame_id = retDebuggerCmdReader.ReadInt32(); - var methodId = retDebuggerCmdReader.ReadInt32(); - var il_pos = retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadByte(); - var method = await context.SdbAgent.GetMethodInfo(methodId, token); - if (method is null) - return false; - - if (await ShouldSkipMethod(sessionId, context, event_kind, 0, frame_count, method, token)) - { - await SendResume(sessionId, token); - return true; - } - - SourceLocation location = method?.Info.GetLocationByIl(il_pos); - if (location == null) - { - return false; - } - - Log("debug", $"frame il offset: {il_pos} method token: {method.Info.Token} assembly name: {method.Info.Assembly.Name}"); - Log("debug", $"\tmethod {method.Name} location: {location}"); - frame = new Frame(method, location, frame_id); - context.CallStack = new List(); - context.CallStack.Add(frame); - } - if (!await EvaluateCondition(sessionId, context, frame, bp, token)) - { - context.ClearState(); - await SendResume(sessionId, token); - return true; - } - - args["why"]["type"] = context.PauseKind; - - await SendEvent(sessionId, "", args, token); - return true; - } - - private async Task GetFrames(SessionId sessionId, ExecutionContext context, JObject args, CancellationToken token) - { - var ctx = context as FirefoxExecutionContext; - var orig_callframes = await SendCommand(sessionId, "frames", args, token); - - var callFrames = new List(); - var frames = new List(); - var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(context.ThreadId); - commandParamsWriter.Write(0); - commandParamsWriter.Write(-1); - var retDebuggerCmdReader = await context.SdbAgent.SendDebuggerAgentCommand(CmdThread.GetFrameInfo, commandParamsWriter, token); - var frame_count = retDebuggerCmdReader.ReadInt32(); - for (int j = 0; j < frame_count; j++) - { - var frame_id = retDebuggerCmdReader.ReadInt32(); - var methodId = retDebuggerCmdReader.ReadInt32(); - var il_pos = retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadByte(); - MethodInfoWithDebugInformation method = await context.SdbAgent.GetMethodInfo(methodId, token); - if (method is null) - continue; - - SourceLocation location = method.Info?.GetLocationByIl(il_pos); - if (location == null) - { - continue; - } - - Log("debug", $"frame il offset: {il_pos} method token: {method.Info.Token} assembly name: {method.Info.Assembly.Name}"); - Log("debug", $"\tmethod {method.Name} location: {location}"); - frames.Add(new Frame(method, location, frame_id)); - - var frameItem = JObject.FromObject(new - { - actor = $"dotnet:scope:{frame_id}", - displayName = method.Name, - type = "call", - state = "on-stack", - asyncCause = (string)null, - where = new - { - actor = location.Id.ToString(), - line = location.Line + 1, - column = location.Column - } - }); - if (j > 0) - frameItem.Add("depth", j); - callFrames.Add(frameItem); - - context.CallStack = frames; - } - foreach (JObject frame in orig_callframes.Value["result"]?["value"]?["frames"]) - { - string function_name = frame["displayName"]?.Value(); - if (function_name != null && !(function_name.StartsWith("Module._mono_wasm", StringComparison.Ordinal) || - function_name.StartsWith("Module.mono_wasm", StringComparison.Ordinal) || - function_name == "mono_wasm_fire_debugger_agent_message_with_data" || - function_name == "_mono_wasm_fire_debugger_agent_message_with_data" || - function_name == "(wasmcall)")) - { - callFrames.Add(frame); - } - } - var o = JObject.FromObject(new - { - frames = callFrames, - from = ctx.ThreadName - }); - - await SendEvent(sessionId, "", o, token); - return false; - } - internal async Task OnGetBreakableLines(MessageId msg_id, string script_id, CancellationToken token) - { - if (!SourceId.TryParse(script_id, out SourceId id)) - return false; - - SourceFile src_file = (await LoadStore(msg_id, false, token)).GetFileById(id); - - await SendEvent(msg_id, "", JObject.FromObject(new { lines = src_file.BreakableLines.ToArray(), from = script_id }), token); - return true; - } - - internal override async Task OnGetScriptSource(MessageId msg_id, string script_id, CancellationToken token) - { - if (!SourceId.TryParse(script_id, out SourceId id)) - return false; - - SourceFile src_file = (await LoadStore(msg_id, false, token)).GetFileById(id); - - try - { - string source = $"// Unable to find document {src_file.FileUriEscaped}"; - - using (Stream data = await src_file.GetSourceAsync(checkHash: false, token: token)) - { - if (data.Length == 0) - return false; - - using (var reader = new StreamReader(data)) - source = await reader.ReadToEndAsync(token); - } - await SendEvent(msg_id, "", JObject.FromObject(new { source, from = script_id }), token); - } - catch (Exception e) - { - var o = JObject.FromObject(new - { - source = $"// Unable to read document ({e.Message})\n" + - $"Local path: {src_file?.FileUriEscaped}\n" + - $"SourceLink path: {src_file?.SourceLinkUri}\n", - from = script_id - }); - - await SendEvent(msg_id, "", o, token); - } - return true; - } - - internal override Task LoadStore(SessionId sessionId, bool tryUseDebuggerProtocol, CancellationToken token) - => base.LoadStore(sessionId, false, token); -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/IDebugMetadataProvider.cs b/src/mono/browser/debugger/BrowserDebugProxy/IDebugMetadataProvider.cs deleted file mode 100644 index 413a85cf762c91..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/IDebugMetadataProvider.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; -using System.Collections.Immutable; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; - -namespace Microsoft.WebAssembly.Diagnostics; - -/// -/// An adapter on top of MetadataReader and WebcilReader for DebugStore compensating -/// for the lack of a common base class on those two types. -/// -public interface IDebugMetadataProvider -{ - public ImmutableArray ReadDebugDirectory(); - public CodeViewDebugDirectoryData ReadCodeViewDebugDirectoryData(DebugDirectoryEntry entry); - public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry); - - public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry); -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/InternalErrorException.cs b/src/mono/browser/debugger/BrowserDebugProxy/InternalErrorException.cs deleted file mode 100644 index 74c21657732d25..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/InternalErrorException.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; - -namespace Microsoft.WebAssembly.Diagnostics; - -public class InternalErrorException : Exception -{ - public InternalErrorException(string message) : base($"Internal error: {message}") - { - } - - public InternalErrorException(string message, Exception? innerException) : base($"Internal error: {message}", innerException) - { - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/JObjectValueCreator.cs b/src/mono/browser/debugger/BrowserDebugProxy/JObjectValueCreator.cs deleted file mode 100644 index 369737fce72a34..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/JObjectValueCreator.cs +++ /dev/null @@ -1,516 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using BrowserDebugProxy; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; -using System.Reflection; - -namespace Microsoft.WebAssembly.Diagnostics; - -internal sealed class JObjectValueCreator -{ - private Dictionary _valueTypes = new(); - private Dictionary _pointerValues = new(); - private readonly MonoSDBHelper _sdbAgent; - private readonly ILogger _logger; - - public JObjectValueCreator(MonoSDBHelper sdbAgent, ILogger logger) - { - _sdbAgent = sdbAgent; - _logger = logger; - } - - public static JObject Create(T value, - string type, - string description, - string className = null, - string objectId = null, - string subtype = null, - bool writable = false, - bool isValueType = false, - bool isEnum = false) - { - var ret = JObject.FromObject(new - { - value = new - { - type, - value, - description - }, - writable - }); - if (className != null) - ret["value"]["className"] = className; - if (objectId != null) - ret["value"]["objectId"] = objectId; - if (subtype != null) - ret["value"]["subtype"] = subtype; - if (isValueType) - ret["value"]["isValueType"] = isValueType; - if (isEnum) - ret["value"]["isEnum"] = isEnum; - return ret; - } - - public static JObject CreateFromPrimitiveType(object v, int? stringId = null) - => v switch - { - string s => Create(s, type: "string", description: s, objectId: $"dotnet:object:{stringId}"), - char c => CreateJObjectForChar(Convert.ToInt32(c)), - bool b => Create(b, type: "boolean", description: b ? "true" : "false", className: "System.Boolean"), - - decimal or float or double or - byte or sbyte or - short or ushort or - int or uint or - long or ulong - => CreateJObjectForNumber(v), - - _ => null - }; - - public static JObject CreateNull(string className) - { - ArgumentNullException.ThrowIfNull(className); - return Create(value: null, - type: "object", - description: className, - className: className, - subtype: "null"); - } - - public async Task ReadAsVariableValue( - MonoBinaryReader retDebuggerCmdReader, - string name, - CancellationToken token, - bool isOwn = false, - int typeIdForObject = -1, - bool forDebuggerDisplayAttribute = false, - bool includeStatic = false) - { - long initialPos = /*retDebuggerCmdReader == null ? 0 : */retDebuggerCmdReader.BaseStream.Position; - ElementType etype = (ElementType)retDebuggerCmdReader.ReadByte(); - JObject ret = null; - switch (etype) - { - case ElementType.I: - case ElementType.U: - case ElementType.Void: - case (ElementType)ValueTypeId.VType: - ret = Create(value: "void", type: "void", description: "void"); - break; - case ElementType.Boolean: - { - var value = retDebuggerCmdReader.ReadInt32(); - ret = CreateFromPrimitiveType(value == 1); - break; - } - case ElementType.I1: - { - var value = retDebuggerCmdReader.ReadSByte(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.I2: - case ElementType.I4: - { - var value = retDebuggerCmdReader.ReadInt32(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.U1: - { - var value = retDebuggerCmdReader.ReadUByte(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.U2: - { - var value = retDebuggerCmdReader.ReadUShort(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.U4: - { - var value = retDebuggerCmdReader.ReadUInt32(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.R4: - { - float value = retDebuggerCmdReader.ReadSingle(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.Char: - { - var value = retDebuggerCmdReader.ReadInt32(); - ret = CreateJObjectForChar(value); - break; - } - case ElementType.I8: - { - long value = retDebuggerCmdReader.ReadInt64(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.U8: - { - ulong value = retDebuggerCmdReader.ReadUInt64(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.R8: - { - double value = retDebuggerCmdReader.ReadDouble(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.FnPtr: - case ElementType.Ptr: - { - ret = await ReadAsPtrValue(etype, retDebuggerCmdReader, name, token); - break; - } - case ElementType.String: - { - var stringId = retDebuggerCmdReader.ReadInt32(); - string value = await _sdbAgent.GetStringValue(stringId, token); - ret = CreateFromPrimitiveType(value, stringId); - break; - } - case ElementType.SzArray: - case ElementType.Array: - { - ret = await ReadAsArray(retDebuggerCmdReader, token); - break; - } - case ElementType.Class: - case ElementType.Object: - { - ret = await ReadAsObjectValue(retDebuggerCmdReader, typeIdForObject, forDebuggerDisplayAttribute, token); - break; - } - case ElementType.ValueType: - { - ret = await ReadAsValueType(retDebuggerCmdReader, name, initialPos, forDebuggerDisplayAttribute, includeStatic, token); - break; - } - case (ElementType)ValueTypeId.Null: - { - var className = await GetNullObjectClassName(); - ret = CreateNull(className); - break; - } - case (ElementType)ValueTypeId.Type: - { - retDebuggerCmdReader.ReadInt32(); - break; - } - default: - { - _logger.LogDebug($"Could not evaluate CreateJObjectForVariableValue invalid type {etype}"); - break; - } - } - if (ret != null) - { - if (isOwn) - ret["isOwn"] = true; - ret["name"] = name; - } - return ret; - - async Task GetNullObjectClassName() - { - string className; - ElementType variableType = (ElementType)retDebuggerCmdReader.ReadByte(); - switch (variableType) - { - case ElementType.String: - case ElementType.Class: - { - var type_id = retDebuggerCmdReader.ReadInt32(); - className = await _sdbAgent.GetTypeName(type_id, token); - break; - - } - case ElementType.SzArray: - case ElementType.Array: - { - ElementType byte_type = (ElementType)retDebuggerCmdReader.ReadByte(); - retDebuggerCmdReader.ReadInt32(); // rank - if (byte_type == ElementType.Class) - { - retDebuggerCmdReader.ReadInt32(); // internal_type_id - } - var type_id = retDebuggerCmdReader.ReadInt32(); - className = await _sdbAgent.GetTypeName(type_id, token); - break; - } - default: - { - var type_id = retDebuggerCmdReader.ReadInt32(); - className = await _sdbAgent.GetTypeName(type_id, token); - break; - } - } - return className; - } - } - - private async Task ReadAsObjectValue(MonoBinaryReader retDebuggerCmdReader, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token) - { - var objectId = retDebuggerCmdReader.ReadInt32(); - var typeIds = await _sdbAgent.GetTypeIdsForObject(objectId, withParents: true, token); - string className = await _sdbAgent.GetTypeName(typeIds[0], token); - string debuggerDisplayAttribute = null; - if (!forDebuggerDisplayAttribute) - debuggerDisplayAttribute = await _sdbAgent.GetValueFromDebuggerDisplayAttribute( - new DotnetObjectId("object", objectId), typeIds[0], token); - var description = className.ToString(); - - if (debuggerDisplayAttribute != null) - { - description = debuggerDisplayAttribute; - } - else if (await _sdbAgent.IsDelegate(objectId, token)) - { - if (typeIdFromAttribute != -1) - { - className = await _sdbAgent.GetTypeName(typeIdFromAttribute, token); - } - - description = await _sdbAgent.GetDelegateMethodDescription(objectId, token); - if (description == "") - { - return Create(value: className, type: "symbol", description: className); - } - } - else - { - var toString = await _sdbAgent.InvokeToStringAsync(typeIds, isValueType: false, isEnum: false, objectId, BindingFlags.DeclaredOnly, invokeToStringInObject: false, token); - if (toString != null) - description = toString; - } - return Create(value: null, type: "object", description: description, className: className, objectId: $"dotnet:object:{objectId}"); - } - - public async Task ReadAsValueType( - MonoBinaryReader retDebuggerCmdReader, - string name, - long initialPos, - bool forDebuggerDisplayAttribute, - bool includeStatic, - CancellationToken token) - { - // FIXME: debugger proxy - var isEnum = retDebuggerCmdReader.ReadByte() == 1; - var isBoxed = retDebuggerCmdReader.ReadByte() == 1; - var typeId = retDebuggerCmdReader.ReadInt32(); - var className = await _sdbAgent.GetTypeName(typeId, token); - var inlineArraySize = -1; - (int MajorVersion, int MinorVersion) = await _sdbAgent.GetVMVersion(token); - if (MajorVersion == 2 && MinorVersion >= 65) - inlineArraySize = retDebuggerCmdReader.ReadInt32(); - var numValues = retDebuggerCmdReader.ReadInt32(); - - if (className.StartsWith("System.Nullable<", StringComparison.Ordinal)) //should we call something on debugger-agent to check??? - { - retDebuggerCmdReader.ReadByte(); //ignoring the boolean type - var isNull = retDebuggerCmdReader.ReadInt32(); - - // Read the value, even if isNull==true, to correctly advance the reader - var value = await ReadAsVariableValue(retDebuggerCmdReader, name, token); - if (isNull != 0) - return value; - else - return Create(null, "object", className, className, subtype: "null", isValueType: true); - } - if (isBoxed && numValues == 1) - { - if (MonoSDBHelper.IsPrimitiveType(className)) - { - return await ReadAsVariableValue(retDebuggerCmdReader, name: null, token); - } - } - - ValueTypeClass valueType = await ValueTypeClass.CreateFromReader( - _sdbAgent, - retDebuggerCmdReader, - initialPos, - className, - typeId, - isEnum, - includeStatic, - inlineArraySize, - token); - _valueTypes[valueType.Id.Value] = valueType; - return await valueType.ToJObject(_sdbAgent, forDebuggerDisplayAttribute, token); - } - public void ClearCache() - { - _valueTypes = new Dictionary(); - _pointerValues = new Dictionary(); - } - - public bool TryGetValueTypeById(int valueTypeId, out ValueTypeClass vt) => _valueTypes.TryGetValue(valueTypeId, out vt); - public PointerValue GetPointerValue(int pointerId) => _pointerValues.TryGetValue(pointerId, out PointerValue pv) ? pv : null; - - private static JObject CreateJObjectForNumber(T value) => Create(value, "number", value.ToString(), writable: true, className: typeof(T).Name); - - private static JObject CreateJObjectForChar(int value) - { - char charValue = Convert.ToChar(value); - var description = $"{value} '{charValue}'"; - return Create(charValue, "symbol", description, writable: true); - } - - private async Task ReadAsPtrValue(ElementType etype, MonoBinaryReader retDebuggerCmdReader, string name, CancellationToken token) - { - string type; - string value; - long valueAddress = retDebuggerCmdReader.ReadInt64(); - var typeId = retDebuggerCmdReader.ReadInt32(); - string className; - if (etype == ElementType.FnPtr) - className = "(*())"; //to keep the old behavior - else - className = "(" + await _sdbAgent.GetTypeName(typeId, token) + ")"; - - int pointerId = 0; - if (valueAddress != 0 && className != "(void*)") - { - pointerId = MonoSDBHelper.GetNextDebuggerObjectId(); - type = "object"; - value = className; - _pointerValues[pointerId] = new PointerValue(valueAddress, typeId, name); - } - else - { - type = "symbol"; - value = className + " " + valueAddress; - } - return Create(value: value, type: type, description: value, className: className, objectId: $"dotnet:pointer:{pointerId}", subtype: "pointer"); - } - - private async Task ReadAsArray(MonoBinaryReader retDebuggerCmdReader, CancellationToken token) - { - var objectId = retDebuggerCmdReader.ReadInt32(); - var className = await _sdbAgent.GetClassNameFromObject(objectId, token); - var arrayType = className.ToString(); - var length = await _sdbAgent.GetArrayDimensions(objectId, token); - if (arrayType.LastIndexOf('[') > 0) - arrayType = arrayType.Insert(arrayType.LastIndexOf('[') + 1, length.ToString()); - if (className.LastIndexOf('[') > 0) - className = className.Insert(arrayType.LastIndexOf('[') + 1, new string(',', length.Rank - 1)); - return Create(value: null, - type: "object", - description: arrayType, - className: className.ToString(), - objectId: "dotnet:array:" + objectId, - subtype: length.Rank == 1 ? "array" : null); - } - - public async Task CreateFixedArrayElement(MonoBinaryReader retDebuggerCmdReader, ElementType etype, string name, CancellationToken token) - { - JObject ret = null; - switch (etype) - { - case ElementType.I: - case ElementType.U: - case ElementType.Void: - case (ElementType)ValueTypeId.VType: - ret = Create(value: "void", type: "void", description: "void"); - break; - case ElementType.Boolean: - { - var value = retDebuggerCmdReader.ReadInt32(); - ret = CreateFromPrimitiveType(value == 1); - break; - } - case ElementType.I1: - { - var value = retDebuggerCmdReader.ReadSByte(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.I2: - case ElementType.I4: - { - var value = retDebuggerCmdReader.ReadInt32(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.U1: - { - var value = retDebuggerCmdReader.ReadUByte(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.U2: - { - var value = retDebuggerCmdReader.ReadUShort(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.U4: - { - var value = retDebuggerCmdReader.ReadUInt32(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.R4: - { - float value = retDebuggerCmdReader.ReadSingle(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.Char: - { - var value = retDebuggerCmdReader.ReadInt32(); - ret = CreateJObjectForChar(value); - break; - } - case ElementType.I8: - { - long value = retDebuggerCmdReader.ReadInt64(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.U8: - { - ulong value = retDebuggerCmdReader.ReadUInt64(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.R8: - { - double value = retDebuggerCmdReader.ReadDouble(); - ret = CreateJObjectForNumber(value); - break; - } - case ElementType.FnPtr: - case ElementType.Ptr: - { - ret = await ReadAsPtrValue(etype, retDebuggerCmdReader, name, token); - break; - } - default: - { - _logger.LogDebug($"Could not evaluate CreateFixedArrayElement invalid type {etype}"); - break; - } - } - ret["name"] = name; - return ret; - } - -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs b/src/mono/browser/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs deleted file mode 100644 index ff3321e7abf4ed..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/MemberObjectsExplorer.cs +++ /dev/null @@ -1,780 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.WebAssembly.Diagnostics; -using Newtonsoft.Json.Linq; - -namespace BrowserDebugProxy -{ - internal static class MemberObjectsExplorer - { - private static bool IsACollectionType(string typeName) - => typeName is not null && - (typeName.StartsWith("System.Collections.Generic", StringComparison.Ordinal) || - typeName.EndsWith("[]", StringComparison.Ordinal)); - - private static string GetNamePrefixForValues(string memberName, string typeName, bool isOwn, DebuggerBrowsableState? state) - { - if (isOwn || state != DebuggerBrowsableState.RootHidden) - return memberName; - - string justClassName = Path.GetExtension(typeName); - if (justClassName[0] == '.') - justClassName = justClassName[1..]; - return $"{memberName} ({justClassName})"; - } - - private static async Task ReadFieldValue( - MonoSDBHelper sdbHelper, - MonoBinaryReader reader, - FieldTypeClass field, - int objectId, - TypeInfoWithDebugInformation typeInfo, - int fieldValueType, - bool isOwn, - int parentTypeId, - GetObjectCommandOptions getObjectOptions, - CancellationToken token) - { - var fieldValue = await sdbHelper.ValueCreator.ReadAsVariableValue( - reader, - field.Name, - token, - isOwn: isOwn, - field.TypeId, - getObjectOptions.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)); - - var typeFieldsBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableFields; - var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; - - if (!typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state)) - { - // for backing fields, we are getting it from the properties - typePropertiesBrowsableInfo.TryGetValue(field.Name, out state); - } - fieldValue[InternalUseFieldName.State.Name] = state?.ToString(); - fieldValue[InternalUseFieldName.Section.Name] = field.Attributes.HasFlag(FieldAttributes.Private) - ? "private" : "result"; - - if (field.IsBackingField) - { - fieldValue[InternalUseFieldName.IsBackingField.Name] = true; - fieldValue[InternalUseFieldName.ParentTypeId.Name] = parentTypeId; - } - if (field.Attributes.HasFlag(FieldAttributes.Static)) - fieldValue[InternalUseFieldName.IsStatic.Name] = true; - - if (getObjectOptions.HasFlag(GetObjectCommandOptions.WithSetter)) - { - var command_params_writer_to_set = new MonoBinaryWriter(); - command_params_writer_to_set.Write(objectId); - command_params_writer_to_set.Write(1); - command_params_writer_to_set.Write(field.Id); - var (data, length) = command_params_writer_to_set.ToBase64(); - - fieldValue.Add("set", JObject.FromObject(new - { - commandSet = CommandSet.ObjectRef, - command = CmdObject.RefSetValues, - buffer = data, - valtype = fieldValueType, - length = length, - id = MonoSDBHelper.GetNewId() - })); - } - - return fieldValue; - } - - private static async Task GetRootHiddenChildren( - MonoSDBHelper sdbHelper, - JObject root, - string rootNamePrefix, - string rootTypeName, - GetObjectCommandOptions getCommandOptions, - bool includeStatic, - CancellationToken token) - { - var rootValue = root?["value"] ?? root["get"]; - - if (rootValue?["subtype"]?.Value() == "null") - return new JArray(); - - var type = rootValue?["type"]?.Value(); - if (type != "object" && type != "function") - return new JArray(); - - if (!DotnetObjectId.TryParse(rootValue?["objectId"]?.Value(), out DotnetObjectId rootObjectId)) - throw new Exception($"Cannot parse object id from {root} for {rootNamePrefix}"); - - // if it's an accessor - if (root["get"] != null) - return await GetRootHiddenChildrenForProperty(); - - if (rootValue?["type"]?.Value() != "object") - return new JArray(); - - // unpack object/valuetype - if (rootObjectId.Scheme is "object" or "valuetype") - { - GetMembersResult members; - if (rootObjectId.Scheme is "valuetype") - { - var valType = sdbHelper.GetValueTypeClass(rootObjectId.Value); - if (valType == null || valType.IsEnum) - return new JArray(); - members = await valType.GetMemberValues(sdbHelper, getCommandOptions, false, includeStatic, token); - } - else members = await GetObjectMemberValues(sdbHelper, rootObjectId.Value, getCommandOptions, token, false, includeStatic); - - if (!IsACollectionType(rootTypeName)) - { - // is a class/valuetype with members - var resultValue = members.Flatten(); - foreach (var item in resultValue) - item["name"] = $"{rootNamePrefix}.{item["name"]}"; - return resultValue; - } - else - { - // a collection - expose elements to be of array scheme - var memberNamedItems = members - .Where(m => m["name"]?.Value() == "Items") - .FirstOrDefault(); - if (memberNamedItems is not null && - DotnetObjectId.TryParse(memberNamedItems["value"]?["objectId"]?.Value(), out DotnetObjectId itemsObjectId) && - itemsObjectId.Scheme == "array") - { - rootObjectId = itemsObjectId; - } - } - } - - if (rootObjectId.Scheme == "array") - { - JArray resultValue = await sdbHelper.GetArrayValues(rootObjectId.Value, token); - - // root hidden item name has to be unique, so we concatenate the root's name to it - foreach (var item in resultValue) - item["name"] = $"{rootNamePrefix}[{item["name"]}]"; - - return resultValue; - } - else - { - return new JArray(); - } - - async Task GetRootHiddenChildrenForProperty() - { - var resMethod = await sdbHelper.InvokeMethod(rootObjectId, token); - return await GetRootHiddenChildren(sdbHelper, resMethod, rootNamePrefix, rootTypeName, getCommandOptions, includeStatic, token); - } - } - - public static Task GetTypeMemberValues( - MonoSDBHelper sdbHelper, - DotnetObjectId dotnetObjectId, - GetObjectCommandOptions getObjectOptions, - CancellationToken token, - bool sortByAccessLevel = false, - bool includeStatic = false) - => dotnetObjectId.IsValueType - ? GetValueTypeMemberValues(sdbHelper, dotnetObjectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic) - : GetObjectMemberValues(sdbHelper, dotnetObjectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic); - - public static async Task ExpandFieldValues( - MonoSDBHelper sdbHelper, - DotnetObjectId id, - int containerTypeId, - int parentTypeId, - IReadOnlyList fields, - GetObjectCommandOptions getCommandOptions, - bool isOwn, - bool includeStatic, - CancellationToken token) - { - JArray fieldValues = new JArray(); - if (fields.Count == 0) - return fieldValues; - - if (getCommandOptions.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute)) - fields = fields.Where(field => field.IsNotPrivate).ToList(); - - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(id.Value); - commandParamsWriter.Write(fields.Count); - foreach (var field in fields) - commandParamsWriter.Write(field.Id); - MonoBinaryReader retDebuggerCmdReader = id.IsValueType - ? await sdbHelper.SendDebuggerAgentCommand(CmdType.GetValues, commandParamsWriter, token) : - await sdbHelper.SendDebuggerAgentCommand(CmdObject.RefGetValues, commandParamsWriter, token); - - var typeInfo = await sdbHelper.GetTypeInfo(containerTypeId, token); - - int numFieldsRead = 0; - foreach (FieldTypeClass field in fields) - { - long initialPos = retDebuggerCmdReader.BaseStream.Position; - int valtype = retDebuggerCmdReader.ReadByte(); - retDebuggerCmdReader.BaseStream.Position = initialPos; - - JObject fieldValue = await ReadFieldValue(sdbHelper, retDebuggerCmdReader, field, id.Value, typeInfo, valtype, isOwn, parentTypeId, getCommandOptions, token); - numFieldsRead++; - - if (typeInfo.Info.IsNonUserCode && getCommandOptions.HasFlag(GetObjectCommandOptions.JustMyCode) && field.Attributes.HasFlag(FieldAttributes.Private)) - continue; - - if (!Enum.TryParse(fieldValue[InternalUseFieldName.State.Name].Value(), out DebuggerBrowsableState fieldState) - || fieldState == DebuggerBrowsableState.Collapsed) - { - fieldValues.Add(fieldValue); - continue; - } - - if (fieldState == DebuggerBrowsableState.Never) - continue; - - string namePrefix = field.Name; - string containerTypeName = await sdbHelper.GetTypeName(containerTypeId, token); - namePrefix = GetNamePrefixForValues(field.Name, containerTypeName, isOwn, fieldState); - string typeName = await sdbHelper.GetTypeName(field.TypeId, token); - - var enumeratedValues = await GetRootHiddenChildren( - sdbHelper, fieldValue, namePrefix, typeName, getCommandOptions, includeStatic, token); - if (enumeratedValues != null) - fieldValues.AddRange(enumeratedValues); - } - - if (numFieldsRead != fields.Count) - throw new Exception($"Bug: Got {numFieldsRead} instead of expected {fields.Count} field values"); - - return fieldValues; - } - - public static Task GetValueTypeMemberValues( - MonoSDBHelper sdbHelper, int valueTypeId, GetObjectCommandOptions getCommandOptions, CancellationToken token, bool sortByAccessLevel = false, bool includeStatic = false) - { - return sdbHelper.ValueCreator.TryGetValueTypeById(valueTypeId, out ValueTypeClass valueType) - ? valueType.GetMemberValues(sdbHelper, getCommandOptions, sortByAccessLevel, includeStatic, token) - : throw new ArgumentException($"Could not find any valuetype with id: {valueTypeId}", nameof(valueTypeId)); - } - - public static async Task GetExpandedMemberValues( - MonoSDBHelper sdbHelper, - string typeName, - string namePrefix, - JObject value, - DebuggerBrowsableState? state, - bool includeStatic, - CancellationToken token) - { - if (state is DebuggerBrowsableState.RootHidden) - { - if (MonoSDBHelper.IsPrimitiveType(typeName)) - return GetHiddenElement(); - - return await GetRootHiddenChildren(sdbHelper, value, namePrefix, typeName, GetObjectCommandOptions.None, includeStatic, token); - - } - else if (state is DebuggerBrowsableState.Never) - { - return GetHiddenElement(); - } - return new JArray(value); - - JArray GetHiddenElement() - { - var emptyHidden = JObject.FromObject(new { name = namePrefix }); - emptyHidden.Add(InternalUseFieldName.Hidden.Name, true); - return new JArray(emptyHidden); - } - } - - public static async Task> ExpandPropertyValues( - MonoSDBHelper sdbHelper, - int typeId, - string typeName, - ArraySegment getterParamsBuffer, - GetObjectCommandOptions getCommandOptions, - DotnetObjectId objectId, - bool isValueType, - bool isOwn, - CancellationToken token, - Dictionary allMembers, - bool includeStatic, - int parentTypeId = -1) - { - using var retDebuggerCmdReader = await sdbHelper.GetTypePropertiesReader(typeId, token); - if (retDebuggerCmdReader == null) - return null; - - var nProperties = retDebuggerCmdReader.ReadInt32(); - var typeInfo = await sdbHelper.GetTypeInfo(typeId, token); - var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; - var parentSuffix = typeName.Split('.')[^1]; - - GetMembersResult ret = new(); - for (int i = 0; i < nProperties; i++) - { - retDebuggerCmdReader.ReadInt32(); //propertyId - string propName = retDebuggerCmdReader.ReadString(); - var getMethodId = retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadInt32(); //setmethod - var attrs = (PropertyAttributes)retDebuggerCmdReader.ReadInt32(); //attrs - if (getMethodId == 0 || await sdbHelper.GetParamCount(getMethodId, token) != 0) - continue; - bool isStatic = await sdbHelper.MethodIsStatic(getMethodId, token); - if (!includeStatic && isStatic) - continue; - - MethodInfoWithDebugInformation getterInfo = await sdbHelper.GetMethodInfo(getMethodId, token); - MethodAttributes getterAttrs = getterInfo.Info.Attributes; - MethodAttributes getterMemberAccessAttrs = getterAttrs & MethodAttributes.MemberAccessMask; - MethodAttributes vtableLayout = getterAttrs & MethodAttributes.VtableLayoutMask; - - if (typeInfo.Info.IsNonUserCode && getCommandOptions.HasFlag(GetObjectCommandOptions.JustMyCode) && getterMemberAccessAttrs == MethodAttributes.Private) - continue; - - bool isNewSlot = (vtableLayout & MethodAttributes.NewSlot) == MethodAttributes.NewSlot; - - typePropertiesBrowsableInfo.TryGetValue(propName, out DebuggerBrowsableState? state); - - // handle parents' members: - if (!allMembers.TryGetValue(propName, out JObject existingMember)) - { - // new member - await AddProperty(getMethodId, parentTypeId, state, propName, getterMemberAccessAttrs, isStatic, isNewSlot: isNewSlot); - continue; - } - - bool isExistingMemberABackingField = existingMember[InternalUseFieldName.IsBackingField.Name]?.Value() == true; - if (isOwn && !isExistingMemberABackingField) - { - // repeated propname on the same type! cannot happen - throw new Exception($"Internal Error: should not happen. propName: {propName}. Existing all members: {string.Join(",", allMembers.Keys)}"); - } - - bool isExistingMemberABackingFieldOwnedByThisType = isExistingMemberABackingField && existingMember[InternalUseFieldName.Owner.Name]?.Value() == typeName; - if (isExistingMemberABackingField && (isOwn || isExistingMemberABackingFieldOwnedByThisType)) - { - // this is the property corresponding to the backing field in *this* type - // `isOwn` would mean that this is the first type that we are looking at - await UpdateBackingFieldWithPropertyAttributes(existingMember, propName, getterMemberAccessAttrs, state); - continue; - } - - var overriddenOrHiddenPropName = $"{propName} ({parentSuffix})"; - if (isNewSlot) - { - // this has `new` keyword if it is newSlot but direct child was not a newSlot: - var child = allMembers.FirstOrDefault( - kvp => (kvp.Key == propName || kvp.Key.StartsWith($"{propName} (")) && kvp.Value[InternalUseFieldName.ParentTypeId.Name]?.Value() == typeId).Value; - bool wasOverriddenByDerivedType = child != null && child[InternalUseFieldName.IsNewSlot.Name]?.Value() != true; - if (wasOverriddenByDerivedType) - { - /* - * property was overridden by a derived type member. We want to show - * only the overridden members. So, remove the backing field - * for this auto-property that was added, with the type name suffix - * - * Two cases: - * 1. auto-prop in base, overridden by auto-prop in derived - * 2. auto-prop in base, overridden by prop in derived - * - * And in both cases we want to remove the backing field for the auto-prop for - * *this* base type - */ - allMembers.Remove(overriddenOrHiddenPropName); - continue; - } - } - - /* - * property was *hidden* by a derived type member. In this case, we - * want to show *both* the members - */ - - JObject backingFieldForHiddenProp = allMembers.GetValueOrDefault(overriddenOrHiddenPropName); - if (backingFieldForHiddenProp is null || backingFieldForHiddenProp[InternalUseFieldName.IsBackingField.Name]?.Value() != true) - { - // hiding with a non-auto property, so nothing to adjust - // add the new property - await AddProperty(getMethodId, parentTypeId, state, overriddenOrHiddenPropName, getterMemberAccessAttrs, isStatic, isNewSlot: isNewSlot); - continue; - } - - await UpdateBackingFieldWithPropertyAttributes(backingFieldForHiddenProp, overriddenOrHiddenPropName, getterMemberAccessAttrs, state); - } - - return allMembers; - - async Task UpdateBackingFieldWithPropertyAttributes(JObject backingField, string autoPropName, MethodAttributes getterMemberAccessAttrs, DebuggerBrowsableState? state) - { - backingField[InternalUseFieldName.Section.Name] = getterMemberAccessAttrs switch - { - MethodAttributes.Private => "private", - _ => "result" - }; - backingField[InternalUseFieldName.State.Name] = state?.ToString(); - - if (state is null) - return; - - string namePrefix = GetNamePrefixForValues(autoPropName, typeName, isOwn, state); - string backingPropTypeName = backingField["value"]?["className"]?.Value(); - var expanded = await GetExpandedMemberValues( - sdbHelper, backingPropTypeName, namePrefix, backingField, state, includeStatic, token); - backingField.Remove(); - allMembers.Remove(autoPropName); - foreach (JObject evalue in expanded) - allMembers[evalue["name"].Value()] = evalue; - } - - async Task AddProperty( - int getMethodId, - int parentTypeId, - DebuggerBrowsableState? state, - string propNameWithSufix, - MethodAttributes getterAttrs, - bool isPropertyStatic, - bool isNewSlot) - { - string returnTypeName = await sdbHelper.GetReturnType(getMethodId, token); - JObject propRet = null; - if (getCommandOptions.HasFlag(GetObjectCommandOptions.AutoExpandable) || getCommandOptions.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute) || (state is DebuggerBrowsableState.RootHidden && IsACollectionType(returnTypeName))) - { - try - { - propRet = await sdbHelper.InvokeMethod(getterParamsBuffer, getMethodId, token, name: propNameWithSufix, isPropertyStatic && !isValueType); - } - catch (Exception) - { - propRet = GetNotAutoExpandableObject(getMethodId, propNameWithSufix, isPropertyStatic); - } - } - else - { - propRet = GetNotAutoExpandableObject(getMethodId, propNameWithSufix, isPropertyStatic); - } - - propRet["isOwn"] = isOwn; - propRet[InternalUseFieldName.Section.Name] = getterAttrs switch - { - MethodAttributes.Private => "private", - _ => "result" - }; - propRet[InternalUseFieldName.State.Name] = state?.ToString(); - if (parentTypeId != -1) - { - propRet[InternalUseFieldName.ParentTypeId.Name] = parentTypeId; - propRet[InternalUseFieldName.IsNewSlot.Name] = isNewSlot; - } - - string namePrefix = GetNamePrefixForValues(propNameWithSufix, typeName, isOwn, state); - var expandedMembers = await GetExpandedMemberValues( - sdbHelper, returnTypeName, namePrefix, propRet, state, includeStatic, token); - foreach (var member in expandedMembers) - { - var key = member["name"]?.Value(); - if (key != null) - { - allMembers.TryAdd(key, member as JObject); - } - } - } - - JObject GetNotAutoExpandableObject(int methodId, string propertyName, bool isStatic) - { - JObject methodIdArgs = JObject.FromObject(new - { - isStatic = isStatic, - containerId = isStatic ? typeId : objectId.Value, - isValueType = isValueType, - methodId = methodId - }); - - return JObject.FromObject(new - { - get = new - { - type = "function", - objectId = $"dotnet:method:{methodIdArgs.ToString(Newtonsoft.Json.Formatting.None)}", - className = "Function", - description = "get " + propertyName + " ()" - }, - name = propertyName - }); - } - } - - public static async Task GetObjectMemberValues( - MonoSDBHelper sdbHelper, - int objectId, - GetObjectCommandOptions getCommandType, - CancellationToken token, - bool sortByAccessLevel = false, - bool includeStatic = false) - { - if (await sdbHelper.IsDelegate(objectId, token)) - { - var description = await sdbHelper.GetDelegateMethodDescription(objectId, token); - var objValues = JObject.FromObject(new - { - value = new - { - type = "symbol", - value = description, - description - }, - name = "Target" - }); - - return GetMembersResult.FromValues(new List() { objValues }); - } - - // 1 - var typeIdsIncludingParents = await sdbHelper.GetTypeIdsForObject(objectId, true, token); - - // 2 - if (!getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)) - { - GetMembersResult debuggerProxy = await sdbHelper.GetValuesFromDebuggerProxyAttributeForObject( - objectId, typeIdsIncludingParents[0], token); - if (debuggerProxy != null) - return debuggerProxy; - } - - // 3. GetProperties - DotnetObjectId id = new DotnetObjectId("object", objectId); - using var commandParamsObjWriter = new MonoBinaryWriter(); - commandParamsObjWriter.WriteObj(id, sdbHelper); - ArraySegment getPropertiesParamBuffer = commandParamsObjWriter.GetParameterBuffer(); - - var allMembers = new Dictionary(); - int typeIdsCnt = typeIdsIncludingParents.Count; - for (int i = 0; i < typeIdsCnt; i++) - { - int typeId = typeIdsIncludingParents[i]; - - int parentTypeId = i + 1 < typeIdsCnt ? typeIdsIncludingParents[i + 1] : -1; - string typeName = await sdbHelper.GetTypeName(typeId, token); - // 0th id is for the object itself, and then its ancestors - bool isOwn = i == 0; - - List thisTypeFields = await sdbHelper.GetTypeFields(typeId, token); - if (!includeStatic) - thisTypeFields = thisTypeFields.Where(f => !f.Attributes.HasFlag(FieldAttributes.Static)).ToList(); - - if (thisTypeFields.Count > 0) - { - var allFields = await ExpandFieldValues( - sdbHelper, id, typeId, parentTypeId, thisTypeFields, getCommandType, isOwn, includeStatic, token); - - if (getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) - { - foreach (var f in allFields) - f[InternalUseFieldName.Hidden.Name] = true; - } - AddOnlyNewFieldValuesByNameTo(allFields, allMembers, typeName, isOwn); - } - - // skip loading properties if not necessary - if (!getCommandType.HasFlag(GetObjectCommandOptions.WithProperties)) - return GetMembersResult.FromValues(allMembers.Values, sortByAccessLevel); - - allMembers = await ExpandPropertyValues( - sdbHelper, - typeId, - typeName, - getPropertiesParamBuffer, - getCommandType, - id, - isValueType: false, - isOwn, - token, - allMembers, - includeStatic, - parentTypeId); - - // ownProperties - // Note: ownProperties should mean that we return members of the klass itself, - // but we are going to ignore that here, because otherwise vscode/chrome don't - // seem to ask for inherited fields at all. - //if (ownProperties) - //break; - /*if (accessorPropertiesOnly) - break;*/ - } - - return GetMembersResult.FromValues(allMembers.Values, sortByAccessLevel); - - static void AddOnlyNewFieldValuesByNameTo(JArray namedValues, IDictionary valuesDict, string typeName, bool isOwn) - { - foreach (var item in namedValues) - { - var name = item["name"]?.Value(); - if (name == null) - continue; - - if (valuesDict.TryAdd(name, item as JObject)) - { - // new member - if (item[InternalUseFieldName.IsBackingField.Name]?.Value() == true) - item[InternalUseFieldName.Owner.Name] = typeName; - continue; - } - - if (isOwn) - throw new Exception($"Internal Error: found an existing member on own type. item: {item}, typeName: {typeName}"); - - var parentSuffix = typeName.Split('.')[^1]; - var parentMemberName = $"{name} ({parentSuffix})"; - valuesDict.Add(parentMemberName, item as JObject); - item["name"] = parentMemberName; - } - } - } - - } - - internal sealed class GetMembersResult - { - // public / protected / internal: - public JArray Result { get; set; } - // private: - public JArray PrivateMembers { get; set; } - - public JObject JObject => JObject.FromObject(new - { - result = Result, - privateProperties = PrivateMembers - }); - - public GetMembersResult() - { - Result = new JArray(); - PrivateMembers = new JArray(); - } - - public GetMembersResult(JArray value, bool sortByAccessLevel) - { - var t = FromValues(value, sortByAccessLevel); - Result = t.Result; - PrivateMembers = t.PrivateMembers; - } - - public void CleanUp() - { - JProperty[] toRemoveInObject = new JProperty[InternalUseFieldName.Count]; - - CleanUpJArray(Result); - CleanUpJArray(PrivateMembers); - - void CleanUpJArray(JArray arr) - { - foreach (JToken item in arr) - { - if (item is not JObject jobj || jobj.Count == 0) - continue; - - int removeCount = 0; - foreach (JProperty jp in jobj.Properties()) - { - if (InternalUseFieldName.IsKnown(jp.Name)) - toRemoveInObject[removeCount++] = jp; - } - - for (int i = 0; i < removeCount; i++) - toRemoveInObject[i].Remove(); - } - } - } - - public static GetMembersResult FromValues(IEnumerable values, bool splitMembersByAccessLevel = false) => - FromValues(new JArray(values), splitMembersByAccessLevel); - - public static GetMembersResult FromValues(JArray values, bool splitMembersByAccessLevel = false) - { - GetMembersResult result = new(); - if (splitMembersByAccessLevel) - { - foreach (var member in values) - result.Split(member); - return result; - } - result.Result.AddRange(values); - return result; - } - - private void Split(JToken member) - { - if (member[InternalUseFieldName.Hidden.Name]?.Value() == true) - return; - - if (member[InternalUseFieldName.Section.Name]?.Value() is not string section) - { - Result.Add(member); - return; - } - - switch (section) - { - case "private": - PrivateMembers.Add(member); - return; - default: - Result.Add(member); - return; - } - } - - public GetMembersResult Clone() => new GetMembersResult() - { - Result = (JArray)Result.DeepClone(), - PrivateMembers = (JArray)PrivateMembers.DeepClone(), - }; - - public IEnumerable Where(Func predicate) - { - foreach (var item in Result) - { - if (predicate(item)) - { - yield return item; - } - } - foreach (var item in PrivateMembers) - { - if (predicate(item)) - { - yield return item; - } - } - } - - internal JToken FirstOrDefault(Func p) - => Result.FirstOrDefault(p) - ?? PrivateMembers.FirstOrDefault(p); - - internal JArray Flatten() - { - var result = new JArray(); - result.AddRange(Result); - result.AddRange(PrivateMembers); - return result; - } - public override string ToString() => $"{JObject}\n"; - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/browser/debugger/BrowserDebugProxy/MemberReferenceResolver.cs deleted file mode 100644 index 9798fd400d06d3..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ /dev/null @@ -1,979 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Text; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; -using System.IO; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Collections.Generic; -using System.Net.WebSockets; -using BrowserDebugProxy; -using System.Globalization; -using System.Reflection; - -namespace Microsoft.WebAssembly.Diagnostics -{ - internal sealed class MemberReferenceResolver - { - private static int evaluationResultObjectId; - private readonly SessionId sessionId; - private readonly int scopeId; - private readonly MonoProxy proxy; - private readonly ExecutionContext context; - private readonly PerScopeCache scopeCache; - private readonly ILogger logger; - private bool localsFetched; - private int linqTypeId; - public ExecutionContext GetContext() => context; - - public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId sessionId, int scopeId, ILogger logger) - { - this.sessionId = sessionId; - this.scopeId = scopeId; - this.proxy = proxy; - this.context = ctx; - this.logger = logger; - scopeCache = ctx.GetCacheForScope(scopeId); - linqTypeId = -1; - } - - public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, SessionId sessionId, JArray objectValues, ILogger logger) - { - this.sessionId = sessionId; - scopeId = -1; - this.proxy = proxy; - this.context = ctx; - this.logger = logger; - scopeCache = new PerScopeCache(objectValues); - localsFetched = true; - linqTypeId = -1; - } - - public async Task GetValueFromObject(JToken objRet, CancellationToken token) - { - if (objRet["value"]?["className"]?.Value() == "System.Exception") - { - if (DotnetObjectId.TryParse(objRet?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) - { - GetMembersResult exceptionObject = await MemberObjectsExplorer.GetTypeMemberValues(context.SdbAgent, objectId, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); - var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("_message")); - exceptionObjectMessage["value"]["value"] = objRet["value"]?["className"]?.Value() + ": " + exceptionObjectMessage["value"]?["value"]?.Value(); - return exceptionObjectMessage["value"]?.Value(); - } - return objRet["value"]?.Value(); - } - - if (objRet["value"]?.Value() != null) - return objRet["value"]?.Value(); - - if (objRet["get"]?.Value() != null && - DotnetObjectId.TryParse(objRet?["get"]?["objectId"]?.Value(), out DotnetObjectId getterObjectId)) - { - var ret = await context.SdbAgent.InvokeMethod(getterObjectId, token); - return await GetValueFromObject(ret, token); - } - return null; - } - - public async Task<(JObject containerObject, ArraySegment remaining)> ResolveStaticMembersInStaticTypes(ArraySegment expressionParts, CancellationToken token) - { - var store = await proxy.LoadStore(sessionId, false, token); - var methodInfo = context.CallStack.FirstOrDefault(s => s.Id == scopeId)?.Method?.Info; - - if (methodInfo == null) - return (null, null); - - string[] parts = expressionParts.ToArray(); - - string fullName = methodInfo.IsAsync == 0 ? methodInfo.TypeInfo.FullName : StripAsyncPartOfFullName(methodInfo.TypeInfo.FullName); - string[] fullNameParts = fullName.Split(".", StringSplitOptions.TrimEntries).ToArray(); - for (int i = 0; i < fullNameParts.Length; i++) - { - string[] fullNamePrefix = fullNameParts[..^i]; - var (memberObject, remaining) = await FindStaticMemberMatchingParts(parts, fullNamePrefix); - if (memberObject != null) - return (memberObject, remaining); - } - return await FindStaticMemberMatchingParts(parts); - - async Task<(JObject, ArraySegment)> FindStaticMemberMatchingParts(string[] parts, string[] fullNameParts = null) - { - string classNameToFind = fullNameParts == null ? "" : string.Join(".", fullNameParts); - int typeId = -1; - for (int i = 0; i < parts.Length; i++) - { - if (!string.IsNullOrEmpty(methodInfo.TypeInfo.Namespace)) - { - typeId = await FindStaticTypeId(methodInfo.TypeInfo.Namespace + "." + classNameToFind); - if (typeId != -1) - continue; - } - typeId = await FindStaticTypeId(classNameToFind); - - string part = parts[i]; - if (typeId != -1) - { - JObject memberObject = await FindStaticMemberInType(classNameToFind, part, typeId); - if (memberObject != null) - { - ArraySegment remaining = null; - if (i < parts.Length - 1) - remaining = parts[i..]; - return (memberObject, remaining); - } - - // Didn't find a member named `part` in `typeId`. - // Could be a nested type. Let's continue the search - // with `part` added to the type name - - typeId = -1; - } - - if (classNameToFind.Length > 0) - classNameToFind += "."; - classNameToFind += part; - } - return (null, null); - } - - // async function full name has a form: namespaceName.d__integer - static string StripAsyncPartOfFullName(string fullName) - => fullName.IndexOf(".<") is int index && index < 0 - ? fullName - : fullName.Substring(0, index); - - - async Task FindStaticMemberInType(string classNameToFind, string name, int typeId) - { - var fields = await context.SdbAgent.GetTypeFields(typeId, token); - foreach (var field in fields) - { - if (field.Name != name) - continue; - - var isInitialized = await context.SdbAgent.TypeIsInitialized(typeId, token); - if (isInitialized == 0) - { - isInitialized = await context.SdbAgent.TypeInitialize(typeId, token); - } - try - { - var staticFieldValue = await context.SdbAgent.GetFieldValue(typeId, field.Id, token); - var valueRet = await GetValueFromObject(staticFieldValue, token); - // we need the full name here - valueRet["className"] = classNameToFind; - return valueRet; - } - catch (Exception ex) - { - logger.LogDebug(ex, $"Failed to get value of field {field.Name} on {classNameToFind} " + - $"because {field.Name} is not a static member of {classNameToFind}."); - } - return null; - } - - var methodId = await context.SdbAgent.GetPropertyMethodIdByName(typeId, name, token); - if (methodId != -1) - { - using var commandParamsObjWriter = new MonoBinaryWriter(); - commandParamsObjWriter.Write(0); //param count - try - { - var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token); - return await GetValueFromObject(retMethod, token); - } - catch (Exception ex) - { - logger.LogDebug(ex, $"Failed to invoke getter of id={methodId} on {classNameToFind}.{name} " + - $"because {name} is not a static member of {classNameToFind}."); - } - } - return null; - } - - async Task FindStaticTypeId(string typeName) - { - foreach (var asm in store.assemblies) - { - var type = asm.GetTypeByName(typeName); - if (type == null) - continue; - - int id = await context.SdbAgent.GetTypeIdFromToken(await asm.GetDebugId(context.SdbAgent, token), type.Token, token); - if (id != -1) - return id; - } - - return -1; - } - } - - // Checks Locals, followed by `this` - public async Task Resolve(string varName, CancellationToken token) - { - // question mark at the end of expression is invalid - if (varName[^1] == '?') - throw new ReturnAsErrorException($"Expected expression.", "ReferenceError"); - - //has method calls - if (varName.Contains('(')) - return null; - - if (scopeCache.MemberReferences.TryGetValue(varName, out JObject ret)) - return ret; - - if (scopeCache.ObjectFields.TryGetValue(varName, out JObject valueRet)) - return await GetValueFromObject(valueRet, token); - - string[] parts = varName.Split(".", StringSplitOptions.TrimEntries); - if (parts.Length == 0 || string.IsNullOrEmpty(parts[0])) - throw new ReturnAsErrorException($"Failed to resolve expression: {varName}", "ReferenceError"); - - JObject retObject = await ResolveAsLocalOrThisMember(parts[0]); - bool throwOnNullReference = parts[0][^1] != '?'; - if (retObject != null && parts.Length > 1) - retObject = await ResolveAsInstanceMember(parts, retObject, throwOnNullReference); - - if (retObject == null) - { - (retObject, ArraySegment remaining) = await ResolveStaticMembersInStaticTypes(parts, token); - if (remaining != null && remaining.Count != 0) - { - if (retObject.IsNullValuedObject()) - { - // NRE on null.$remaining - retObject = null; - } - else - { - retObject = await ResolveAsInstanceMember(remaining, retObject, throwOnNullReference); - } - } - } - - scopeCache.MemberReferences[varName] = retObject; - return retObject; - - async Task ResolveAsLocalOrThisMember(string name) - { - if (scopeCache.Locals.Count == 0 && !localsFetched) - { - try - { - await proxy.GetScopeProperties(sessionId, scopeId, token); - } - catch (Exception ex) - { - throw new ReturnAsErrorException($"BUG: Unable to get properties for scope: {scopeId}. {ex}", ex.GetType().Name); - } - localsFetched = true; - } - - // remove null-condition, otherwise TryGet by name fails - if (name[^1] == '?' || name[^1] == '!') - name = name.Remove(name.Length - 1); - - if (scopeCache.Locals.TryGetValue(name, out JObject obj)) - return obj["value"]?.Value(); - - if (!scopeCache.Locals.TryGetValue("this", out JObject objThis)) - return null; - - if (!DotnetObjectId.TryParse(objThis?["value"]?["objectId"]?.Value(), out DotnetObjectId objectId)) - return null; - - ValueOrError valueOrError = await proxy.RuntimeGetObjectMembers(sessionId, objectId, null, token); - if (valueOrError.IsError) - { - logger.LogDebug($"ResolveAsLocalOrThisMember failed with : {valueOrError.Error}"); - return null; - } - - JToken objRet = valueOrError.Value.FirstOrDefault(objPropAttr => objPropAttr["name"].Value() == name); - if (objRet != null) - return await GetValueFromObject(objRet, token); - - return null; - } - - async Task ResolveAsInstanceMember(ArraySegment parts, JObject baseObject, bool throwOnNullReference) - { - JObject resolvedObject = baseObject; - // parts[0] - name of baseObject - for (int i = 1; i < parts.Count; i++) - { - string part = parts[i]; - if (part.Length == 0) - return null; - - bool hasCurrentPartNullCondition = part[^1] == '?'; - - // current value of resolvedObject is on parts[i - 1] - if (resolvedObject.IsNullValuedObject()) - { - // trying null.$member - if (throwOnNullReference) - throw new ReturnAsErrorException($"Expression threw NullReferenceException trying to access \"{part}\" on a null-valued object.", "ReferenceError"); - - if (i == parts.Count - 1) - { - // this is not ideal, it returns the last object - // that had objectId and was null-valued, - // so the class/description of object are not of the last part - return resolvedObject; - } - - // check if null condition is correctly applied: should we throw or return null-object - throwOnNullReference = !hasCurrentPartNullCondition; - continue; - } - - if (!DotnetObjectId.TryParse(resolvedObject?["objectId"]?.Value(), out DotnetObjectId objectId)) - { - if (!throwOnNullReference) - throw new ReturnAsErrorException($"Operation '?' not allowed on primitive type - '{parts[i - 1]}'", "ReferenceError"); - throw new ReturnAsErrorException($"Cannot find member '{part}' on a primitive type", "ReferenceError"); - } - - var args = JObject.FromObject(new { forDebuggerDisplayAttribute = true }); - ValueOrError valueOrError = await proxy.RuntimeGetObjectMembers(sessionId, objectId, args, token); - if (valueOrError.IsError) - { - logger.LogDebug($"ResolveAsInstanceMember failed with : {valueOrError.Error}"); - return null; - } - - if (part[^1] == '!' || part[^1] == '?') - part = part.Remove(part.Length - 1); - - JToken objRet = valueOrError.Value.FirstOrDefault(objPropAttr => objPropAttr["name"]?.Value() == part); - if (objRet == null) - return null; - - resolvedObject = await GetValueFromObject(objRet, token); - if (resolvedObject == null) - return null; - throwOnNullReference = !hasCurrentPartNullCondition; - } - return resolvedObject; - } - } - - public async Task Resolve( - ElementAccessExpressionSyntax elementAccess, - Dictionary memberAccessValues, - List nestedIndexObject, - List variableDefinitions, - CancellationToken token) - { - try - { - JObject rootObject = null; - string elementAccessStrExpression = elementAccess.Expression.ToString(); - rootObject = await Resolve(elementAccessStrExpression, token); - - if (rootObject == null) - { - // it might be a jagged array where the previously added nestedIndexObject should be treated as a new rootObject - rootObject = nestedIndexObject.LastOrDefault(); - if (rootObject != null) - nestedIndexObject.RemoveAt(nestedIndexObject.Count - 1); - } - - ElementIndexInfo elementIdxInfo = await GetElementIndexInfo(nestedIndexObject); - if (elementIdxInfo is null) - return null; - - // 1. Parse the indexes - int elementIdx = 0; - var elementAccessStr = elementAccess.ToString(); - - // 2. Get the value - var type = rootObject?["type"]?.Value(); - if (!DotnetObjectId.TryParse(rootObject?["objectId"]?.Value(), out DotnetObjectId objectId)) - throw new InvalidOperationException($"Cannot apply indexing with [] to a primitive object of type '{type}'"); - - bool isMultidimensional = elementIdxInfo.DimensionsCount != 1; - switch (objectId.Scheme) - { - case "valuetype": //can be an inlined array - { - if (!context.SdbAgent.ValueCreator.TryGetValueTypeById(objectId.Value, out ValueTypeClass valueType)) - throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of scheme '{objectId.Scheme}'"); - var typeInfo = await context.SdbAgent.GetTypeInfo(valueType.TypeId, token); - if (valueType.InlineArray == null) - { - JObject vtResult = await InvokeGetItemOnJObject(rootObject, valueType.TypeId, objectId, elementIdxInfo, token); - if (vtResult != null) - return vtResult; - } - if (int.TryParse(elementIdxInfo.ElementIdxStr, out elementIdx) && elementIdx >= 0 && elementIdx < valueType.InlineArray.Count) - return (JObject)valueType.InlineArray[elementIdx]["value"]; - throw new InvalidOperationException($"Index is outside the bounds of the inline array"); - } - case "array": - rootObject["value"] = await context.SdbAgent.GetArrayValues(objectId.Value, token); - if (!isMultidimensional) - { - int.TryParse(elementIdxInfo.ElementIdxStr, out elementIdx); - return (JObject)rootObject["value"][elementIdx]["value"]; - } - else - { - return (JObject)(((JArray)rootObject["value"]).FirstOrDefault(x => x["name"].Value() == elementIdxInfo.ElementIdxStr)["value"]); - } - case "object": - // ToDo: try to use the get_Item for string as well - if (!isMultidimensional && type == "string") - { - var eaExpressionFormatted = elementAccessStrExpression.Replace('.', '_'); // instance_str - variableDefinitions.Add(new(eaExpressionFormatted, rootObject, ExpressionEvaluator.ConvertJSToCSharpLocalVariableAssignment(eaExpressionFormatted, rootObject))); - var eaFormatted = elementAccessStr.Replace('.', '_'); // instance_str[1] - var variableDef = await ExpressionEvaluator.GetVariableDefinitions(this, variableDefinitions, invokeToStringInObject: false, token); - return await ExpressionEvaluator.EvaluateSimpleExpression(this, eaFormatted, elementAccessStr, variableDef, logger, token); - } - if (elementIdxInfo.Indexers is null || elementIdxInfo.Indexers.Count == 0) - throw new InternalErrorException($"Unable to write index parameter to invoke the method in the runtime."); - - List typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token); - JObject objResult = await InvokeGetItemOnJObject(rootObject, typeIds[0], objectId, elementIdxInfo, token); - if (objResult == null) - throw new InvalidOperationException($"Cannot apply indexing with [] to an object of type '{rootObject?["className"]?.Value()}'"); - return objResult; - default: - throw new InvalidOperationException($"Cannot apply indexing with [] to an expression of scheme '{objectId.Scheme}'"); - } - } - catch (Exception ex) - { - throw new ReturnAsErrorException($"Unable to evaluate element access '{elementAccess}': {ex.Message}", ex.GetType().Name); - } - - async Task GetElementIndexInfo(List nestedIndexers) - { - if (elementAccess.ArgumentList is null) - return null; - - int dimCnt = elementAccess.ArgumentList.Arguments.Count; - LiteralExpressionSyntax indexingExpression = null; - StringBuilder elementIdxStr = new StringBuilder(); - List indexers = new(); - // nesting should be resolved in reverse order - int nestedIndexersCnt = nestedIndexers.Count - 1; - for (int i = 0; i < dimCnt; i++) - { - JObject indexObject; - var arg = elementAccess.ArgumentList.Arguments[i]; - if (i != 0) - { - elementIdxStr.Append(", "); - } - // e.g. x[1] - if (arg.Expression is LiteralExpressionSyntax) - { - indexingExpression = arg.Expression as LiteralExpressionSyntax; - string expression = indexingExpression.ToString(); - elementIdxStr.Append(expression); - indexers.Add(indexingExpression); - } - - // e.g. x[a] or x[a.b] - else if (arg.Expression is IdentifierNameSyntax) - { - var argParm = arg.Expression as IdentifierNameSyntax; - - // x[a.b] - memberAccessValues.TryGetValue(argParm.Identifier.Text, out indexObject); - - // x[a] - indexObject ??= await Resolve(argParm.Identifier.Text, token); - elementIdxStr.Append(indexObject["value"].ToString()); - indexers.Add(indexObject); - } - // nested indexing, e.g. x[a[0]], x[a[b[1]]], x[a[0], b[1]] - else if (arg.Expression is ElementAccessExpressionSyntax) - { - if (nestedIndexers == null || nestedIndexersCnt < 0) - throw new InvalidOperationException($"Cannot resolve nested indexing"); - JObject nestedIndexObject = nestedIndexers[nestedIndexersCnt]; - nestedIndexers.RemoveAt(nestedIndexersCnt); - elementIdxStr.Append(nestedIndexObject["value"].ToString()); - indexers.Add(nestedIndexObject); - nestedIndexersCnt--; - } - // indexing with expressions, e.g. x[a + 1] - else - { - string expression = arg.ToString(); - var variableDef = await ExpressionEvaluator.GetVariableDefinitions(this, variableDefinitions, invokeToStringInObject: false, token); - indexObject = await ExpressionEvaluator.EvaluateSimpleExpression(this, expression, expression, variableDef, logger, token); - string idxType = indexObject["type"].Value(); - if (idxType != "number") - throw new InvalidOperationException($"Cannot index with an object of type '{idxType}'"); - elementIdxStr.Append(indexObject["value"].ToString()); - indexers.Add(indexObject); - } - } - return new ElementIndexInfo( - DimensionsCount: dimCnt, - ElementIdxStr: elementIdxStr.ToString(), - Indexers: indexers); - } - } - - private async Task InvokeGetItemOnJObject( - JObject rootObject, - int typeId, - DotnetObjectId objectId, - ElementIndexInfo elementIdxInfo, - CancellationToken token) - { - int[] methodIds = await context.SdbAgent.GetMethodIdsByName(typeId, "get_Item", BindingFlags.Default, token); - if (methodIds == null || methodIds.Length == 0) - throw new InvalidOperationException($"Type '{rootObject?["className"]?.Value()}' cannot be indexed."); - var type = rootObject?["type"]?.Value(); - - // ToDo: optimize the loop by choosing the right method at once without trying out them all - for (int i = 0; i < methodIds.Length; i++) - { - MethodInfoWithDebugInformation methodInfo = await context.SdbAgent.GetMethodInfo(methodIds[i], token); - ParameterInfo[] paramInfo = methodInfo.GetParametersInfo(); - if (paramInfo.Length != elementIdxInfo.DimensionsCount) - continue; - try - { - if (!CheckParametersCompatibility(paramInfo, elementIdxInfo.Indexers)) - continue; - ArraySegment buffer = await WriteIndexObjectAsIndices(objectId, elementIdxInfo.Indexers, paramInfo); - JObject getItemRetObj = await context.SdbAgent.InvokeMethod(buffer, methodIds[i], token); - return (JObject)getItemRetObj["value"]; - } - catch (Exception ex) - { - logger.LogDebug($"Attempt number {i + 1} out of {methodIds.Length} of invoking method {methodInfo.Name} with parameter named {paramInfo[0].Name} on type {type} failed. Method Id = {methodIds[i]}.\nInner exception: {ex}."); - continue; - } - } - return null; - - async Task> WriteIndexObjectAsIndices(DotnetObjectId rootObjId, List indexObjects, ParameterInfo[] paramInfo) - { - using var writer = new MonoBinaryWriter(); - writer.WriteObj(rootObjId, context.SdbAgent); - writer.Write(indexObjects.Count); // number of method args - foreach ((ParameterInfo pi, object indexObject) in paramInfo.Zip(indexObjects)) - { - if (indexObject is JObject indexJObject) - { - // indexed by an identifier name syntax - if (!await writer.WriteJsonValue(indexJObject, context.SdbAgent, pi.TypeCode, token)) - throw new InternalErrorException($"Parsing index of type {indexJObject["type"].Value()} to write it into the buffer failed."); - } - else if (indexObject is LiteralExpressionSyntax expression) - { - // indexed by a literal expression syntax - if (!await writer.WriteConst(expression, context.SdbAgent, token)) - throw new InternalErrorException($"Parsing literal expression index = {expression} to write it into the buffer failed."); - } - else - { - throw new InternalErrorException($"Unexpected index type."); - } - } - return writer.GetParameterBuffer(); - } - } - - private static bool CheckParametersCompatibility(ParameterInfo[] paramInfos, List indexObjects) - { - if (paramInfos.Length != indexObjects.Count) - return false; - foreach ((ParameterInfo paramInfo, object indexObj) in paramInfos.Zip(indexObjects)) - { - string argumentType = "", argumentClassName = ""; - bool isArray = false; - if (indexObj is JObject indexJObj) - { - argumentType = indexJObj["type"]?.Value(); - argumentClassName = indexJObj["className"]?.Value(); - isArray = indexJObj["subtype"]?.Value()?.Equals("array") == true; - } - else if (indexObj is LiteralExpressionSyntax literal) - { - // any primitive literal is an object - if (paramInfo.TypeCode.Value == ElementType.Object) - continue; - switch (literal.Kind()) - { - case SyntaxKind.NumericLiteralExpression: - argumentType = "number"; - break; - case SyntaxKind.StringLiteralExpression: - argumentType = "string"; - break; - case SyntaxKind.TrueLiteralExpression: - case SyntaxKind.FalseLiteralExpression: - argumentType = "boolean"; - break; - case SyntaxKind.CharacterLiteralExpression: - argumentType = "symbol"; - break; - case SyntaxKind.NullLiteralExpression: - // do not check - continue; - } - } - if (!CheckParameterCompatibility(paramInfo.TypeCode, argumentType, argumentClassName, isArray)) - return false; - } - return true; - } - - private static bool CheckParameterCompatibility(ElementType? paramTypeCode, string argumentType, string argumentClassName, bool isArray) - { - if (!paramTypeCode.HasValue) - return true; - - switch (paramTypeCode.Value) - { - case ElementType.Object: - if (argumentType != "object" || isArray) - return false; - break; - case ElementType.I2: - case ElementType.I4: - case ElementType.I8: - case ElementType.R4: - case ElementType.R8: - case ElementType.U2: - case ElementType.U4: - case ElementType.U8: - if (argumentType != "number") - return false; - if (argumentType == "object") - return false; - break; - case ElementType.Char: - if (argumentType != "string" && argumentType != "symbol") - return false; - if (argumentType == "object") - return false; - break; - case ElementType.Boolean: - if (argumentType == "boolean") - return true; - if (argumentType == "number" && (argumentClassName == "Single" || argumentClassName == "Double")) - return false; - if (argumentType == "object") - return false; - if (argumentType == "string" || argumentType == "symbol") - return false; - break; - case ElementType.String: - if (argumentType != "string") - return false; - break; - default: - return true; - } - return true; - } - - public async Task<(JObject, string)> ResolveInvocationInfo(InvocationExpressionSyntax method, CancellationToken token) - { - var methodName = ""; - try - { - JObject rootObject = null; - var expr = method.Expression; - if (expr is MemberAccessExpressionSyntax memberAccessExpressionSyntax) - { - rootObject = await Resolve(memberAccessExpressionSyntax.Expression.ToString(), token); - methodName = memberAccessExpressionSyntax.Name.ToString(); - - if (rootObject.IsNullValuedObject()) - throw new ReturnAsErrorException($"Expression '{memberAccessExpressionSyntax}' evaluated to null", "NullReferenceException"); - } - else if (expr is IdentifierNameSyntax && scopeCache.ObjectFields.TryGetValue("this", out JObject thisValue)) - { - rootObject = await GetValueFromObject(thisValue, token); - methodName = expr.ToString(); - } - return (rootObject, methodName); - } - catch (Exception ex) when (ex is not ReturnAsErrorException) - { - throw new Exception($"Unable to evaluate method '{methodName}'", ex); - } - } - - private static readonly string[] primitiveTypes = new string[] { "string", "number", "boolean", "symbol" }; - - public async Task Resolve(InvocationExpressionSyntax method, Dictionary memberAccessValues, CancellationToken token) - { - (JObject rootObject, string methodName) = await ResolveInvocationInfo(method, token); - if (rootObject == null) - throw new ReturnAsErrorException($"Failed to resolve root object for {method}", "ReferenceError"); - - // primitives don't have objectId - if (!DotnetObjectId.TryParse(rootObject["objectId"]?.Value(), out DotnetObjectId objectId) && - primitiveTypes.Contains(rootObject["type"]?.Value())) - return null; - - if (method.ArgumentList == null) - throw new InternalErrorException($"Failed to resolve method call for {method}, list of arguments is null."); - - bool isExtensionMethod = false; - try - { - List typeIds; - if (objectId.IsValueType) - { - if (!context.SdbAgent.ValueCreator.TryGetValueTypeById(objectId.Value, out ValueTypeClass valueType)) - throw new Exception($"Could not find valuetype {objectId}"); - typeIds = new List(1) { valueType.TypeId }; - } - else - { - typeIds = await context.SdbAgent.GetTypeIdsForObject(objectId.Value, true, token); - } - int[] methodIds = await context.SdbAgent.GetMethodIdsByName(typeIds[0], methodName, BindingFlags.Default, token); - if (methodIds == null) - { - //try to search on System.Linq.Enumerable - int methodId = await FindMethodIdOnLinqEnumerable(typeIds, methodName); - if (methodId == 0) - { - var typeName = await context.SdbAgent.GetTypeName(typeIds[0], token); - throw new ReturnAsErrorException($"Method '{methodName}' not found in type '{typeName}'", "ReferenceError"); - } - methodIds = new int[] { methodId }; - } - // get information about params in all overloads for *methodName* - List methodInfos = await GetMethodParamInfosForMethods(methodIds); - int passedArgsCnt = method.ArgumentList.Arguments.Count; - int maxMethodParamsCnt = methodInfos.Max(v => v.GetParametersInfo().Length); - if (isExtensionMethod) - { - // implicit *this* parameter - maxMethodParamsCnt--; - } - if (passedArgsCnt > maxMethodParamsCnt) - throw new ReturnAsErrorException($"Unable to evaluate method '{methodName}'. Too many arguments passed.", "ArgumentError"); - - foreach (var methodInfo in methodInfos) - { - ParameterInfo[] methodParamsInfo = methodInfo.GetParametersInfo(); - int methodParamsCnt = isExtensionMethod ? methodParamsInfo.Length - 1 : methodParamsInfo.Length; - int optionalParams = methodParamsInfo.Count(v => v.Value != null); - if (passedArgsCnt > methodParamsCnt || passedArgsCnt < methodParamsCnt - optionalParams) - { - // this overload does not match the number of params passed, try another one - continue; - } - int methodId = methodInfo.DebugId; - using var commandParamsObjWriter = new MonoBinaryWriter(); - - if (isExtensionMethod) - { - commandParamsObjWriter.Write(methodParamsCnt + 1); - commandParamsObjWriter.WriteObj(objectId, context.SdbAgent); - } - else - { - // instance method - commandParamsObjWriter.WriteObj(objectId, context.SdbAgent); - commandParamsObjWriter.Write(methodParamsCnt); - } - - int argIndex = 0; - // explicitly passed arguments - for (; argIndex < passedArgsCnt; argIndex++) - { - var arg = method.ArgumentList.Arguments[argIndex]; - if (arg.Expression is LiteralExpressionSyntax literal) - { - if (!await commandParamsObjWriter.WriteConst(literal, context.SdbAgent, token)) - throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write LiteralExpressionSyntax into binary writer."); - } - else if (arg.Expression is PrefixUnaryExpressionSyntax negativeLiteral) - { - if (!commandParamsObjWriter.WriteConst(negativeLiteral)) - throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write PrefixUnaryExpressionSyntax into binary writer."); - } - else if (arg.Expression is IdentifierNameSyntax identifierName) - { - if (!memberAccessValues.TryGetValue(identifierName.Identifier.Text, out JObject argValue)) - argValue = await Resolve(identifierName.Identifier.Text, token); - if (!await commandParamsObjWriter.WriteJsonValue(argValue, context.SdbAgent, methodParamsInfo[argIndex].TypeCode, token)) - throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write IdentifierNameSyntax into binary writer."); - } - else if (arg.Expression is MemberAccessExpressionSyntax memberAccess) - { - JObject argValue = await Resolve(memberAccess.ToString(), token); - if (!await commandParamsObjWriter.WriteJsonValue(argValue, context.SdbAgent, methodParamsInfo[argIndex].TypeCode, token)) - throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write IdentifierNameSyntax into binary writer."); - } - else - { - throw new InternalErrorException($"Unable to evaluate method '{methodName}'. Unable to write into binary writer, not recognized expression type: {arg.Expression.GetType().Name}"); - } - } - // optional arguments that were not overwritten - for (; argIndex < methodParamsCnt; argIndex++) - { - if (!await commandParamsObjWriter.WriteConst(methodParamsInfo[argIndex].TypeCode, methodParamsInfo[argIndex].Value, context.SdbAgent, token)) - throw new InternalErrorException($"Unable to write optional parameter {methodParamsInfo[argIndex].Name} value in method '{methodName}' to the mono buffer."); - } - try - { - var retMethod = await context.SdbAgent.InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token); - return await GetValueFromObject(retMethod, token); - } - catch - { - // try further methodIds, we're looking for a method with the same type of params that the user passed - logger.LogDebug($"InvokeMethod failed due to parameter type mismatch for {methodName} with {methodParamsCnt} parameters, including {optionalParams} optional."); - continue; - } - } - throw new ReturnAsErrorException($"No implementation of method '{methodName}' matching '{method}' found in type {rootObject["className"]}.", "ArgumentError"); - } - catch (Exception ex) when (ex is not ReturnAsErrorException) - { - throw new ReturnAsErrorException($"Unable to evaluate method '{method}': {ex.Message}", ex.GetType().Name); - } - - async Task FindMethodIdOnLinqEnumerable(IList typeIds, string methodName) - { - if (linqTypeId == -1) - { - linqTypeId = await context.SdbAgent.GetTypeByName("System.Linq.Enumerable", token); - if (linqTypeId == 0) - { - logger.LogDebug($"Cannot find type 'System.Linq.Enumerable'"); - return 0; - } - } - - int[] newMethodIds = await context.SdbAgent.GetMethodIdsByName(linqTypeId, methodName, BindingFlags.Default, token); - if (newMethodIds == null) - return 0; - - foreach (int typeId in typeIds) - { - List genericTypeArgs = await context.SdbAgent.GetTypeParamsOrArgsForGenericType(typeId, token); - if (genericTypeArgs.Count > 0) - { - isExtensionMethod = true; - return await context.SdbAgent.MakeGenericMethod(newMethodIds[0], genericTypeArgs, token); - } - } - - return 0; - } - - async Task> GetMethodParamInfosForMethods(int[] methodIds) - { - List allMethodInfos = new(); - for (int i = 0; i < methodIds.Length; i++) - { - var ithMethodInfo = await context.SdbAgent.GetMethodInfo(methodIds[i], token); - if (ithMethodInfo != null) - allMethodInfos.Add(ithMethodInfo); - } - return allMethodInfos; - } - } - - public JObject ConvertCSharpToJSType(object v, Type type) - { - if (v is JObject jobj) - return jobj; - - if (v is null) - return JObjectValueCreator.CreateNull("")?["value"] as JObject; - - if (v is Array arr) - { - return CacheEvaluationResult( - JObject.FromObject( - new - { - type = "object", - subtype = "array", - value = new JArray(arr.Cast().Select((val, idx) => JObject.FromObject( - new - { - value = ConvertCSharpToJSType(val, val.GetType()), - name = $"{idx}" - }))), - description = v.ToString(), - className = type.ToString() - })); - } - - string typeName = v.GetType().ToString(); - jobj = JObjectValueCreator.CreateFromPrimitiveType(v); - return jobj is not null - ? jobj["value"] as JObject - : JObjectValueCreator.Create(value: null, - type: "object", - description: v.ToString(), - className: typeName)?["value"] as JObject; - } - - private JObject CacheEvaluationResult(JObject value) - { - if (IsDuplicated(value, out JObject duplicate)) - return value; - - var evalResultId = Interlocked.Increment(ref evaluationResultObjectId); - string id = $"dotnet:evaluationResult:{evalResultId}"; - if (!value.TryAdd("objectId", id)) - { - logger.LogWarning($"EvaluationResult cache request passed with ID: {value["objectId"].Value()}. Overwritting it with a automatically assigned ID: {id}."); - value["objectId"] = id; - } - scopeCache.EvaluationResults.Add(id, value); - return value; - - bool IsDuplicated(JObject er, out JObject duplicate) - { - var type = er["type"].Value(); - var subtype = er["subtype"].Value(); - var value = er["value"]; - var description = er["description"].Value(); - var className = er["className"].Value(); - duplicate = scopeCache.EvaluationResults.FirstOrDefault( - pair => pair.Value["type"].Value() == type - && pair.Value["subtype"].Value() == subtype - && pair.Value["description"].Value() == description - && pair.Value["className"].Value() == className - && JToken.DeepEquals(pair.Value["value"], value)).Value; - return duplicate != null; - } - } - - public JObject TryGetEvaluationResult(string id) - { - JObject val; - if (!scopeCache.EvaluationResults.TryGetValue(id, out val)) - logger.LogError($"EvaluationResult of ID: {id} does not exist in the cache."); - return val; - } - - private sealed record ElementIndexInfo( - string ElementIdxStr, - // keeps JObjects and LiteralExpressionSyntaxes: - List Indexers, - int DimensionsCount = 1); - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/MetadataDebugSummary.cs b/src/mono/browser/debugger/BrowserDebugProxy/MetadataDebugSummary.cs deleted file mode 100644 index 714f0e9f1e4f65..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/MetadataDebugSummary.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; -using System.Threading; -using Microsoft.FileFormats.PE; - -namespace Microsoft.WebAssembly.Diagnostics; - -/// -/// Information we can extract directly from the assembly image using metadata readers -/// -internal sealed class MetadataDebugSummary -{ - internal MetadataReader? PdbMetadataReader { get; private init; } - internal bool IsPortableCodeView { get; private init; } - internal PdbChecksum[] PdbChecksums { get; private init; } - - internal CodeViewDebugDirectoryData? CodeViewData { get; private init; } - - private MetadataDebugSummary(MetadataReader? pdbMetadataReader, bool isPortableCodeView, PdbChecksum[] pdbChecksums, CodeViewDebugDirectoryData? codeViewData) - { - PdbMetadataReader = pdbMetadataReader; - IsPortableCodeView = isPortableCodeView; - PdbChecksums = pdbChecksums; - CodeViewData = codeViewData; - } - - internal static MetadataDebugSummary Create(MonoProxy monoProxy, SessionId sessionId, string name, IDebugMetadataProvider provider, byte[]? pdb, CancellationToken token) - { - var entries = provider.ReadDebugDirectory(); - CodeViewDebugDirectoryData? codeViewData = null; - bool isPortableCodeView = false; - List pdbChecksums = new(); - DebugDirectoryEntry? embeddedPdbEntry = null; - foreach (var entry in entries) - { - switch (entry.Type) - { - case DebugDirectoryEntryType.CodeView: - codeViewData = provider.ReadCodeViewDebugDirectoryData(entry); - if (entry.IsPortableCodeView) - isPortableCodeView = true; - break; - case DebugDirectoryEntryType.PdbChecksum: - var checksum = provider.ReadPdbChecksumDebugDirectoryData(entry); - pdbChecksums.Add(new PdbChecksum(checksum.AlgorithmName, checksum.Checksum.ToArray())); - break; - case DebugDirectoryEntryType.EmbeddedPortablePdb: - embeddedPdbEntry = entry; - break; - default: - break; - } - } - - MetadataReader? pdbMetadataReader = null; - if (pdb != null) - { - var pdbStream = new MemoryStream(pdb); - try - { - // MetadataReaderProvider.FromPortablePdbStream takes ownership of the stream - pdbMetadataReader = MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader(); - } - catch (BadImageFormatException) - { - monoProxy.SendLog(sessionId, $"Warning: Unable to read debug information of: {name} (use DebugType=Portable/Embedded)", token); - } - } - else - { - if (embeddedPdbEntry != null && embeddedPdbEntry.Value.DataSize != 0) - { - pdbMetadataReader = provider.ReadEmbeddedPortablePdbDebugDirectoryData(embeddedPdbEntry.Value).GetMetadataReader(); - } - } - - return new MetadataDebugSummary(pdbMetadataReader, isPortableCodeView, pdbChecksums.ToArray(), codeViewData); - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/browser/debugger/BrowserDebugProxy/MonoProxy.cs deleted file mode 100644 index 6531a68654b44e..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/MonoProxy.cs +++ /dev/null @@ -1,1984 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System.Net.Http; -using BrowserDebugProxy; -using static System.Formats.Asn1.AsnWriter; -using System.Reflection; -using System.Collections.Concurrent; - -namespace Microsoft.WebAssembly.Diagnostics -{ - internal class MonoProxy : DevToolsProxy - { - internal List UrlSymbolServerList { get; private set; } - internal string CachePathSymbolServer { get; private set; } - private readonly HashSet sessions = new HashSet(); - private static readonly string[] s_executionContextIndependentCDPCommandNames = { "DotnetDebugger.setDebuggerProperty", "DotnetDebugger.runTests" }; - internal ConcurrentExecutionContextDictionary Contexts = new(); - - public static HttpClient HttpClient => new HttpClient(); - - // index of the runtime in a same JS page/process - public int RuntimeId { get; private init; } - public bool JustMyCode { get; private set; } - private PauseOnExceptionsKind _defaultPauseOnExceptions { get; set; } - - public MonoProxy(ILogger logger, int runtimeId = 0, string loggerId = "", ProxyOptions options = null) : base(options, logger, loggerId) - { - UrlSymbolServerList = new List(); - RuntimeId = runtimeId; - _defaultPauseOnExceptions = PauseOnExceptionsKind.Unset; - JustMyCode = options?.JustMyCode ?? false; - } - - internal virtual Task SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) => SendCommand(id, "Runtime.evaluate", JObject.FromObject(cmd), token); - - internal void SendLog(SessionId sessionId, string message, CancellationToken token, string type = "warning") - { - if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) - return; - /*var o = JObject.FromObject(new - { - entry = JObject.FromObject(new - { - source = "recommendation", - level = "warning", - text = message - }) - }); - SendEvent(id, "Log.enabled", null, token); - SendEvent(id, "Log.entryAdded", o, token);*/ - var o = JObject.FromObject(new - { - type, - args = new JArray(JObject.FromObject(new - { - type = "string", - value = message, - })), - executionContextId = context.Id - }); - SendEvent(sessionId, "Runtime.consoleAPICalled", o, token); - } - - protected override async Task AcceptEvent(SessionId sessionId, JObject parms, CancellationToken token) - { - var method = parms["method"].Value(); - var args = parms["params"] as JObject; - switch (method) - { - case "Runtime.consoleAPICalled": - { - // Don't process events from sessions we aren't tracking - if (!Contexts.ContainsKey(sessionId)) - return false; - string type = args["type"]?.ToString(); - if (type == "debug") - { - JToken a = args["args"]; - if (a is null) - break; - - int aCount = a.Count(); - if (aCount > 1 && a[0]?["value"]?.ToString() == MonoConstants.EVENT_RAISED) - { - if (a.Type != JTokenType.Array) - { - logger.LogDebug($"Invalid event raised args, expected an array: {a.Type}"); - } - else - { - if (aCount > 2 && - JObjectTryParse(a?[2]?["value"]?.Value(), out JObject raiseArgs) && - JObjectTryParse(a?[1]?["value"]?.Value(), out JObject eventArgs)) - { - await OnJSEventRaised(sessionId, eventArgs, token); - - if (raiseArgs?["trace"]?.Value() == true) { - // Let the message show up on the console - return false; - } - } - } - - // Don't log this message in the console - return true; - } - } - break; - } - - case "Runtime.executionContextCreated": - { - await SendEvent(sessionId, method, args, token); - JToken ctx = args?["context"]; - var aux_data = ctx?["auxData"] as JObject; - int id = ctx["id"].Value(); - if (aux_data != null) - { - bool? is_default = aux_data["isDefault"]?.Value(); - if (is_default == true) - { - await OnDefaultContext(sessionId, new ExecutionContext(new MonoSDBHelper(this, logger, sessionId), id, aux_data, _defaultPauseOnExceptions), token); - } - } - return true; - } - case "Runtime.executionContextDestroyed": - { - Contexts.DestroyContext(sessionId, args["executionContextId"].Value()); - return false; - } - case "Runtime.executionContextsCleared": - { - Contexts.ClearContexts(sessionId); - return false; - } - case "Debugger.scriptParsed": - { - if (args["url"]?.ToString()?.Contains("/_framework/") == true) //is from dotnet runtime framework - { - if (Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) - context.FrameworkScriptList.Add(args["scriptId"].Value()); - } - return false; - } - case "Debugger.paused": - { - return await OnDebuggerPaused(sessionId, args, token); - } - - case "Debugger.breakpointResolved": - { - break; - } - - case "Target.attachedToTarget": - { - var targetType = args["targetInfo"]["type"]?.ToString(); - if (targetType == "page") - await AttachToTarget(new SessionId(args["sessionId"]?.ToString()), token); - else if (targetType == "worker") - { - var workerSessionId = new SessionId(args["sessionId"]?.ToString()); - Contexts.CreateWorkerExecutionContext(workerSessionId, new SessionId(parms["sessionId"]?.ToString()), logger); - await SendCommand(workerSessionId, "Runtime.runIfWaitingForDebugger", new JObject(), token); - } - break; - } - - case "Target.targetDestroyed": - { - await SendMonoCommand(sessionId, MonoCommands.DetachDebugger(RuntimeId), token); - break; - } - } - return false; - } - - protected async Task OnDebuggerPaused(SessionId sessionId, JObject args, CancellationToken token) - { - if (args?["callFrames"]?.Value()?.Count == 0) //new browser version can send pause of type "instrumentation" with an empty callstack - return false; - - if (args["asyncStackTraceId"] != null) - { - if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context)) - return false; - if (context.CopyDataFromParentContext()) - { - var store = await LoadStore(sessionId, true, token); - foreach (var source in store.AllSources()) - { - await OnSourceFileAdded(sessionId, source, context, token, false); - } - } - } - - //TODO figure out how to stich out more frames and, in particular what happens when real wasm is on the stack - string top_func = args?["callFrames"]?[0]?["functionName"]?.Value(); - switch (top_func) { - // keep function names un-mangled via src\mono\browser\runtime\rollup.config.js - case "mono_wasm_set_entrypoint_breakpoint": - case "_mono_wasm_set_entrypoint_breakpoint": - { - await OnSetEntrypointBreakpoint(sessionId, args, token); - return true; - } - case "mono_wasm_runtime_ready": - case "_mono_wasm_runtime_ready": - { - await RuntimeReady(sessionId, token); - await SendResume(sessionId, token); - if (!JustMyCode) - await ReloadSymbolsFromSymbolServer(sessionId, Contexts.GetCurrentContext(sessionId), token); - return true; - } - case "mono_wasm_fire_debugger_agent_message_with_data_to_pause": - case "_mono_wasm_fire_debugger_agent_message_with_data_to_pause": - try - { - return await OnReceiveDebuggerAgentEvent(sessionId, args, await GetLastDebuggerAgentBuffer(sessionId, args, token), token); - } - catch (Exception) //if the page is refreshed maybe it stops here. - { - await SendResume(sessionId, token); - return true; - } - case "mono_wasm_fire_debugger_agent_message_with_data": - case "_mono_wasm_fire_debugger_agent_message_with_data": - { - //the only reason that we would get pause in this method is because the user is stepping out - //and as we don't want to pause in a debugger related function we continue stepping out - await SendCommand(sessionId, "Debugger.stepOut", new JObject(), token); - return true; - } - default: - { - if (JustMyCode) - { - if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context) || !context.IsRuntimeReady) - { - // For worker sessions where runtime isn't ready yet, - // resume instead of forwarding to IDE (which would leave worker stuck) - if (context?.ParentContext != null) - { - await SendResume(sessionId, token); - return true; - } - return false; - } - //avoid pausing when justMyCode is enabled and it's a wasm function - if (args?["callFrames"]?[0]?["scopeChain"]?[0]?["type"]?.Value()?.Equals("wasm-expression-stack") == true) - { - await SendCommand(sessionId, "Debugger.stepOut", new JObject(), token); - return true; - } - //avoid pausing when justMyCode is enabled and it's a framework function - var scriptId = args?["callFrames"]?[0]?["location"]?["scriptId"]?.Value(); - if (!context.IsSkippingHiddenMethod && !context.IsSteppingThroughMethod && scriptId is not null && context.FrameworkScriptList.Contains(scriptId.Value)) - { - await SendCommand(sessionId, "Debugger.stepOut", new JObject(), token); - return true; - } - } - break; - } - } - return false; - } - - protected virtual async Task SendResume(SessionId id, CancellationToken token) - { - await SendCommand(id, "Debugger.resume", new JObject(), token); - } - protected async Task IsRuntimeAlreadyReadyAlready(SessionId sessionId, CancellationToken token) - { - if (Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context) && context.IsRuntimeReady) - return true; - - Result res = await SendMonoCommand(sessionId, MonoCommands.IsRuntimeReady(RuntimeId), token); - if (!res.IsOk || res.Value?["result"]?["value"]?.Type != JTokenType.Boolean) //if runtime is not ready this may be the response - return false; - return res.Value?["result"]?["value"]?.Value() ?? false; - } - private static PauseOnExceptionsKind GetPauseOnExceptionsStatusFromString(string state) - { - PauseOnExceptionsKind pauseOnException; - if (Enum.TryParse(state, true, out pauseOnException)) - return pauseOnException; - return PauseOnExceptionsKind.Unset; - } - - protected override async Task AcceptCommand(MessageId id, JObject parms, CancellationToken token) - { - var method = parms["method"].Value(); - var args = parms["params"] as JObject; - // Inspector doesn't use the Target domain or sessions - // so we try to init immediately - if (id == SessionId.Null) - await AttachToTarget(id, token); - - if (!Contexts.TryGetCurrentExecutionContextValue(id, out ExecutionContext context) && !s_executionContextIndependentCDPCommandNames.Contains(method)) - { - if (method == "Debugger.setPauseOnExceptions") - { - string state = args["state"].Value(); - var pauseOnException = GetPauseOnExceptionsStatusFromString(state); - if (pauseOnException != PauseOnExceptionsKind.Unset) - _defaultPauseOnExceptions = pauseOnException; - } - return method.StartsWith("DotnetDebugger.", StringComparison.OrdinalIgnoreCase); - } - - switch (method) - { - case "Target.attachToTarget": - { - Result resp = await SendCommand(id, method, args, token); - await AttachToTarget(new SessionId(resp.Value["sessionId"]?.ToString()), token); - break; - } - - case "Debugger.enable": - { - Result resp = await SendCommand(id, method, args, token); - - if (!resp.IsOk) - { - SendResponse(id, resp, token); - return true; - } - - context.DebugId = resp.Value["DebugId"]?.ToString(); - - if (await IsRuntimeAlreadyReadyAlready(id, token)) - await RuntimeReady(id, token); - - SendResponse(id, resp, token); - return true; - } - - case "Debugger.getScriptSource": - { - string script = args?["scriptId"]?.Value(); - return await OnGetScriptSource(id, script, token); - } - - case "Runtime.compileScript": - { - string exp = args?["expression"]?.Value(); - if (exp.StartsWith("//dotnet:", StringComparison.Ordinal)) - { - OnCompileDotnetScript(id, token); - return true; - } - break; - } - - case "Debugger.getPossibleBreakpoints": - { - Result resp = await SendCommand(id, method, args, token); - if (resp.IsOk && resp.Value["locations"].HasValues) - { - SendResponse(id, resp, token); - return true; - } - - var start = SourceLocation.Parse(args?["start"] as JObject); - //FIXME support variant where restrictToFunction=true and end is omitted - var end = SourceLocation.Parse(args?["end"] as JObject); - if (start != null && end != null && await GetPossibleBreakpoints(id, start, end, token)) - return true; - - SendResponse(id, resp, token); - return true; - } - - case "Debugger.setBreakpoint": - { - break; - } - - case "Debugger.setBreakpointByUrl": - { - Result resp = await SendCommand(id, method, args, token); - if (!resp.IsOk) - { - SendResponse(id, resp, token); - return true; - } - try - { - string bpid = resp.Value["breakpointId"]?.ToString(); - IEnumerable locations = resp.Value["locations"]?.Values(); - var request = BreakpointRequest.Parse(bpid, args); - - // is the store done loading? - bool loaded = context.Source.Task.IsCompleted; - if (!loaded) - { - // Send and empty response immediately if not - // and register the breakpoint for resolution - context.BreakpointRequests[bpid] = request; - SendResponse(id, resp, token); - } - - if (await IsRuntimeAlreadyReadyAlready(id, token)) - { - DebugStore store = await RuntimeReady(id, token); - - Log("verbose", $"BP req {args}"); - await SetBreakpoint(id, store, request, !loaded, false, token); - } - - if (loaded) - { - // we were already loaded so we should send a response - // with the locations included and register the request - context.BreakpointRequests[bpid] = request; - var result = Result.OkFromObject(request.AsSetBreakpointByUrlResponse(locations)); - SendResponse(id, result, token); - - } - } - catch (Exception e) - { - logger.LogDebug($"Debugger.setBreakpointByUrl - {args} - failed with exception: {e}"); - SendResponse(id, Result.Err($"Debugger.setBreakpointByUrl - {args} - failed with exception: {e}"), token); - } - return true; - } - - case "Debugger.removeBreakpoint": - { - await RemoveBreakpoint(id, args, false, token); - break; - } - - case "Debugger.resume": - { - await OnResume(id, token); - break; - } - - case "Debugger.stepInto": - { - return await Step(id, StepKind.Into, token); - } - case "Debugger.setVariableValue": - { - if (!DotnetObjectId.TryParse(args?["callFrameId"], out DotnetObjectId objectId)) - return false; - switch (objectId.Scheme) - { - case "scope": - return await OnSetVariableValue(id, - objectId.Value, - args?["variableName"]?.Value(), - args?["newValue"], - token); - default: - return false; - } - } - - case "Debugger.stepOut": - { - return await Step(id, StepKind.Out, token); - } - - case "Debugger.stepOver": - { - return await Step(id, StepKind.Over, token); - } - case "Runtime.evaluate": - { - if (context.CallStack != null) - { - Frame scope = context.CallStack.First(); - return await OnEvaluateOnCallFrame(id, - scope.Id, - args?["expression"]?.Value(), token); - } - break; - } - case "Debugger.evaluateOnCallFrame": - { - if (!DotnetObjectId.TryParse(args?["callFrameId"], out DotnetObjectId objectId)) - return false; - - switch (objectId.Scheme) - { - case "scope": - return await OnEvaluateOnCallFrame(id, - objectId.Value, - args?["expression"]?.Value(), token); - default: - return false; - } - } - - case "Runtime.getProperties": - { - if (!DotnetObjectId.TryParse(args?["objectId"], out DotnetObjectId objectId)) - break; - - var valueOrError = await RuntimeGetObjectMembers(id, objectId, args, token, true); - if (valueOrError.IsError) - { - logger.LogDebug($"Runtime.getProperties: {valueOrError.Error}"); - SendResponse(id, valueOrError.Error.Value, token); - return true; - } - if (valueOrError.Value.JObject == null) - { - SendResponse(id, Result.Err($"Failed to get properties for '{objectId}'"), token); - return true; - } - SendResponse(id, Result.OkFromObject(valueOrError.Value.JObject), token); - return true; - } - - case "Runtime.releaseObject": - { - if (!(DotnetObjectId.TryParse(args["objectId"], out DotnetObjectId objectId) && objectId.Scheme == "cfo_res")) - break; - - await SendMonoCommand(id, MonoCommands.ReleaseObject(RuntimeId, objectId), token); - SendResponse(id, Result.OkFromObject(new { }), token); - return true; - } - - case "Debugger.setPauseOnExceptions": - { - string state = args["state"].Value(); - var pauseOnException = GetPauseOnExceptionsStatusFromString(state); - if (pauseOnException != PauseOnExceptionsKind.Unset) - context.PauseOnExceptions = pauseOnException; - - if (context.IsRuntimeReady) - await context.SdbAgent.EnableExceptions(context.PauseOnExceptions, token); - // Pass this on to JS too - return false; - } - - case "Runtime.callFunctionOn": - { - try { - return await CallOnFunction(id, args, token); - } - catch (Exception ex) { - logger.LogDebug($"Runtime.callFunctionOn failed for {id} with args {args}: {ex}"); - SendResponse(id, - Result.Exception(new ArgumentException( - $"Runtime.callFunctionOn not supported with ({args["objectId"]}).")), - token); - return true; - } - } - - // Protocol extensions - case "DotnetDebugger.setDebuggerProperty": - { - foreach (KeyValuePair property in args) - { - switch (property.Key) - { - case "JustMyCodeStepping": - await SetJustMyCode(id, (bool)property.Value, context, token); - break; - default: - logger.LogDebug($"DotnetDebugger.setDebuggerProperty failed for {property.Key} with value {property.Value}"); - break; - } - } - return true; - } - case "DotnetDebugger.setNextIP": - { - var loc = SourceLocation.Parse(args?["location"] as JObject); - if (loc == null) - return false; - bool ret = await OnSetNextIP(id, loc, token); - if (ret) - SendResponse(id, Result.OkFromObject(new { }), token); - else - SendResponse(id, Result.Err("Set next instruction pointer failed."), token); - return true; - } - case "DotnetDebugger.applyUpdates": - { - if (await ApplyUpdates(id, args, token)) - SendResponse(id, Result.OkFromObject(new { }), token); - else - SendResponse(id, Result.Err("ApplyUpdate failed."), token); - return true; - } - case "DotnetDebugger.setSymbolOptions": - { - SendResponse(id, Result.OkFromObject(new { }), token); - CachePathSymbolServer = args["symbolOptions"]?["cachePath"]?.Value(); - var urls = args["symbolOptions"]?["searchPaths"]?.Value(); - if (urls == null) - return true; - UrlSymbolServerList.Clear(); - UrlSymbolServerList.AddRange(urls.Values()); - if (!JustMyCode) - { - if (!await IsRuntimeAlreadyReadyAlready(id, token)) - return true; - return await ReloadSymbolsFromSymbolServer(id, context, token); - } - return true; - } - case "DotnetDebugger.getMethodLocation": - { - SendResponse(id, await GetMethodLocation(id, args, token), token); - return true; - } - case "DotnetDebugger.setEvaluationOptions": - { - //receive the available options from DAP to variables, stack and evaluate commands. - try { - if (args["options"]?["noFuncEval"]?.Value() == true) - context.AutoEvaluateProperties = false; - else - context.AutoEvaluateProperties = true; - SendResponse(id, Result.OkFromObject(new { }), token); - } - catch (Exception ex) - { - logger.LogDebug($"DotnetDebugger.setEvaluationOptions failed for {id} with args {args}: {ex}"); - SendResponse(id, - Result.Exception(new ArgumentException( - $"DotnetDebugger.setEvaluationOptions got incorrect argument ({args})")), - token); - } - return true; - } - case "DotnetDebugger.runTests": - { - SendResponse(id, Result.OkFromObject(new { }), token); - while (!await IsRuntimeAlreadyReadyAlready(id, token)) //retry on debugger-tests until the runtime is ready - await Task.Delay(1000, token); - await RuntimeReady(id, token); - return true; - } - } - // for Dotnetdebugger.* messages, treat them as handled, thus not passing them on to the browser - return method.StartsWith("DotnetDebugger.", StringComparison.OrdinalIgnoreCase); - } - - private async Task ReloadSymbolsFromSymbolServer(SessionId id, ExecutionContext context, CancellationToken token) - { - DebugStore store = await LoadStore(id, true, token); - store.UpdateSymbolStore(UrlSymbolServerList, CachePathSymbolServer); - await store.ReloadAllPDBsFromSymbolServersAndSendSources(this, id, context, token); - return true; - } - - private async Task ApplyUpdates(MessageId id, JObject args, CancellationToken token) - { - var context = Contexts.GetCurrentContext(id); - string moduleGUID = args["moduleGUID"]?.Value(); - string dmeta = args["dmeta"]?.Value(); - string dil = args["dil"]?.Value(); - string dpdb = args["dpdb"]?.Value(); - var moduleId = await context.SdbAgent.GetModuleId(moduleGUID, token); - var applyUpdates = await context.SdbAgent.ApplyUpdates(moduleId, dmeta, dil, dpdb, token); - return applyUpdates; - } - - private async Task SetJustMyCode(MessageId id, bool isEnabled, ExecutionContext context, CancellationToken token) - { - if (JustMyCode != isEnabled && !isEnabled) - { - JustMyCode = isEnabled; - if (await IsRuntimeAlreadyReadyAlready(id, token)) - await ReloadSymbolsFromSymbolServer(id, context, token); - } - JustMyCode = isEnabled; - SendResponse(id, Result.OkFromObject(new { justMyCodeEnabled = JustMyCode }), token); - } - internal async Task GetMethodLocation(MessageId id, JObject args, CancellationToken token) - { - DebugStore store = await RuntimeReady(id, token); - string aname = args["assemblyName"]?.Value(); - string typeName = args["typeName"]?.Value(); - string methodName = args["methodName"]?.Value(); - if (aname == null || typeName == null || methodName == null) - { - return Result.Err("Invalid protocol message '" + args + "'."); - } - - // GetAssemblyByName seems to work on file names - AssemblyInfo assembly = store.GetAssemblyByName(aname); - assembly ??= store.GetAssemblyByName(aname + ".dll"); - if (assembly == null) - { - return Result.Err($"Assembly '{aname}' not found," + - $"needed to get method location of '{typeName}:{methodName}'"); - } - - TypeInfo type = assembly.GetTypeByName(typeName); - if (type == null) - { - return Result.Err($"Type '{typeName}' not found."); - } - - MethodInfo methodInfo = type.Methods.FirstOrDefault(m => m.Name == methodName); - if (methodInfo?.Source is null) - { - // Maybe this is an async method, in which case the debug info is attached - // to the async method implementation, in class named: - // `{type_name}.::MoveNext` - methodInfo = assembly.TypesByName.Values.SingleOrDefault(t => t.FullName.StartsWith($"{typeName}.<{methodName}>"))? - .Methods.FirstOrDefault(mi => mi.Name == "MoveNext"); - } - - if (methodInfo == null) - { - return Result.Err($"Method '{typeName}:{methodName}' not found."); - } - - string src_url = methodInfo.Assembly.Sources.Single(sf => sf.SourceId == methodInfo.SourceId).Url.ToString(); - - return Result.OkFromObject(new - { - result = new { line = methodInfo.StartLocation.Line, column = methodInfo.StartLocation.Column, url = src_url } - }); - } - - private async Task CallOnFunction(MessageId id, JObject args, CancellationToken token) - { - var context = Contexts.GetCurrentContext(id); - if (!DotnetObjectId.TryParse(args["objectId"], out DotnetObjectId objectId)) { - return false; - } - switch (objectId.Scheme) - { - case "method": - args["details"] = await context.SdbAgent.GetMethodProxy(objectId.ValueAsJson, token); - break; - case "object": - args["details"] = await context.SdbAgent.GetObjectProxy(objectId.Value, token); - break; - case "valuetype": - var valueType = context.SdbAgent.GetValueTypeClass(objectId.Value); - if (valueType == null) - throw new Exception($"Internal Error: No valuetype found for {objectId}."); - args["details"] = await valueType.GetProxy(context.SdbAgent, token); - break; - case "pointer": - args["details"] = await context.SdbAgent.GetPointerContent(objectId.Value, token); - break; - case "array": - args["details"] = await context.SdbAgent.GetArrayValuesProxy(objectId.Value, token); - break; - case "cfo_res": - Result cfo_res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(RuntimeId, args), token); - cfo_res = Result.OkFromObject(new { result = cfo_res.Value?["result"]?["value"]}); - SendResponse(id, cfo_res, token); - return true; - case "scope": - { - SendResponse(id, - Result.Exception(new ArgumentException( - $"Runtime.callFunctionOn not supported with scope ({objectId}).")), - token); - return true; - } - default: - return false; - } - Result res = await SendMonoCommand(id, MonoCommands.CallFunctionOn(RuntimeId, args), token); - if (!res.IsOk) - { - SendResponse(id, res, token); - return true; - } - if (res.Value?["result"]?["value"]?["type"] == null) //it means that is not a buffer returned from the debugger-agent - { - byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); - var retDebuggerCmdReader = new MonoBinaryReader(newBytes); - retDebuggerCmdReader.ReadByte(); //number of objects returned. - var obj = await context.SdbAgent.ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, "ret", token); - /*JTokenType? res_value_type = res.Value?["result"]?["value"]?.Type;*/ - res = Result.OkFromObject(new { result = obj["value"]}); - SendResponse(id, res, token); - return true; - } - res = Result.OkFromObject(new { result = res.Value?["result"]?["value"]}); - SendResponse(id, res, token); - return true; - } - - private async Task OnSetVariableValue(MessageId id, int scopeId, string varName, JToken varValue, CancellationToken token) - { - ExecutionContext context = Contexts.GetCurrentContext(id); - Frame scope = context.CallStack.FirstOrDefault(s => s.Id == scopeId); - if (scope == null) - return false; - var varIds = scope.Method.Info.GetLiveVarsAt(scope.Location.IlLocation.Offset); - if (varIds == null) - return false; - var varToSetValue = varIds.FirstOrDefault(v => v.Name == varName); - if (varToSetValue == null) - return false; - var res = await context.SdbAgent.SetVariableValue(context.ThreadId, scopeId, varToSetValue.Index, varValue["value"].Value(), token); - if (res) - SendResponse(id, Result.Ok(new JObject()), token); - else - SendResponse(id, Result.Err($"Unable to set '{varValue["value"].Value()}' to variable '{varName}'"), token); - return true; - } - - internal async Task> RuntimeGetObjectMembers(SessionId id, DotnetObjectId objectId, JToken args, CancellationToken token, bool sortByAccessLevel = false) - { - var context = Contexts.GetCurrentContext(id); - GetObjectCommandOptions getObjectOptions = GetObjectCommandOptions.WithProperties; - if (args != null) - { - if (args["accessorPropertiesOnly"]?.Value() == true) - getObjectOptions |= GetObjectCommandOptions.AccessorPropertiesOnly; - - if (args["ownProperties"]?.Value() == true) - getObjectOptions |= GetObjectCommandOptions.OwnProperties; - - if (args["forDebuggerDisplayAttribute"]?.Value() == true) - getObjectOptions |= GetObjectCommandOptions.ForDebuggerDisplayAttribute; - } - if (context.AutoEvaluateProperties) - getObjectOptions |= GetObjectCommandOptions.AutoExpandable; - if (JustMyCode) - getObjectOptions |= GetObjectCommandOptions.JustMyCode; - try - { - switch (objectId.Scheme) - { - // ToDo: fix Exception types here - case "scope": - GetMembersResult resScope = await GetScopeProperties(id, objectId.Value, token); - resScope.CleanUp(); - return ValueOrError.WithValue(resScope); - case "valuetype": - var resValue = await MemberObjectsExplorer.GetValueTypeMemberValues( - context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic: true); - resValue?.CleanUp(); - return resValue switch - { - null => ValueOrError.WithError($"Could not get properties for {objectId}"), - _ => ValueOrError.WithValue(resValue) - }; - case "array": - var resArr = await context.SdbAgent.GetArrayValues(objectId.Value, token); - return ValueOrError.WithValue(GetMembersResult.FromValues(resArr)); - case "method": - var resMethod = await context.SdbAgent.InvokeMethod(objectId, token); - return ValueOrError.WithValue(GetMembersResult.FromValues(new JArray(resMethod))); - case "object": - var resObj = await MemberObjectsExplorer.GetObjectMemberValues( - context.SdbAgent, objectId.Value, getObjectOptions, token, sortByAccessLevel, includeStatic: true); - resObj.CleanUp(); - return ValueOrError.WithValue(resObj); - case "pointer": - var resPointer = new JArray { await context.SdbAgent.GetPointerContent(objectId.Value, token) }; - return ValueOrError.WithValue(GetMembersResult.FromValues(resPointer)); - case "cfo_res": - Result res = await SendMonoCommand(id, MonoCommands.GetDetails(RuntimeId, objectId.Value, args), token); - string value_json_str = res.Value["result"]?["value"]?["__value_as_json_string__"]?.Value(); - if (res.IsOk && value_json_str == null) - return ValueOrError.WithError( - $"Internal error: Could not find expected __value_as_json_string__ field in the result: {res}"); - - return value_json_str != null - ? ValueOrError.WithValue(GetMembersResult.FromValues(JArray.Parse(value_json_str))) - : ValueOrError.WithError(res); - case "evaluationResult": - JArray evaluationRes = (JArray)context.SdbAgent.GetEvaluationResultProperties(objectId.ToString()); - return ValueOrError.WithValue(GetMembersResult.FromValues(evaluationRes)); - default: - return ValueOrError.WithError($"RuntimeGetProperties: unknown object id scheme: {objectId.Scheme}"); - } - } - catch (Exception ex) - { - return ValueOrError.WithError($"RuntimeGetProperties: Failed to get properties for {objectId}: {ex}"); - } - } - - protected async Task EvaluateCondition(SessionId sessionId, ExecutionContext context, Frame mono_frame, Breakpoint bp, CancellationToken token) - { - if (string.IsNullOrEmpty(bp?.Condition) || mono_frame == null) - return true; - - string condition = bp.Condition; - - if (bp.ConditionAlreadyEvaluatedWithError) - return false; - try { - var resolver = new MemberReferenceResolver(this, context, sessionId, mono_frame.Id, logger); - JObject retValue = await resolver.Resolve(condition, token); - retValue ??= await ExpressionEvaluator.CompileAndRunTheExpression(condition, resolver, logger, token); - if (retValue?["value"]?.Type == JTokenType.Boolean || - retValue?["value"]?.Type == JTokenType.Integer || - retValue?["value"]?.Type == JTokenType.Float) { - if (retValue?["value"]?.Value() == true) - return true; - } - else if (retValue?["value"] != null && // null object, missing value - retValue?["value"]?.Type != JTokenType.Null) - { - return true; - } - } - catch (ReturnAsErrorException ree) - { - logger.LogDebug($"Unable to evaluate breakpoint condition '{condition}': {ree}"); - SendLog(sessionId, $"Unable to evaluate breakpoint condition '{condition}': {ree.Message}", token, type: "error"); - bp.ConditionAlreadyEvaluatedWithError = true; - ReportDebuggerExceptionToTelemetry("EvaluateCondition", sessionId, token); - } - catch (Exception e) - { - Log("info", $"Unable to evaluate breakpoint condition '{condition}': {e}"); - bp.ConditionAlreadyEvaluatedWithError = true; - ReportDebuggerExceptionToTelemetry("EvaluateCondition", sessionId, token); - } - return false; - } - - private async Task ProcessEnC(SessionId sessionId, ExecutionContext context, MonoBinaryReader retDebuggerCmdReader, CancellationToken token) - { - int moduleId = retDebuggerCmdReader.ReadInt32(); - int meta_size = retDebuggerCmdReader.ReadInt32(); - byte[] meta_buf = retDebuggerCmdReader.ReadBytes(meta_size); - int pdb_size = retDebuggerCmdReader.ReadInt32(); - byte[] pdb_buf = retDebuggerCmdReader.ReadBytes(pdb_size); - - var assemblyName = await context.SdbAgent.GetAssemblyNameFromModule(moduleId, token); - DebugStore store = await LoadStore(sessionId, true, token); - AssemblyInfo asm = store.GetAssemblyByName(assemblyName); - var methods = DebugStore.EnC(context.SdbAgent, asm, meta_buf, pdb_buf); - foreach (var method in methods) - { - await ResetBreakpoint(sessionId, store, method, token); - } - var files = methods.Distinct(new MethodInfo.SourceComparer()); - foreach (var file in files) - { - JObject scriptSource = JObject.FromObject(file.Source.ToScriptSource(context.Id, context.AuxData)); - Log("debug", $"sending after update {file.Source.Url} {context.Id} {sessionId.sessionId}"); - await SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token); - } - return true; - } - - private async Task SendBreakpointsOfMethodUpdated(SessionId sessionId, ExecutionContext context, MonoBinaryReader retDebuggerCmdReader, CancellationToken token) - { - var methodId = retDebuggerCmdReader.ReadInt32(); - var method = await context.SdbAgent.GetMethodInfo(methodId, token); - if (method == null || method.Info.Source is null) - { - return true; - } - foreach (var req in context.BreakpointRequests.Values) - { - if (req.TryResolve(method.Info.Source)) - { - await SetBreakpoint(sessionId, context.store, req, true, true, token); - } - } - return true; - } - - protected virtual async Task ShouldSkipMethod(SessionId sessionId, ExecutionContext context, EventKind event_kind, int frameNumber, int totalFrames, MethodInfoWithDebugInformation method, CancellationToken token) - { - var shouldReturn = await SkipMethod( - isSkippable: context.IsSkippingHiddenMethod, - shouldBeSkipped: event_kind != EventKind.UserBreak, - StepKind.Over); - context.IsSkippingHiddenMethod = false; - if (shouldReturn) - return true; - - shouldReturn = await SkipMethod( - isSkippable: context.IsSteppingThroughMethod, - shouldBeSkipped: event_kind != EventKind.UserBreak && event_kind != EventKind.Breakpoint, - StepKind.Over); - context.IsSteppingThroughMethod = false; - if (shouldReturn) - return true; - - if (frameNumber != 0) - return false; - - if (method?.Info?.DebuggerAttrInfo?.DoAttributesAffectCallStack(JustMyCode) == true) - { - if (method.Info.DebuggerAttrInfo.ShouldStepOut(event_kind)) - { - if (event_kind == EventKind.Step) - context.IsSkippingHiddenMethod = true; - if (await SkipMethod(isSkippable: true, shouldBeSkipped: true, StepKind.Out)) - return true; - } - if (!method.Info.DebuggerAttrInfo.HasStepperBoundary) - { - if (event_kind == EventKind.Step || - (JustMyCode && (event_kind == EventKind.Breakpoint || event_kind == EventKind.UserBreak))) - { - if (context.IsResumedAfterBp) - context.IsResumedAfterBp = false; - else if (event_kind != EventKind.UserBreak) - context.IsSteppingThroughMethod = true; - if (await SkipMethod(isSkippable: true, shouldBeSkipped: true, StepKind.Out)) - return true; - } - if (event_kind == EventKind.Breakpoint) - context.IsResumedAfterBp = true; - } - } - else - { - if (!JustMyCode && method?.Info?.DebuggerAttrInfo?.HasNonUserCode == true && !method.Info.hasDebugInformation) - { - if (event_kind == EventKind.Step) - context.IsSkippingHiddenMethod = true; - if (await SkipMethod(isSkippable: true, shouldBeSkipped: true, StepKind.Out)) - return true; - } - } - return false; - async Task SkipMethod(bool isSkippable, bool shouldBeSkipped, StepKind stepKind) - { - if (isSkippable && shouldBeSkipped) - { - if (frameNumber + 1 == totalFrames && stepKind == StepKind.Out) //is the last managed frame - await SendCommand(sessionId, "Debugger.stepOut", new JObject(), token); - else - await TryStepOnManagedCodeAndStepOutIfNotPossible(sessionId, context, stepKind, token); - return true; - } - return false; - } - } - - - protected virtual async Task SendCallStack(SessionId sessionId, ExecutionContext context, string reason, int thread_id, Breakpoint bp, JObject data, JObject args, EventKind event_kind, CancellationToken token) - { - var orig_callframes = args?["callFrames"]?.Values(); - var callFrames = new List(); - var frames = new List(); - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(thread_id); - commandParamsWriter.Write(0); - commandParamsWriter.Write(-1); - using var retDebuggerCmdReader = await context.SdbAgent.SendDebuggerAgentCommand(CmdThread.GetFrameInfo, commandParamsWriter, token); - var frame_count = retDebuggerCmdReader.ReadInt32(); - //Console.WriteLine("frame_count - " + frame_count); - for (int j = 0; j < frame_count; j++) { - var frame_id = retDebuggerCmdReader.ReadInt32(); - var methodId = retDebuggerCmdReader.ReadInt32(); - var il_pos = retDebuggerCmdReader.ReadInt32(); - var flags = retDebuggerCmdReader.ReadByte(); - DebugStore store = await LoadStore(sessionId, true, token); - var method = await context.SdbAgent.GetMethodInfo(methodId, token); - - if (await ShouldSkipMethod(sessionId, context, event_kind, j, frame_count, method, token)) - return true; - - SourceLocation location = method?.Info.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) - { - continue; - } - - // logger.LogTrace($"frame il offset: {il_pos} method token: {method.Info.Token} assembly name: {method.Info.Assembly.Name}"); - // logger.LogTrace($"\tmethod {method.Name} location: {location}"); - frames.Add(new Frame(method, location, frame_id)); - - callFrames.Add(new - { - functionName = method.Name, - callFrameId = $"dotnet:scope:{frame_id}", - functionLocation = method.Info.StartLocation.AsLocation(), - - location = location.AsLocation(), - - url = store.ToUrl(location), - - scopeChain = new[] - { - new - { - type = "local", - @object = new - { - @type = "object", - className = "Object", - description = "Object", - objectId = $"dotnet:scope:{frame_id}", - }, - name = method.Name, - startLocation = method.Info.StartLocation.AsLocation(), - endLocation = method.Info.EndLocation.AsLocation(), - } - } - }); - - context.CallStack = frames; - } - string[] bp_list = new string[bp == null ? 0 : 1]; - if (bp != null) - bp_list[0] = bp.StackId; - - foreach (JObject frame in orig_callframes) - { - string function_name = frame["functionName"]?.Value(); - string url = frame["url"]?.Value(); - var isWasmExpressionStack = frame["scopeChain"]?[0]?["type"]?.Value()?.Equals("wasm-expression-stack") == true; - if (!(function_name.StartsWith("wasm-function", StringComparison.Ordinal) || - url.StartsWith("wasm://", StringComparison.Ordinal) || - url.EndsWith(".wasm", StringComparison.Ordinal) || - JustMyCode && isWasmExpressionStack || - function_name.StartsWith("_mono_wasm_fire_debugger_agent_message", StringComparison.Ordinal) || - function_name.StartsWith("mono_wasm_fire_debugger_agent_message", StringComparison.Ordinal))) - { - await SymbolicateFunctionName(sessionId, context, frame, token); - callFrames.Add(frame); - } - } - var o = JObject.FromObject(new - { - callFrames, - reason, - data, - hitBreakpoints = bp_list, - }); - if (args["asyncStackTraceId"] != null) - o["asyncStackTraceId"] = args["asyncStackTraceId"]; - if (!await EvaluateCondition(sessionId, context, context.CallStack.First(), bp, token)) - { - context.ClearState(); - await SendResume(sessionId, token); - return true; - } - await SendEvent(sessionId, "Debugger.paused", o, token); - - return true; - } - - private async Task SymbolicateFunctionName(SessionId sessionId, ExecutionContext context, JObject frame, CancellationToken token) - { - string funcPrefix = "$func"; - string functionName = frame["functionName"]?.Value(); - if (!functionName.StartsWith(funcPrefix, StringComparison.Ordinal) || !int.TryParse(functionName[funcPrefix.Length..], out var funcId)) - { - return; - } - - if (context.WasmFunctionIds is null) - { - Result getIds = await SendMonoCommand(sessionId, MonoCommands.GetWasmFunctionIds(RuntimeId), token); - if (getIds.IsOk) - { - string[] symbols = getIds.Value?["result"]?["value"]?.ToObject(); - context.WasmFunctionIds = symbols; - } - else - { - context.WasmFunctionIds = Array.Empty(); - } - } - - if (context.WasmFunctionIds.Length > funcId) - frame["functionName"] = context.WasmFunctionIds[funcId]; - } - - internal virtual void SaveLastDebuggerAgentBufferReceivedToContext(SessionId sessionId, Task debuggerAgentBufferTask) - { - } - - internal async Task GetLastDebuggerAgentBuffer(SessionId sessionId, JObject args, CancellationToken token) - { - if (args?["callFrames"].Value().Count == 0 || args["callFrames"][0]["scopeChain"].Value().Count == 0) - return Result.Err($"Unexpected callFrames {args}"); - var argsNew = JObject.FromObject(new - { - objectId = args["callFrames"][0]["scopeChain"][0]["object"]["objectId"].Value(), - }); - Result res = await SendCommand(sessionId, "Runtime.getProperties", argsNew, token); - return res; - } - - internal async Task OnReceiveDebuggerAgentEvent(SessionId sessionId, JObject args, Result debuggerAgentBuffer, CancellationToken token) - { - var debuggerAgentBufferTask = SendMonoCommand(sessionId, MonoCommands.GetDebuggerAgentBufferReceived(RuntimeId), token); - SaveLastDebuggerAgentBufferReceivedToContext(sessionId, debuggerAgentBufferTask); - if (!debuggerAgentBuffer.IsOk || debuggerAgentBuffer.Value?["result"].Value().Count == 0) - { - logger.LogTrace($"Unexpected DebuggerAgentBufferReceived {debuggerAgentBuffer}"); - return false; - } - ExecutionContext context = Contexts.GetCurrentContext(sessionId); - byte[] newBytes = Convert.FromBase64String(debuggerAgentBuffer.Value?["result"]?[0]?["value"]?["value"]?.Value()); - using var retDebuggerCmdReader = new MonoBinaryReader(newBytes); - retDebuggerCmdReader.ReadBytes(11); //skip HEADER_LEN - retDebuggerCmdReader.ReadByte(); //suspend_policy - var number_of_events = retDebuggerCmdReader.ReadInt32(); //number of events -> should be always one - for (int i = 0 ; i < number_of_events; i++) { - var event_kind = (EventKind)retDebuggerCmdReader.ReadByte(); //event kind - var request_id = retDebuggerCmdReader.ReadInt32(); //request id - if (event_kind == EventKind.Step) - await context.SdbAgent.ClearSingleStep(request_id, token); - int thread_id = retDebuggerCmdReader.ReadInt32(); - context.ThreadId = thread_id; - switch (event_kind) - { - case EventKind.MethodUpdate: - { - var ret = await SendBreakpointsOfMethodUpdated(sessionId, context, retDebuggerCmdReader, token); - await SendResume(sessionId, token); - return ret; - } - case EventKind.EnC: - { - var ret = await ProcessEnC(sessionId, context, retDebuggerCmdReader, token); - await SendResume(sessionId, token); - return ret; - } - case EventKind.Exception: - { - string reason = "exception"; - int object_id = retDebuggerCmdReader.ReadInt32(); - var caught = retDebuggerCmdReader.ReadByte(); - var exceptionObject = await MemberObjectsExplorer.GetObjectMemberValues( - context.SdbAgent, object_id, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.OwnProperties, token); - var exceptionObjectMessage = exceptionObject.FirstOrDefault(attr => attr["name"].Value().Equals("_message")); - var data = JObject.FromObject(new - { - type = "object", - subtype = "error", - className = await context.SdbAgent.GetClassNameFromObject(object_id, token), - uncaught = caught == 0, - description = exceptionObjectMessage["value"]["value"].Value(), - objectId = $"dotnet:object:{object_id}" - }); - - var ret = await SendCallStack(sessionId, context, reason, thread_id, null, data, args, event_kind, token); - return ret; - } - case EventKind.UserBreak: - case EventKind.Step: - case EventKind.Breakpoint: - { - if (event_kind == EventKind.Step) - context.PauseKind = "resumeLimit"; - else if (event_kind == EventKind.Breakpoint) - context.PauseKind = "breakpoint"; - Breakpoint bp = context.BreakpointRequests.Values.SelectMany(v => v.Locations).FirstOrDefault(b => b.RemoteId == request_id); - if (bp == null && context.ParentContext != null) - { - bp = context.ParentContext.BreakpointRequests.Values.SelectMany(v => v.Locations).FirstOrDefault(b => b.RemoteId == request_id); - } - if (request_id == context.TempBreakpointForSetNextIP) - { - context.TempBreakpointForSetNextIP = -1; - await context.SdbAgent.RemoveBreakpoint(request_id, token); - } - string reason = "other";//other means breakpoint - int methodId = 0; - if (event_kind != EventKind.UserBreak) - methodId = retDebuggerCmdReader.ReadInt32(); - var ret = await SendCallStack(sessionId, context, reason, thread_id, bp, null, args, event_kind, token); - return ret; - } - } - } - return false; - } - - protected async Task OnDefaultContext(SessionId sessionId, ExecutionContext context, CancellationToken token) - { - Log("verbose", "Default context created, clearing state and sending events"); - Contexts.OnDefaultContextUpdate(sessionId, context); - if (await IsRuntimeAlreadyReadyAlready(sessionId, token)) - await RuntimeReady(sessionId, token); - } - - protected async Task OnResume(MessageId msg_id, CancellationToken token) - { - ExecutionContext context = Contexts.GetCurrentContext(msg_id); - if (context.CallStack != null) - { - // Stopped on managed code - await SendMonoCommand(msg_id, MonoCommands.Resume(RuntimeId), token); - } - - //discard managed frames - Contexts.GetCurrentContext(msg_id).ClearState(); - } - protected async Task TryStepOnManagedCodeAndStepOutIfNotPossible(SessionId sessionId, ExecutionContext context, StepKind kind, CancellationToken token) - { - var step = await context.SdbAgent.Step(context.ThreadId, kind, token); - if (!step) //it will return false if it's the last managed frame and the runtime added the single step breakpoint in a MONO_WRAPPER_RUNTIME_INVOKE - { - context.ClearState(); - await SendCommand(sessionId, "Debugger.stepOut", new JObject(), token); - return false; - } - - context.ClearState(); - - await SendResume(sessionId, token); - return true; - } - - protected async Task Step(MessageId msgId, StepKind kind, CancellationToken token) - { - ExecutionContext context = Contexts.GetCurrentContext(msgId); - if (context.CallStack == null) - return false; - - if (context.CallStack.Count <= 1 && kind == StepKind.Out) - { - Frame scope = context.CallStack.FirstOrDefault(); - if (scope is null || !(await context.SdbAgent.IsAsyncMethod(scope.Method.DebugId, token))) - return false; - } - var ret = await TryStepOnManagedCodeAndStepOutIfNotPossible(msgId, context, kind, token); - if (ret) - SendResponse(msgId, Result.Ok(new JObject()), token); - return ret; - } - - private async Task OnJSEventRaised(SessionId sessionId, JObject eventArgs, CancellationToken token) - { - string eventName = eventArgs?["eventName"]?.Value(); - if (string.IsNullOrEmpty(eventName)) - { - logger.LogDebug($"Missing name for raised js event: {eventArgs}"); - return false; - } - - logger.LogDebug($"OnJsEventRaised: args: {eventArgs.ToString().TruncateLogMessage()}"); - - switch (eventName) - { - case "AssemblyLoaded": - return await OnAssemblyLoadedJSEvent(sessionId, eventArgs, token); - default: - { - logger.LogDebug($"Unknown js event name: {eventName} with args {eventArgs}"); - return await Task.FromResult(false); - } - } - } - - private async Task OnAssemblyLoadedJSEvent(SessionId sessionId, JObject eventArgs, CancellationToken token) - { - try - { - var store = await LoadStore(sessionId, true, token); - var assembly_name = eventArgs?["assembly_name"]?.Value(); - - if (store.GetAssemblyByName(assembly_name) != null) - { - Log("debug", $"Got AssemblyLoaded event for {assembly_name}, but skipping it as it has already been loaded."); - return true; - } - - var assembly_b64 = eventArgs?["assembly_b64"]?.ToObject(); - var pdb_b64 = eventArgs?["pdb_b64"]?.ToObject(); - - if (string.IsNullOrEmpty(assembly_b64)) - { - logger.LogDebug("No assembly data provided to load."); - return false; - } - - var assembly_data = Convert.FromBase64String(assembly_b64); - var pdb_data = string.IsNullOrEmpty(pdb_b64) ? null : Convert.FromBase64String(pdb_b64); - - var context = Contexts.GetCurrentContext(sessionId); - foreach (var source in store.Add(sessionId, new AssemblyAndPdbData(assembly_data, pdb_data), token)) - { - await OnSourceFileAdded(sessionId, source, context, token); - } - - return true; - } - catch (Exception e) - { - logger.LogDebug($"Failed to load assemblies and PDBs: {e}"); - return false; - } - } - - private async Task OnSetEntrypointBreakpoint(SessionId sessionId, JObject args, CancellationToken token) - { - try - { - ExecutionContext context = Contexts.GetCurrentContext(sessionId); - - var argsNew = JObject.FromObject(new - { - callFrameId = args?["callFrames"]?[0]?["callFrameId"]?.Value(), - expression = "_assembly_name_str + '|' + _entrypoint_method_token", - }); - Result assemblyAndMethodToken = await SendCommand(sessionId, "Debugger.evaluateOnCallFrame", argsNew, token); - if (!assemblyAndMethodToken.IsOk) - { - logger.LogDebug("Failure evaluating _assembly_name_str + '|' + _entrypoint_method_token"); - return; - } - logger.LogDebug($"Entrypoint assembly and method token {assemblyAndMethodToken.Value["result"]["value"].Value()}"); - - var assemblyAndMethodTokenArr = assemblyAndMethodToken.Value["result"]["value"].Value().Split('|', StringSplitOptions.TrimEntries); - var assemblyName = assemblyAndMethodTokenArr[0]; - var methodToken = Convert.ToInt32(assemblyAndMethodTokenArr[1]) & 0xffffff; //token - - var store = await LoadStore(sessionId, true, token); - AssemblyInfo assembly = store.GetAssemblyByName(assemblyName); - if (assembly == null) - { - logger.LogDebug($"Could not find entrypoint assembly {assemblyName} in the store"); - return; - } - var method = assembly.GetMethodByToken(methodToken); - if (method.StartLocation == null) //It's an async method and we need to get the MoveNext method to add the breakpoint - method = assembly.Methods.FirstOrDefault(m => m.Value.KickOffMethod == methodToken).Value; - if (method == null) - { - logger.LogDebug($"Could not find entrypoint method {methodToken} in assembly {assemblyName}"); - return; - } - var sourceFile = assembly.Sources.FirstOrDefault(sf => sf.SourceId == method.SourceId); - if (sourceFile == null) - { - logger.LogDebug($"Could not source file {method.SourceName} for method {method.Name} in assembly {assemblyName}"); - return; - } - string bpId = $"auto:{method.StartLocation.Line}:{method.StartLocation.Column}:{sourceFile.DotNetUrlEscaped}"; - BreakpointRequest request = new(bpId, JObject.FromObject(new - { - lineNumber = method.StartLocation.Line, - columnNumber = method.StartLocation.Column, - url = sourceFile.Url - })); - context.BreakpointRequests[bpId] = request; - if (request.TryResolve(sourceFile)) - await SetBreakpoint(sessionId, context.store, request, sendResolvedEvent: false, fromEnC: false, token); - logger.LogInformation($"Adding bp req {request}"); - } - catch (Exception e) - { - logger.LogDebug($"Unable to set entrypoint breakpoint. {e}"); - } - finally - { - await SendResume(sessionId, token); - } - } - private Result AddCallStackInfoToException(Result _error, ExecutionContext context, int scopeId) - { - try { - var retStackTrace = new JArray(); - foreach (var call in context.CallStack) - { - if (call.Id < scopeId) - continue; - retStackTrace.Add(JObject.FromObject(new - { - functionName = call.Method.Name, - scriptId = call.Location.Id.ToString(), - url = context.Store.ToUrl(call.Location), - lineNumber = call.Location.Line, - columnNumber = call.Location.Column - })); - } - if (!_error.Value.ContainsKey("exceptionDetails")) - _error.Value["exceptionDetails"] = new JObject(); - _error.Value["exceptionDetails"]["stackTrace"] = JObject.FromObject(new {callFrames = retStackTrace}); - return _error; - } - catch (Exception e) - { - logger.LogDebug($"Unable to add stackTrace information to exception. {e}"); - } - return _error; - } - - private async Task OnEvaluateOnCallFrame(MessageId msg_id, int scopeId, string expression, CancellationToken token) - { - ExecutionContext context = Contexts.GetCurrentContext(msg_id); - try - { - if (context.CallStack == null) - return false; - - var resolver = new MemberReferenceResolver(this, context, msg_id, scopeId, logger); - - JObject retValue = await resolver.Resolve(expression, token); - retValue ??= await ExpressionEvaluator.CompileAndRunTheExpression(expression, resolver, logger, token); - - if (retValue != null) - { - SendResponse(msg_id, Result.OkFromObject(new - { - result = retValue - }), token); - } - else - { - SendResponse(msg_id, AddCallStackInfoToException(Result.Err($"Unable to evaluate '{expression}'"), context, scopeId), token); - } - } - catch (ReturnAsErrorException ree) - { - SendResponse(msg_id, AddCallStackInfoToException(ree.Error, context, scopeId), token); - ReportDebuggerExceptionToTelemetry("OnEvaluateOnCallFrame", msg_id, token); - } - catch (Exception e) - { - logger.LogDebug($"Error in EvaluateOnCallFrame for expression '{expression}' with '{e}."); - var ree = new ReturnAsErrorException(e.Message, e.GetType().Name); - SendResponse(msg_id, AddCallStackInfoToException(ree.Error, context, scopeId), token); - ReportDebuggerExceptionToTelemetry("OnEvaluateOnCallFrame", msg_id, token); - } - - return true; - } - - private void ReportDebuggerExceptionToTelemetry(string callingFunction, SessionId msg_id, CancellationToken token) - { - JObject reportBlazorDebugException = JObject.FromObject(new - { - exceptionType = "uncaughtException", - exception = $"BlazorDebugger exception at {callingFunction}", - }); - SendEvent(msg_id, "DotnetDebugger.reportBlazorDebugException", reportBlazorDebugException, token); - } - - internal async Task GetScopeProperties(SessionId msg_id, int scopeId, CancellationToken token) - { - try - { - ExecutionContext context = Contexts.GetCurrentContext(msg_id); - Frame scope = context.CallStack.FirstOrDefault(s => s.Id == scopeId); - if (scope == null) - throw new Exception($"Could not find scope with id #{scopeId}"); - - VarInfo[] varIds = scope.Method.Info.GetLiveVarsAt(scope.Location.IlLocation.Offset); - - var values = await context.SdbAgent.StackFrameGetValues(scope.Method, context.ThreadId, scopeId, varIds, scope.Location.IlLocation.Offset, token); - if (values != null) - { - if (values == null || values.Count == 0) - return new GetMembersResult(); - - PerScopeCache frameCache = context.GetCacheForScope(scopeId); - foreach (JObject value in values) - { - frameCache.Locals[value["name"]?.Value()] = value; - } - return GetMembersResult.FromValues(values); - } - return new GetMembersResult(); - } - catch (Exception exception) - { - throw new Exception($"Error resolving scope properties {exception.Message}"); - } - } - - private async Task SetMonoBreakpoint(SessionId sessionId, string reqId, SourceLocation location, string condition, CancellationToken token) - { - var context = Contexts.GetCurrentContext(sessionId); - var bp = new Breakpoint(reqId, location, condition, BreakpointState.Pending); - string asm_name = bp.Location.IlLocation.Method.Assembly.Name; - int method_token = bp.Location.IlLocation.Method.Token; - int il_offset = bp.Location.IlLocation.Offset; - - var assembly_id = await context.SdbAgent.GetAssemblyId(asm_name, token); - var methodId = await context.SdbAgent.GetMethodIdByToken(assembly_id, method_token, token); - //the breakpoint can be invalid because a race condition between the changes already applied on runtime and not applied yet on debugger side - var breakpoint_id = await context.SdbAgent.SetBreakpointNoThrow(methodId, il_offset, token); - - if (breakpoint_id > 0) - { - bp.RemoteId = breakpoint_id; - bp.State = BreakpointState.Active; - //Log ("verbose", $"BP local id {bp.LocalId} enabled with remote id {bp.RemoteId}"); - } - return bp; - } - - internal virtual async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, ExecutionContext context, CancellationToken token, bool resolveBreakpoints = true) - { - JObject scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData)); - // Log("debug", $"sending {source.Url} {context.Id} {sessionId.sessionId}"); - await SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token); - if (!resolveBreakpoints) - return; - var breakpointRequests = context.BreakpointRequests.Values.ToList(); //this can be changed while we are looping it and cause an exception - foreach (var req in breakpointRequests) - { - if (req.TryResolve(source)) - { - try - { - await SetBreakpoint(sessionId, context.store, req, true, false, token); - } - catch (DebuggerAgentException e) - { - //it's not a wasm page then the command throws an error - if (!e.Message.Contains("getDotnetRuntime is not defined")) - logger.LogDebug($"Unexpected error on RuntimeReady {e}"); - return; - } - } - } - } - - internal virtual async Task LoadStore(SessionId sessionId, bool tryUseDebuggerProtocol, CancellationToken token) - { - ExecutionContext context = Contexts.GetCurrentContext(sessionId); - - if (Interlocked.CompareExchange(ref context.store, new DebugStore(this, logger), null) != null) - return await context.Source.Task; - - try - { - string[] loaded_files = await GetLoadedFiles(sessionId, context, token); - if (loaded_files == null) - { - SendLog(sessionId, $"Failed to get the list of loaded files. Managed code debugging won't work due to this.", token); - } - else - { - var useDebuggerProtocol = false; - if (tryUseDebuggerProtocol) - { - (int MajorVersion, int MinorVersion) = await context.SdbAgent.GetVMVersion(token); - if (MajorVersion == 2 && MinorVersion >= 61) - useDebuggerProtocol = true; - } - - await foreach (SourceFile source in context.store.Load(sessionId, loaded_files, context, useDebuggerProtocol, token)) - { - await OnSourceFileAdded(sessionId, source, context, token); - } - } - } - catch (Exception e) - { - logger.LogError($"failed: {e}"); - context.Source.SetException(e); - } - - if (!context.Source.Task.IsCompleted) - context.Source.SetResult(context.store); - return context.store; - async Task GetLoadedFiles(SessionId sessionId, ExecutionContext context, CancellationToken token) - { - if (context.LoadedFiles != null) - return context.LoadedFiles; - - Result loaded = await SendMonoCommand(sessionId, MonoCommands.GetLoadedFiles(RuntimeId), token); - if (!loaded.IsOk) - { - SendLog(sessionId, $"Error on mono_wasm_get_loaded_files {loaded}", token); - return null; - } - - string[] files = loaded.Value?["result"]?["value"]?.ToObject(); - if (files == null) - SendLog(sessionId, $"Error extracting the list of loaded_files from the result of mono_wasm_get_loaded_files: {loaded}", token); - - return files; - } - } - - protected async Task RuntimeReady(SessionId sessionId, CancellationToken token) - { - try - { - ExecutionContext context = Contexts.GetCurrentContext(sessionId); - if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource(), null) != null) - return await context.ready.Task; - await context.SdbAgent.SendDebuggerAgentCommand(CmdEventRequest.ClearAllBreakpoints, null, token); - - if (context.PauseOnExceptions != PauseOnExceptionsKind.None && context.PauseOnExceptions != PauseOnExceptionsKind.Unset) - await context.SdbAgent.EnableExceptions(context.PauseOnExceptions, token); - - await context.SdbAgent.SetProtocolVersion(token); - await context.SdbAgent.EnableReceiveRequests(EventKind.UserBreak, token); - await context.SdbAgent.EnableReceiveRequests(EventKind.EnC, token); - await context.SdbAgent.EnableReceiveRequests(EventKind.MethodUpdate, token); - - DebugStore store = await LoadStore(sessionId, true, token); - context.ready.SetResult(store); - await SendEvent(sessionId, "Mono.runtimeReady", new JObject(), token); - await SendMonoCommand(sessionId, MonoCommands.SetDebuggerAttached(RuntimeId), token); - context.SdbAgent.ResetStore(store); - return store; - } - catch (DebuggerAgentException e) - { - //it's not a wasm page then the command throws an error - if (!e.Message.Contains("getDotnetRuntime is not defined")) - logger.LogDebug($"Unexpected error on RuntimeReady {e}"); - return null; - } - catch (Exception e) - { - logger.LogDebug($"Unexpected error on RuntimeReady {e}"); - return null; - } - } - - private static IEnumerable> GetBPReqLocations(DebugStore store, BreakpointRequest req, bool ifNoneFoundThenFindNext = false) - { - var comparer = new SourceLocation.LocationComparer(); - // if column is specified the frontend wants the exact matches - // and will clear the bp if it isn't close enough - var bpLocations = store.FindBreakpointLocations(req, ifNoneFoundThenFindNext); - IEnumerable> locations = bpLocations.Distinct(comparer) - .OrderBy(l => l.Column) - .GroupBy(l => l.Id); - if (ifNoneFoundThenFindNext && !locations.Any()) - { - locations = bpLocations.GroupBy(l => l.Id); - } - return locations; - } - - private async Task ResetBreakpoint(SessionId msg_id, DebugStore store, MethodInfo method, CancellationToken token) - { - ExecutionContext context = Contexts.GetCurrentContext(msg_id); - foreach (var req in context.BreakpointRequests.Values) - { - if (req.Method != null) - { - if (req.Method.Assembly.Id == method.Assembly.Id && req.Method.Token == method.Token) { - var locations = GetBPReqLocations(store, req); - foreach (IGrouping sourceId in locations) - { - SourceLocation loc = sourceId.First(); - if (req.Locations.Any(b => b.Location.IlLocation.Offset != loc.IlLocation.Offset)) - { - await RemoveBreakpoint(msg_id, JObject.FromObject(new {breakpointId = req.Id}), true, token); - break; - } - } - } - } - } - } - - protected async Task RemoveBreakpoint(SessionId msg_id, JObject args, bool isEnCReset, CancellationToken token) - { - string bpid = args?["breakpointId"]?.Value(); - - ExecutionContext context = Contexts.GetCurrentContext(msg_id); - if (!context.BreakpointRequests.TryGetValue(bpid, out BreakpointRequest breakpointRequest)) - return; - - foreach (Breakpoint bp in breakpointRequest.Locations) - { - var breakpoint_removed = await context.SdbAgent.RemoveBreakpoint(bp.RemoteId, token); - if (breakpoint_removed) - { - bp.RemoteId = -1; - if (isEnCReset) - bp.State = BreakpointState.Pending; - else - bp.State = BreakpointState.Disabled; - } - } - if (!isEnCReset) - context.BreakpointRequests.Remove(bpid); - } - - protected async Task SetBreakpoint(SessionId sessionId, DebugStore store, BreakpointRequest req, bool sendResolvedEvent, bool fromEnC, CancellationToken token) - { - ExecutionContext context = Contexts.GetCurrentContext(sessionId); - if ((!fromEnC && req.Locations.Count != 0) || (fromEnC && req.Locations.Any(bp => bp.State == BreakpointState.Active))) - { - if (!fromEnC) - Log("debug", $"locations already loaded for {req.Id}"); - return; - } - - var locations = GetBPReqLocations(store, req, true); - - logger.LogDebug("BP request for '{Req}' runtime ready {Context.RuntimeReady}", req, context.IsRuntimeReady); - - var breakpoints = new List(); - foreach (IGrouping sourceId in locations) - { - SourceLocation loc = sourceId.First(); - req.Method = loc.IlLocation.Method; - if (req.Method.DebuggerAttrInfo.HasDebuggerHidden) - continue; - Breakpoint bp = await SetMonoBreakpoint(sessionId, req.Id, loc, req.Condition, token); - - // If we didn't successfully enable the breakpoint - // don't add it to the list of locations for this id - if (bp.State != BreakpointState.Active) - continue; - - breakpoints.Add(bp); - - var resolvedLocation = new - { - breakpointId = req.Id, - location = loc.AsLocation() - }; - - if (sendResolvedEvent) - await SendEvent(sessionId, "Debugger.breakpointResolved", JObject.FromObject(resolvedLocation), token); - } - - req.Locations.AddRange(breakpoints); - return; - } - - private async Task GetPossibleBreakpoints(MessageId msg, SourceLocation start, SourceLocation end, CancellationToken token) - { - List bps = (await RuntimeReady(msg, token)).FindPossibleBreakpoints(start, end); - - if (bps == null) - return false; - - var response = new { locations = bps.Select(b => b.AsLocation()) }; - - SendResponse(msg, Result.OkFromObject(response), token); - return true; - } - - private void OnCompileDotnetScript(MessageId msg_id, CancellationToken token) - { - SendResponse(msg_id, Result.OkFromObject(new { }), token); - } - - private static bool IsNestedMethod(DebugStore store, Frame scope, SourceLocation foundLocation, SourceLocation targetLocation) - { - if (foundLocation.Line != targetLocation.Line || foundLocation.Column != targetLocation.Column) - { - SourceFile doc = store.GetFileById(scope.Method.Info.SourceId); - foreach (var method in doc.Methods) - { - if (method.Token == scope.Method.Info.Token) - continue; - if (method.IsLexicallyContainedInMethod(scope.Method.Info)) - continue; - SourceLocation newFoundLocation = DebugStore.FindBreakpointLocations(targetLocation, targetLocation, scope.Method.Info) - .FirstOrDefault(); - if (!(newFoundLocation is null)) - return true; - } - } - return false; - } - - private async Task OnSetNextIP(MessageId sessionId, SourceLocation targetLocation, CancellationToken token) - { - DebugStore store = await RuntimeReady(sessionId, token); - ExecutionContext context = Contexts.GetCurrentContext(sessionId); - Frame scope = context.CallStack.First(); - - SourceLocation foundLocation = DebugStore.FindBreakpointLocations(targetLocation, targetLocation, scope.Method.Info) - .FirstOrDefault(); - - if (foundLocation is null) - return false; - - //search if it's a nested method and it's return false because we cannot move to another method - if (IsNestedMethod(store, scope, foundLocation, targetLocation)) - return false; - - var ilOffset = foundLocation.IlLocation; - var ret = await context.SdbAgent.SetNextIP(scope.Method, context.ThreadId, ilOffset, token); - - if (!ret) - return false; - - var breakpointId = await context.SdbAgent.SetBreakpointNoThrow(scope.Method.DebugId, ilOffset.Offset, token); - if (breakpointId == -1) - return false; - - context.TempBreakpointForSetNextIP = breakpointId; - await SendResume(sessionId, token); - return true; - } - - internal virtual async Task OnGetScriptSource(MessageId msg_id, string script_id, CancellationToken token) - { - if (!SourceId.TryParse(script_id, out SourceId id)) - return false; - - SourceFile src_file = (await LoadStore(msg_id, true, token)).GetFileById(id); - - try - { - string source = $"// Unable to find document {src_file.FileUriEscaped}"; - - using (Stream data = await src_file.GetSourceAsync(checkHash: false, token: token)) - { - if (data is MemoryStream && data.Length == 0) - return false; - - using (var reader = new StreamReader(data)) - source = await reader.ReadToEndAsync(token); - } - SendResponse(msg_id, Result.OkFromObject(new { scriptSource = source }), token); - } - catch (Exception e) - { - var o = new - { - scriptSource = $"// Unable to read document ({e.Message})\n" + - $"Local path: {src_file?.FileUriEscaped}\n" + - $"SourceLink path: {src_file?.SourceLinkUri}\n" - }; - - SendResponse(msg_id, Result.OkFromObject(o), token); - } - return true; - } - - private async Task AttachToTarget(SessionId sessionId, CancellationToken token) - { - // see https://github.com/mono/mono/issues/19549 for background - if (sessions.Add(sessionId)) - { - await SendMonoCommand(sessionId, new MonoCommands("globalThis.dotnetDebugger = true"), token); - Result res = await SendCommand(sessionId, - "Page.addScriptToEvaluateOnNewDocument", - JObject.FromObject(new { source = $"globalThis.dotnetDebugger = true; delete navigator.constructor.prototype.webdriver;" }), - token); - - if (sessionId != SessionId.Null && !res.IsOk) - sessions.Remove(sessionId); - } - } - - private bool JObjectTryParse(string str, out JObject obj, bool log_exception = true) - { - obj = null; - if (string.IsNullOrEmpty(str)) - return false; - - try - { - obj = JObject.Parse(str); - return true; - } - catch (JsonReaderException jre) - { - if (log_exception) - logger.LogDebug($"Could not parse {str}. Failed with {jre}"); - return false; - } - } - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/browser/debugger/BrowserDebugProxy/MonoSDBHelper.cs deleted file mode 100644 index bd38a0d0286e2f..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ /dev/null @@ -1,2682 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; -using System.Text.RegularExpressions; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.CSharp; -using System.Reflection; -using System.Text; -using System.Runtime.CompilerServices; -using BrowserDebugProxy; -using Microsoft.FileFormats.PE; - -namespace Microsoft.WebAssembly.Diagnostics -{ - internal enum TokenType - { - MdtModule = 0x00000000, // - MdtTypeRef = 0x01000000, // - MdtTypeDef = 0x02000000, // - MdtFieldDef = 0x04000000, // - MdtMethodDef = 0x06000000, // - MdtParamDef = 0x08000000, // - MdtInterfaceImpl = 0x09000000, // - MdtMemberRef = 0x0a000000, // - MdtCustomAttribute = 0x0c000000, // - MdtPermission = 0x0e000000, // - MdtSignature = 0x11000000, // - MdtEvent = 0x14000000, // - MdtProperty = 0x17000000, // - MdtModuleRef = 0x1a000000, // - MdtTypeSpec = 0x1b000000, // - MdtAssembly = 0x20000000, // - MdtAssemblyRef = 0x23000000, // - MdtFile = 0x26000000, // - MdtExportedType = 0x27000000, // - MdtManifestResource = 0x28000000, // - MdtGenericParam = 0x2a000000, // - MdtMethodSpec = 0x2b000000, // - MdtGenericParamConstraint = 0x2c000000, - MdtString = 0x70000000, // - MdtName = 0x71000000, // - MdtBaseType = 0x72000000, // Leave this on the high end value. This does not correspond to metadata table - } - - [Flags] - internal enum GetObjectCommandOptions - { - None = 0, - WithSetter = 1, - AccessorPropertiesOnly = 2, - OwnProperties = 4, - ForDebuggerProxyAttribute = 8, - ForDebuggerDisplayAttribute = 16, - WithProperties = 32, - JustMyCode = 64, - AutoExpandable = 128 - } - - internal enum CommandSet { - Vm = 1, - ObjectRef = 9, - StringRef = 10, - Thread = 11, - ArrayRef = 13, - EventRequest = 15, - StackFrame = 16, - AppDomain = 20, - Assembly = 21, - Method = 22, - Type = 23, - Module = 24, - Field = 25, - Event = 64, - Pointer = 65 - } - - internal enum EventKind { - VmStart = 0, - VmDeath = 1, - ThreadStart = 2, - ThreadDeath = 3, - AppDomainCreate = 4, - AppDomainUnload = 5, - MethodEntry = 6, - MethodExit = 7, - AssemblyLoad = 8, - AssemblyUnload = 9, - Breakpoint = 10, - Step = 11, - TypeLoad = 12, - Exception = 13, - KeepAlive = 14, - UserBreak = 15, - UserLog = 16, - Crash = 17, - EnC = 18, - MethodUpdate = 19 - } - - internal enum ModifierKind { - Count = 1, - ThreadOnly = 3, - LocationOnly = 7, - ExceptionOnly = 8, - Step = 10, - AssemblyOnly = 11, - SourceFileOnly = 12, - TypeNameOnly = 13 - } - - - internal enum SuspendPolicy { - None = 0, - EventThread = 1, - All = 2 - } - - internal enum CmdVM { - Version = 1, - AllThreads = 2, - Suspend = 3, - Resume = 4, - Exit = 5, - Dispose = 6, - InvokeMethod = 7, - SetProtocolVersion = 8, - AbortInvoke = 9, - SetKeepAlive = 10, - GetTypesForSourceFile = 11, - GetTypes = 12, - InvokeMethods = 13, - StartBuffering = 14, - StopBuffering = 15, - VmReadMemory = 16, - VmWriteMemory = 17, - GetAssemblyByName = 18, - GetModuleByGUID = 19, - GetAssemblyAndPdbBytes = 20 - } - - internal enum CmdFrame { - GetValues = 1, - GetThis = 2, - SetValues = 3, - GetDomain = 4, - SetThis = 5, - GetArgument = 6, - GetArguments = 7 - } - - internal enum CmdEvent { - Composite = 100 - } - - internal enum CmdThread { - GetFrameInfo = 1, - GetName = 2, - GetState = 3, - GetInfo = 4, - /* FIXME: Merge into GetInfo when the major protocol version is increased */ - GetId = 5, - /* Ditto */ - GetTid = 6, - SetIp = 7, - GetElapsedTime = 8 - } - - internal enum CmdEventRequest { - Set = 1, - Clear = 2, - ClearAllBreakpoints = 3 - } - - internal enum CmdAppDomain { - GetRootDomain = 1, - GetFriendlyName = 2, - GetAssemblies = 3, - GetEntryAssembly = 4, - CreateString = 5, - GetCorLib = 6, - CreateBoxedValue = 7, - CreateByteArray = 8, - } - - internal enum CmdAssembly { - GetLocation = 1, - GetEntryPoint = 2, - GetManifestModule = 3, - GetObject = 4, - GetType = 5, - GetName = 6, - GetDomain = 7, - GetMetadataBlob = 8, - GetIsDynamic = 9, - GetPdbBlob = 10, - GetTypeFromToken = 11, - GetMethodFromToken = 12, - HasDebugInfo = 13, - HasDebugInfoLoaded = 18 - } - - internal enum CmdModule { - GetInfo = 1, - ApplyChanges = 2, - } - - internal enum CmdPointer{ - GetValue = 1 - } - - internal enum CmdMethod { - GetName = 1, - GetDeclaringType = 2, - GetDebugInfo = 3, - GetParamInfo = 4, - GetLocalsInfo = 5, - GetInfo = 6, - GetBody = 7, - ResolveToken = 8, - GetCattrs = 9, - MakeGenericMethod = 10, - Token = 11, - Assembly = 12, - ClassToken = 13, - AsyncDebugInfo = 14, - GetNameFull = 15, - GetPrettyName = 16 - } - - internal enum CmdType { - GetInfo = 1, - GetMethods = 2, - GetFields = 3, - GetValues = 4, - GetObject = 5, - GetSourceFiles = 6, - SetValues = 7, - IsAssignableFrom = 8, - GetProperties = 9, - GetCattrs = 10, - GetFieldCattrs = 11, - GetPropertyCattrs = 12, - /* FIXME: Merge into GetSourceFiles when the major protocol version is increased */ - GetSourceFiles2 = 13, - /* FIXME: Merge into GetValues when the major protocol version is increased */ - GetValues2 = 14, - GetMethodsByNameFlags = 15, - GetInterfaces = 16, - GetInterfacesMap = 17, - IsInitialized = 18, - CreateInstance = 19, - GetValueSize = 20, - GetValuesICorDbg = 21, - GetParents = 22, - Initialize = 23, - } - - internal enum CmdArray { - GetLength = 1, - GetValues = 2, - SetValues = 3, - RefGetType = 4 - } - - - internal enum CmdField { - GetInfo = 1 - } - - internal enum CmdString { - GetValue = 1, - GetLength = 2, - GetChars = 3 - } - - internal enum CmdObject { - RefGetType = 1, - RefGetValues = 2, - RefIsCollected = 3, - RefGetAddress = 4, - RefGetDomain = 5, - RefSetValues = 6, - RefGetInfo = 7, - GetValuesICorDbg = 8, - RefDelegateGetMethod = 9, - RefIsDelegate = 10 - } - - internal enum ElementType { - End = 0x00, - Void = 0x01, - Boolean = 0x02, - Char = 0x03, - I1 = 0x04, - U1 = 0x05, - I2 = 0x06, - U2 = 0x07, - I4 = 0x08, - U4 = 0x09, - I8 = 0x0a, - U8 = 0x0b, - R4 = 0x0c, - R8 = 0x0d, - String = 0x0e, - Ptr = 0x0f, - ByRef = 0x10, - ValueType = 0x11, - Class = 0x12, - Var = 0x13, - Array = 0x14, - GenericInst = 0x15, - TypedByRef = 0x16, - I = 0x18, - U = 0x19, - FnPtr = 0x1b, - Object = 0x1c, - SzArray = 0x1d, - MVar = 0x1e, - CModReqD = 0x1f, - CModOpt = 0x20, - Internal = 0x21, - Modifier = 0x40, - Sentinel = 0x41, - Pinned = 0x45, - - Type = 0x50, - Boxed = 0x51, - Enum = 0x55 - } - - internal enum ValueTypeId { - Null = 0xf0, - Type = 0xf1, - VType = 0xf2, - FixedArray = 0xf3 - } - internal enum MonoTypeNameFormat{ - FormatIL, - FormatReflection, - FullName, - AssemblyQualified - } - - internal enum StepFilter { - None = 0, - StaticCtor = 1, - DebuggerHidden = 2, - DebuggerStepThrough = 4, - DebuggerNonUserCode = 8 - } - - internal enum StepSize - { - Minimal, - LineColumn - } - - internal sealed class AssemblyAndPdbData - { - public bool IsAsmMetadataOnly { get; init; } - public byte[] AsmBytes { get; set; } - public byte[] PdbBytes { get; set; } - public bool HasDebugInfo { get; set; } - public int PdbAge { get; set; } - public Guid PdbGuid { get; set; } - public string PdbPath { get; set; } - public int PdbUncompressedSize { get; set; } - public bool IsPortableCodeView { get; init; } - public List PdbChecksums { get; init; } - internal AssemblyAndPdbData(byte[] asm, byte[] pdb) - { - AsmBytes = asm; - PdbBytes = pdb; - } - internal AssemblyAndPdbData() - { - IsPortableCodeView = true; - IsAsmMetadataOnly = true; - PdbChecksums = new(); - } - } - - internal sealed record ArrayDimensions - { - internal int Rank { get; } - internal int [] Bounds { get; } - internal int TotalLength { get; } - public ArrayDimensions(int [] rank) - { - Rank = rank.Length; - Bounds = rank; - TotalLength = 1; - for (int i = 0 ; i < Rank ; i++) - TotalLength *= Bounds[i]; - } - - public override string ToString() - { - return $"{string.Join(", ", Bounds)}"; - } - internal string GetArrayIndexString(int idx) - { - if (idx < 0 || idx >= TotalLength) - return "Invalid Index"; - int[] arrayStr = new int[Rank]; - int rankStart = 0; - while (idx > 0) - { - int boundLimit = 1; - for (int i = Rank - 1; i >= rankStart; i--) - { - int lastBoundLimit = boundLimit; - boundLimit *= Bounds[i]; - if (idx < boundLimit) - { - arrayStr[i] = (int)(idx / lastBoundLimit); - idx -= arrayStr[i] * lastBoundLimit; - rankStart = i; - break; - } - } - } - return $"{string.Join(", ", arrayStr)}"; - } - } - - internal sealed class MethodInfoWithDebugInformation - { - private ParameterInfo[] _paramsInfo; - public MethodInfo Info { get; } - public int DebugId { get; } - public string Name { get; } - public ParameterInfo[] GetParametersInfo() - { - if (_paramsInfo != null) - return _paramsInfo; - _paramsInfo = Info.GetParametersInfo(); - return _paramsInfo; - } - - public MethodInfoWithDebugInformation(MethodInfo info, int debugId, string name) - { - Info = info; - DebugId = debugId; - Name = name; - } - } - - internal sealed class TypeInfoWithDebugInformation - { - public TypeInfo Info { get; } - public int DebugId { get; } - public string Name { get; } - public List FieldsList { get; set; } - public byte[] PropertiesBuffer { get; set; } - public List TypeParamsOrArgsForGenericType { get; set; } - - public TypeInfoWithDebugInformation(TypeInfo typeInfo, int debugId, string name) - { - Info = typeInfo; - DebugId = debugId; - Name = name; - } - } - - internal sealed class MonoBinaryReader : BinaryReader - { - public bool HasError { get; } - - private MonoBinaryReader(Stream stream, bool hasError = false) : base(stream) - { - HasError = hasError; - } - - public MonoBinaryReader(byte [] data) : base(new MemoryStream(data)) {} - - public static MonoBinaryReader From(Result result) - { - byte[] newBytes = Array.Empty(); - if (result.IsOk) { - newBytes = Convert.FromBase64String(result.Value?["result"]?["value"]?["value"]?.Value()); - } - return new MonoBinaryReader(new MemoryStream(newBytes), !result.IsOk); - } - - public override string ReadString() - { - var valueLen = ReadInt32(); - if (valueLen == 0) - return string.Empty; - byte[] value = new byte[valueLen]; - Read(value, 0, valueLen); - - return new string(Encoding.UTF8.GetChars(value, 0, valueLen)); - } - - // SDB encodes these as 4 bytes - public override sbyte ReadSByte() => (sbyte)ReadInt32(); - public byte ReadUByte() => (byte)ReadUInt32(); - public ushort ReadUShort() => (ushort)ReadUInt32(); - - // Big endian overrides - public override int ReadInt32() => ReadBigEndian(); - public override double ReadDouble() => ReadBigEndian(); - public override uint ReadUInt32() => ReadBigEndian(); - public override float ReadSingle() => ReadBigEndian(); - public override ulong ReadUInt64() => ReadBigEndian(); - public override long ReadInt64() => ReadBigEndian(); - - private unsafe T ReadBigEndian() where T : struct - { - Span data = stackalloc byte[sizeof(T)]; - T ret = default; - Read(data); - if (BitConverter.IsLittleEndian) - { - data.Reverse(); - } - data.CopyTo(new Span(&ret, data.Length)); - return ret; - } - } - - internal sealed class MonoBinaryWriter : BinaryWriter - { - public MonoBinaryWriter() : base(new MemoryStream(20)) {} - - public override void Write(string val) - { - var bytes = Encoding.UTF8.GetBytes(val); - WriteByteArray(bytes); - } - - public override void Write(long val) => WriteBigEndian(val); - public override void Write(int val) => WriteBigEndian(val); - - private unsafe void WriteBigEndian(T val) where T : struct - { - Span data = stackalloc byte[sizeof(T)]; - new Span(&val, data.Length).CopyTo(data); - if (BitConverter.IsLittleEndian) - { - data.Reverse(); - } - base.Write(data); - } - - internal void Write(ElementType type, T value) where T : struct => Write((byte)type, value); - - private void Write(T1 type, T2 value) where T1 : struct where T2 : struct - { - WriteBigEndian(type); - WriteBigEndian(value); - } - - public void WriteObj(DotnetObjectId objectId, MonoSDBHelper SdbHelper) - { - switch (objectId.Scheme) - { - case "object": - { - Write(ElementType.Class, objectId.Value); - break; - } - case "array": - { - Write(ElementType.Array, objectId.Value); - break; - } - case "valuetype": - { - if (!SdbHelper.ValueCreator.TryGetValueTypeById(objectId.Value, out ValueTypeClass vt)) - throw new ArgumentException($"Could not find any valuetype with id: {objectId.Value}", nameof(objectId.Value)); - Write(vt.Buffer); - break; - } - default: - { - throw new NotImplementedException($"Writing object of scheme: {objectId.Scheme} is not supported"); - } - } - } - - public void WriteByteArray(byte[] bytes) - { - Write(bytes.Length); - Write(bytes); - } - - public async Task WriteConst(ElementType? type, object value, MonoSDBHelper SdbHelper, CancellationToken token) - { - switch (type) - { - case ElementType.I1: - case ElementType.I2: - case ElementType.I4: - Write((ElementType)type, (int)value); - return true; - case ElementType.Char: - int intCharVal = (int)value; - if (value.GetType() == typeof(char)) - intCharVal = (int)(char)value; - Write((ElementType)type, intCharVal); - return true; - case ElementType.Boolean: - int intBoolVal = (int)value; - if (value.GetType() == typeof(bool)) - intBoolVal = (bool)value ? 1 : 0; - Write((ElementType)type, intBoolVal); - return true; - case ElementType.U1: - case ElementType.U2: - case ElementType.U4: - Write((ElementType)type, (uint)value); - return true; - case ElementType.I8: - Write((ElementType)type, (long)value); - return true; - case ElementType.U8: - Write((ElementType)type, (ulong)value); - return true; - case ElementType.R4: - Write((ElementType)type, (float)value); - return true; - case ElementType.R8: - Write((ElementType)type, (double)value); - return true; - case ElementType.String: - int stringId = await SdbHelper.CreateString((string)value, token); - Write(ElementType.String, stringId); - return true; - case null: - if (value == null) - return false; - //ConstantTypeCode.NullReference - Write((byte)value); - Write((byte)0); //not used - Write((int)0); //not used - return true; - } - return false; - } - - public bool WriteConst(PrefixUnaryExpressionSyntax constValue) - { - switch (constValue.Kind()) - { - case SyntaxKind.UnaryMinusExpression: - { - switch (constValue.Operand) - { - case LiteralExpressionSyntax les: - { - return WriteNumber(les.Token.Value, convertToNegative: true); - } - default: - { - // not supported yet - break; - } - } - break; - } - case SyntaxKind.UnaryPlusExpression: - { - switch (constValue.Operand) - { - case LiteralExpressionSyntax les: - { - return WriteNumber(les.Token.Value, convertToNegative: false); - } - default: - { - // not supported yet - break; - } - } - break; - } - } - return false; - } - - public async Task WriteConst(LiteralExpressionSyntax constValue, MonoSDBHelper SdbHelper, CancellationToken token) - { - switch (constValue.Kind()) - { - case SyntaxKind.NumericLiteralExpression: - { - return WriteNumber(constValue.Token.Value); - } - case SyntaxKind.StringLiteralExpression: - { - int stringId = await SdbHelper.CreateString((string)constValue.Token.Value, token); - Write(ElementType.String, stringId); - return true; - } - case SyntaxKind.TrueLiteralExpression: - { - Write(ElementType.Boolean, 1); - return true; - } - case SyntaxKind.FalseLiteralExpression: - { - Write(ElementType.Boolean, 0); - return true; - } - case SyntaxKind.NullLiteralExpression: - { - Write((byte)ValueTypeId.Null); - Write((byte)0); //not used - Write((int)0); //not used - return true; - } - case SyntaxKind.CharacterLiteralExpression: - { - Write(ElementType.Char, (int)(char)constValue.Token.Value); - return true; - } - } - return false; - } - - public bool WriteNumber(object number, bool convertToNegative=false) - { - int coeff = convertToNegative ? -1 : 1; - switch (number) - { - case double d: - Write(ElementType.R8, d * coeff); - break; - case float f: - Write(ElementType.R4, f * coeff); - break; - case long l: - Write(ElementType.I8, l * coeff); - break; - case ulong ul: - Write(ElementType.U8, ul); - break; - case byte b: - Write(ElementType.U1, (int)b); - break; - case sbyte sb: - Write(ElementType.I1, (uint)sb); - break; - case ushort us: - Write(ElementType.U2, (int)us); - break; - case short s: - Write(ElementType.I2, (uint)s * coeff); - break; - case uint ui: - Write(ElementType.U4, ui); - break; - case IntPtr ip: - Write(ElementType.I, (int)ip); - break; - case UIntPtr up: - Write(ElementType.U, (uint)up); - break; - default: - Write(ElementType.I4, (int)number * coeff); - break; - } - return true; - } - - public async Task WriteJsonValue(JObject objValue, MonoSDBHelper SdbHelper, ElementType? expectedType, CancellationToken token) - { - switch (objValue["type"].Value()) - { - case "number": - { - var expected = expectedType is not null ? expectedType.Value : ElementType.I4; - switch (expected) - { - case ElementType.I1: - case ElementType.I2: - case ElementType.I4: - Write(expected, objValue["value"].Value()); - break; - case ElementType.U1: - case ElementType.U2: - case ElementType.U4: - Write(expected, objValue["value"].Value()); - break; - case ElementType.I8: - Write(expected, objValue["value"].Value()); - break; - case ElementType.U8: - Write(expected, objValue["value"].Value()); - break; - case ElementType.R4: - Write(expected, objValue["value"].Value()); - break; - case ElementType.R8: - Write(expected, objValue["value"].Value()); - break; - default: - objValue["value"].Value(); - break; - }; - return true; - } - case "symbol": - { - Write(ElementType.Char, (int)objValue["value"].Value()); - return true; - } - case "string": - { - int stringId = await SdbHelper.CreateString(objValue["value"].Value(), token); - Write(ElementType.String, stringId); - return true; - } - case "boolean": - { - Write(ElementType.Boolean, objValue["value"].Value() ? 1 : 0); - return true; - } - case "object": - { - DotnetObjectId.TryParse(objValue["objectId"]?.Value(), out DotnetObjectId objectId); - WriteObj(objectId, SdbHelper); - return true; - } - } - return false; - } - - public ArraySegment GetParameterBuffer() - { - ((MemoryStream)BaseStream).TryGetBuffer(out var segment); - return segment; - } - - public (string data, int length) ToBase64() { - var segment = GetParameterBuffer(); - return (Convert.ToBase64String(segment), segment.Count); - } - } - internal sealed class FieldTypeClass - { - public int Id { get; } - public string Name { get; } - public int TypeId { get; } - public bool IsNotPrivate { get; } - public bool IsBackingField { get; } - public FieldAttributes Attributes { get; } - public FieldTypeClass(int id, string name, int typeId, bool isBackingField, FieldAttributes attributes) - { - Id = id; - Name = name; - TypeId = typeId; - IsNotPrivate = (Attributes & FieldAttributes.FieldAccessMask & FieldAttributes.Public) != 0; - Attributes = attributes; - IsBackingField = isBackingField; - } - } - - internal sealed class PointerValue - { - public long address; - public int typeId; - public string varName; - private JObject _value; - - public PointerValue(long address, int typeId, string varName) - { - this.address = address; - this.typeId = typeId; - this.varName = varName; - } - - public async Task GetValue(MonoSDBHelper sdbHelper, CancellationToken token) - { - if (_value == null) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(address); - commandParamsWriter.Write(typeId); - using var retDebuggerCmdReader = await sdbHelper.SendDebuggerAgentCommand(CmdPointer.GetValue, commandParamsWriter, token); - string displayVarName = varName; - if (int.TryParse(varName, out _)) - displayVarName = $"[{varName}]"; - _value = await sdbHelper.ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, "*" + displayVarName, token); - } - - return _value; - } - } - internal sealed partial class MonoSDBHelper - { - public const string WebcilInWasmExtension = ".wasm"; - - private static int debuggerObjectId; - private static int cmdId = 1; //cmdId == 0 is used by events which come from runtime - private const int MINOR_VERSION = 66; - private const int MAJOR_VERSION = 2; - - private int VmMinorVersion { get; set; } - private int VmMajorVersion { get; set; } - - private Dictionary methods; - private Dictionary assemblies; - private Dictionary types; - - private readonly MonoProxy proxy; - private DebugStore store; - private readonly SessionId sessionId; - - internal readonly ILogger logger; - - [GeneratedRegex(@"\<(?[^)]*)\>(?[^)]*)(__)(?\d+)", RegexOptions.Singleline)] - private static partial Regex RegexForAsyncLocals { get; } //5__1 // works - - [GeneratedRegex(@"\$VB\$ResumableLocal_(?[^\$]*)\$(?\d+)", RegexOptions.Singleline)] - private static partial Regex RegexForVBAsyncLocals { get; } //$VB$ResumableLocal_testVbScope$2 - - [GeneratedRegex(@"VB\$StateMachine_(\d+)_(?.*)", RegexOptions.Singleline)] - private static partial Regex RegexForVBAsyncMethodName { get; } //VB$StateMachine_2_RunVBScope - - [GeneratedRegex(@"\<([^>]*)\>([d][_][_])([0-9]*)")] - private static partial Regex RegexForAsyncMethodName { get; } - - [GeneratedRegex(@"[`][0-9]+")] - private static partial Regex RegexForGenericArgs { get; } - - [GeneratedRegex("^(((?'Open'<)[^<>]*)+((?'Close-Open'>)[^<>]*)+)*(?(Open)(?!))[^<>]*")] - private static partial Regex RegexForNestedLeftRightAngleBrackets { get; } // b__3_0 - - public JObjectValueCreator ValueCreator { get; init; } - - public static int GetNewId() { return cmdId++; } - public static int GetNewObjectId() => Interlocked.Increment(ref debuggerObjectId); - - public MonoSDBHelper(MonoProxy proxy, ILogger logger, SessionId sessionId) - { - this.proxy = proxy; - this.logger = logger; - this.sessionId = sessionId; - this.VmMajorVersion = -1; - this.VmMinorVersion = -1; - ValueCreator = new(this, logger); - ResetStore(null); - } - - public MonoSDBHelper Clone(SessionId sessionId) - => new MonoSDBHelper(proxy, logger, sessionId) - { - VmMajorVersion = VmMajorVersion, - VmMinorVersion = VmMinorVersion, - store = store, - }; - - public void ResetStore(DebugStore store) - { - this.store = store; - this.methods = new(); - this.assemblies = new(); - this.types = new(); - ClearCache(); - } - - public void ResetTypes() { - this.types = new(); - } - - public async Task GetAssemblyInfo(int assemblyId, CancellationToken token) - { - if (assemblies.TryGetValue(assemblyId, out AssemblyInfo asm)) - { - return asm; - } - var assemblyName = await GetAssemblyName(assemblyId, token); - - asm = store.GetAssemblyByName(assemblyName); - - if (asm == null) - { - assemblyName = await GetAssemblyFileNameFromId(assemblyId, token); //maybe is a lazy loaded assembly - asm = store.GetAssemblyByName(assemblyName); - if (asm == null) - { - asm = AssemblyInfo.WithoutDebugInfo(logger); - logger.LogDebug($"Created assembly without debug information: {assemblyName}"); - } - } - else - { - if (asm.asmMetadataReader is null) //load on demand - { - var assemblyAndPdbData = await GetDataFromAssemblyAndPdbAsync(asm.Name, true, token); - if (assemblyAndPdbData is not null) - asm.LoadInfoFromBytes(proxy, sessionId, assemblyAndPdbData, token); - } - } - asm.SetDebugId(assemblyId); - assemblies[assemblyId] = asm; - return asm; - } - public static string GetPrettierMethodName(string methodName) - { - methodName = methodName.Replace(':', '.'); - methodName = methodName.Replace('/', '.'); - methodName = RegexForGenericArgs.Replace(methodName, ""); - return methodName; - } - - public async Task GetMethodInfo(int methodId, CancellationToken token) - { - if (methods.TryGetValue(methodId, out MethodInfoWithDebugInformation methodDebugInfo)) - { - return methodDebugInfo; - } - var methodToken = await GetMethodToken(methodId, token); - var assemblyId = await GetAssemblyIdFromMethod(methodId, token); - - var asm = await GetAssemblyInfo(assemblyId, token); - - if (asm == null) - { - logger.LogDebug($"Unable to find assembly: {assemblyId}"); - return null; - } - - var method = asm.GetMethodByToken(methodToken); - - string methodName = await GetMethodName(methodId, token); - //get information from runtime - method ??= await CreateMethodInfoFromRuntimeInformation(asm, methodId, methodName, methodToken, token); - var type = await GetTypeFromMethodIdAsync(methodId, token); - var typeInfo = await GetTypeInfo(type, token); - try { - (int MajorVersion, int MinorVersion) = await GetVMVersion(token); - if (MajorVersion == 2 && MinorVersion >= 62) - { - if (typeInfo.Info.IsCompilerGenerated || method.IsCompilerGenerated) - { - methodName = await GetPrettyMethodName(methodId, isAnonymous: true, token); - } - else if (await GetIsAsyncFromMethodId(methodId, token)) - { - methodName = await GetPrettyMethodName(methodId, isAnonymous: false, token); - method.IsAsync = 1; - } - else - { - methodName = GetPrettierMethodName(methodName); - } - } - else - { - methodName = GetPrettierMethodName(methodName); - } - } - catch (Exception e) - { - logger.LogDebug($"Unable to generate a pretty method name: {methodName} - {e}"); - methodName = GetPrettierMethodName(methodName); - } - methods[methodId] = new MethodInfoWithDebugInformation(method, methodId, methodName); - return methods[methodId]; - } - - public async Task CreateMethodInfoFromRuntimeInformation (AssemblyInfo asm, int methodId, string methodName, int methodToken, CancellationToken token ) - { - var typeToken = await GetTypeTokenFromMethodId(methodId, token); - TypeInfo typeInfo = asm.TypesByToken[typeToken]; - var attrs = await GetAttributesFromMethodId(methodId, token); - return new MethodInfo(asm, methodName, methodToken, typeInfo, attrs); - } - public async Task GetTypeInfo(int typeId, CancellationToken token) - { - if (types.TryGetValue(typeId, out TypeInfoWithDebugInformation typeDebugInfo)) - { - return typeDebugInfo; - } - - var typeToken = await GetTypeToken(typeId, token); - var typeName = await GetTypeName(typeId, token); - var assemblyId = await GetAssemblyFromType(typeId, token); - var asm = await GetAssemblyInfo(assemblyId, token); - - if (asm == null) - { - logger.LogDebug($"Unable to find assembly: {assemblyId}"); - return null; - } - - asm.TypesByToken.TryGetValue(typeToken, out TypeInfo type); - - type ??= asm.CreateTypeInfo(typeName, typeToken); - - types[typeId] = new TypeInfoWithDebugInformation(type, typeId, typeName); - return types[typeId]; - } - - public void ClearCache() => ValueCreator.ClearCache(); - - public async Task<(int, int)> GetVMVersion(CancellationToken token) - { - if (VmMajorVersion != -1) - return (VmMajorVersion, VmMinorVersion); - using var commandParamsWriter = new MonoBinaryWriter(); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdVM.Version, commandParamsWriter, token); - retDebuggerCmdReader.ReadString(); //vm version - VmMajorVersion = retDebuggerCmdReader.ReadInt32(); - VmMinorVersion = retDebuggerCmdReader.ReadInt32(); - return (VmMajorVersion, VmMinorVersion); - } - - public async Task SetProtocolVersion(CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(MAJOR_VERSION); - commandParamsWriter.Write(MINOR_VERSION); - commandParamsWriter.Write((byte)0); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdVM.SetProtocolVersion, commandParamsWriter, token); - return true; - } - - public async Task EnableReceiveRequests(EventKind eventKind, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write((byte)eventKind); - commandParamsWriter.Write((byte)SuspendPolicy.None); - commandParamsWriter.Write((byte)0); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdEventRequest.Set, commandParamsWriter, token); - return true; - } - - internal async Task SendDebuggerAgentCommand(T command, MonoBinaryWriter arguments, CancellationToken token, bool throwOnError = true) - { - Result res = await proxy.SendMonoCommand(sessionId, MonoCommands.SendDebuggerAgentCommand(proxy.RuntimeId, GetNewId(), (int)GetCommandSetForCommand(command), (int)(object)command, arguments?.ToBase64().data ?? string.Empty), token); - return !res.IsOk && throwOnError - ? throw new DebuggerAgentException($"SendDebuggerAgentCommand failed for {command}: {res}") - : MonoBinaryReader.From(res); - } - - private static CommandSet GetCommandSetForCommand(T command) => - command switch { - CmdVM => CommandSet.Vm, - CmdObject => CommandSet.ObjectRef, - CmdString => CommandSet.StringRef, - CmdThread => CommandSet.Thread, - CmdArray => CommandSet.ArrayRef, - CmdEventRequest => CommandSet.EventRequest, - CmdFrame => CommandSet.StackFrame, - CmdAppDomain => CommandSet.AppDomain, - CmdAssembly => CommandSet.Assembly, - CmdMethod => CommandSet.Method, - CmdType => CommandSet.Type, - CmdModule => CommandSet.Module, - CmdField => CommandSet.Field, - CmdEvent => CommandSet.Event, - CmdPointer => CommandSet.Pointer, - _ => throw new Exception ("Unknown CommandSet") - }; - - internal async Task SendDebuggerAgentCommandWithParms(T command, (string data, int length) encoded, int type, string extraParm, CancellationToken token, bool throwOnError = true) - { - Result res = await proxy.SendMonoCommand(sessionId, MonoCommands.SendDebuggerAgentCommandWithParms(proxy.RuntimeId, GetNewId(), (int)GetCommandSetForCommand(command), (int)(object)command, encoded.data, encoded.length, type, extraParm), token); - return !res.IsOk && throwOnError - ? throw new DebuggerAgentException($"SendDebuggerAgentCommand failed for {command}: {res.Error}") - : MonoBinaryReader.From(res); - } - - public async Task CreateString(string value, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdAppDomain.GetRootDomain, commandParamsWriter, token); - var root = retDebuggerCmdReader.ReadInt32(); - commandParamsWriter.Write(root); - commandParamsWriter.Write(value); - using var stringDebuggerCmdReader = await SendDebuggerAgentCommand(CmdAppDomain.CreateString, commandParamsWriter, token); - return stringDebuggerCmdReader.ReadInt32(); - } - - public async Task GetAttributesFromMethodId(int methodId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.GetInfo, commandParamsWriter, token); - var flags = retDebuggerCmdReader.ReadInt32(); - return (MethodAttributes) flags; - } - - public async Task GetTypeTokenFromMethodId(int methodId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.ClassToken, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32(); //token - } - - public async Task GetMethodToken(int methodId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.Token, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32() & 0xffffff; //token - } - - public async Task MakeGenericMethod(int methodId, List genericTypes, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - commandParamsWriter.Write(genericTypes.Count); - foreach (var genericType in genericTypes) - { - commandParamsWriter.Write(genericType); - } - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.MakeGenericMethod, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task GetMethodIdByToken(int assembly_id, int method_token, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(assembly_id); - commandParamsWriter.Write(method_token | (int)TokenType.MdtMethodDef); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdAssembly.GetMethodFromToken, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task GetAssemblyIdFromType(int type_id, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(type_id); - commandParamsWriter.Write((int) MonoTypeNameFormat.FormatReflection); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetInfo, commandParamsWriter, token); - retDebuggerCmdReader.ReadString(); //namespace - retDebuggerCmdReader.ReadString(); //name - retDebuggerCmdReader.ReadString(); //formatted name - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task> GetTypeParamsOrArgsForGenericType(int typeId, CancellationToken token) - { - var typeInfo = await GetTypeInfo(typeId, token); - - if (typeInfo is null) - return null; - - if (typeInfo.TypeParamsOrArgsForGenericType != null) - return typeInfo.TypeParamsOrArgsForGenericType; - - var ret = new List(); - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeId); - commandParamsWriter.Write((int) MonoTypeNameFormat.FormatReflection); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetInfo, commandParamsWriter, token); - - retDebuggerCmdReader.ReadString(); //namespace - retDebuggerCmdReader.ReadString(); //name - retDebuggerCmdReader.ReadString(); //name full - retDebuggerCmdReader.ReadInt32(); //assembly_id - retDebuggerCmdReader.ReadInt32(); //module_id - retDebuggerCmdReader.ReadInt32(); //type_id - retDebuggerCmdReader.ReadInt32(); //rank type - retDebuggerCmdReader.ReadInt32(); //type token - retDebuggerCmdReader.ReadByte(); //rank - retDebuggerCmdReader.ReadInt32(); //flags - retDebuggerCmdReader.ReadByte(); - int nested = retDebuggerCmdReader.ReadInt32(); - for (int i = 0 ; i < nested; i++) - { - retDebuggerCmdReader.ReadInt32(); //nested type - } - retDebuggerCmdReader.ReadInt32(); //typeid - int generics = retDebuggerCmdReader.ReadInt32(); - for (int i = 0 ; i < generics; i++) - { - ret.Add(retDebuggerCmdReader.ReadInt32()); //generic type - } - - typeInfo.TypeParamsOrArgsForGenericType = ret; - - return ret; - } - - public async Task GetAssemblyIdFromMethod(int methodId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.Assembly, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32(); //assembly_id - } - - public async Task GetAssemblyId(string asm_name, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(asm_name); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdVM.GetAssemblyByName, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task GetModuleId(string moduleGuid, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - var guidArray = Convert.FromBase64String(moduleGuid); - commandParamsWriter.WriteByteArray(guidArray); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdVM.GetModuleByGUID, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task GetAssemblyNameFromModule(int moduleId, CancellationToken token) - { - using var command_params_writer = new MonoBinaryWriter(); - command_params_writer.Write(moduleId); - - using var ret_debugger_cmd_reader = await SendDebuggerAgentCommand(CmdModule.GetInfo, command_params_writer, token); - ret_debugger_cmd_reader.ReadString(); - return ret_debugger_cmd_reader.ReadString(); - } - - public async Task GetAssemblyName(int assembly_id, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(assembly_id); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdAssembly.GetLocation, commandParamsWriter, token); - string result = retDebuggerCmdReader.ReadString(); - if (result.EndsWith(".webcil")) { - /* don't leak .webcil names to the debugger - work in terms of the original .dlls */ - string baseName = result.Substring(0, result.Length - 7); - result = baseName + ".dll"; - } - if (result.EndsWith(WebcilInWasmExtension)) { - /* don't leak webcil .wasm names to the debugger - work in terms of the original .dlls */ - string baseName = result.Substring(0, result.Length - WebcilInWasmExtension.Length); - result = baseName + ".dll"; - } - return result; - } - - public async Task GetFullAssemblyName(int assemblyId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(assemblyId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdAssembly.GetName, commandParamsWriter, token); - var name = retDebuggerCmdReader.ReadString(); - return name; - } - - public async Task GetAssemblyFileNameFromId(int assemblyId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(assemblyId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdAssembly.GetName, commandParamsWriter, token); - var name = retDebuggerCmdReader.ReadString(); - return name.Remove(name.IndexOf(',')) + ".dll"; - } - - public async Task GetMethodName(int methodId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.GetNameFull, commandParamsWriter, token); - return retDebuggerCmdReader.ReadString(); - } - - public async Task GetPrettyMethodName(int methodId, bool isAnonymous, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.GetPrettyName, commandParamsWriter, token); - var type = (ElementType)retDebuggerCmdReader.ReadInt32(); - switch (type) - { - case ElementType.GenericInst: - { - var ret = retDebuggerCmdReader.ReadString(); - if (ret.IndexOf(':') is int index && index > 0) - ret = ret.Substring(0, index); - ret = RegexForAsyncMethodName.Replace(ret, "$1"); - var numGenericTypeArgs = retDebuggerCmdReader.ReadInt32(); - var numGenericMethodArgs = retDebuggerCmdReader.ReadInt32(); - int numTotalGenericArgs = numGenericTypeArgs + numGenericMethodArgs; - var genericArgs = new List(capacity: numTotalGenericArgs); - for (int i = 0; i < numTotalGenericArgs; i++) - { - var typeArgC = retDebuggerCmdReader.ReadString(); - typeArgC = RegexForGenericArgs.Replace(typeArgC, ""); - genericArgs.Add(typeArgC); - } - var match = RegexForGenericArgs.Match(ret); - while (match.Success) - { - var countArgs = Convert.ToInt32(match.Value.Remove(0, 1)); - ret = ret.Remove(match.Index, match.Value.Length); - ret = ret.Insert(match.Index, $"<{string.Join(", ", genericArgs.Take(countArgs))}>"); - genericArgs.RemoveRange(0, countArgs); - match = RegexForGenericArgs.Match(ret); - } - ret = ret.Replace('/', '.'); - return ret; - } - case ElementType.Class: - { - var ret = new StringBuilder(100); - var countNested = retDebuggerCmdReader.ReadInt32(); - var anonymousMethodId = ""; - for (int i = 0 ; i <= countNested; i++) - { - var klassName = retDebuggerCmdReader.ReadString(); - if (klassName.Contains("<>")) - { - if (anonymousMethodId.LastIndexOf('_') >= 0) - anonymousMethodId = klassName.Substring(klassName.LastIndexOf('_') + 1); - } - else if (klassName.StartsWith("VB$")) - { - var match = RegexForVBAsyncMethodName.Match(klassName); - if (match.Success) - ret = ret.Insert(0, match.Groups["methodName"].Value); - else - ret = ret.Insert(0, klassName); - } - else - { - var matchOnClassName = RegexForNestedLeftRightAngleBrackets.Match(klassName); - if (matchOnClassName.Success && matchOnClassName.Groups["Close"].Captures.Count > 0) - klassName = matchOnClassName.Groups["Close"].Captures[0].Value; - if (ret.Length > 0) - ret = ret.Insert(0, "."); - ret = ret.Insert(0, klassName); - } - } - var methodName = retDebuggerCmdReader.ReadString(); - var matchOnMethodName = RegexForNestedLeftRightAngleBrackets.Match(methodName); - if (matchOnMethodName.Success && matchOnMethodName.Groups["Close"].Captures.Count > 0) - { - if (isAnonymous && anonymousMethodId.Length == 0 && methodName.Contains("__")) - anonymousMethodId = methodName.Substring(methodName.IndexOf("__") + 2); - methodName = matchOnMethodName.Groups["Close"].Captures[0].Value; - ret.Append($".{methodName}"); - } - if (isAnonymous && anonymousMethodId.Length > 0) - ret.Append($".AnonymousMethod__{anonymousMethodId}"); - return ret.ToString(); - } - default: - { - return retDebuggerCmdReader.ReadString(); - } - } - } - public async Task MethodIsStatic(int methodId, CancellationToken token) - { - var methodInfo = await GetMethodInfo(methodId, token); - if (methodInfo != null) - return methodInfo.Info.IsStatic(); - var attrs = await GetAttributesFromMethodId(methodId, token); - return (attrs & MethodAttributes.Static) > 0; - } - - public async Task GetParamCount(int methodId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.GetParamInfo, commandParamsWriter, token); - retDebuggerCmdReader.ReadInt32(); - int param_count = retDebuggerCmdReader.ReadInt32(); - return param_count; - } - - public async Task GetReturnType(int methodId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.GetParamInfo, commandParamsWriter, token); - retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadInt32(); - var retType = retDebuggerCmdReader.ReadInt32(); - var ret = await GetTypeName(retType, token); - return ret; - } - - public async Task GetParameters(int methodId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.GetParamInfo, commandParamsWriter, token); - retDebuggerCmdReader.ReadInt32(); - var paramCount = retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadInt32(); - var retType = retDebuggerCmdReader.ReadInt32(); - var parameters = "("; - for (int i = 0 ; i < paramCount; i++) - { - var paramType = retDebuggerCmdReader.ReadInt32(); - parameters += await GetTypeName(paramType, token); - parameters = parameters.Replace("System.Func", "Func"); - if (i + 1 < paramCount) - parameters += ","; - } - parameters += ")"; - return parameters; - } - - public async Task SetBreakpointNoThrow(int methodId, long il_offset, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write((byte)EventKind.Breakpoint); - commandParamsWriter.Write((byte)SuspendPolicy.None); - commandParamsWriter.Write((byte)1); - commandParamsWriter.Write((byte)ModifierKind.LocationOnly); - commandParamsWriter.Write(methodId); - commandParamsWriter.Write(il_offset); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdEventRequest.Set, commandParamsWriter, token, throwOnError: false); - if (retDebuggerCmdReader.HasError) - return -1; - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task RemoveBreakpoint(int breakpoint_id, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write((byte)EventKind.Breakpoint); - commandParamsWriter.Write((int) breakpoint_id); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdEventRequest.Clear, commandParamsWriter, token); - - if (retDebuggerCmdReader != null) - return true; - return false; - } - - public async Task Step(int thread_id, StepKind kind, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write((byte)EventKind.Step); - commandParamsWriter.Write((byte)SuspendPolicy.None); - commandParamsWriter.Write((byte)1); - commandParamsWriter.Write((byte)ModifierKind.Step); - commandParamsWriter.Write(thread_id); - commandParamsWriter.Write((int)StepSize.LineColumn); - commandParamsWriter.Write((int)kind); - commandParamsWriter.Write((int)(StepFilter.StaticCtor)); //filter - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdEventRequest.Set, commandParamsWriter, token, throwOnError: false); - if (retDebuggerCmdReader.HasError) - return false; - var isBPOnManagedCode = retDebuggerCmdReader.ReadInt32(); - if (isBPOnManagedCode == 0) - return false; - return true; - } - - public async Task ClearSingleStep(int req_id, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write((byte)EventKind.Step); - commandParamsWriter.Write((int) req_id); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdEventRequest.Clear, commandParamsWriter, token, throwOnError: false); - return !retDebuggerCmdReader.HasError ? true : false; - } - - public async Task GetFieldValue(int typeId, int fieldId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeId); - commandParamsWriter.Write(1); - commandParamsWriter.Write(fieldId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetValues, commandParamsWriter, token); - return await ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, "", token); - } - - public async Task TypeIsInitialized(int typeId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.IsInitialized, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task TypeInitialize(int typeId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.Initialize, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task GetTypePropertiesReader(int typeId, CancellationToken token) - { - var typeInfo = await GetTypeInfo(typeId, token); - - if (typeInfo is null) - return null; - - if (typeInfo.PropertiesBuffer is not null) - return new MonoBinaryReader(typeInfo.PropertiesBuffer); - - var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeId); - - var reader = await SendDebuggerAgentCommand(CmdType.GetProperties, commandParamsWriter, token); - typeInfo.PropertiesBuffer = ((MemoryStream)reader.BaseStream).ToArray(); - return reader; - } - - public async Task> GetTypeFields(int typeId, CancellationToken token) - { - var typeInfo = await GetTypeInfo(typeId, token); - - if (typeInfo.FieldsList != null) { - return typeInfo.FieldsList; - } - - var ret = new List(); - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetFields, commandParamsWriter, token); - var nFields = retDebuggerCmdReader.ReadInt32(); - - for (int i = 0 ; i < nFields; i++) - { - int fieldId = retDebuggerCmdReader.ReadInt32(); //fieldId - string fieldNameStr = retDebuggerCmdReader.ReadString(); - int fieldTypeId = retDebuggerCmdReader.ReadInt32(); //typeId - int attrs = retDebuggerCmdReader.ReadInt32(); //attrs - FieldAttributes fieldAttrs = (FieldAttributes)attrs; - int isSpecialStatic = retDebuggerCmdReader.ReadInt32(); //is_special_static - if (isSpecialStatic == 1) - continue; - - bool isBackingField = false; - if (fieldNameStr.Contains("k__BackingField")) - { - isBackingField = true; - fieldNameStr = fieldNameStr.Replace("k__BackingField", ""); - fieldNameStr = fieldNameStr.Replace("<", ""); - fieldNameStr = fieldNameStr.Replace(">", ""); - } - ret.Add(new FieldTypeClass(fieldId, fieldNameStr, fieldTypeId, isBackingField, fieldAttrs)); - } - typeInfo.FieldsList = ret; - return ret; - } - - private static string ReplaceCommonClassNames(string className) => - new StringBuilder(className) - .Replace("System.String", "string") - .Replace("System.Boolean", "bool") - .Replace("System.Char", "char") - .Replace("System.SByte", "sbyte") - .Replace("System.Int32", "int") - .Replace("System.Int64", "long") - .Replace("System.Single", "float") - .Replace("System.Double", "double") - .Replace("System.Byte", "byte") - .Replace("System.UInt16", "ushort") - .Replace("System.UInt32", "uint") - .Replace("System.UInt64", "ulong") - .Replace("System.Object", "object") - .Replace("System.Void", "void") - //.Replace("System.Decimal", "decimal") - .ToString(); - - internal async Task GetCAttrsFromType(int typeId, string attrName, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeId); - commandParamsWriter.Write(0); - var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetCattrs, commandParamsWriter, token); - var count = retDebuggerCmdReader.ReadInt32(); - if (count == 0) - return null; - for (int i = 0 ; i < count; i++) - { - var methodId = retDebuggerCmdReader.ReadInt32(); - using var commandCattrParamsWriter = new MonoBinaryWriter(); - commandCattrParamsWriter.Write(methodId); - using var retDebuggerCmdReader2 = await SendDebuggerAgentCommand(CmdMethod.GetDeclaringType, commandCattrParamsWriter, token); - var customAttributeTypeId = retDebuggerCmdReader2.ReadInt32(); - var customAttributeName = await GetTypeName(customAttributeTypeId, token); - if (customAttributeName == attrName) - return retDebuggerCmdReader; - - //reading buffer only to advance the reader to the next cattr - for (int k = 0; k < 2; k++) - { - var parmCount = retDebuggerCmdReader.ReadInt32(); - for (int j = 0; j < parmCount; j++) - { - //to typed_args - await ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, "varName", token); - } - } - } - return null; - } - - public async Task GetAssemblyFromType(int type_id, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(type_id); - commandParamsWriter.Write((int) MonoTypeNameFormat.FormatReflection); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetInfo, commandParamsWriter, token); - - retDebuggerCmdReader.ReadString(); - retDebuggerCmdReader.ReadString(); - retDebuggerCmdReader.ReadString(); - - return retDebuggerCmdReader.ReadInt32(); - } - - public JToken GetEvaluationResultProperties(string id) - { - ExecutionContext context = proxy.Contexts.GetCurrentContext(sessionId); - var resolver = new MemberReferenceResolver(proxy, context, sessionId, context.CallStack.First().Id, logger); - var evaluationResult = resolver.TryGetEvaluationResult(id); - return evaluationResult["value"]; - } - - public async Task GetValueFromDebuggerDisplayAttribute(DotnetObjectId dotnetObjectId, int typeId, CancellationToken token) - { - string expr = ""; - try { - var getCAttrsRetReader = await GetCAttrsFromType(typeId, "System.Diagnostics.DebuggerDisplayAttribute", token); - if (getCAttrsRetReader == null) - return null; - - var parmCount = getCAttrsRetReader.ReadInt32(); - var monoType = (ElementType)getCAttrsRetReader.ReadByte(); //MonoTypeEnum -> MONO_TYPE_STRING - if (monoType != ElementType.String) - return null; - - var stringId = getCAttrsRetReader.ReadInt32(); - var dispAttrStr = await GetStringValue(stringId, token); - ExecutionContext context = proxy.Contexts.GetCurrentContext(sessionId); - GetMembersResult members = await GetTypeMemberValues( - dotnetObjectId, - GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerDisplayAttribute, - token); - JArray objectValues = new JArray(members.Flatten()); - - var thisObj = JObjectValueCreator.Create(value: "", type: "object", description: "", writable: false, objectId: dotnetObjectId.ToString()); - thisObj["name"] = "this"; - objectValues.Add(thisObj); - - var resolver = new MemberReferenceResolver(proxy, context, sessionId, objectValues, logger); - if (dispAttrStr.Length == 0) - return null; - - if (dispAttrStr.Contains(", nq")) - { - dispAttrStr = dispAttrStr.Replace(", nq", ""); - } - if (dispAttrStr.Contains(",nq")) - { - dispAttrStr = dispAttrStr.Replace(",nq", ""); - } - if (dispAttrStr.Contains(", raw")) - { - dispAttrStr = dispAttrStr.Replace(", raw", ""); - } - if (dispAttrStr.Contains(",raw")) - { - dispAttrStr = dispAttrStr.Replace(",raw", ""); - } - expr = "$\"" + dispAttrStr + "\""; - JObject retValue = await resolver.Resolve(expr, token); - retValue ??= await ExpressionEvaluator.CompileAndRunTheExpression(expr, resolver, logger, token); - - return retValue?["value"]?.Value(); - } - catch (Exception ex) - { - logger.LogDebug($"Could not evaluate DebuggerDisplayAttribute - {expr} - {await GetTypeName(typeId, token)}: {ex}"); - } - return null; - } - - [GeneratedRegex(@"`\d+")] - private static partial Regex RegexForGenericArity { get; } - - [GeneratedRegex(@"[[, ]+]")] - private static partial Regex RegexForSquareBrackets { get; } - - public async Task GetTypeName(int typeId, CancellationToken token) - { - string className = await GetTypeNameOriginal(typeId, token); - className = className.Replace("+", "."); - className = RegexForGenericArity.Replace(className, ""); - className = RegexForSquareBrackets.Replace(className, "__SQUARED_BRACKETS__"); - //className = className.Replace("[]", "__SQUARED_BRACKETS__"); - className = className.Replace("[", "<"); - className = className.Replace("]", ">"); - className = className.Replace("__SQUARED_BRACKETS__", "[]"); - className = className.Replace(",", ", "); - className = ReplaceCommonClassNames(className); - return className; - } - - public async Task GetTypeNameOriginal(int typeId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeId); - commandParamsWriter.Write((int) MonoTypeNameFormat.FormatReflection); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetInfo, commandParamsWriter, token); - retDebuggerCmdReader.ReadString(); //namespace - retDebuggerCmdReader.ReadString(); //class name - return retDebuggerCmdReader.ReadString(); //class name formatted - } - - public async Task GetTypeToken(int typeId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeId); - commandParamsWriter.Write((int) MonoTypeNameFormat.FormatReflection); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetInfo, commandParamsWriter, token); - retDebuggerCmdReader.ReadString(); //namespace - retDebuggerCmdReader.ReadString(); //class name - retDebuggerCmdReader.ReadString(); //class name formatted - retDebuggerCmdReader.ReadInt32(); //assemblyid - retDebuggerCmdReader.ReadInt32(); //moduleId - retDebuggerCmdReader.ReadInt32(); //parent typeId - retDebuggerCmdReader.ReadInt32(); //array typeId - return retDebuggerCmdReader.ReadInt32(); //token - } - - public async Task GetStringValue(int string_id, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(string_id); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdString.GetValue, commandParamsWriter, token); - var isUtf16 = retDebuggerCmdReader.ReadByte(); - if (isUtf16 == 0) { - return retDebuggerCmdReader.ReadString(); - } - return null; - } - public async Task GetArrayDimensions(int object_id, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(object_id); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdArray.GetLength, commandParamsWriter, token); - var length = retDebuggerCmdReader.ReadInt32(); - var rank = new int[length]; - for (int i = 0 ; i < length; i++) - { - rank[i] = retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadInt32(); //lower_bound - } - return new ArrayDimensions(rank); - } - - public async Task> GetTypeIdsForObject(int object_id, bool withParents, CancellationToken token) - { - List ret = new List(); - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(object_id); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdObject.RefGetType, commandParamsWriter, token); - var type_id = retDebuggerCmdReader.ReadInt32(); - ret.Add(type_id); - if (withParents) - { - using var commandParentsParamsWriter = new MonoBinaryWriter(); - commandParentsParamsWriter.Write(type_id); - using var parentsCmdReader = await SendDebuggerAgentCommand(CmdType.GetParents, commandParentsParamsWriter, token); - var parentsCount = parentsCmdReader.ReadInt32(); - for (int i = 0 ; i < parentsCount; i++) - { - ret.Add(parentsCmdReader.ReadInt32()); - } - } - return ret; - } - - public async Task GetClassNameFromObject(int object_id, CancellationToken token) - { - var type_id = await GetTypeIdsForObject(object_id, false, token); - return await GetTypeName(type_id[0], token); - } - - public async Task GetTypeIdFromToken(int assemblyId, int typeToken, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write((int)assemblyId); - commandParamsWriter.Write((int)typeToken); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdAssembly.GetTypeFromToken, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task GetMethodIdsByName(int type_id, string method_name, BindingFlags extraFlags, CancellationToken token) - { - if (type_id <= 0) - throw new DebuggerAgentException($"Invalid type_id {type_id} (method_name: {method_name}"); - - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write((int)type_id); - commandParamsWriter.Write(method_name); - commandParamsWriter.Write((int)(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | extraFlags)); - commandParamsWriter.Write((int)1); //case sensitive - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdType.GetMethodsByNameFlags, commandParamsWriter, token); - var nMethods = retDebuggerCmdReader.ReadInt32(); - if (nMethods == 0) - return null; - int[] methodIds = new int[nMethods]; - for (int i = 0; i < nMethods; i++) - methodIds[i] = retDebuggerCmdReader.ReadInt32(); - return methodIds; - } - - public async Task IsDelegate(int objectId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write((int)objectId); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdObject.RefIsDelegate, commandParamsWriter, token); - return retDebuggerCmdReader.ReadByte() == 1; - } - - public async Task GetDelegateMethod(int objectId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write((int)objectId); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdObject.RefDelegateGetMethod, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task GetDelegateMethodDescription(int objectId, CancellationToken token) - { - var methodId = await GetDelegateMethod(objectId, token); - - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - //Console.WriteLine("methodId - " + methodId); - if (methodId == 0) - return ""; - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.GetName, commandParamsWriter, token); - var methodName = retDebuggerCmdReader.ReadString(); - - var returnType = await GetReturnType(methodId, token); - var parameters = await GetParameters(methodId, token); - - return $"{returnType} {methodName} {parameters}"; - } - - public async Task InvokeMethod(ArraySegment argsBuffer, int methodId, CancellationToken token, string name = null, bool isMethodStatic = false) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - if (!isMethodStatic) - commandParamsWriter.Write(argsBuffer); - commandParamsWriter.Write(0); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdVM.InvokeMethod, commandParamsWriter, token); - retDebuggerCmdReader.ReadByte(); //number of objects returned. - return await ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, name, token); - } - - public Task InvokeMethod(int objectId, int methodId, bool isValueType, CancellationToken token, bool isMethodStatic = false) - { - if (isValueType && !isMethodStatic) - { - return ValueCreator.TryGetValueTypeById(objectId, out var valueType) - ? InvokeMethod(valueType.Buffer, methodId, token) - : throw new ArgumentException($"Could not find valuetype with id {objectId}, for method id: {methodId}", nameof(objectId)); - } - using var commandParamsObjWriter = new MonoBinaryWriter(); - if (!isMethodStatic) - commandParamsObjWriter.Write(ElementType.Class, objectId); - return InvokeMethod(commandParamsObjWriter.GetParameterBuffer(), methodId, token); - } - - public Task InvokeMethod(DotnetObjectId dotnetObjectId, CancellationToken token, int methodId = -1) - { - if (dotnetObjectId.Scheme == "method") - { - JObject args = dotnetObjectId.ValueAsJson; - int? objectId = args["containerId"]?.Value(); - int? embeddedMethodId = args["methodId"]?.Value(); - bool isMethodStatic = args["isStatic"]?.Value() == true; - - return objectId == null || embeddedMethodId == null - ? throw new ArgumentException($"Invalid object id for a method, with missing container, or methodId", nameof(dotnetObjectId)) - : InvokeMethod(objectId.Value, - embeddedMethodId.Value, - isValueType: args["isValueType"]?.Value() == true, - token, - isMethodStatic); - } - - return dotnetObjectId.Scheme is "object" or "valuetype" - ? InvokeMethod(dotnetObjectId.Value, methodId, isValueType: dotnetObjectId.IsValueType, token) - : throw new ArgumentException($"Cannot invoke method with id {methodId} on {dotnetObjectId}", nameof(dotnetObjectId)); - } - - public async Task InvokeToStringAsync(IEnumerable typeIds, bool isValueType, bool isEnum, int objectId, BindingFlags extraFlags, bool invokeToStringInObject, CancellationToken token) - { - try - { - foreach (var typeId in typeIds) - { - var typeInfo = await GetTypeInfo(typeId, token); - if (typeInfo == null || (typeInfo.Name == "object" && !invokeToStringInObject)) - continue; - Microsoft.WebAssembly.Diagnostics.MethodInfo methodInfo = typeInfo.Info.Methods.FirstOrDefault(m => m.Name == "ToString"); - if (!isEnum && methodInfo == null) - continue; - int[] methodIds = await GetMethodIdsByName(typeId, "ToString", extraFlags, token); - if (methodIds == null) - continue; - foreach (var methodId in methodIds) - { - var methodInfoFromRuntime = await GetMethodInfo(methodId, token); - if (methodInfoFromRuntime?.Info?.GetParametersInfo()?.Length > 0) - continue; - var retMethod = await InvokeMethod(objectId, methodId, isValueType, token); - return retMethod["value"]?["value"].Value(); - } - } - } - catch (Exception e) - { - logger.LogDebug($"Error while evaluating ToString method: {e}"); - } - return null; - } - - public async Task GetPropertyMethodIdByName(int typeId, string propertyName, CancellationToken token) - { - using var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token); - if (retDebuggerCmdReader == null) - return -1; - - var nProperties = retDebuggerCmdReader.ReadInt32(); - for (int i = 0 ; i < nProperties; i++) - { - retDebuggerCmdReader.ReadInt32(); //propertyId - string propertyNameStr = retDebuggerCmdReader.ReadString(); - var getMethodId = retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadInt32(); //setmethod - var attrs = retDebuggerCmdReader.ReadInt32(); //attrs - if (propertyNameStr == propertyName) - { - return getMethodId; - } - } - return -1; - } - - public async Task GetPointerContent(int pointerId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - PointerValue pointerValue = ValueCreator.GetPointerValue(pointerId); - if (pointerValue == null) - throw new ArgumentException($"Could not find any pointer with id: {pointerId}", nameof(pointerId)); - return await pointerValue.GetValue(this, token); - } - - public static int GetNextDebuggerObjectId() => Interlocked.Increment(ref debuggerObjectId); - - public async Task GetIsAsyncFromMethodId(int methodId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.AsyncDebugInfo, commandParamsWriter, token); - return retDebuggerCmdReader.ReadByte() == 1; - } - - public async Task GetTypeFromMethodIdAsync(int methodId, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(methodId); - - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdMethod.GetDeclaringType, commandParamsWriter, token); - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task IsAsyncMethod(int methodId, CancellationToken token) - { - var methodInfo = await GetMethodInfo(methodId, token); - if (methodInfo != null && methodInfo.Info.IsAsync != -1) - { - return methodInfo.Info.IsAsync == 1; - } - methodInfo.Info.IsAsync = Convert.ToInt32(await GetIsAsyncFromMethodId(methodId, token)); - return methodInfo.Info.IsAsync == 1; - } - - private static bool IsClosureReferenceField (string fieldName) - { - // mcs is "$locvar" - // old mcs is "<>f__ref" - // csc is "CS$<>" - // roslyn is "<>8__" - return fieldName.StartsWith ("CS$<>", StringComparison.Ordinal) || - fieldName.StartsWith ("<>f__ref", StringComparison.Ordinal) || - fieldName.StartsWith ("$locvar", StringComparison.Ordinal) || - fieldName.StartsWith ("<>8__", StringComparison.Ordinal); - } - - public async Task GetHoistedLocalVariables(MethodInfoWithDebugInformation method, int objectId, IEnumerable asyncLocals, int offset, CancellationToken token) - { - JArray asyncLocalsFull = new JArray(); - List objectsAlreadyRead = new(); - objectsAlreadyRead.Add(objectId); - foreach (var asyncLocal in asyncLocals) - { - var fieldName = asyncLocal["name"].Value(); - if (fieldName.EndsWith("__this", StringComparison.Ordinal)) - { - asyncLocal["name"] = "this"; - } - else if (IsClosureReferenceField(fieldName)) //same code that has on debugger-libs - { - if (DotnetObjectId.TryParse(asyncLocal?["value"]?["objectId"]?.Value(), out DotnetObjectId dotnetObjectId)) - { - if (!objectsAlreadyRead.Contains(dotnetObjectId.Value)) - { - var asyncProxyMembersFromObject = await MemberObjectsExplorer.GetObjectMemberValues( - this, dotnetObjectId.Value, GetObjectCommandOptions.WithProperties, token); - var hoistedLocalVariable = await GetHoistedLocalVariables(method, dotnetObjectId.Value, asyncProxyMembersFromObject.Flatten(), offset, token); - asyncLocalsFull = new JArray(asyncLocalsFull.Union(hoistedLocalVariable)); - } - } - continue; - } - else if (fieldName.StartsWith("<>", StringComparison.Ordinal)) //examples: <>t__builder, <>1__state - { - continue; - } - else if (fieldName.StartsWith('<')) //examples: 5__2 - { - var match = RegexForAsyncLocals.Match(fieldName); - if (match.Success) - { - if (!method.Info.ContainsAsyncScope(Convert.ToInt32(match.Groups["scopeId"].Value), offset)) - continue; - asyncLocal["name"] = match.Groups["varName"].Value; - } - } - //VB language - else if (fieldName.StartsWith("$VB$Local_", StringComparison.Ordinal)) - { - asyncLocal["name"] = fieldName.Remove(0, 10); - } - else if (fieldName.StartsWith("$VB$ResumableLocal_", StringComparison.Ordinal)) - { - var match = RegexForVBAsyncLocals.Match(fieldName); - if (match.Success) - { - if (!method.Info.ContainsAsyncScope(Convert.ToInt32(match.Groups["scopeId"].Value) + 1, offset)) - continue; - asyncLocal["name"] = match.Groups["varName"].Value; - } - } - else if (fieldName.StartsWith('$')) - { - continue; - } - asyncLocalsFull.Add(asyncLocal); - } - return asyncLocalsFull; - } - - public async Task StackFrameGetValues(MethodInfoWithDebugInformation method, int thread_id, int frame_id, VarInfo[] varIds, int offset, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(thread_id); - commandParamsWriter.Write(frame_id); - commandParamsWriter.Write(varIds.Length); - foreach (var var in varIds) - { - commandParamsWriter.Write(var.Index); - } - - if (await IsAsyncMethod(method.DebugId, token)) - { - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetThis, commandParamsWriter, token); - retDebuggerCmdReader.ReadByte(); //ignore type - var objectId = retDebuggerCmdReader.ReadInt32(); - GetMembersResult asyncProxyMembers = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithProperties, token, includeStatic: true); - var asyncLocals = await GetHoistedLocalVariables(method, objectId, asyncProxyMembers.Flatten(), offset, token); - return asyncLocals; - } - - JArray locals = new JArray(); - using var localsDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetValues, commandParamsWriter, token); - foreach (var var in varIds) - { - try - { - var var_json = await ValueCreator.ReadAsVariableValue(localsDebuggerCmdReader, var.Name, token, includeStatic: true); - locals.Add(var_json); - } - catch (Exception ex) - { - logger.LogDebug($"Failed to create value for local var {var}: {ex}"); - continue; - } - } - if (!method.Info.IsStatic()) - { - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetThis, commandParamsWriter, token); - var var_json = await ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, "this", token); - var_json.Add("fieldOffset", -1); - locals.Add(var_json); - } - return locals; - - } - - public async Task GetArrayValues(int arrayId, CancellationToken token) - { - var dimensions = await GetArrayDimensions(arrayId, token); - var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(arrayId); - commandParamsWriter.Write(0); - commandParamsWriter.Write(dimensions.TotalLength); - var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdArray.GetValues, commandParamsWriter, token); - JArray array = new JArray(); - for (int i = 0; i < dimensions.TotalLength; i++) - { - var var_json = await ValueCreator.ReadAsVariableValue(retDebuggerCmdReader, dimensions.GetArrayIndexString(i), token); - array.Add(var_json); - } - return array; - } - - public async Task GetArrayValuesProxy(int arrayId, CancellationToken token) - { - var length = await GetArrayDimensions(arrayId, token); - var arrayProxy = JObject.FromObject(new - { - items = await GetArrayValues(arrayId, token), - dimensionsDetails = length.Bounds - }); - return arrayProxy; - } - - public async Task EnableExceptions(PauseOnExceptionsKind state, CancellationToken token) - { - if (state == PauseOnExceptionsKind.Unset) - { - logger.LogDebug($"Trying to setPauseOnExceptions using status Unset"); - return false; - } - - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write((byte)EventKind.Exception); - commandParamsWriter.Write((byte)SuspendPolicy.None); - commandParamsWriter.Write((byte)1); - commandParamsWriter.Write((byte)ModifierKind.ExceptionOnly); - commandParamsWriter.Write(0); //exc_class - if (state == PauseOnExceptionsKind.All) - commandParamsWriter.Write((byte)1); //caught - else - commandParamsWriter.Write((byte)0); //caught - - if (state == PauseOnExceptionsKind.Uncaught || state == PauseOnExceptionsKind.All) - commandParamsWriter.Write((byte)1); //uncaught - else - commandParamsWriter.Write((byte)0); //uncaught - - commandParamsWriter.Write((byte)1);//subclasses - commandParamsWriter.Write((byte)0);//not_filtered_feature - commandParamsWriter.Write((byte)0);//everything_else - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdEventRequest.Set, commandParamsWriter, token); - return true; - } - - public async Task GetTypeByName(string typeToSearch, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeToSearch); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdVM.GetTypes, commandParamsWriter, token); - var count = retDebuggerCmdReader.ReadInt32(); //count ret - return retDebuggerCmdReader.ReadInt32(); - } - - public async Task GetValuesFromDebuggerProxyAttributeForObject(int objectId, int typeId, CancellationToken token) - { - try - { - int methodId = await FindDebuggerProxyConstructorIdFor(typeId, token); - if (methodId == -1) - return null; - - using var ctorArgsWriter = new MonoBinaryWriter(); - ctorArgsWriter.Write((byte)ValueTypeId.Null); - ctorArgsWriter.Write((byte)0); //not used - ctorArgsWriter.Write(0); //not used - ctorArgsWriter.Write((int)1); // num args - ctorArgsWriter.Write((byte)ElementType.Object); - ctorArgsWriter.Write(objectId); - - var retMethod = await InvokeMethod(ctorArgsWriter.GetParameterBuffer(), methodId, token); - if (!DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value(), out DotnetObjectId dotnetObjectId)) - throw new Exception($"Invoking .ctor ({methodId}) for DebuggerTypeProxy on type {typeId} returned {retMethod}"); - - GetMembersResult members = await GetTypeMemberValues(dotnetObjectId, - GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerProxyAttribute, - token); - - return members; - } - catch (Exception e) - { - logger.LogDebug($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}"); - } - - return null; - } - - public async Task GetValuesFromDebuggerProxyAttributeForValueTypes(int valueTypeId, int typeId, CancellationToken token) - { - try - { - var typeName = await GetTypeName(typeId, token); - int methodId = await FindDebuggerProxyConstructorIdFor(typeId, token); - if (methodId == -1) - return null; - - using var ctorArgsWriter = new MonoBinaryWriter(); - ctorArgsWriter.Write((byte)ValueTypeId.Null); - - if (!ValueCreator.TryGetValueTypeById(valueTypeId, out var valueType)) - return null; - ctorArgsWriter.Write((byte)0); //not used but needed - ctorArgsWriter.Write(0); //not used but needed - ctorArgsWriter.Write((int)1); // num args - ctorArgsWriter.Write(valueType.Buffer); - var retMethod = await InvokeMethod(ctorArgsWriter.GetParameterBuffer(), methodId, token); - if (!DotnetObjectId.TryParse(retMethod?["value"]?["objectId"]?.Value(), out DotnetObjectId dotnetObjectId)) - throw new Exception($"Invoking .ctor ({methodId}) for DebuggerTypeProxy on type {typeId} returned {retMethod}"); - GetMembersResult members = await GetTypeMemberValues(dotnetObjectId, - GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerProxyAttribute, - token); - return members; - } - catch (Exception e) - { - logger.LogDebug($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}"); - return null; - } - } - - private async Task FindDebuggerProxyConstructorIdFor(int typeId, CancellationToken token) - { - try - { - var getCAttrsRetReader = await GetCAttrsFromType(typeId, "System.Diagnostics.DebuggerTypeProxyAttribute", token); - if (getCAttrsRetReader == null) - return -1; - - var parmCount = getCAttrsRetReader.ReadInt32(); - if (parmCount != 1) - throw new InternalErrorException($"Expected to find custom attribute with only one argument, but it has {parmCount} parameters."); - - byte monoParamTypeId = getCAttrsRetReader.ReadByte(); - // FIXME: DebuggerTypeProxyAttribute(string) - not supported - if ((ValueTypeId)monoParamTypeId != ValueTypeId.Type) - { - logger.LogDebug($"DebuggerTypeProxy attribute is only supported with a System.Type parameter type. Got {(ValueTypeId)monoParamTypeId}"); - return -1; - } - - var typeProxyTypeId = getCAttrsRetReader.ReadInt32(); - - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(typeProxyTypeId); - var originalClassName = await GetTypeNameOriginal(typeProxyTypeId, token); - - if (originalClassName.IndexOf('[') > 0) - { - string className = originalClassName; - className = className.Remove(className.IndexOf('[')); - var assemblyId = await GetAssemblyIdFromType(typeProxyTypeId, token); - var assemblyName = await GetFullAssemblyName(assemblyId, token); - - StringBuilder typeToSearch = new(className); - typeToSearch.Append('['); - List genericTypeArgs = await GetTypeParamsOrArgsForGenericType(typeId, token); - for (int k = 0; k < genericTypeArgs.Count; k++) - { - // typeToSearch += '['; - var assemblyIdArg = await GetAssemblyIdFromType(genericTypeArgs[k], token); - var assemblyNameArg = await GetFullAssemblyName(assemblyIdArg, token); - var classNameArg = await GetTypeNameOriginal(genericTypeArgs[k], token); - typeToSearch.Append($"{(k == 0 ? "" : ",")}[{classNameArg}, {assemblyNameArg}]"); - } - typeToSearch.Append($"], {assemblyName}"); - var genericTypeId = await GetTypeByName(typeToSearch.ToString(), token); - if (genericTypeId < 0) - { - logger.LogDebug($"Could not find instantiated generic type id for {typeToSearch}."); - return -1; - } - typeProxyTypeId = genericTypeId; - } - int[] constructorIds = await GetMethodIdsByName(typeProxyTypeId, ".ctor", BindingFlags.DeclaredOnly, token); - if (constructorIds is null) - throw new InternalErrorException($"Could not find any constructor for DebuggerProxy type: {originalClassName}"); - - if (constructorIds.Length == 1) - return constructorIds[0]; - - string expectedConstructorParamType = await GetTypeName(typeId, token); - foreach (var methodId in constructorIds) - { - var methodInfoFromRuntime = await GetMethodInfo(methodId, token); - // avoid calling to runtime if possible - var ps = methodInfoFromRuntime.Info.GetParametersInfo(); - if (ps.Length != 1) - continue; - string parameters = await GetParameters(methodId, token); - if (string.IsNullOrEmpty(parameters)) - throw new InternalErrorException($"Could not get method's parameter types. MethodId = {methodId}."); - if (parameters == $"({expectedConstructorParamType})") - return methodId; - } - throw new InternalErrorException($"Could not find a matching constructor for DebuggerProxy type: {originalClassName}"); - } - catch (Exception e) - { - logger.LogDebug($"Could not evaluate DebuggerTypeProxyAttribute of type {await GetTypeName(typeId, token)} - {e}"); - return -1; - } - } - - public ValueTypeClass GetValueTypeClass(int valueTypeId) - { - if (ValueCreator.TryGetValueTypeById(valueTypeId, out ValueTypeClass vt)) - return vt; - throw new ArgumentException($"Could not find any valuetype with id: {valueTypeId}", nameof(valueTypeId)); - } - - public Task GetTypeMemberValues(DotnetObjectId dotnetObjectId, GetObjectCommandOptions getObjectOptions, CancellationToken token) - => dotnetObjectId.IsValueType - ? MemberObjectsExplorer.GetValueTypeMemberValues(this, dotnetObjectId.Value, getObjectOptions, token) - : MemberObjectsExplorer.GetObjectMemberValues(this, dotnetObjectId.Value, getObjectOptions, token); - - - public async Task GetMethodProxy(JObject objectId, CancellationToken token) - { - var containerId = objectId["containerId"].Value(); - var methodId = objectId["methodId"].Value(); - var isValueType = objectId["isValueType"].Value(); - return await InvokeMethod(containerId, methodId, isValueType, token); - } - - public async Task GetObjectProxy(int objectId, CancellationToken token) - { - GetMembersResult members = await MemberObjectsExplorer.GetObjectMemberValues(this, objectId, GetObjectCommandOptions.WithSetter, token); - JArray ret = members.Flatten(); - var typeIds = await GetTypeIdsForObject(objectId, true, token); - foreach (var typeId in typeIds) - { - var retDebuggerCmdReader = await GetTypePropertiesReader(typeId, token); - if (retDebuggerCmdReader == null) - return null; - - var nProperties = retDebuggerCmdReader.ReadInt32(); - for (int i = 0 ; i < nProperties; i++) - { - retDebuggerCmdReader.ReadInt32(); //propertyId - string propertyNameStr = retDebuggerCmdReader.ReadString(); - var getMethodId = retDebuggerCmdReader.ReadInt32(); - var setMethodId = retDebuggerCmdReader.ReadInt32(); //setmethod - var attrValue = retDebuggerCmdReader.ReadInt32(); //attrs - //Console.WriteLine($"{propertyNameStr} - {attrValue}"); - if (ret.Where(attribute => attribute["name"].Value().Equals(propertyNameStr)).Any()) - { - var attr = ret.Where(attribute => attribute["name"].Value().Equals(propertyNameStr)).First(); - - using var command_params_writer_to_set = new MonoBinaryWriter(); - command_params_writer_to_set.Write(setMethodId); - command_params_writer_to_set.Write((byte)ElementType.Class); - command_params_writer_to_set.Write(objectId); - command_params_writer_to_set.Write(1); - var (data, length) = command_params_writer_to_set.ToBase64(); - - if (attr["set"] != null) - { - attr["set"] = JObject.FromObject(new { - commandSet = CommandSet.Vm, - command = CmdVM.InvokeMethod, - buffer = data, - valtype = attr["set"]["valtype"], - length, - id = GetNewId() - }); - } - continue; - } - else - { - var command_params_writer_to_get = new MonoBinaryWriter(); - command_params_writer_to_get.Write(getMethodId); - command_params_writer_to_get.Write((byte)ElementType.Class); - command_params_writer_to_get.Write(objectId); - command_params_writer_to_get.Write(0); - var (data, length) = command_params_writer_to_get.ToBase64(); - - ret.Add(JObject.FromObject(new { - get = JObject.FromObject(new { - commandSet = CommandSet.Vm, - command = CmdVM.InvokeMethod, - buffer = data, - length = length, - id = GetNewId() - }), - name = propertyNameStr - })); - } - if (await MethodIsStatic(getMethodId, token)) - continue; - } - } - return ret; - } - - public async Task SetVariableValue(int thread_id, int frame_id, int varId, string newValue, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(thread_id); - commandParamsWriter.Write(frame_id); - commandParamsWriter.Write(1); - commandParamsWriter.Write(varId); - JArray locals = new JArray(); - using var getDebuggerCmdReader = await SendDebuggerAgentCommand(CmdFrame.GetValues, commandParamsWriter, token); - int etype = getDebuggerCmdReader.ReadByte(); - using var setDebuggerCmdReader = await SendDebuggerAgentCommandWithParms(CmdFrame.SetValues, commandParamsWriter.ToBase64(), etype, newValue, token, throwOnError: false); - return !setDebuggerCmdReader.HasError; - } - - public async Task SetNextIP(MethodInfoWithDebugInformation method, int threadId, IlLocation ilOffset, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(threadId); - commandParamsWriter.Write(method.DebugId); - commandParamsWriter.Write((long)ilOffset.Offset); - using var getDebuggerCmdReader = await SendDebuggerAgentCommand(CmdThread.SetIp, commandParamsWriter, token); - return !getDebuggerCmdReader.HasError; - } - - public async Task CreateByteArray(string diff, CancellationToken token) - { - var diffArr = Convert.FromBase64String(diff); - using var commandParamsWriter = new MonoBinaryWriter(); - using var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdAppDomain.GetRootDomain, commandParamsWriter, token); - var root = retDebuggerCmdReader.ReadInt32(); - - commandParamsWriter.Write(root); - commandParamsWriter.WriteByteArray(diffArr); - using var arrayDebuggerCmdReader = await SendDebuggerAgentCommand(CmdAppDomain.CreateByteArray, commandParamsWriter, token); - return arrayDebuggerCmdReader.ReadInt32(); - } - - public async Task ApplyUpdates(int moduleId, string dmeta, string dil, string dpdb, CancellationToken token) - { - int dpdbId = -1; - var dmetaId = await CreateByteArray(dmeta, token); - var dilId = await CreateByteArray(dil, token); - if (dpdb != null) - dpdbId = await CreateByteArray(dpdb, token); - - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(moduleId); - commandParamsWriter.Write(dmetaId); - commandParamsWriter.Write(dilId); - if (dpdbId != -1) - commandParamsWriter.Write(dpdbId); - else - commandParamsWriter.Write((byte)ValueTypeId.Null); - await SendDebuggerAgentCommand(CmdModule.ApplyChanges, commandParamsWriter, token); - return true; - } - - public async Task HasDebugInfoLoadedByRuntimeAsync(string assemblyName, CancellationToken token) - { - var assemblyId = await GetAssemblyId(assemblyName, token); - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(assemblyId); - using var retDebuggerCmdReader1 = await SendDebuggerAgentCommand(CmdAssembly.HasDebugInfoLoaded, commandParamsWriter, token); - return retDebuggerCmdReader1.ReadByte() == 1; - } - - public async Task GetDataFromAssemblyAndPdbAsync(string assemblyName, bool ignoreJMC, CancellationToken token) - { - if (!ignoreJMC && proxy.JustMyCode && !assemblyName.StartsWith("System.Private.CoreLib", StringComparison.Ordinal) && !(await HasDebugInfoLoadedByRuntimeAsync(assemblyName, token))) - return null; //only load symbols if JustMyCode is disabled, or it's corelib or has debug info loaded by runtime which mean it's an user assembly - using var commandParamsWriter = new MonoBinaryWriter(); - byte[] assembly_buf = null; - byte[] pdb_buf = null; - var pdbUncompressedSize = 0; - (int MajorVersion, int MinorVersion) = await GetVMVersion(token); - - commandParamsWriter.Write(assemblyName); - var retDebuggerCmdReader = await SendDebuggerAgentCommand(CmdVM.GetAssemblyAndPdbBytes, commandParamsWriter, token); - int assembly_size = retDebuggerCmdReader.ReadInt32(); - if (assembly_size > 0) - assembly_buf = retDebuggerCmdReader.ReadBytes(assembly_size); - if (MajorVersion == 2 && MinorVersion >= 64 || MajorVersion >= 2) - pdbUncompressedSize = retDebuggerCmdReader.ReadInt32(); - int pdb_size = retDebuggerCmdReader.ReadInt32(); - if (pdb_size > 0) - pdb_buf = retDebuggerCmdReader.ReadBytes(pdb_size); - - if (!(MajorVersion == 2 && MinorVersion >= 64 || MajorVersion >= 2)) //versions older than 2.64 do not support this new format of GetAssemblyAndPdbBytes - return new(assembly_buf, pdb_buf); - - AssemblyAndPdbData data = new(); - data.AsmBytes = assembly_buf; - data.PdbBytes = pdb_buf; - data.HasDebugInfo = retDebuggerCmdReader.ReadBoolean(); - data.PdbUncompressedSize = pdbUncompressedSize; - if (!data.HasDebugInfo) - return data; - - data.PdbAge = retDebuggerCmdReader.ReadInt32(); - var pdbGuidSize = retDebuggerCmdReader.ReadInt32(); - data.PdbGuid = new Guid(retDebuggerCmdReader.ReadBytes(pdbGuidSize)); - data.PdbPath = retDebuggerCmdReader.ReadString(); - var pdbChecksumCount = retDebuggerCmdReader.ReadInt32(); - for (int i = 0; i < pdbChecksumCount; i++) - { - var algorithmName = retDebuggerCmdReader.ReadString(); - var pdbChecksumSize = retDebuggerCmdReader.ReadInt32(); - data.PdbChecksums.Add(new PdbChecksum(algorithmName, retDebuggerCmdReader.ReadBytes(pdbChecksumSize))); - } - return data; - } - private static readonly string[] s_primitiveTypeNames = new[] - { - "bool", - "char", - "string", - "byte", - "sbyte", - "int", - "uint", - "long", - "ulong", - "short", - "ushort", - "float", - "double", - }; - - public static bool IsPrimitiveType(string simplifiedClassName) - => s_primitiveTypeNames.Contains(simplifiedClassName); - - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/PortableExecutableDebugMetadataProvider.cs b/src/mono/browser/debugger/BrowserDebugProxy/PortableExecutableDebugMetadataProvider.cs deleted file mode 100644 index 94aebb35772d23..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/PortableExecutableDebugMetadataProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; -using System.Collections.Immutable; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; - -namespace Microsoft.WebAssembly.Diagnostics; - -public class PortableExecutableDebugMetadataProvider : IDebugMetadataProvider -{ - private readonly PEReader _peReader; - public PortableExecutableDebugMetadataProvider(PEReader peReader) - { - _peReader = peReader; - } - public ImmutableArray ReadDebugDirectory() => _peReader.ReadDebugDirectory(); - - public CodeViewDebugDirectoryData ReadCodeViewDebugDirectoryData(DebugDirectoryEntry entry) => _peReader.ReadCodeViewDebugDirectoryData(entry); - - public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry) => _peReader.ReadPdbChecksumDebugDirectoryData(entry); - - public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry) => _peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry); -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/ProxyOptions.cs b/src/mono/browser/debugger/BrowserDebugProxy/ProxyOptions.cs deleted file mode 100644 index 665d2e71d500c6..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/ProxyOptions.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; - -#nullable enable - -namespace Microsoft.WebAssembly.Diagnostics; - -public class ProxyOptions -{ - public Uri DevToolsUrl { get; set; } = new Uri($"http://localhost:9222"); - public int? OwnerPid { get; set; } - public int FirefoxProxyPort { get; set; } - public int FirefoxDebugPort { get; set; } = 6000; - public int DevToolsProxyPort { get; set; } - public int DevToolsDebugPort - { - get => DevToolsUrl.Port; - set - { - var builder = new UriBuilder(DevToolsUrl) - { - Port = value - }; - DevToolsUrl = builder.Uri; - } - } - public string? LogPath { get; set; } - public bool RunningForBlazor { get; set; } - public bool IgnoreProxyForLocalAddress { get; set; } - public bool IsFirefoxDebugging { get; set; } - public bool JustMyCode { get; set; } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/RunLoopExitState.cs b/src/mono/browser/debugger/BrowserDebugProxy/RunLoopExitState.cs deleted file mode 100644 index 5184ae2e49ce76..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/RunLoopExitState.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; - -namespace Microsoft.WebAssembly.Diagnostics; - -public record RunLoopExitState(RunLoopStopReason reason, Exception? exception) -{ -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/RunLoopStopReason.cs b/src/mono/browser/debugger/BrowserDebugProxy/RunLoopStopReason.cs deleted file mode 100644 index dd57103b80b3c3..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/RunLoopStopReason.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.WebAssembly.Diagnostics; - -public enum RunLoopStopReason -{ - Shutdown, - Cancelled, - Exception, - ConnectionClosed -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/SignatureTypeProvider.cs b/src/mono/browser/debugger/BrowserDebugProxy/SignatureTypeProvider.cs deleted file mode 100644 index 9353dafacc80e4..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/SignatureTypeProvider.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Reflection.Metadata; -using System.Text; -using System.Threading.Tasks; -using Microsoft.WebAssembly.Diagnostics; - -namespace Microsoft.WebAssembly.Diagnostics; - -internal sealed class SignatureTypeProvider : ISignatureTypeProvider -{ - public ElementType GetPrimitiveType(PrimitiveTypeCode typeCode) - => typeCode switch - { - PrimitiveTypeCode.Boolean => ElementType.Boolean, - PrimitiveTypeCode.Byte => ElementType.U1, - PrimitiveTypeCode.Char => ElementType.Char, - PrimitiveTypeCode.Double => ElementType.R8, - PrimitiveTypeCode.Int16 => ElementType.I2, - PrimitiveTypeCode.Int32 => ElementType.I4, - PrimitiveTypeCode.Int64 => ElementType.I8, - PrimitiveTypeCode.IntPtr => ElementType.Ptr, - PrimitiveTypeCode.Object => ElementType.Object, - PrimitiveTypeCode.SByte => ElementType.I1, - PrimitiveTypeCode.Single => ElementType.R4, - PrimitiveTypeCode.String => ElementType.String, - PrimitiveTypeCode.TypedReference => ElementType.ValueType, - PrimitiveTypeCode.UInt16 => ElementType.U2, - PrimitiveTypeCode.UInt32 => ElementType.U4, - PrimitiveTypeCode.UInt64 => ElementType.U8, - PrimitiveTypeCode.UIntPtr => ElementType.Ptr, - PrimitiveTypeCode.Void => ElementType.Void, - _ => ElementType.End, - }; - - ElementType ISignatureTypeProvider.GetFunctionPointerType(MethodSignature signature) => ElementType.FnPtr; - ElementType ISignatureTypeProvider.GetModifiedType(ElementType modifier, ElementType unmodifiedType, bool isRequired) => ElementType.Object; - ElementType ISignatureTypeProvider.GetPinnedType(ElementType elementType) => ElementType.Object; - ElementType IConstructedTypeProvider.GetArrayType(ElementType elementType, ArrayShape shape) => ElementType.Array; - ElementType IConstructedTypeProvider.GetByReferenceType(ElementType elementType) => ElementType.Object; - ElementType IConstructedTypeProvider.GetGenericInstantiation(ElementType genericType, ImmutableArray typeArguments) => ElementType.Object; - ElementType IConstructedTypeProvider.GetPointerType(ElementType elementType) => ElementType.Ptr; - ElementType ISZArrayTypeProvider.GetSZArrayType(ElementType elementType) => ElementType.SzArray; - ElementType ISignatureTypeProvider.GetGenericMethodParameter(object genericContext, int index) => ElementType.Object; - ElementType ISignatureTypeProvider.GetGenericTypeParameter(object genericContext, int index) => ElementType.Object; - ElementType ISignatureTypeProvider.GetTypeFromSpecification(MetadataReader reader, object genericContext, TypeSpecificationHandle handle, byte rawTypeKind) => ElementType.Object; - ElementType ISimpleTypeProvider.GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) => ElementType.Object; - ElementType ISimpleTypeProvider.GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) => ElementType.Object; -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/ValueOrError.cs b/src/mono/browser/debugger/BrowserDebugProxy/ValueOrError.cs deleted file mode 100644 index b5d07851206ee2..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/ValueOrError.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; - -namespace Microsoft.WebAssembly.Diagnostics; - -public struct ValueOrError -{ - public TValue? Value { get; init; } - public Result? Error { get; init; } - - public bool IsError => Error != null; - - private ValueOrError(TValue? value = default, Result? error = default) - { - if (value != null && error != null) - throw new ArgumentException($"Both {nameof(value)}, and {nameof(error)} cannot be non-null"); - - if (value == null && error == null) - throw new ArgumentException($"Both {nameof(value)}, and {nameof(error)} cannot be null"); - - Value = value; - Error = error; - } - - public static ValueOrError WithValue(TValue value) => new ValueOrError(value: value); - public static ValueOrError WithError(Result err) => new ValueOrError(error: err); - public static ValueOrError WithError(string msg) => new ValueOrError(error: Result.Err(msg)); -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/ValueTypeClass.cs b/src/mono/browser/debugger/BrowserDebugProxy/ValueTypeClass.cs deleted file mode 100644 index 85050d584f4279..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/ValueTypeClass.cs +++ /dev/null @@ -1,351 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.WebAssembly.Diagnostics; -using Newtonsoft.Json.Linq; -using Microsoft.Extensions.Logging; - -namespace BrowserDebugProxy -{ - internal sealed class ValueTypeClass - { - private bool autoExpand; - private JArray proxy; - private GetMembersResult _combinedResult; - private bool propertiesExpanded; - private bool fieldsExpanded; - private readonly string className; - private JArray fields; - public List InlineArray { get; init; } - public DotnetObjectId Id { get; init; } - public byte[] Buffer { get; init; } - public int TypeId { get; init; } - public bool IsEnum { get; init; } - - public ValueTypeClass(byte[] buffer, string className, JArray fields, int typeId, bool isEnum, List inlineArray = null) - { - var valueTypeId = MonoSDBHelper.GetNewObjectId(); - var objectId = new DotnetObjectId("valuetype", valueTypeId); - - Buffer = buffer; - this.fields = fields; - this.className = className; - TypeId = typeId; - autoExpand = ShouldAutoExpand(className); - Id = objectId; - IsEnum = isEnum; - InlineArray = inlineArray; - } - - public override string ToString() => $"{{ ValueTypeClass: typeId: {TypeId}, Id: {Id}, Id: {Id}, fields: {fields} }}"; - - public static async Task CreateFromReader( - MonoSDBHelper sdbAgent, - MonoBinaryReader cmdReader, - long initialPos, - string className, - int typeId, - bool isEnum, - bool includeStatic, - int inlineArraySize, - CancellationToken token) - { - var typeInfo = await sdbAgent.GetTypeInfo(typeId, token); - var typeFieldsBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableFields; - var typePropertiesBrowsableInfo = typeInfo?.Info?.DebuggerBrowsableProperties; - - IReadOnlyList fieldTypes = await sdbAgent.GetTypeFields(typeId, token); - - JArray fields = new(); - List inlineArray = null; - JObject lastWritableFieldValue = null; - if (includeStatic) - { - IEnumerable staticFields = - fieldTypes.Where(f => f.Attributes.HasFlag(FieldAttributes.Static)); - foreach (var field in staticFields) - { - var fieldValue = await sdbAgent.GetFieldValue(typeId, field.Id, token); - fields.Add(GetFieldWithMetadata(field, fieldValue, isStatic: true)); - } - } - - IEnumerable writableFields = fieldTypes - .Where(f => !f.Attributes.HasFlag(FieldAttributes.Literal) - && !f.Attributes.HasFlag(FieldAttributes.Static)); - foreach (var field in writableFields) - { - //check if it's fixed size array and behave as a inline array - ElementType etype = (ElementType)cmdReader.ReadByte(); - if (etype == (ElementType)ValueTypeId.FixedArray && writableFields.Count() == 1) - { - ElementType elementType = (ElementType)cmdReader.ReadByte(); - var arraySize = cmdReader.ReadInt32(); - inlineArray = new(arraySize + 1); - for (int i = 0; i < arraySize; i++) - { - inlineArray.Add(await sdbAgent.ValueCreator.CreateFixedArrayElement(cmdReader, elementType, $"{i}", token)); - } - } - else - { - cmdReader.BaseStream.Position-=sizeof(byte); - lastWritableFieldValue = await sdbAgent.ValueCreator.ReadAsVariableValue(cmdReader, field.Name, token, isOwn: true, field.TypeId, forDebuggerDisplayAttribute: false); - fields.Add(GetFieldWithMetadata(field, lastWritableFieldValue, isStatic: false)); - } - } - if (inlineArraySize > 0) - { - inlineArray = new(inlineArraySize+1); - inlineArray.Add(lastWritableFieldValue); - var firstFieldtypeId = writableFields.First().TypeId; - for (int i = 1; i < inlineArraySize; i++) - { - //the valuetype has a single instance field in inline-arrays - var inlineArrayItem = await sdbAgent.ValueCreator.ReadAsVariableValue(cmdReader, $"{i}", token, isOwn: true, firstFieldtypeId, forDebuggerDisplayAttribute: false); - inlineArray.Add(inlineArrayItem); - } - } - long endPos = cmdReader.BaseStream.Position; - cmdReader.BaseStream.Position = initialPos; - byte[] valueTypeBuffer = new byte[endPos - initialPos]; - cmdReader.Read(valueTypeBuffer, 0, (int)(endPos - initialPos)); - cmdReader.BaseStream.Position = endPos; - - return new ValueTypeClass(valueTypeBuffer, className, fields, typeId, isEnum, inlineArray); - - JObject GetFieldWithMetadata(FieldTypeClass field, JObject fieldValue, bool isStatic) - { - // GetFieldValue returns JObject without name and we need this information - if (isStatic) - fieldValue["name"] = field.Name; - FieldAttributes attr = field.Attributes & FieldAttributes.FieldAccessMask; - fieldValue[InternalUseFieldName.Section.Name] = attr == FieldAttributes.Private ? "private" : "result"; - - if (field.IsBackingField) - { - fieldValue[InternalUseFieldName.IsBackingField.Name] = true; - return fieldValue; - } - typeFieldsBrowsableInfo.TryGetValue(field.Name, out DebuggerBrowsableState? state); - fieldValue[InternalUseFieldName.State.Name] = state?.ToString(); - return fieldValue; - } - } - - public async Task ToJObject(MonoSDBHelper sdbAgent, bool forDebuggerDisplayAttribute, CancellationToken token) - { - string description = className; - if (ShouldAutoInvokeToString(className) || IsEnum) - { - var toString = await sdbAgent.InvokeToStringAsync(new int[]{ TypeId }, isValueType: true, IsEnum, Id.Value, IsEnum ? BindingFlags.Default : BindingFlags.DeclaredOnly, invokeToStringInObject: false, token); - if (toString == null) - sdbAgent.logger.LogDebug($"Error while evaluating ToString method on typeId = {TypeId}"); - else - description = toString; - if (className.Equals("System.Guid")) - description = description.ToUpperInvariant(); //to keep the old behavior - } - else if (!forDebuggerDisplayAttribute) - { - string displayString = await sdbAgent.GetValueFromDebuggerDisplayAttribute(Id, TypeId, token); - if (displayString != null) - { - description = displayString; - } - else - { - var toString = await sdbAgent.InvokeToStringAsync(new int[]{ TypeId }, isValueType: true, IsEnum, Id.Value, IsEnum ? BindingFlags.Default : BindingFlags.DeclaredOnly, invokeToStringInObject: false, token); - if (toString != null) - description = toString; - } - } - return JObjectValueCreator.Create( - IsEnum ? fields[0]["value"] : null, - "object", - description, - className, - Id.ToString(), - isValueType: true, - isEnum: IsEnum); - } - - public async Task GetProxy(MonoSDBHelper sdbHelper, CancellationToken token) - { - if (proxy != null) - return proxy; - - var retDebuggerCmdReader = await sdbHelper.GetTypePropertiesReader(TypeId, token); - if (retDebuggerCmdReader == null) - return null; - - if (!fieldsExpanded) - { - await ExpandedFieldValues(sdbHelper, includeStatic: false, token); - fieldsExpanded = true; - } - proxy = new JArray(fields); - - var nProperties = retDebuggerCmdReader.ReadInt32(); - - for (int i = 0; i < nProperties; i++) - { - retDebuggerCmdReader.ReadInt32(); //propertyId - string propertyNameStr = retDebuggerCmdReader.ReadString(); - - var getMethodId = retDebuggerCmdReader.ReadInt32(); - retDebuggerCmdReader.ReadInt32(); //setmethod - retDebuggerCmdReader.ReadInt32(); //attrs - if (await sdbHelper.MethodIsStatic(getMethodId, token)) - continue; - using var command_params_writer_to_proxy = new MonoBinaryWriter(); - command_params_writer_to_proxy.Write(getMethodId); - command_params_writer_to_proxy.Write(Buffer); - command_params_writer_to_proxy.Write(0); - - var (data, length) = command_params_writer_to_proxy.ToBase64(); - proxy.Add(JObject.FromObject(new - { - get = JObject.FromObject(new - { - commandSet = CommandSet.Vm, - command = CmdVM.InvokeMethod, - buffer = data, - length = length, - id = MonoSDBHelper.GetNewId() - }), - name = propertyNameStr - })); - } - return proxy; - } - - public async Task GetMemberValues( - MonoSDBHelper sdbHelper, GetObjectCommandOptions getObjectOptions, bool sortByAccessLevel, bool includeStatic, CancellationToken token) - { - if (getObjectOptions.HasFlag(GetObjectCommandOptions.AutoExpandable) && !getObjectOptions.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) - autoExpand = true; - // 1 - if (!propertiesExpanded) - { - await ExpandPropertyValues(sdbHelper, sortByAccessLevel, includeStatic, token); - propertiesExpanded = true; - } - - // 2 - GetMembersResult result = null; - if (!getObjectOptions.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute)) - { - // FIXME: cache? - result = await sdbHelper.GetValuesFromDebuggerProxyAttributeForValueTypes(Id.Value, TypeId, token); - } - - if (result == null && getObjectOptions.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) - { - // 3 - just properties, skip fields - result = _combinedResult.Clone(); - RemovePropertiesFrom(result.Result); - RemovePropertiesFrom(result.PrivateMembers); - } - - // 4 - fields + properties - result ??= _combinedResult.Clone(); - - return result; - - static void RemovePropertiesFrom(JArray collection) - { - List toRemove = new(); - foreach (JToken jt in collection) - { - if (jt is not JObject obj || obj["get"] != null) - continue; - toRemove.Add(jt); - } - foreach (var jt in toRemove) - { - collection.Remove(jt); - } - } - } - - public async Task ExpandedFieldValues(MonoSDBHelper sdbHelper, bool includeStatic, CancellationToken token) - { - JArray visibleFields = new(); - foreach (JObject field in fields) - { - if (!Enum.TryParse(field[InternalUseFieldName.State.Name]?.Value(), out DebuggerBrowsableState state)) - { - visibleFields.Add(field); - continue; - } - var fieldValue = field["value"] ?? field["get"]; - string typeName = fieldValue?["className"]?.Value(); - JArray fieldMembers = await MemberObjectsExplorer.GetExpandedMemberValues( - sdbHelper, typeName, field["name"]?.Value(), field, state, includeStatic, token); - visibleFields.AddRange(fieldMembers); - } - fields = visibleFields; - } - - public async Task ExpandPropertyValues(MonoSDBHelper sdbHelper, bool splitMembersByAccessLevel, bool includeStatic, CancellationToken token) - { - using var commandParamsWriter = new MonoBinaryWriter(); - commandParamsWriter.Write(TypeId); - using MonoBinaryReader getParentsReader = await sdbHelper.SendDebuggerAgentCommand(CmdType.GetParents, commandParamsWriter, token); - int numParents = getParentsReader.ReadInt32(); - - if (!fieldsExpanded) - { - await ExpandedFieldValues(sdbHelper, includeStatic, token); - fieldsExpanded = true; - } - - var allMembers = new Dictionary(); - foreach (var f in fields) - allMembers[f["name"].Value()] = f as JObject; - - int typeId = TypeId; - var parentsCntPlusSelf = numParents + 1; - for (int i = 0; i < parentsCntPlusSelf; i++) - { - // isParent: - if (i != 0) typeId = getParentsReader.ReadInt32(); - - allMembers = await MemberObjectsExplorer.ExpandPropertyValues( - sdbHelper, - typeId, - className, - Buffer, - autoExpand ? GetObjectCommandOptions.AutoExpandable : GetObjectCommandOptions.None, - Id, - isValueType: true, - isOwn: i == 0, - token, - allMembers, - includeStatic); - } - _combinedResult = GetMembersResult.FromValues(allMembers.Values, splitMembersByAccessLevel); - } - - private static bool ShouldAutoExpand(string className) - => className is "System.DateTime" or - "System.DateTimeOffset" or - "System.TimeSpan"; - - private static bool ShouldAutoInvokeToString(string className) - => className is "System.DateTime" or - "System.DateTimeOffset" or - "System.TimeSpan" or - "System.Decimal" or - "System.Guid"; - } -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/WasmHost.cs b/src/mono/browser/debugger/BrowserDebugProxy/WasmHost.cs deleted file mode 100644 index 1b487c6b5683d3..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/WasmHost.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.WebAssembly.Diagnostics; - -public enum WasmHost -{ - Chrome, - Firefox -} diff --git a/src/mono/browser/debugger/BrowserDebugProxy/WebcilDebugMetadataProvider.cs b/src/mono/browser/debugger/BrowserDebugProxy/WebcilDebugMetadataProvider.cs deleted file mode 100644 index 993bbb78957398..00000000000000 --- a/src/mono/browser/debugger/BrowserDebugProxy/WebcilDebugMetadataProvider.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable enable - -using System; -using System.Collections.Immutable; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; -using Microsoft.NET.WebAssembly.Webcil; - -namespace Microsoft.WebAssembly.Diagnostics; - -public class WebcilDebugMetadataProvider : IDebugMetadataProvider -{ - private readonly WebcilReader _webcilReader; - - public WebcilDebugMetadataProvider(WebcilReader webcilReader) - { - _webcilReader = webcilReader; - } - public ImmutableArray ReadDebugDirectory() => _webcilReader.ReadDebugDirectory(); - - public CodeViewDebugDirectoryData ReadCodeViewDebugDirectoryData(DebugDirectoryEntry entry) => _webcilReader.ReadCodeViewDebugDirectoryData(entry); - - public PdbChecksumDebugDirectoryData ReadPdbChecksumDebugDirectoryData(DebugDirectoryEntry entry) => _webcilReader.ReadPdbChecksumDebugDirectoryData(entry); - - public MetadataReaderProvider ReadEmbeddedPortablePdbDebugDirectoryData(DebugDirectoryEntry entry) => _webcilReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry); -} diff --git a/src/mono/browser/debugger/DetectRuntimeConfiguration.props b/src/mono/browser/debugger/DetectRuntimeConfiguration.props deleted file mode 100644 index 0e9e73bde04e19..00000000000000 --- a/src/mono/browser/debugger/DetectRuntimeConfiguration.props +++ /dev/null @@ -1,22 +0,0 @@ - - - <_TopDir>$([MSBuild]::NormalizeDirectory($(MSBuildThisFileDirectory), '..', '..', '..', '..')) - <_DotnetJsRelativePath>$([System.IO.Path]::Combine('runtimes', 'browser-wasm', 'native', 'dotnet.js')) - <_ReleaseConfigDetected Condition="Exists('$(_TopDir)artifacts/bin/microsoft.netcore.app.runtime.browser-wasm/Release/runtimes/browser-wasm/native/dotnet.js')">true - <_DebugConfigDetected Condition="Exists('$(_TopDir)artifacts/bin/microsoft.netcore.app.runtime.browser-wasm/Debug/runtimes/browser-wasm/native/dotnet.js')">true - - <_OriginalRuntimeConfiguration>$(RuntimeConfiguration) - Release - Debug - - Release - - - - - - - - diff --git a/src/mono/browser/debugger/Directory.Build.props b/src/mono/browser/debugger/Directory.Build.props deleted file mode 100644 index c13ea1ed0a66b0..00000000000000 --- a/src/mono/browser/debugger/Directory.Build.props +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/mono/browser/debugger/README.md b/src/mono/browser/debugger/README.md deleted file mode 100644 index 938e8c037bcf37..00000000000000 --- a/src/mono/browser/debugger/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Wasm debugger - -## Debug proxy - -- Protocol messages are truncated when logged, to 64k, by default. But this can be changed by setting `WASM_DONT_TRUNCATE_LOG_MESSAGES=1`. - -## Projects - -- `DebuggerTestSuite` - project with all the tests, and the test harness -- `Wasm.Debugger.Tests` - a wrapper project to fit in the global build diff --git a/src/mono/browser/debugger/debugger.md b/src/mono/browser/debugger/debugger.md deleted file mode 100644 index 65c6f7f996a07d..00000000000000 --- a/src/mono/browser/debugger/debugger.md +++ /dev/null @@ -1,25 +0,0 @@ -# Mono Wasm Debugger - -## Native debugging - -It's possible to debug native code and managed code using the `BrowserDebugProxy` project. - -Steps: -- Install [C/C++ DevTools Support (DWARF) extension](https://goo.gle/wasm-debugging-extension) on chrome. -- Enable DWARF support: Open DevTools, click on the settings, click on experiments and enable WebAssembly Debugging: Enable DWARF support.
-![image](https://user-images.githubusercontent.com/4503299/170745664-fc7d185c-469c-4443-9c57-545bd79588b8.png) -- Start the WebAssembly App Without Debugging from VS, or `dotnet run` on command line. -- Run chrome using this startup parameter: `--remote-debugging-port=9222` -- Go to your Blazor App Page -- Press Ctrl-Alt-D (windows) it will open the debugger page -- It will show something like this in the Address Bar: ``http://localhost:9222/devtools/inspector.html?ws=127.0.0.1:9300/devtools/page/97FCDA5A332CA3B72031790B26A264EF`` -- Open another tab and go to ``chrome://inspect``
-![image](https://user-images.githubusercontent.com/4503299/170746026-8921892b-b936-458d-84f2-8a49b76755d4.png) -- Click on configure and add the port that was showed in the Address Bar: ``127.0.0.1:9300``
-![image](https://user-images.githubusercontent.com/4503299/170746126-b2edd688-dcc5-4b67-9162-465782646363.png) -- After some seconds the tabs available to debug will appear
-![image](https://user-images.githubusercontent.com/4503299/170746234-456ac8e9-180d-4173-a2fa-93cb8293514a.png) -- Choose the WebAssembly Page and click on Inspect
-![image](https://user-images.githubusercontent.com/4503299/170746341-809f8876-3f46-4c5c-b2b3-6f92af8beaa1.png) -- Open the Sources tab and click on ``file://`` -- You will see c# files and c files available to add breakpoints. diff --git a/src/mono/browser/debugger/debugger.slnx b/src/mono/browser/debugger/debugger.slnx deleted file mode 100644 index 8dcb75d9305c8b..00000000000000 --- a/src/mono/browser/debugger/debugger.slnx +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/mono/browser/runtime/rollup.config.js b/src/mono/browser/runtime/rollup.config.js index 60cd58d58302e7..295318ee13e9eb 100644 --- a/src/mono/browser/runtime/rollup.config.js +++ b/src/mono/browser/runtime/rollup.config.js @@ -22,7 +22,7 @@ const wasmEnableThreads = process.env.WasmEnableThreads === "true" ? true : fals const wasmEnableSIMD = process.env.WASM_ENABLE_SIMD === "1" ? true : false; const wasmEnableExceptionHandling = process.env.WASM_ENABLE_EH === "1" ? true : false; const wasmEnableJsInteropByValue = process.env.ENABLE_JS_INTEROP_BY_VALUE == "1" ? true : false; -// because of stack walk at src/mono/browser/debugger/BrowserDebugProxy/MonoProxy.cs +// keep the debugger agent message function names for the debugger agent stack walk // and unit test at with timers.mjs const keep_fnames = /(mono_wasm_runtime_ready|mono_wasm_fire_debugger_agent_message_with_data|mono_wasm_fire_debugger_agent_message_with_data_to_pause|mono_wasm_schedule_timer_tick)/; const keep_classnames = /(ManagedObject|ManagedError|Span|ArraySegment)/; diff --git a/src/mono/nuget/Microsoft.NETCore.BrowserDebugHost.Transport/Microsoft.NETCore.BrowserDebugHost.Transport.pkgproj b/src/mono/nuget/Microsoft.NETCore.BrowserDebugHost.Transport/Microsoft.NETCore.BrowserDebugHost.Transport.pkgproj deleted file mode 100644 index 15337e230f9bb4..00000000000000 --- a/src/mono/nuget/Microsoft.NETCore.BrowserDebugHost.Transport/Microsoft.NETCore.BrowserDebugHost.Transport.pkgproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - - false - Internal package for sharing BrowserDebugHost. - - - - - - - - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\BrowserDebugHost.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\BrowserDebugHost.runtimeconfig.json" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\BrowserDebugProxy.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.NET.WebAssembly.Webcil.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.CSharp.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Newtonsoft.Json.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.CSharp.Scripting.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.Scripting.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.SymbolStore.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.FileFormats.dll" /> - - - - - - diff --git a/src/mono/nuget/mono-packages.proj b/src/mono/nuget/mono-packages.proj index 9c8dbbf3d74486..6bbe970afdf4da 100644 --- a/src/mono/nuget/mono-packages.proj +++ b/src/mono/nuget/mono-packages.proj @@ -5,7 +5,6 @@ - @@ -18,7 +17,6 @@ - diff --git a/src/mono/wasi/Makefile b/src/mono/wasi/Makefile index fb55697fc71760..b39710b1aaf261 100644 --- a/src/mono/wasi/Makefile +++ b/src/mono/wasi/Makefile @@ -73,7 +73,5 @@ submit-tests-helix: $(_MSBUILD_WASM_BUILD_ARGS) \ $(MSBUILD_ARGS) -build-dbg-proxy: - $(DOTNET) build $(TOP)/src/mono/browser/debugger/wasiDebugHost $(MSBUILD_ARGS) build-app-host: $(DOTNET) build $(TOP)/src/mono/wasm/host $(_MSBUILD_WASM_BUILD_ARGS) $(MSBUILD_ARGS) diff --git a/src/mono/wasi/README.md b/src/mono/wasi/README.md index 45f1d2fb5ee981..d6862f44f00426 100644 --- a/src/mono/wasi/README.md +++ b/src/mono/wasi/README.md @@ -108,4 +108,4 @@ Finally, you can build and run the sample: ### 4. Debug it -For detailed WASI debugging instructions, see the [WebAssembly Debugging Reference](../../../docs/workflow/debugging/mono/wasm-debugging.md#for-wasi-applications). +For detailed WASI debugging instructions, see the [WebAssembly Debugging Reference](../../../docs/workflow/debugging/mono/wasm-debugging.md). diff --git a/src/mono/wasm/features.md b/src/mono/wasm/features.md index a1f9c7af433a64..d14c761173f56e 100644 --- a/src/mono/wasm/features.md +++ b/src/mono/wasm/features.md @@ -356,7 +356,7 @@ You can add following elements in your .csproj ``` See also DWARF [WASM debugging](https://developer.chrome.com/blog/wasm-debugging-2020/) in Chrome. -For more details see also [debugger.md](../browser/debugger/debugger.md) and [wasm-debugging.md](../../../docs/workflow/debugging/mono/wasm-debugging.md) +For more details see also [wasm-debugging.md](../../../docs/workflow/debugging/mono/wasm-debugging.md) ### Mono runtime logging and tracing diff --git a/src/mono/wasm/host/BrowserHost.cs b/src/mono/wasm/host/BrowserHost.cs index df465070045321..0bf3d0e61a20b3 100644 --- a/src/mono/wasm/host/BrowserHost.cs +++ b/src/mono/wasm/host/BrowserHost.cs @@ -16,7 +16,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.WebAssembly.AppHost.DevServer; -using Microsoft.WebAssembly.Diagnostics; #nullable enable @@ -34,27 +33,20 @@ public BrowserHost(BrowserArguments args, ILogger logger) } public static async Task InvokeAsync(CommonConfiguration commonArgs, - ILoggerFactory loggerFactory, + ILoggerFactory _, ILogger logger, CancellationToken token) { var args = new BrowserArguments(commonArgs); args.Validate(); var host = new BrowserHost(args, logger); - await host.RunAsync(loggerFactory, token); + await host.RunAsync(token); return 0; } - private async Task RunAsync(ILoggerFactory loggerFactory, CancellationToken token) + private async Task RunAsync(CancellationToken token) { - if (_args.CommonConfig.Debugging && !_args.CommonConfig.UseStaticWebAssets) - { - ProxyOptions options = _args.CommonConfig.ToProxyOptions(); - _ = Task.Run(() => DebugProxyHost.RunDebugProxyAsync(options, Array.Empty(), loggerFactory, token), token) - .ConfigureAwait(false); - } - Dictionary envVars = new(); if (_args.CommonConfig.HostProperties.EnvironmentVariables is not null) { diff --git a/src/mono/wasm/host/CommonConfiguration.cs b/src/mono/wasm/host/CommonConfiguration.cs index 2dd050ace4ec33..5d936eb175f1d4 100644 --- a/src/mono/wasm/host/CommonConfiguration.cs +++ b/src/mono/wasm/host/CommonConfiguration.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.IO; using System.Text.Json; -using Microsoft.WebAssembly.Diagnostics; namespace Microsoft.WebAssembly.AppHost; @@ -42,7 +41,7 @@ private CommonConfiguration(string[] args) List hostArgsList = new(); var options = new OptionSet { - { "debug|d", "Start debug server", _ => Debugging = true }, + { "debug|d", "Enable runtime debugging", _ => Debugging = true }, { "host|h=", "Host config name", v => hostArg = v }, { "runtime-config|r=", "runtimeconfig.json path for the app", v => RuntimeConfigPath = v }, { "extra-host-arg=", "Extra argument to be passed to the host", hostArgsList.Add }, @@ -109,21 +108,6 @@ private CommonConfiguration(string[] args) HostArguments = hostArgsList; } - public ProxyOptions ToProxyOptions() - { - ProxyOptions options = new(); - if (HostProperties.ChromeProxyPort is not null) - options.DevToolsProxyPort = HostProperties.ChromeProxyPort.Value; - if (HostProperties.ChromeDebuggingPort is not null) - options.DevToolsDebugPort = HostProperties.ChromeDebuggingPort.Value; - if (HostProperties.FirefoxProxyPort is not null) - options.FirefoxProxyPort = HostProperties.FirefoxProxyPort.Value; - if (HostProperties.FirefoxDebuggingPort is not null) - options.FirefoxDebugPort = HostProperties.FirefoxDebuggingPort.Value; - options.LogPath = "."; - return options; - } - public static void CheckPathOrInAppPath(string appPath, string? path, string argName) { if (string.IsNullOrEmpty(path)) diff --git a/src/mono/wasm/host/DevServer/DebugProxyLauncher.cs b/src/mono/wasm/host/DevServer/DebugProxyLauncher.cs deleted file mode 100644 index 647ea293991687..00000000000000 --- a/src/mono/wasm/host/DevServer/DebugProxyLauncher.cs +++ /dev/null @@ -1,216 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; - -namespace Microsoft.WebAssembly.AppHost.DevServer; - -internal static class DebugProxyLauncher -{ - private static readonly object LaunchLock = new object(); - private static readonly TimeSpan DebugProxyLaunchTimeout = TimeSpan.FromSeconds(10); - private static Task? LaunchedDebugProxyUrl; - private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: (?.*)$", RegexOptions.None, TimeSpan.FromSeconds(10)); - private static readonly Regex ApplicationStartedRegex = new Regex(@"^\s*Application started\. Press Ctrl\+C to shut down\.$", RegexOptions.None, TimeSpan.FromSeconds(10)); - private static readonly Regex NowListeningFirefoxRegex = new Regex(@"^\s*Debug proxy for firefox now listening on tcp://(?.*)\. And expecting firefox at port 6000\.$", RegexOptions.None, TimeSpan.FromSeconds(10)); - private static readonly string[] MessageSuppressionPrefixes = new[] - { - "Hosting environment:", - "Content root path:", - "Now listening on:", - "Application started. Press Ctrl+C to shut down.", - "Debug proxy for firefox now", - }; - - public static Task EnsureLaunchedAndGetUrl(IServiceProvider serviceProvider, string devToolsHost, bool isFirefox) - { - lock (LaunchLock) - { - LaunchedDebugProxyUrl ??= LaunchAndGetUrl(serviceProvider, devToolsHost, isFirefox); - - return LaunchedDebugProxyUrl; - } - } - - private static async Task LaunchAndGetUrl(IServiceProvider serviceProvider, string devToolsHost, bool isFirefox) - { - var tcs = new TaskCompletionSource(); - - var environment = serviceProvider.GetRequiredService(); - var executablePath = LocateDebugProxyExecutable(environment); - var ownerPid = Environment.ProcessId; - - var processStartInfo = new ProcessStartInfo - { - FileName = "dotnet" + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""), - Arguments = $"exec \"{executablePath}\" --OwnerPid {ownerPid} --DevToolsUrl {devToolsHost} --IsFirefoxDebugging {isFirefox} --FirefoxProxyPort 6001", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - }; - RemoveUnwantedEnvironmentVariables(processStartInfo.Environment); - - var debugProxyProcess = Process.Start(processStartInfo); - if (debugProxyProcess is null) - { - tcs.TrySetException(new InvalidOperationException("Unable to start debug proxy process.")); - } - else - { - PassThroughConsoleOutput(debugProxyProcess); - CompleteTaskWhenServerIsReady(debugProxyProcess, isFirefox, tcs); - - new CancellationTokenSource(DebugProxyLaunchTimeout).Token.Register(() => - { - tcs.TrySetException(new TimeoutException($"Failed to start the debug proxy within the timeout period of {DebugProxyLaunchTimeout.TotalSeconds} seconds.")); - }); - } - - return await tcs.Task; - } - - private static void RemoveUnwantedEnvironmentVariables(IDictionary environment) - { - // Generally we expect to pass through most environment variables, since dotnet might - // need them for arbitrary reasons to function correctly. However, we specifically don't - // want to pass through any ASP.NET Core hosting related ones, since the child process - // shouldn't be trying to use the same port numbers, etc. In particular we need to break - // the association with IISExpress and the MS-ASPNETCORE-TOKEN check. - // For more context on this, see https://github.com/dotnet/aspnetcore/issues/20308. - var keysToRemove = environment.Keys.Where(key => key.StartsWith("ASPNETCORE_", StringComparison.Ordinal)).ToList(); - foreach (var key in keysToRemove) - { - environment.Remove(key); - } - } - - [UnconditionalSuppressMessage("SingleFile", "IL3000:Avoid accessing Assembly file path when publishing as a single file", Justification = "Not published as a single file")] - private static string LocateDebugProxyExecutable(IWebHostEnvironment environment) - { - if (string.IsNullOrEmpty(environment.ApplicationName)) - { - throw new InvalidOperationException("IWebHostEnvironment.ApplicationName is required to be set in order to start the debug proxy."); - } - var assembly = Assembly.Load(environment.ApplicationName); - var debugProxyPath = Path.Combine( - Path.GetDirectoryName(assembly.Location)!, - "BrowserDebugHost.dll" - ); - - if (!File.Exists(debugProxyPath)) - { - throw new FileNotFoundException( - $"Cannot start debug proxy because it cannot be found at '{debugProxyPath}'"); - } - - return debugProxyPath; - } - - private static void PassThroughConsoleOutput(Process process) - { - process.OutputDataReceived += (sender, eventArgs) => - { - // It's confusing if the debug proxy emits its own startup status messages, because the developer - // may think the ports/environment/paths refer to their actual application. So we want to suppress - // them, but we can't stop the debug proxy app from emitting the messages entirely (e.g., via - // SuppressStatusMessages) because we need the "Now listening on" one to detect the chosen port. - // Instead, we'll filter out known strings from the passthrough logic. It's legit to hardcode these - // strings because they are also hardcoded like this inside WebHostExtensions.cs and can't vary - // according to culture. - if (eventArgs.Data is not null) - { - foreach (var prefix in MessageSuppressionPrefixes) - { - if (eventArgs.Data.StartsWith(prefix, StringComparison.Ordinal)) - { - return; - } - } - } - - Console.WriteLine(eventArgs.Data); - }; - } - - private static void CompleteTaskWhenServerIsReady(Process aspNetProcess, bool isFirefox, TaskCompletionSource taskCompletionSource) - { - string? capturedUrl = null; - var errorEncountered = false; - - aspNetProcess.ErrorDataReceived += OnErrorDataReceived; - aspNetProcess.BeginErrorReadLine(); - - aspNetProcess.OutputDataReceived += OnOutputDataReceived; - aspNetProcess.BeginOutputReadLine(); - - void OnErrorDataReceived(object sender, DataReceivedEventArgs eventArgs) - { - if (!string.IsNullOrEmpty(eventArgs.Data)) - { - taskCompletionSource.TrySetException(new InvalidOperationException( - eventArgs.Data)); - errorEncountered = true; - } - } - - void OnOutputDataReceived(object sender, DataReceivedEventArgs eventArgs) - { - if (string.IsNullOrEmpty(eventArgs.Data)) - { - if (!errorEncountered) - { - taskCompletionSource.TrySetException(new InvalidOperationException( - "Expected output has not been received from the application.")); - } - return; - } - - if (ApplicationStartedRegex.IsMatch(eventArgs.Data) && !isFirefox) - { - aspNetProcess.OutputDataReceived -= OnOutputDataReceived; - aspNetProcess.ErrorDataReceived -= OnErrorDataReceived; - if (!string.IsNullOrEmpty(capturedUrl)) - { - taskCompletionSource.TrySetResult(capturedUrl); - } - else - { - taskCompletionSource.TrySetException(new InvalidOperationException( - "The application started listening without first advertising a URL")); - } - } - else - { - var matchFirefox = NowListeningFirefoxRegex.Match(eventArgs.Data); - if (matchFirefox.Success && isFirefox) - { - aspNetProcess.OutputDataReceived -= OnOutputDataReceived; - aspNetProcess.ErrorDataReceived -= OnErrorDataReceived; - capturedUrl = matchFirefox.Groups["url"].Value; - taskCompletionSource.TrySetResult(capturedUrl); - return; - } - var match = NowListeningRegex.Match(eventArgs.Data); - if (match.Success) - { - capturedUrl = match.Groups["url"].Value; - capturedUrl = capturedUrl.Replace("http://", "ws://"); - capturedUrl = capturedUrl.Replace("https://", "wss://"); - } - } - } - } -} diff --git a/src/mono/wasm/host/DevServer/DevServerStartup.cs b/src/mono/wasm/host/DevServer/DevServerStartup.cs index c7b86334f67af4..496e5dc8687f66 100644 --- a/src/mono/wasm/host/DevServer/DevServerStartup.cs +++ b/src/mono/wasm/host/DevServer/DevServerStartup.cs @@ -38,8 +38,6 @@ public static void Configure(IApplicationBuilder app, IOptions app.UseDeveloperExceptionPage(); EnableConfiguredPathbase(app, configuration); - app.UseWebAssemblyDebugging(); - DevServerOptions options = optionsContainer.Value; if (options.WebServerUseCrossOriginPolicy) diff --git a/src/mono/wasm/host/DevServer/WebAssemblyNetDebugProxyAppBuilderExtensions.cs b/src/mono/wasm/host/DevServer/WebAssemblyNetDebugProxyAppBuilderExtensions.cs deleted file mode 100644 index c15f8948b2e353..00000000000000 --- a/src/mono/wasm/host/DevServer/WebAssemblyNetDebugProxyAppBuilderExtensions.cs +++ /dev/null @@ -1,522 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Dynamic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Sockets; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; -using System.Web; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Extensions; - -namespace Microsoft.WebAssembly.AppHost.DevServer; - -internal static class WebAssemblyNetDebugProxyAppBuilderExtensions -{ - /// - /// Adds middleware needed for debugging Blazor WebAssembly applications - /// inside Chromium dev tools. - /// - public static void UseWebAssemblyDebugging(this IApplicationBuilder app) - { - app.Map("/_framework/debug", app => - { - app.Run(async (context) => - { - var queryParams = HttpUtility.ParseQueryString(context.Request.QueryString.Value!); - var browserParam = queryParams.Get("browser"); - Uri? browserUrl = null; - var devToolsHost = "http://localhost:9222"; - if (browserParam != null) - { - browserUrl = new Uri(browserParam); - devToolsHost = $"http://{browserUrl.Host}:{browserUrl.Port}"; - } - var isFirefox = string.IsNullOrEmpty(queryParams.Get("isFirefox")) ? false : true; - if (isFirefox) - { - devToolsHost = "localhost:6000"; - } - var debugProxyBaseUrl = await DebugProxyLauncher.EnsureLaunchedAndGetUrl(context.RequestServices, devToolsHost, isFirefox); - var requestPath = context.Request.Path.ToString(); - if (requestPath == string.Empty) - { - requestPath = "/"; - } - - switch (requestPath) - { - case "/": - var targetPickerUi = new TargetPickerUi(debugProxyBaseUrl, devToolsHost); - if (isFirefox) - { - await targetPickerUi.DisplayFirefox(context); - } - else - { - await targetPickerUi.Display(context); - } - break; - case "/ws-proxy": - context.Response.Redirect($"{debugProxyBaseUrl}{browserUrl!.PathAndQuery}"); - break; - default: - context.Response.StatusCode = (int)HttpStatusCode.NotFound; - break; - } - }); - }); - } -} - -internal sealed class TargetPickerUi -{ - private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = true, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - }; - - private readonly string _browserHost; - private readonly string _debugProxyUrl; - - /// - /// Initialize a new instance of . - /// - /// The debug proxy url. - /// The dev tools host. - public TargetPickerUi([StringSyntax(StringSyntaxAttribute.Uri)] string debugProxyUrl, string devToolsHost) - { - _debugProxyUrl = debugProxyUrl; - _browserHost = devToolsHost; - } - - /// - /// Display the ui. - /// - /// The . - /// The . - public async Task DisplayFirefox(HttpContext context) - { - static async Task SendMessageToBrowser(NetworkStream toStream, ExpandoObject args, CancellationToken token) - { - var msg = JsonSerializer.Serialize(args); - var bytes = Encoding.UTF8.GetBytes(msg); - var bytesWithHeader = Encoding.UTF8.GetBytes($"{bytes.Length}:").Concat(bytes).ToArray(); - await toStream.WriteAsync(bytesWithHeader, token).AsTask(); - } -#pragma warning disable CA1835 - static async Task ReceiveMessageLoop(TcpClient browserDebugClientConnect, CancellationToken token) - { - var toStream = browserDebugClientConnect.GetStream(); - var bytesRead = 0; - var _lengthBuffer = new byte[10]; - while (bytesRead == 0 || Convert.ToChar(_lengthBuffer[bytesRead - 1]) != ':') - { - if (!browserDebugClientConnect.Connected) - { - return ""; - } - - if (bytesRead + 1 > _lengthBuffer.Length) - { - throw new IOException($"Protocol error: did not get the expected length preceding a message, " + - $"after reading {bytesRead} bytes. Instead got: {Encoding.UTF8.GetString(_lengthBuffer)}"); - } - - int readLen = await toStream.ReadAsync(_lengthBuffer, bytesRead, 1, token); - bytesRead += readLen; - } - string str = Encoding.UTF8.GetString(_lengthBuffer, 0, bytesRead - 1); - if (!int.TryParse(str, out int messageLen)) - { - return ""; - } - byte[] buffer = new byte[messageLen]; - bytesRead = await toStream.ReadAsync(buffer, 0, messageLen, token); - while (bytesRead != messageLen) - { - if (!browserDebugClientConnect.Connected) - { - return ""; - } - bytesRead += await toStream.ReadAsync(buffer, bytesRead, messageLen - bytesRead, token); - } - var messageReceived = Encoding.UTF8.GetString(buffer, 0, messageLen); - return messageReceived; - } - static async Task EvaluateOnBrowser(NetworkStream toStream, string? to, string text, CancellationToken token) - { - dynamic message = new ExpandoObject(); - dynamic options = new ExpandoObject(); - dynamic awaitObj = new ExpandoObject(); - awaitObj.@await = true; - options.eager = true; - options.mapped = awaitObj; - message.to = to; - message.type = "evaluateJSAsync"; - message.text = text; - message.options = options; - await SendMessageToBrowser(toStream, message, token); - } -#pragma warning restore CA1835 - - context.Response.ContentType = "text/html"; - var request = context.Request; - var targetApplicationUrl = request.Query["url"]; - var browserDebugClientConnect = new TcpClient(); - if (IPEndPoint.TryParse(_debugProxyUrl, out IPEndPoint? endpoint)) - { - try - { - await browserDebugClientConnect.ConnectAsync(endpoint.Address, 6000); - } - catch (Exception) - { - context.Response.StatusCode = 404; - await context.Response.WriteAsync($@"WARNING: -Open about:config: -- enable devtools.debugger.remote-enabled -- enable devtools.chrome.enabled -- disable devtools.debugger.prompt-connection -Open firefox with remote debugging enabled on port 6000: -firefox --start-debugger-server 6000 -new-tab about:debugging"); - return; - } - var source = new CancellationTokenSource(); - var token = source.Token; - var toStream = browserDebugClientConnect.GetStream(); - dynamic messageListTabs = new ExpandoObject(); - messageListTabs.type = "listTabs"; - messageListTabs.to = "root"; - await SendMessageToBrowser(toStream, messageListTabs, token); - var tabToRedirect = -1; - var foundAboutDebugging = false; - string? consoleActorId = null; - string? toCmd = null; - while (browserDebugClientConnect.Connected) - { - var res = System.Text.Json.JsonDocument.Parse(await ReceiveMessageLoop(browserDebugClientConnect, token)).RootElement; - var hasTabs = res.TryGetProperty("tabs", out var tabs); - var hasType = res.TryGetProperty("type", out var type); - if (hasType && type.GetString()?.Equals("tabListChanged", StringComparison.Ordinal) == true) - { - await SendMessageToBrowser(toStream, messageListTabs, token); - } - else - { - if (hasTabs) - { - var tabsList = tabs.Deserialize(); - if (tabsList == null) - { - continue; - } - foreach (var tab in tabsList) - { - var hasUrl = tab.TryGetProperty("url", out var urlInTab); - var hasActor = tab.TryGetProperty("actor", out var actorInTab); - var hasBrowserId = tab.TryGetProperty("browserId", out var browserIdInTab); - if (string.IsNullOrEmpty(consoleActorId)) - { - if (hasUrl && urlInTab.GetString()?.StartsWith("about:debugging#", StringComparison.InvariantCultureIgnoreCase) == true) - { - foundAboutDebugging = true; - - toCmd = hasActor ? actorInTab.GetString() : ""; - if (tabToRedirect != -1) - { - break; - } - } - if (hasUrl && urlInTab.GetString()?.Equals(targetApplicationUrl, StringComparison.Ordinal) == true) - { - tabToRedirect = hasBrowserId ? browserIdInTab.GetInt32() : -1; - if (foundAboutDebugging) - { - break; - } - } - } - else if (hasUrl && urlInTab.GetString()?.StartsWith("about:devtools", StringComparison.InvariantCultureIgnoreCase) == true) - { - return; - } - } - if (!foundAboutDebugging) - { - context.Response.StatusCode = 404; - await context.Response.WriteAsync("WARNING: Open about:debugging tab before pressing Debugging Hotkey"); - return; - } - if (string.IsNullOrEmpty(consoleActorId)) - { - await EvaluateOnBrowser(toStream, consoleActorId, $"if (AboutDebugging.store.getState().runtimes.networkRuntimes.find(element => element.id == \"{_debugProxyUrl}\").runtimeDetails !== null) {{ AboutDebugging.actions.selectPage(\"runtime\", \"{_debugProxyUrl}\"); if (AboutDebugging.store.getState().runtimes.selectedRuntimeId == \"{_debugProxyUrl}\") AboutDebugging.actions.inspectDebugTarget(\"tab\", {tabToRedirect})}};", token); - } - } - } - if (!string.IsNullOrEmpty(consoleActorId)) - { - var hasInput = res.TryGetProperty("input", out var input); - if (hasInput && input.GetString()?.StartsWith("AboutDebugging.actions.addNetworkLocation(", StringComparison.InvariantCultureIgnoreCase) == true) - { - await EvaluateOnBrowser(toStream, consoleActorId, $"if (AboutDebugging.store.getState().runtimes.networkRuntimes.find(element => element.id == \"{_debugProxyUrl}\").runtimeDetails !== null) {{ AboutDebugging.actions.selectPage(\"runtime\", \"{_debugProxyUrl}\"); if (AboutDebugging.store.getState().runtimes.selectedRuntimeId == \"{_debugProxyUrl}\") AboutDebugging.actions.inspectDebugTarget(\"tab\", {tabToRedirect})}};", token); - } - if (hasInput && input.GetString()?.StartsWith("if (AboutDebugging.store.getState()", StringComparison.InvariantCultureIgnoreCase) == true) - { - await EvaluateOnBrowser(toStream, consoleActorId, $"if (AboutDebugging.store.getState().runtimes.networkRuntimes.find(element => element.id == \"{_debugProxyUrl}\").runtimeDetails !== null) {{ AboutDebugging.actions.selectPage(\"runtime\", \"{_debugProxyUrl}\"); if (AboutDebugging.store.getState().runtimes.selectedRuntimeId == \"{_debugProxyUrl}\") AboutDebugging.actions.inspectDebugTarget(\"tab\", {tabToRedirect})}};", token); - } - } - else - { - var hasTarget = res.TryGetProperty("target", out var target); - JsonElement consoleActor = default; - var hasConsoleActor = hasTarget && target.TryGetProperty("consoleActor", out consoleActor); - var hasActor = res.TryGetProperty("actor", out var actor); - if (hasConsoleActor && !string.IsNullOrEmpty(consoleActor.GetString())) - { - consoleActorId = consoleActor.GetString(); - await EvaluateOnBrowser(toStream, consoleActorId, $"AboutDebugging.actions.addNetworkLocation(\"{_debugProxyUrl}\"); AboutDebugging.actions.connectRuntime(\"{_debugProxyUrl}\");", token); - } - else if (hasActor && !string.IsNullOrEmpty(actor.GetString())) - { - dynamic messageWatchTargets = new ExpandoObject(); - messageWatchTargets.type = "watchTargets"; - messageWatchTargets.targetType = "frame"; - messageWatchTargets.to = actor.GetString(); - await SendMessageToBrowser(toStream, messageWatchTargets, token); - dynamic messageWatchResources = new ExpandoObject(); - messageWatchResources.type = "watchResources"; - messageWatchResources.resourceTypes = new string[1] { "console-message" }; - messageWatchResources.to = actor.GetString(); - await SendMessageToBrowser(toStream, messageWatchResources, token); - } - else if (!string.IsNullOrEmpty(toCmd)) - { - dynamic messageGetWatcher = new ExpandoObject(); - messageGetWatcher.type = "getWatcher"; - messageGetWatcher.isServerTargetSwitchingEnabled = true; - messageGetWatcher.to = toCmd; - await SendMessageToBrowser(toStream, messageGetWatcher, token); - } - } - } - - } - return; - } - - /// - /// Display the ui. - /// - /// The . - /// The . - public async Task Display(HttpContext context) - { - context.Response.ContentType = "text/html"; - - var request = context.Request; - var targetApplicationUrl = request.Query["url"]; - - var debuggerTabsListUrl = $"{_browserHost}/json"; - IEnumerable availableTabs; - - try - { - availableTabs = await GetOpenedBrowserTabs(); - } - catch (Exception ex) - { - await context.Response.WriteAsync($@" -

Unable to find debuggable browser tab

-

- Could not get a list of browser tabs from {debuggerTabsListUrl}. - Ensure your browser is running with debugging enabled. -

-

Resolution

-

-

If you are using Google Chrome or Chromium for your development, follow these instructions:

- {GetLaunchChromeInstructions(targetApplicationUrl.ToString())} -

-

-

If you are using Microsoft Edge (80+) for your development, follow these instructions:

- {GetLaunchEdgeInstructions(targetApplicationUrl.ToString())} -

-This should launch a new browser window with debugging enabled..

-

Underlying exception:

-
{ex}
- "); - - return; - } - - var matchingTabs = string.IsNullOrEmpty(targetApplicationUrl) - ? availableTabs.ToList() - : availableTabs.Where(t => t.Url.Equals(targetApplicationUrl, StringComparison.Ordinal)).ToList(); - - if (matchingTabs.Count == 1) - { - // We know uniquely which tab to debug, so just redirect - var devToolsUrlWithProxy = GetDevToolsUrlWithProxy(matchingTabs.Single()); - context.Response.Redirect(devToolsUrlWithProxy); - } - else if (matchingTabs.Count == 0) - { - await context.Response.WriteAsync("

No inspectable pages found

"); - - var suffix = string.IsNullOrEmpty(targetApplicationUrl) - ? string.Empty - : $" matching the URL {WebUtility.HtmlEncode(targetApplicationUrl)}"; - await context.Response.WriteAsync($"

The list of targets returned by {WebUtility.HtmlEncode(debuggerTabsListUrl)} contains no entries{suffix}.

"); - await context.Response.WriteAsync("

Make sure your browser is displaying the target application.

"); - } - else - { - await context.Response.WriteAsync("

Inspectable pages

"); - await context.Response.WriteAsync(@" - - "); - - foreach (var tab in matchingTabs) - { - var devToolsUrlWithProxy = GetDevToolsUrlWithProxy(tab); - await context.Response.WriteAsync( - $"" - + $"

{WebUtility.HtmlEncode(tab.Title)}

{WebUtility.HtmlEncode(tab.Url)}" - + $"
"); - } - } - } - - private string GetDevToolsUrlWithProxy(BrowserTab tabToDebug) - { - var underlyingV8Endpoint = new Uri(tabToDebug.WebSocketDebuggerUrl); - var proxyEndpoint = new Uri(_debugProxyUrl); - var devToolsUrlAbsolute = new Uri(new Uri(_browserHost), relativeUri: NormalizeDevtoolsFrontendUrl(tabToDebug.DevtoolsFrontendUrl)); - var devToolsUrlWithProxy = $"{devToolsUrlAbsolute.Scheme}://{devToolsUrlAbsolute.Authority}{devToolsUrlAbsolute.AbsolutePath}?{underlyingV8Endpoint.Scheme}={proxyEndpoint.Authority}{underlyingV8Endpoint.PathAndQuery}"; - return devToolsUrlWithProxy; - - static string NormalizeDevtoolsFrontendUrl(string devtoolsFrontendUrl) - { - // Currently frontend url can be: - // - absolute (since v135 of chrome and edge) - // chrome example: https://chrome-devtools-frontend.appspot.com/serve_rev/@031848bc6ad02b97854f3d6154d3aefd0434756a/inspector.html?ws=localhost:9222/devtools/page/719FE9D3B43570193235446E0AB36859 - // edge example: https://aka.ms/docs-landing-page/serve_rev/@4e2c41645f24197463afa2ab6aa999352ee8255c/inspector.html?ws=localhost:9222/devtools/page/3A4D56E09776321628432588FC9299F4 - // - relative (managed as fallback for brosers with prior version) - // example: /devtools/inspector.html?ws=localhost:9222/devtools/page/DAB7FB6187B554E10B0BD18821265734 - // The absolute url can't be used as-is because is not valid for debugging and cannot be made relative because of lack "devtools" segment - // before "inspector.html" but we can keep the query string and append to the default "devtools/inspector.html" browser devtools page - - const string DefaultBrowserDevToolsPagePath = "devtools/inspector.html"; - - if (devtoolsFrontendUrl.AsSpan().TrimStart('/').StartsWith(DefaultBrowserDevToolsPagePath)) - { - return devtoolsFrontendUrl; - } - - UriHelper.FromAbsolute(devtoolsFrontendUrl, out _, out _, out _, out var query, out _); - return $"{DefaultBrowserDevToolsPagePath}{query}"; - } - } - - private string GetLaunchChromeInstructions(string targetApplicationUrl) - { - var profilePath = Path.Combine(Path.GetTempPath(), "blazor-chrome-debug"); - var debuggerPort = new Uri(_browserHost).Port; - - if (OperatingSystem.IsWindows()) - { - return $@"

Press Win+R and enter the following:

-

chrome --remote-debugging-port={debuggerPort} --user-data-dir=""{profilePath}"" {targetApplicationUrl}

"; - } - else if (OperatingSystem.IsLinux()) - { - return $@"

In a terminal window execute the following:

-

google-chrome --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {targetApplicationUrl}

"; - } - else if (OperatingSystem.IsMacOS()) - { - return $@"

Execute the following:

-

open -n /Applications/Google\ Chrome.app --args --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {targetApplicationUrl}

"; - } - else - { - throw new InvalidOperationException("Unknown OS platform"); - } - } - - private string GetLaunchEdgeInstructions(string targetApplicationUrl) - { - var profilePath = Path.Combine(Path.GetTempPath(), "blazor-edge-debug"); - var debuggerPort = new Uri(_browserHost).Port; - - if (OperatingSystem.IsWindows()) - { - return $@"

Press Win+R and enter the following:

-

msedge --remote-debugging-port={debuggerPort} --user-data-dir=""{profilePath}"" --no-first-run {targetApplicationUrl}

"; - } - else if (OperatingSystem.IsMacOS()) - { - return $@"

In a terminal window execute the following:

-

open -n /Applications/Microsoft\ Edge.app --args --remote-debugging-port={debuggerPort} --user-data-dir={profilePath} {targetApplicationUrl}

"; - } - else - { - return $@"

Edge is not current supported on your platform

"; - } - } - - private async Task> GetOpenedBrowserTabs() - { - using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; - var jsonResponse = await httpClient.GetStringAsync($"{_browserHost}/json"); - return JsonSerializer.Deserialize(jsonResponse, JsonOptions)!; - } - - private sealed record BrowserTab - ( - string Id, - string Type, - string Url, - string Title, - string DevtoolsFrontendUrl, - string WebSocketDebuggerUrl - ); -} diff --git a/src/mono/wasm/host/README.md b/src/mono/wasm/host/README.md index 6eb8718c66b611..687cbee9191abd 100644 --- a/src/mono/wasm/host/README.md +++ b/src/mono/wasm/host/README.md @@ -4,7 +4,7 @@ WasmAppHost is used when `dotnet run` executes for projects targeting wasm. ## Command line arguments -- **--debug** | **-d**: Whether to start debug server. [More on debugging](../debugger/debugger.md). +- **--debug** | **-d**: Whether to enable runtime debugging. - **--host** | **-h**: A host configuration name. - **--runtime-config** | **-r**: A path for the runtimeconfig.json to use. @@ -15,10 +15,6 @@ The `runtimeconfig.template.json` is a template that used by the .NET runtime to - `webServerPort`: A port number to start HTTP server on, defaults to `9000`. - `perHostConfig`: An array of configuration per host type. - `defaultConfig`: A name of the default per-host configuration. -- `firefoxProxyPort`: A port number where Mono debug proxy for Firefox is listening. -- `firefoxDebuggingPort`: A port number where Firefox is listening for remote debugging. -- `chromeProxyPort`: A port number where Mono debug proxy for Chrome is listening. -- `chromeDebuggingPort`: A port number where Chrome is listening for remote debugging. ## Per host configuration diff --git a/src/mono/wasm/host/RunConfiguration.cs b/src/mono/wasm/host/RunConfiguration.cs index 732c40cc32aace..55132a1e2fdd57 100644 --- a/src/mono/wasm/host/RunConfiguration.cs +++ b/src/mono/wasm/host/RunConfiguration.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Text.Json; -using Microsoft.WebAssembly.Diagnostics; namespace Microsoft.WebAssembly.AppHost; @@ -56,19 +55,4 @@ public RunConfiguration(string runtimeConfigPath, string? hostArg) throw new Exception($"Unknown host {HostConfig.HostString} in config named {HostConfig.Name}"); Host = wasmHost; } - - public ProxyOptions ToProxyOptions() - { - ProxyOptions options = new(); - if (HostProperties.ChromeProxyPort is not null) - options.DevToolsProxyPort = HostProperties.ChromeProxyPort.Value; - if (HostProperties.ChromeDebuggingPort is not null) - options.DevToolsDebugPort = HostProperties.ChromeDebuggingPort.Value; - if (HostProperties.FirefoxProxyPort is not null) - options.FirefoxProxyPort = HostProperties.FirefoxProxyPort.Value; - if (HostProperties.FirefoxDebuggingPort is not null) - options.FirefoxDebugPort = HostProperties.FirefoxDebuggingPort.Value; - options.LogPath = "."; - return options; - } } diff --git a/src/mono/wasm/host/RuntimeConfigJson.cs b/src/mono/wasm/host/RuntimeConfigJson.cs index 3ad30dd88015ae..0e6bb50c4ffd3a 100644 --- a/src/mono/wasm/host/RuntimeConfigJson.cs +++ b/src/mono/wasm/host/RuntimeConfigJson.cs @@ -20,10 +20,6 @@ internal sealed record WasmHostProperties( string MainAssembly, string[] RuntimeArguments, IDictionary? EnvironmentVariables, - int? FirefoxProxyPort, - int? FirefoxDebuggingPort, - int? ChromeProxyPort, - int? ChromeDebuggingPort, int WebServerPort = 0) { // using an explicit property because the deserializer doesn't like diff --git a/src/mono/wasm/host/WasmAppHost.csproj b/src/mono/wasm/host/WasmAppHost.csproj index 1f580c86b357e0..de341b15547f63 100644 --- a/src/mono/wasm/host/WasmAppHost.csproj +++ b/src/mono/wasm/host/WasmAppHost.csproj @@ -9,20 +9,5 @@ LatestMajor - - - - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\BrowserDebugHost.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\BrowserDebugHost.runtimeconfig.json" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\BrowserDebugProxy.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.CSharp.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Newtonsoft.Json.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.CSharp.Scripting.dll" /> - <_browserDebugHostFiles Include="$(ArtifactsDir)bin\BrowserDebugHost\$(TargetArchitecture)\$(Configuration)\Microsoft.CodeAnalysis.Scripting.dll" /> - - - - diff --git a/src/mono/wasm/host/WebServerStartup.cs b/src/mono/wasm/host/WebServerStartup.cs index 8714ef5234dc54..2b04367d322ef9 100644 --- a/src/mono/wasm/host/WebServerStartup.cs +++ b/src/mono/wasm/host/WebServerStartup.cs @@ -3,13 +3,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Net.WebSockets; -using System.Runtime.InteropServices; using System.Threading.Tasks; -using System.Web; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server.Features; @@ -28,48 +25,9 @@ namespace Microsoft.WebAssembly.AppHost; internal sealed class WebServerStartup { private readonly IWebHostEnvironment _hostingEnvironment; - private static readonly object LaunchLock = new object(); - private static string LaunchedDebugProxyUrl = ""; private ILogger? _logger; public WebServerStartup(IWebHostEnvironment hostingEnvironment) => _hostingEnvironment = hostingEnvironment; - public static int StartDebugProxy(string devToolsHost) - { - //we need to start another process, otherwise it will be running the BrowserDebugProxy in the same process that will be debugged, so pausing in a breakpoint - //on managed code will freeze because it will not be able to continue executing the BrowserDebugProxy to get the locals value - var executablePath = Path.Combine(System.AppContext.BaseDirectory, "BrowserDebugHost.dll"); - var ownerPid = Environment.ProcessId; - // generate a random port in a given range, skipping the ports blocked by browsers: https://chromestatus.com/feature/5064283639513088 - var generateRandomPort = GetNextRandomExcept(5000..5300, - 5060, // SIP - 5061 // SIPS - ); - var processStartInfo = new ProcessStartInfo - { - FileName = "dotnet" + (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ".exe" : ""), - Arguments = $"exec \"{executablePath}\" --OwnerPid {ownerPid} --DevToolsUrl {devToolsHost} --DevToolsProxyPort {generateRandomPort}", - UseShellExecute = false, - RedirectStandardOutput = true, - }; - var debugProxyProcess = Process.Start(processStartInfo); - if (debugProxyProcess is null) - { - throw new InvalidOperationException("Unable to start debug proxy process."); - } - return generateRandomPort; - - static int GetNextRandomExcept(Range range, params int[] except) - { - int current; - do - { - current = Random.Shared.Next(range.Start.Value, range.End.Value); - } while (Array.IndexOf(except, current) >= 0); - - return current; - } - } - public void Configure(IApplicationBuilder app, IOptions optionsContainer, TaskCompletionSource realUrlsAvailableTcs, @@ -129,36 +87,6 @@ public void Configure(IApplicationBuilder app, }); } - app.Map("/debug", app => - { - app.Run(async (context) => - { - //debug from VS - var queryParams = HttpUtility.ParseQueryString(context.Request.QueryString.Value!); - var browserParam = queryParams.Get("browser"); - Uri? browserUrl = null; - var devToolsHost = "http://localhost:9222"; - if (browserParam != null) - { - browserUrl = new Uri(browserParam); - devToolsHost = $"http://{browserUrl.Host}:{browserUrl.Port}"; - } - lock (LaunchLock) - { - if (LaunchedDebugProxyUrl == "") - { - LaunchedDebugProxyUrl = $"http://localhost:{StartDebugProxy(devToolsHost)}"; - } - } - var requestPath = context.Request.Path.ToString(); - if (requestPath == string.Empty) - { - requestPath = "/"; - } - context.Response.Redirect($"{LaunchedDebugProxyUrl}{browserUrl!.PathAndQuery}"); - await Task.FromResult(0); - }); - }); app.UseEndpoints(endpoints => { endpoints.MapGet("/", context =>