MonoImage *image;
GHashTable *doc_hash;
GHashTable *method_hash;
+ gboolean is_embedded;
};
typedef struct {
}
static MonoPPDBFile*
-create_ppdb_file (MonoImage *ppdb_image)
+create_ppdb_file (MonoImage *ppdb_image, gboolean is_embedded_ppdb)
{
MonoPPDBFile *ppdb;
ppdb->image = ppdb_image;
ppdb->doc_hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) doc_free);
ppdb->method_hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify) g_free);
+ ppdb->is_embedded = is_embedded_ppdb;
return ppdb;
}
guint8 *ppdb_data = NULL;
guint8 *to_free = NULL;
int ppdb_size = 0, ppdb_compressed_size = 0;
+ gboolean is_embedded_ppdb = FALSE;
if (image->tables [MONO_TABLE_DOCUMENT].rows) {
/* Embedded ppdb */
mono_image_addref (image);
- return create_ppdb_file (image);
+ return create_ppdb_file (image, TRUE);
}
if (!get_pe_debug_info (image, pe_guid, &pe_age, &pe_timestamp, &ppdb_data, &ppdb_size, &ppdb_compressed_size)) {
raw_contents = data;
size = ppdb_size;
to_free = data;
+ is_embedded_ppdb = TRUE;
}
#endif
return NULL;
}
- return create_ppdb_file (ppdb_image);
+ return create_ppdb_file (ppdb_image, is_embedded_ppdb);
}
void
MonoImage *
mono_ppdb_get_image (MonoPPDBFile *ppdb)
{
- return ppdb->image;
+ return ppdb->image;
+}
+
+
+gboolean
+mono_ppdb_is_embedded (MonoPPDBFile *ppdb)
+{
+ return ppdb->is_embedded;
}
void
char *
mono_ppdb_get_sourcelink (MonoDebugHandle *handle);
+gboolean
+mono_ppdb_is_embedded (MonoPPDBFile *ppdb);
+
#endif
#include <emscripten.h>
#include "mono/metadata/assembly-internals.h"
+#include "mono/metadata/debug-mono-ppdb.h"
static int log_level = 1;
extern void mono_wasm_add_array_item (int);
extern void mono_wasm_set_is_async_method (guint64);
extern void mono_wasm_add_typed_value (const char *type, const char *str_value, double value);
+extern void mono_wasm_asm_loaded (const char *asm_name, const char *assembly_data, guint32 assembly_len, const char *pdb_data, guint32 pdb_len);
G_END_DECLS
static void describe_object_properties_for_klass (void *obj, MonoClass *klass, gboolean isAsyncLocalThis, int gpflags);
static void handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame);
+static void assembly_loaded (MonoProfiler *prof, MonoAssembly *assembly);
//FIXME move all of those fields to the profiler object
static gboolean debugger_enabled;
mono_profiler_set_jit_done_callback (prof, jit_done);
//FIXME support multiple appdomains
mono_profiler_set_domain_loaded_callback (prof, appdomain_load);
+ mono_profiler_set_assembly_loaded_callback (prof, assembly_loaded);
obj_to_objref = g_hash_table_new (NULL, NULL);
objrefs = g_hash_table_new_full (NULL, NULL, NULL, mono_debugger_free_objref);
}
static void
+assembly_loaded (MonoProfiler *prof, MonoAssembly *assembly)
+{
+ DEBUG_PRINTF (2, "assembly_loaded callback called for %s\n", assembly->aname.name);
+ MonoImage *assembly_image = assembly->image;
+ MonoImage *pdb_image = NULL;
+ if (mono_has_pdb_checksum ((char *) assembly_image->raw_data, assembly_image->raw_data_len)) { //if it's a release assembly we don't need to send to DebuggerProxy
+ MonoDebugHandle *handle = mono_debug_get_handle (assembly_image);
+ if (handle) {
+ MonoPPDBFile *ppdb = handle->ppdb;
+ if (!mono_ppdb_is_embedded (ppdb)) { //if it's an embedded pdb we don't need to send pdb extrated to DebuggerProxy.
+ pdb_image = mono_ppdb_get_image (ppdb);
+ mono_wasm_asm_loaded (assembly_image->assembly_name, assembly_image->raw_data, assembly_image->raw_data_len, pdb_image->raw_data, pdb_image->raw_data_len);
+ return;
+ }
+ }
+ mono_wasm_asm_loaded (assembly_image->assembly_name, assembly_image->raw_data, assembly_image->raw_data_len, NULL, 0);
+ }
+}
+
+static void
handle_exception (MonoException *exc, MonoContext *throw_ctx, MonoContext *catch_ctx, StackFrameInfo *catch_frame)
{
ERROR_DECL (error);
run-tests-%:
PATH="$(JSVU):$(PATH)" $(DOTNET) build $(TOP)/src/libraries/$*/tests/ /t:Test /p:TargetOS=Browser /p:TargetArchitecture=wasm /p:Configuration=$(CONFIG) $(MSBUILD_ARGS)
-build-debugger-test-app:
- $(DOTNET) build --configuration debug --nologo /p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Configuration=Debug /p:RuntimeConfiguration=$(CONFIG) $(TOP)/src/mono/wasm/debugger/tests
- cp $(TOP)/src/mono/wasm/debugger/tests/debugger-driver.html $(TOP)/src/mono/wasm/debugger/tests/bin/Debug/publish
- cp $(TOP)/src/mono/wasm/debugger/tests/other.js $(TOP)/src/mono/wasm/debugger/tests/bin/Debug/publish
- cp $(TOP)/src/mono/wasm/debugger/tests/runtime-debugger.js $(TOP)/src/mono/wasm/debugger/tests/bin/Debug/publish
+build-debugger-test-app:
+ $(DOTNET) build --configuration debug --nologo /p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Configuration=Debug /p:RuntimeConfiguration=$(CONFIG) $(TOP)/src/mono/wasm/debugger/tests/debugger-test
+ $(DOTNET) build --configuration debug --nologo /p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Configuration=Debug /p:RuntimeConfiguration=$(CONFIG) $(TOP)/src/mono/wasm/debugger/tests/lazy-debugger-test
+ $(DOTNET) build --configuration debug --nologo /p:TargetArchitecture=wasm /p:TargetOS=Browser /p:Configuration=Debug /p:RuntimeConfiguration=$(CONFIG) $(TOP)/src/mono/wasm/debugger/tests/lazy-debugger-test-embedded
+ cp $(TOP)/src/mono/wasm/debugger/tests/debugger-test/debugger-driver.html $(TOP)/src/mono/wasm/debugger/tests/debugger-test/bin/Debug/publish
+ cp $(TOP)/src/mono/wasm/debugger/tests/debugger-test/other.js $(TOP)/src/mono/wasm/debugger/tests/debugger-test/bin/Debug/publish
+ cp $(TOP)/src/mono/wasm/debugger/tests/debugger-test/runtime-debugger.js $(TOP)/src/mono/wasm/debugger/tests/debugger-test/bin/Debug/publish
+ cp $(TOP)/src/mono/wasm/debugger/tests/lazy-debugger-test/bin/Debug/publish/managed/* $(TOP)/src/mono/wasm/debugger/tests/debugger-test/bin/Debug/publish
+ cp $(TOP)/src/mono/wasm/debugger/tests/lazy-debugger-test-embedded/bin/Debug/publish/managed/* $(TOP)/src/mono/wasm/debugger/tests/debugger-test/bin/Debug/publish
run-debugger-tests: build-debugger-test-app build-dbg-testsuite
if [ ! -z "$(TEST_FILTER)" ]; then \
- export TEST_SUITE_PATH=$(TOP)/src/mono/wasm/debugger/tests/bin/Debug/publish; \
+ export TEST_SUITE_PATH=$(TOP)/src/mono/wasm/debugger/tests/debugger-test/bin/Debug/publish; \
export LC_ALL=en_US.UTF-8; \
$(DOTNET) test $(TOP)/src/mono/wasm/debugger/DebuggerTestSuite --filter FullyQualifiedName~$(TEST_FILTER); \
unset TEST_SUITE_PATH LC_ALL; \
else \
- export TEST_SUITE_PATH=$(TOP)/src/mono/wasm/debugger/tests/bin/Debug/publish; \
+ export TEST_SUITE_PATH=$(TOP)/src/mono/wasm/debugger/tests/debugger-test/bin/Debug/publish; \
export LC_ALL=en_US.UTF-8; \
$(DOTNET) test $(TOP)/src/mono/wasm/debugger/DebuggerTestSuite $(TEST_ARGS); \
unset TEST_SUITE_PATH LC_ALL; \
public int Id => id;
public string Name => image.Name;
+ // "System.Threading", instead of "System.Threading, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
+ public string AssemblyNameUnqualified => image.Assembly.Name.Name;
+
public SourceFile GetDocById(int document)
{
return sources.FirstOrDefault(s => s.SourceId.Document == document);
private List<AssemblyInfo> assemblies = new List<AssemblyInfo>();
private readonly HttpClient client;
private readonly ILogger logger;
+ private readonly IAssemblyResolver resolver;
public DebugStore(ILogger logger, HttpClient client)
{
this.client = client;
this.logger = logger;
+ this.resolver = new DefaultAssemblyResolver();
}
public DebugStore(ILogger logger) : this(logger, new HttpClient())
public Task<byte[][]> Data { get; set; }
}
+ public IEnumerable<SourceFile> Add(SessionId sessionId, byte[] assembly_data, byte[] pdb_data)
+ {
+ AssemblyInfo assembly = null;
+ try
+ {
+ assembly = new AssemblyInfo(this.resolver, sessionId.ToString(), assembly_data, pdb_data);
+ }
+ catch (Exception e)
+ {
+ logger.LogDebug($"Failed to load assembly: ({e.Message})");
+ yield break;
+ }
+
+ if (assembly == null)
+ yield break;
+
+ if (GetAssemblyByUnqualifiedName(assembly.AssemblyNameUnqualified) != 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<SourceFile> Load(SessionId sessionId, string[] loaded_files, [EnumeratorCancellation] CancellationToken token)
{
var asm_files = new List<string>();
{
string candidate_pdb = Path.ChangeExtension(url, "pdb");
string pdb = pdb_files.FirstOrDefault(n => n == candidate_pdb);
- if (pdb == null)
- continue;
steps.Add(
new DebugItem
}
}
- var resolver = new DefaultAssemblyResolver();
foreach (DebugItem step in steps)
{
AssemblyInfo assembly = null;
try
{
byte[][] bytes = await step.Data.ConfigureAwait(false);
- assembly = new AssemblyInfo(resolver, step.Url, bytes[0], bytes[1]);
+ assembly = new AssemblyInfo(this.resolver, step.Url, bytes[0], bytes[1]);
}
catch (Exception e)
{
if (assembly == null)
continue;
+ if (GetAssemblyByUnqualifiedName(assembly.AssemblyNameUnqualified) != 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 AssemblyInfo GetAssemblyByName(string name) => assemblies.FirstOrDefault(a => a.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
+ public AssemblyInfo GetAssemblyByUnqualifiedName(string name) => assemblies.FirstOrDefault(a => a.AssemblyNameUnqualified.Equals(name, StringComparison.InvariantCultureIgnoreCase));
+
/*
V8 uses zero based indexing for both line and column.
PPDBs uses one based indexing for both line and column.
try
{
using HttpResponseMessage response = await client.GetAsync(downloadURL, token);
+ if (!response.IsSuccessStatusCode)
+ {
+ Log("info", $"Unable to download symbols on demand url:{downloadURL} assembly: {asm.Name}");
+ continue;
+ }
+
using Stream streamToReadFrom = await response.Content.ReadAsStreamAsync(token);
var portablePdbReaderProvider = new PdbReaderProvider();
ISymbolReader symbolReader = portablePdbReaderProvider.GetSymbolReader(asm.Image, streamToReadFrom);
break;
}
- Log("info", "Unable to load symbols on demand assembly: {asm.Name}");
+ Log("info", $"Unable to load symbols on demand assembly: {asm.Name}");
return null;
}
switch (eventName)
{
+ case "AssemblyLoaded":
+ return await OnAssemblyLoadedJSEvent(sessionId, eventArgs, token);
default:
{
logger.LogDebug($"Unknown js event name: {eventName} with args {eventArgs}");
}
}
+ private async Task<bool> OnAssemblyLoadedJSEvent(SessionId sessionId, JObject eventArgs, CancellationToken token)
+ {
+ try
+ {
+ var store = await LoadStore(sessionId, token);
+ var assembly_name = eventArgs?["assembly_name"]?.Value<string>();
+
+ if (store.GetAssemblyByUnqualifiedName(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<string>();
+ var pdb_b64 = eventArgs?["pdb_b64"]?.ToObject<string>();
+
+ 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 = GetContext(sessionId);
+ foreach (var source in store.Add(sessionId, assembly_data, pdb_data))
+ {
+ 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<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, string expression, CancellationToken token)
{
try
return bp;
}
+ private async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, ExecutionContext context, CancellationToken token)
+ {
+ JObject scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData));
+ Log("debug", $"sending {source.Url} {context.Id} {sessionId.sessionId}");
+
+ SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token);
+
+ foreach (var req in context.BreakpointRequests.Values)
+ {
+ if (req.TryResolve(source))
+ {
+ await SetBreakpoint(sessionId, context.store, req, true, token);
+ }
+ }
+ }
+
private async Task<DebugStore> LoadStore(SessionId sessionId, CancellationToken token)
{
ExecutionContext context = GetContext(sessionId);
await
foreach (SourceFile source in context.store.Load(sessionId, loaded_files, token).WithCancellation(token))
{
- var scriptSource = JObject.FromObject(source.ToScriptSource(context.Id, context.AuxData));
- Log("verbose", $"\tsending {source.Url} {context.Id} {sessionId.sessionId}");
-
- SendEvent(sessionId, "Debugger.scriptParsed", scriptSource, token);
-
- foreach (BreakpointRequest req in context.BreakpointRequests.Values)
- {
- if (req.TryResolve(source))
- {
- await SetBreakpoint(sessionId, context.store, req, true, token);
- }
- }
+ await OnSourceFileAdded(sessionId, source, context, token);
}
}
catch (Exception e)
using System;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
Assert.False(tcs.Task == t, "Event should not have been logged");
});
}
+
+ [Theory]
+ [InlineData(true, 1)]
+ [InlineData(false, 0)]
+ public async Task DuplicateAssemblyLoadedEventNotLoadedFromBundle(bool load_pdb, int expected_count)
+ => await AssemblyLoadedEventTest(
+ "lazy-debugger-test",
+ Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.dll"),
+ load_pdb ? Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.pdb") : null,
+ "/lazy-debugger-test.cs",
+ expected_count
+ );
+
+ [Theory]
+ [InlineData(true, 1)]
+ [InlineData(false, 1)] // Since it's being loaded from the bundle, it will have the pdb even if we don't provide one
+ public async Task DuplicateAssemblyLoadedEventForAssemblyFromBundle(bool load_pdb, int expected_count)
+ => await AssemblyLoadedEventTest(
+ "debugger-test",
+ Path.Combine(DebuggerTestAppPath, "managed/debugger-test.dll"),
+ load_pdb ? Path.Combine(DebuggerTestAppPath, "managed/debugger-test.pdb") : null,
+ "/debugger-test.cs",
+ expected_count
+ );
+
+ [Fact]
+ public async Task DuplicateAssemblyLoadedEventWithEmbeddedPdbNotLoadedFromBundle()
+ => await AssemblyLoadedEventTest(
+ "lazy-debugger-test-embedded",
+ Path.Combine(DebuggerTestAppPath, "lazy-debugger-test-embedded.dll"),
+ null,
+ "/lazy-debugger-test-embedded.cs",
+ expected_count: 1
+ );
+
+ async Task AssemblyLoadedEventTest(string asm_name, string asm_path, string pdb_path, string source_file, int expected_count)
+ {
+ var insp = new Inspector();
+ var scripts = SubscribeToScripts(insp);
+
+ int event_count = 0;
+ var tcs = new TaskCompletionSource<bool>();
+ insp.On("Debugger.scriptParsed", async (args, c) =>
+ {
+ try
+ {
+ var url = args["url"]?.Value<string>();
+ if (url?.EndsWith(source_file) == true)
+ {
+ event_count ++;
+ if (event_count > expected_count)
+ tcs.SetResult(false);
+ }
+ }
+ catch (Exception ex)
+ {
+ tcs.SetException(ex);
+ }
+
+ await Task.CompletedTask;
+ });
+
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ byte[] bytes = File.ReadAllBytes(asm_path);
+ string asm_base64 = Convert.ToBase64String(bytes);
+
+ string pdb_base64 = String.Empty;
+ if (pdb_path != null)
+ {
+ bytes = File.ReadAllBytes(pdb_path);
+ pdb_base64 = Convert.ToBase64String(bytes);
+ }
+
+ var expression = $@"MONO.mono_wasm_raise_debug_event({{
+ eventName: 'AssemblyLoaded',
+ assembly_name: '{asm_name}',
+ assembly_b64: '{asm_base64}',
+ pdb_b64: '{pdb_base64}'
+ }});";
+
+ var res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), ctx.token);
+ Assert.True(res.IsOk, $"Expected to pass for {expression}");
+
+ res = await ctx.cli.SendCommand($"Runtime.evaluate", JObject.FromObject(new { expression }), ctx.token);
+ Assert.True(res.IsOk, $"Expected to pass for {expression}");
+
+ var t = await Task.WhenAny(tcs.Task, Task.Delay(2000));
+ if (t.IsFaulted)
+ throw t.Exception;
+
+ Assert.True(event_count <= expected_count, $"number of scriptParsed events received. Expected: {expected_count}, Actual: {event_count}");
+ });
+ }
}
}
{
protected Task startTask;
- static string FindTestPath()
+ static string s_debuggerTestAppPath;
+ protected static string DebuggerTestAppPath
+ {
+ get
+ {
+ if (s_debuggerTestAppPath == null)
+ s_debuggerTestAppPath = FindTestPath();
+
+ return s_debuggerTestAppPath;
+ }
+ }
+
+ static protected string FindTestPath()
{
//FIXME how would I locate it otherwise?
var test_path = Environment.GetEnvironmentVariable("TEST_SUITE_PATH");
public DebuggerTestBase(string driver = "debugger-driver.html")
{
- startTask = TestHarnessProxy.Start(FindChromePath(), FindTestPath(), driver);
+ startTask = TestHarnessProxy.Start(FindChromePath(), DebuggerTestAppPath, driver);
}
public Task Ready() => startTask;
// The .NET Foundation licenses this file to you under the MIT license.
using System;
+using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.WebAssembly.Diagnostics;
?.Where(f => f["functionName"]?.Value<string>() == function_name)
?.FirstOrDefault();
+ [Fact]
+ public async Task DebugLazyLoadedAssemblyWithPdb()
+ {
+ var insp = new Inspector();
+ var scripts = SubscribeToScripts(insp);
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ int line = 9;
+ await SetBreakpoint(".*/lazy-debugger-test.cs$", line, 0, use_regex: true);
+ await LoadAssemblyDynamically(
+ Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.dll"),
+ Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.pdb"));
+
+ var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs";
+ Assert.Contains(source_location, scripts.Values);
+
+ var pause_location = await EvaluateAndCheck(
+ "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test] LazyMath:IntAdd', 5, 10); }, 1);",
+ source_location, line, 8,
+ "IntAdd");
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ CheckNumber(locals, "a", 5);
+ CheckNumber(locals, "b", 10);
+ });
+ }
+
+ [Fact]
+ public async Task DebugLazyLoadedAssemblyWithEmbeddedPdb()
+ {
+ var insp = new Inspector();
+ var scripts = SubscribeToScripts(insp);
+ await Ready();
+
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ int line = 9;
+ await SetBreakpoint(".*/lazy-debugger-test-embedded.cs$", line, 0, use_regex: true);
+ await LoadAssemblyDynamically(
+ Path.Combine(DebuggerTestAppPath, "lazy-debugger-test-embedded.dll"),
+ null);
+
+ var source_location = "dotnet://lazy-debugger-test-embedded.dll/lazy-debugger-test-embedded.cs";
+ Assert.Contains(source_location, scripts.Values);
+
+ var pause_location = await EvaluateAndCheck(
+ "window.setTimeout(function () { invoke_static_method('[lazy-debugger-test-embedded] LazyMath:IntAdd', 5, 10); }, 1);",
+ source_location, line, 8,
+ "IntAdd");
+ var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value<string>());
+ CheckNumber(locals, "a", 5);
+ CheckNumber(locals, "b", 10);
+ });
+ }
+
+ [Fact]
+ public async Task CannotDebugLazyLoadedAssemblyWithoutPdb()
+ {
+ var insp = new Inspector();
+ var scripts = SubscribeToScripts(insp);
+ await Ready();
+ await insp.Ready(async (cli, token) =>
+ {
+ ctx = new DebugTestContext(cli, insp, token, scripts);
+
+ int line = 9;
+ await SetBreakpoint(".*/lazy-debugger-test.cs$", line, 0, use_regex: true);
+ await LoadAssemblyDynamically(
+ Path.Combine(DebuggerTestAppPath, "lazy-debugger-test.dll"),
+ null);
+
+ // wait to bit to catch if the event might be raised a bit late
+ await Task.Delay(1000);
+
+ var source_location = "dotnet://lazy-debugger-test.dll/lazy-debugger-test.cs";
+ Assert.DoesNotContain(source_location, scripts.Values);
+ });
+ }
+
+ async Task LoadAssemblyDynamically(string asm_file, string pdb_file)
+ {
+ // Simulate loading an assembly into the framework
+ byte[] bytes = File.ReadAllBytes(asm_file);
+ string asm_base64 = Convert.ToBase64String(bytes);
+
+ string pdb_base64 = null;
+ if (pdb_file != null) {
+ bytes = File.ReadAllBytes(pdb_file);
+ pdb_base64 = Convert.ToBase64String(bytes);
+ }
+
+ var load_assemblies = JObject.FromObject(new
+ {
+ expression = $"{{ let asm_b64 = '{asm_base64}'; let pdb_b64 = '{pdb_base64}'; invoke_static_method('[debugger-test] LoadDebuggerTest:LoadLazyAssembly', asm_b64, pdb_b64); }}"
+ });
+
+ Result load_assemblies_res = await ctx.cli.SendCommand("Runtime.evaluate", load_assemblies, ctx.token);
+ Assert.True(load_assemblies_res.IsOk);
+ }
+
//TODO add tests covering basic stepping behavior as step in/out/over
}
}
StaticMethodWithLocalEmptyStructAsync().Wait();
}
}
+
+public class LoadDebuggerTest {
+ public static void LoadLazyAssembly(string asm_base64, string pdb_base64)
+ {
+ byte[] asm_bytes = Convert.FromBase64String(asm_base64);
+ byte[] pdb_bytes = null;
+ if (pdb_base64 != null)
+ pdb_bytes = Convert.FromBase64String(pdb_base64);
+
+ var loadedAssembly = System.Runtime.Loader.AssemblyLoadContext.Default.LoadFromStream(new System.IO.MemoryStream(asm_bytes), new System.IO.MemoryStream(pdb_bytes));
+ Console.WriteLine($"Loaded - {loadedAssembly}");
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+public partial class LazyMath
+{
+ public static int IntAdd(int a, int b)
+ {
+ int c = a + b;
+ return c;
+ }
+}
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk" DefaultTargets="BuildApp">
+ <PropertyGroup>
+ <TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
+ <TargetArchitecture>wasm</TargetArchitecture>
+ <TargetOS>Browser</TargetOS>
+ <MicrosoftNetCoreAppRuntimePackDir>$(ArtifactsBinDir)microsoft.netcore.app.runtime.browser-wasm\Release\runtimes\browser-wasm\</MicrosoftNetCoreAppRuntimePackDir>
+ <BuildDir>$(MSBuildThisFileDirectory)obj\$(Configuration)\wasm</BuildDir>
+ <AppDir>$(MSBuildThisFileDirectory)bin\$(Configuration)\publish</AppDir>
+ <RuntimeBuildConfig Condition="'$(RuntimeBuildConfig)' == ''">$(Configuration)</RuntimeBuildConfig>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <OutputType>Library</OutputType>
+ <NoWarn>219</NoWarn>
+ <RunAnalyzers>false</RunAnalyzers>
+ <DebugType>embedded</DebugType>
+ </PropertyGroup>
+
+ <Target Name="RebuildWasmAppBuilder">
+ <ItemGroup>
+ <WasmAppBuildProject Include="$(RepoTasksDir)mobile.tasks\WasmAppBuilder\WasmAppBuilder.csproj" />
+ </ItemGroup>
+
+ <MSBuild Projects="@(WasmAppBuildProject)"
+ Properties="Configuration=$(Configuration);MSBuildRestoreSessionId=$([System.Guid]::NewGuid())"
+ Targets="Restore"/>
+
+ <MSBuild Projects="@(WasmAppBuildProject)"
+ Properties="Configuration=$(Configuration)"
+ Targets="Build;Publish"/>
+ </Target>
+
+ <UsingTask TaskName="WasmAppBuilder"
+ AssemblyFile="$(ArtifactsBinDir)WasmAppBuilder\$(Configuration)\$(NetCoreAppCurrent)\publish\WasmAppBuilder.dll"/>
+
+ <Target Name="BuildApp" DependsOnTargets="RebuildWasmAppBuilder;Build">
+ <ItemGroup>
+ <AssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackDir)native"/>
+ <AssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackDir)lib\$(NetCoreAppCurrent)"/>
+ </ItemGroup>
+ <WasmAppBuilder
+ AppDir="$(AppDir)"
+ MicrosoftNetCoreAppRuntimePackDir="$(MicrosoftNetCoreAppRuntimePackDir)"
+ MainAssembly="$(ArtifactsBinDir)lazy-debugger-test-embedded/wasm/Debug/lazy-debugger-test-embedded.dll"
+ MainJS="$(MonoProjectRoot)wasm\runtime-test.js"
+ DebugLevel="1"
+ AssemblySearchPaths="@(AssemblySearchPaths)"
+ ExtraAssemblies="$(ArtifactsBinDir)\System.Private.Runtime.InteropServices.JavaScript\$(NetCoreAppCurrent)-Browser-$(RuntimeConfiguration)\System.Private.Runtime.InteropServices.JavaScript.dll"/>
+ <Exec Command="chmod a+x $(AppDir)/run-v8.sh" />
+ </Target>
+
+</Project>
\ No newline at end of file
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+public partial class LazyMath
+{
+ public static int IntAdd(int a, int b)
+ {
+ int c = a + b;
+ return c;
+ }
+}
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk" DefaultTargets="BuildApp">
+ <PropertyGroup>
+ <TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
+ <TargetArchitecture>wasm</TargetArchitecture>
+ <TargetOS>Browser</TargetOS>
+ <MicrosoftNetCoreAppRuntimePackDir>$(ArtifactsBinDir)microsoft.netcore.app.runtime.browser-wasm\Release\runtimes\browser-wasm\</MicrosoftNetCoreAppRuntimePackDir>
+ <BuildDir>$(MSBuildThisFileDirectory)obj\$(Configuration)\wasm</BuildDir>
+ <AppDir>$(MSBuildThisFileDirectory)bin\$(Configuration)\publish</AppDir>
+ <RuntimeBuildConfig Condition="'$(RuntimeBuildConfig)' == ''">$(Configuration)</RuntimeBuildConfig>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <OutputType>Library</OutputType>
+ <NoWarn>219</NoWarn>
+ <RunAnalyzers>false</RunAnalyzers>
+ <DebugType>portable</DebugType>
+ </PropertyGroup>
+
+ <Target Name="RebuildWasmAppBuilder">
+ <ItemGroup>
+ <WasmAppBuildProject Include="$(RepoTasksDir)mobile.tasks\WasmAppBuilder\WasmAppBuilder.csproj" />
+ </ItemGroup>
+
+ <MSBuild Projects="@(WasmAppBuildProject)"
+ Properties="Configuration=$(Configuration);MSBuildRestoreSessionId=$([System.Guid]::NewGuid())"
+ Targets="Restore"/>
+
+ <MSBuild Projects="@(WasmAppBuildProject)"
+ Properties="Configuration=$(Configuration)"
+ Targets="Build;Publish"/>
+ </Target>
+
+ <UsingTask TaskName="WasmAppBuilder"
+ AssemblyFile="$(ArtifactsBinDir)WasmAppBuilder\$(Configuration)\$(NetCoreAppCurrent)\publish\WasmAppBuilder.dll"/>
+
+ <Target Name="BuildApp" DependsOnTargets="RebuildWasmAppBuilder;Build">
+ <ItemGroup>
+ <AssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackDir)native"/>
+ <AssemblySearchPaths Include="$(MicrosoftNetCoreAppRuntimePackDir)lib\$(NetCoreAppCurrent)"/>
+ </ItemGroup>
+ <WasmAppBuilder
+ AppDir="$(AppDir)"
+ MicrosoftNetCoreAppRuntimePackDir="$(MicrosoftNetCoreAppRuntimePackDir)"
+ MainAssembly="$(ArtifactsBinDir)lazy-debugger-test/wasm/Debug/lazy-debugger-test.dll"
+ MainJS="$(MonoProjectRoot)wasm\runtime-test.js"
+ DebugLevel="1"
+ AssemblySearchPaths="@(AssemblySearchPaths)"
+ ExtraAssemblies="$(ArtifactsBinDir)\System.Private.Runtime.InteropServices.JavaScript\$(NetCoreAppCurrent)-Browser-$(RuntimeConfiguration)\System.Private.Runtime.InteropServices.JavaScript.dll"/>
+ <Exec Command="chmod a+x $(AppDir)/run-v8.sh" />
+ </Target>
+
+</Project>
\ No newline at end of file
int mono_wasm_register_root (char *start, size_t size, const char *name);
void mono_wasm_deregister_root (char *addr);
+int mono_wasm_assembly_already_added (const char *assembly_name);
void mono_ee_interp_init (const char *opts);
void mono_marshal_ilgen_init (void);
return mono_has_pdb_checksum (data, size);
}
+EMSCRIPTEN_KEEPALIVE int
+mono_wasm_assembly_already_added (const char *assembly_name)
+{
+ if (assembly_count == 0)
+ return 0;
+
+ WasmAssembly *entry = assemblies;
+ while (entry != NULL) {
+ if (strcmp (entry->assembly.name, assembly_name) == 0)
+ return 1;
+ entry = entry->next;
+ }
+
+ return 0;
+}
+
typedef struct WasmSatelliteAssembly_ WasmSatelliteAssembly;
struct WasmSatelliteAssembly_ {
module ["mono_wasm_release_roots"] = MONO.mono_wasm_release_roots;
},
+ _base64Converter: {
+ // Code from JSIL:
+ // https://github.com/sq/JSIL/blob/1d57d5427c87ab92ffa3ca4b82429cd7509796ba/JSIL.Libraries/Includes/Bootstrap/Core/Classes/System.Convert.js#L149
+ // Thanks to Katelyn Gadd @kg
+
+ _base64Table: [
+ 'A', 'B', 'C', 'D',
+ 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L',
+ 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X',
+ 'Y', 'Z',
+ 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x',
+ 'y', 'z',
+ '0', '1', '2', '3',
+ '4', '5', '6', '7',
+ '8', '9',
+ '+', '/'
+ ],
+
+ _makeByteReader: function (bytes, index, count) {
+ var position = (typeof (index) === "number") ? index : 0;
+ var endpoint;
+
+ if (typeof (count) === "number")
+ endpoint = (position + count);
+ else
+ endpoint = (bytes.length - position);
+
+ var result = {
+ read: function () {
+ if (position >= endpoint)
+ return false;
+
+ var nextByte = bytes[position];
+ position += 1;
+ return nextByte;
+ }
+ };
+
+ Object.defineProperty(result, "eof", {
+ get: function () {
+ return (position >= endpoint);
+ },
+ configurable: true,
+ enumerable: true
+ });
+
+ return result;
+ },
+
+ toBase64StringImpl: function (inArray, offset, length) {
+ var reader = this._makeByteReader(inArray, offset, length);
+ var result = "";
+ var ch1 = 0, ch2 = 0, ch3 = 0, bits = 0, equalsCount = 0, sum = 0;
+ var mask1 = (1 << 24) - 1, mask2 = (1 << 18) - 1, mask3 = (1 << 12) - 1, mask4 = (1 << 6) - 1;
+ var shift1 = 18, shift2 = 12, shift3 = 6, shift4 = 0;
+
+ while (true) {
+ ch1 = reader.read();
+ ch2 = reader.read();
+ ch3 = reader.read();
+
+ if (ch1 === false)
+ break;
+ if (ch2 === false) {
+ ch2 = 0;
+ equalsCount += 1;
+ }
+ if (ch3 === false) {
+ ch3 = 0;
+ equalsCount += 1;
+ }
+
+ // Seems backwards, but is right!
+ sum = (ch1 << 16) | (ch2 << 8) | (ch3 << 0);
+
+ bits = (sum & mask1) >> shift1;
+ result += this._base64Table[bits];
+ bits = (sum & mask2) >> shift2;
+ result += this._base64Table[bits];
+
+ if (equalsCount < 2) {
+ bits = (sum & mask3) >> shift3;
+ result += this._base64Table[bits];
+ }
+
+ if (equalsCount === 2) {
+ result += "==";
+ } else if (equalsCount === 1) {
+ result += "=";
+ } else {
+ bits = (sum & mask4) >> shift4;
+ result += this._base64Table[bits];
+ }
+ }
+
+ return result;
+ },
+ },
+
_mono_wasm_root_buffer_prototype: {
_check_in_range: function (index) {
if ((index >= this.__count) || (index < 0))
};
debugger;
},
+
+ mono_wasm_asm_loaded: function (assembly_name, assembly_ptr, assembly_len, pdb_ptr, pdb_len) {
+ // Only trigger this codepath for assemblies loaded after app is ready
+ if (MONO.mono_wasm_runtime_is_ready !== true)
+ return;
+
+ if (!this.mono_wasm_assembly_already_added)
+ this.mono_wasm_assembly_already_added = Module.cwrap ("mono_wasm_assembly_already_added", 'number', ['string']);
+
+ // And for assemblies that have not already been loaded
+ const assembly_name_str = assembly_name !== 0 ? Module.UTF8ToString(assembly_name).concat('.dll') : '';
+ if (this.mono_wasm_assembly_already_added(assembly_name_str))
+ return;
+
+ const assembly_data = new Uint8Array(Module.HEAPU8.buffer, assembly_ptr, assembly_len);
+ const assembly_b64 = MONO._base64Converter.toBase64StringImpl(assembly_data);
+
+ let pdb_b64;
+ if (pdb_ptr) {
+ const pdb_data = new Uint8Array(Module.HEAPU8.buffer, pdb_ptr, pdb_len);
+ pdb_b64 = MONO._base64Converter.toBase64StringImpl(pdb_data);
+ }
+
+ MONO.mono_wasm_raise_debug_event({
+ eventName: 'AssemblyLoaded',
+ assembly_name: assembly_name_str,
+ assembly_b64,
+ pdb_b64
+ });
+ },
};
autoAddDeps(MonoSupportLib, '$MONO')