diff --git a/eng/pipelines/common/templates/wasm-debugger-tests.yml b/eng/pipelines/common/templates/wasm-debugger-tests.yml index 94fd9e024e695..ff3b33fa54252 100644 --- a/eng/pipelines/common/templates/wasm-debugger-tests.yml +++ b/eng/pipelines/common/templates/wasm-debugger-tests.yml @@ -4,6 +4,8 @@ parameters: isWasmOnlyBuild: false browser: 'chrome' shouldContinueOnError: false + extraBuildArgs: '' + nameSuffix: '' platforms: [] jobs: @@ -30,8 +32,11 @@ jobs: jobParameters: testGroup: innerloop isExtraPlatforms: ${{ parameters.isExtraPlatformsBuild }} - nameSuffix: Mono_DebuggerTests_${{ parameters.browser }} - buildArgs: -s mono+libs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:TestWasmDebuggerTests=true /p:TestAssemblies=false /p:BrowserHost=$(_hostedOs) /p:DebuggerHost=${{ parameters.browser }} + ${{ if eq(parameters.nameSuffix, '') }}: + nameSuffix: Mono_DebuggerTests_${{ parameters.browser }} + ${{ else }}: + nameSuffix: ${{ parameters.nameSuffix }} + buildArgs: -s mono+libs+libs.tests -c $(_BuildConfig) /p:ArchiveTests=true /p:TestWasmDebuggerTests=true /p:TestAssemblies=false /p:BrowserHost=$(_hostedOs) /p:DebuggerHost=${{ parameters.browser }} ${{ parameters.extraBuildArgs }} timeoutInMinutes: 180 # if !alwaysRun, then: # if this is runtime-wasm (isWasmOnlyBuild): diff --git a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml index 28ed6617773db..a5b6e050b2407 100644 --- a/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml +++ b/eng/pipelines/extra-platforms/runtime-extra-platforms-wasm.yml @@ -99,7 +99,7 @@ jobs: - WasmTestOnBrowser - WasmTestOnNodeJS - # Library tests with full threading + # Library tests with full threading - template: /eng/pipelines/common/templates/wasm-library-tests.yml parameters: platforms: @@ -212,6 +212,17 @@ jobs: # ff tests are unstable currently shouldContinueOnError: true + - template: /eng/pipelines/common/templates/wasm-debugger-tests.yml + parameters: + platforms: + - Browser_wasm + - Browser_wasm_win + extraBuildArgs: /p:MonoWasmBuildVariant=multithread /p:WasmEnableThreads=true + nameSuffix: DebuggerTests_MultiThreaded + alwaysRun: ${{ parameters.isWasmOnlyBuild }} + isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} + isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} + # Disable for now #- template: /eng/pipelines/coreclr/perf-wasm-jobs.yml #parameters: diff --git a/eng/pipelines/runtime-wasm-dbgtests.yml b/eng/pipelines/runtime-wasm-dbgtests.yml new file mode 100644 index 0000000000000..85e66692f648a --- /dev/null +++ b/eng/pipelines/runtime-wasm-dbgtests.yml @@ -0,0 +1,41 @@ +trigger: none + +variables: + - template: /eng/pipelines/common/variables.yml + +jobs: + +# +# Evaluate paths +# +- template: /eng/pipelines/common/evaluate-default-paths.yml + +# Debugger tests +- template: /eng/pipelines/common/templates/wasm-debugger-tests.yml + parameters: + platforms: + - Browser_wasm + - Browser_wasm_win + isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} + isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} + +- template: /eng/pipelines/common/templates/wasm-debugger-tests.yml + parameters: + platforms: + - Browser_wasm + - Browser_wasm_win + extraBuildArgs: /p:MonoWasmBuildVariant=multithread /p:WasmEnableThreads=true + nameSuffix: DebuggerTests_MultiThreaded + isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} + isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} + +- template: /eng/pipelines/common/templates/wasm-debugger-tests.yml + parameters: + platforms: + - Browser_wasm_firefox + browser: firefox + isExtraPlatformsBuild: ${{ parameters.isExtraPlatformsBuild }} + isWasmOnlyBuild: ${{ parameters.isWasmOnlyBuild }} + alwaysRun: ${{ parameters.isWasmOnlyBuild }} + # ff tests are unstable currently + shouldContinueOnError: true diff --git a/src/mono/mono/component/debugger-agent.c b/src/mono/mono/component/debugger-agent.c index 6956c42ab28b2..8e13638fe2a53 100644 --- a/src/mono/mono/component/debugger-agent.c +++ b/src/mono/mono/component/debugger-agent.c @@ -323,9 +323,6 @@ typedef struct { /* * Globals */ -#ifdef TARGET_WASM -static DebuggerTlsData debugger_wasm_thread; -#endif static AgentConfig agent_config; /* @@ -414,7 +411,7 @@ static gint32 suspend_count; /* Whenever to buffer reply messages and send them together */ static gboolean buffer_replies; -#ifndef TARGET_WASM + #define GET_TLS_DATA_FROM_THREAD(thread) \ DebuggerTlsData *tls = NULL; \ mono_loader_lock(); \ @@ -424,15 +421,6 @@ static gboolean buffer_replies; #define GET_DEBUGGER_TLS() \ DebuggerTlsData *tls; \ tls = (DebuggerTlsData *)mono_native_tls_get_value (debugger_tls_id); -#else -/* the thread argument is omitted on wasm, to avoid compiler warning */ -#define GET_TLS_DATA_FROM_THREAD(...) \ - DebuggerTlsData *tls; \ - tls = &debugger_wasm_thread; -#define GET_DEBUGGER_TLS() \ - DebuggerTlsData *tls; \ - tls = &debugger_wasm_thread; -#endif #define GET_EXTRA_SPACE_FOR_REF_FIELDS(klass) \ extra_space_size = 0; \ @@ -528,6 +516,8 @@ static void process_profiler_event (EventKind event, gpointer arg); static void invalidate_frames (DebuggerTlsData *tls); +static void mono_init_debugger_agent_common (MonoProfilerHandle *prof); + /* Callbacks used by debugger-engine */ static MonoContext* tls_get_restore_state (void *the_tls); static gboolean try_process_suspend (void *tls, MonoContext *ctx, gboolean from_breakpoint); @@ -814,25 +804,13 @@ mono_debugger_agent_init_internal (void) mono_profiler_set_domain_loaded_callback (prof, appdomain_load); mono_profiler_set_domain_unloading_callback (prof, appdomain_start_unload); mono_profiler_set_domain_unloaded_callback (prof, appdomain_unload); - mono_profiler_set_thread_started_callback (prof, thread_startup); - mono_profiler_set_thread_stopped_callback (prof, thread_end); mono_profiler_set_assembly_loaded_callback (prof, assembly_load); mono_profiler_set_assembly_unloading_callback (prof, assembly_unload); - mono_profiler_set_jit_done_callback (prof, jit_done); mono_profiler_set_jit_failed_callback (prof, jit_failed); mono_profiler_set_gc_finalizing_callback (prof, gc_finalizing); mono_profiler_set_gc_finalized_callback (prof, gc_finalized); - mono_native_tls_alloc (&debugger_tls_id, NULL); - - /* Needed by the hash_table_new_type () call below */ - mono_gc_base_init (); - - thread_to_tls = mono_g_hash_table_new_type_internal ((GHashFunc)mono_object_hash_internal, NULL, MONO_HASH_KEY_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger TLS Table"); - - tid_to_thread = mono_g_hash_table_new_type_internal (NULL, NULL, MONO_HASH_VALUE_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger Thread Table"); - - tid_to_thread_obj = mono_g_hash_table_new_type_internal (NULL, NULL, MONO_HASH_VALUE_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger Thread Object Table"); + mono_init_debugger_agent_common (&prof); pending_assembly_loads = g_ptr_array_new (); @@ -852,7 +830,6 @@ mono_debugger_agent_init_internal (void) } mono_de_set_log_level (log_level, log_file); - ids_init (); objrefs_init (); suspend_init (); @@ -1636,6 +1613,31 @@ static GHashTable *obj_to_objref; /* Protected by the dbg lock */ static MonoGHashTable *suspended_objs; +static void +mono_init_debugger_agent_common (MonoProfilerHandle *prof) +{ + ids_init (); + + event_requests = g_ptr_array_new (); + + pending_assembly_loads = g_ptr_array_new (); + + mono_profiler_set_thread_started_callback (*prof, thread_startup); + mono_profiler_set_thread_stopped_callback (*prof, thread_end); + mono_profiler_set_jit_done_callback (*prof, jit_done); + + mono_native_tls_alloc (&debugger_tls_id, NULL); + + /* Needed by the hash_table_new_type () call below */ + mono_gc_base_init (); + + thread_to_tls = mono_g_hash_table_new_type_internal ((GHashFunc)mono_object_hash_internal, NULL, MONO_HASH_KEY_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger TLS Table"); + + tid_to_thread = mono_g_hash_table_new_type_internal (NULL, NULL, MONO_HASH_VALUE_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger Thread Table"); + + tid_to_thread_obj = mono_g_hash_table_new_type_internal (NULL, NULL, MONO_HASH_VALUE_GC, MONO_ROOT_SOURCE_DEBUGGER, NULL, "Debugger Thread Object Table"); +} + #ifdef TARGET_WASM void mono_init_debugger_agent_for_wasm (int log_level_parm, MonoProfilerHandle *prof) @@ -1646,23 +1648,17 @@ mono_init_debugger_agent_for_wasm (int log_level_parm, MonoProfilerHandle *prof) int ntransports = 0; DebuggerTransport *transports = mono_debugger_agent_get_transports (&ntransports); - ids_init(); objrefs = g_hash_table_new_full (NULL, NULL, NULL, mono_debugger_free_objref); obj_to_objref = g_hash_table_new (NULL, NULL); - pending_assembly_loads = g_ptr_array_new (); log_level = log_level_parm; - event_requests = g_ptr_array_new (); + vm_start_event_sent = TRUE; transport = &transports [0]; - memset(&debugger_wasm_thread, 0, sizeof(DebuggerTlsData)); - mono_native_tls_alloc (&debugger_tls_id, NULL); - mono_native_tls_set_value (debugger_tls_id, &debugger_wasm_thread); - agent_config.enabled = TRUE; - mono_profiler_set_jit_done_callback (*prof, jit_done); + mono_init_debugger_agent_common (prof); } void @@ -2255,14 +2251,17 @@ save_thread_context (MonoContext *ctx) void mono_wasm_save_thread_context (void) { - debugger_wasm_thread.really_suspended = TRUE; - mono_thread_state_init_from_current (&debugger_wasm_thread.context); + DebuggerTlsData* tls = mono_wasm_get_tls (); + tls->really_suspended = TRUE; + mono_thread_state_init_from_current (&tls->context); } DebuggerTlsData* mono_wasm_get_tls (void) { - return &debugger_wasm_thread; + MonoThread *thread = mono_thread_current (); + GET_TLS_DATA_FROM_THREAD (thread); + return tls; } #endif @@ -2855,7 +2854,11 @@ wait_for_suspend (void) static gboolean is_suspended (void) { +#ifdef HOST_WASM + return true; +#else return count_threads_to_wait_for () == 0; +#endif } static void @@ -3762,6 +3765,7 @@ process_event (EventKind event, gpointer arg, gint32 il_offset, MonoContext *ctx #ifdef TARGET_WASM PRINT_DEBUG_MSG (1, "[%p] Sent %d events %s(%d), suspend=%d.\n", (gpointer) (gsize) mono_native_thread_id_get (), nevents, event_to_string (event), ecount, suspend_policy); + mono_wasm_save_thread_context(); #endif send_success = send_packet (CMD_SET_EVENT, CMD_COMPOSITE, &buf); @@ -3910,8 +3914,9 @@ thread_startup (MonoProfiler *prof, uintptr_t tid) /* * suspend_vm () could have missed this thread, so wait for a resume. */ - +#ifndef HOST_WASM suspend_current_func (); +#endif } static void @@ -9313,7 +9318,7 @@ thread_commands (int command, guint8 *p, guint8 *end, Buffer *buf) // Wait for suspending if it already started // FIXME: Races with suspend_count -#ifndef HOST_WASI +#if !defined(HOST_WASI) && !defined(HOST_WASM) while (!is_suspended ()) { if (suspend_count) wait_for_suspend (); @@ -9498,9 +9503,7 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf) int objid; ErrorCode err; MonoThread *thread_obj; -#ifndef TARGET_WASM MonoInternalThread *thread; -#endif int pos, i, len, frame_idx; StackFrame *frame; MonoDebugMethodJitInfo *jit; @@ -9514,16 +9517,10 @@ frame_commands (int command, guint8 *p, guint8 *end, Buffer *buf) if (err != ERR_NONE) return err; -#ifndef TARGET_WASM thread = THREAD_TO_INTERNAL (thread_obj); -#endif id = decode_id (p, &p, end); -#ifndef TARGET_WASM GET_TLS_DATA_FROM_THREAD (thread); -#else - GET_TLS_DATA_FROM_THREAD (); -#endif g_assert (tls); for (i = 0; i < tls->frame_count; ++i) { diff --git a/src/mono/mono/component/mini-wasm-debugger.c b/src/mono/mono/component/mini-wasm-debugger.c index 98da53aa693b4..f9071eafef1bf 100644 --- a/src/mono/mono/component/mini-wasm-debugger.c +++ b/src/mono/mono/component/mini-wasm-debugger.c @@ -41,7 +41,7 @@ EMSCRIPTEN_KEEPALIVE gboolean mono_wasm_send_dbg_command_with_parms (int id, Mdb //JS functions imported that we use -extern void mono_wasm_fire_debugger_agent_message (void); +extern void mono_wasm_fire_debugger_agent_message_with_data (const char *data, int len); 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 @@ -382,7 +382,7 @@ mono_wasm_send_dbg_command_with_parms (int id, MdbgProtCommandSet command_set, i goto done; } MdbgProtBuffer bufWithParms; - buffer_init (&bufWithParms, 128); + m_dbgprot_buffer_init (&bufWithParms, 128); m_dbgprot_buffer_add_data (&bufWithParms, data, size); if (!write_value_to_buffer(&bufWithParms, valtype, newvalue)) { mono_wasm_add_dbg_command_received(0, id, 0, 0); @@ -410,11 +410,11 @@ mono_wasm_send_dbg_command (int id, MdbgProtCommandSet command_set, int command, } ss_calculate_framecount (NULL, NULL, TRUE, NULL, NULL); MdbgProtBuffer buf; - buffer_init (&buf, 128); gboolean no_reply; MdbgProtErrorCode error = 0; if (command_set == MDBGPROT_CMD_SET_VM && command == MDBGPROT_CMD_VM_INVOKE_METHOD ) { + m_dbgprot_buffer_init (&buf, 128); DebuggerTlsData* tls = mono_wasm_get_tls (); InvokeData invoke_data; memset (&invoke_data, 0, sizeof (InvokeData)); @@ -426,6 +426,7 @@ mono_wasm_send_dbg_command (int id, MdbgProtCommandSet command_set, int command, char* assembly_name = m_dbgprot_decode_string (data, &data, data + size); if (assembly_name == NULL) { + m_dbgprot_buffer_init (&buf, 128); m_dbgprot_buffer_add_int (&buf, 0); m_dbgprot_buffer_add_int (&buf, 0); } @@ -435,16 +436,20 @@ mono_wasm_send_dbg_command (int id, MdbgProtCommandSet command_set, int command, int symfile_size = 0; const unsigned char* assembly_bytes = mono_wasm_get_assembly_bytes (assembly_name, &assembly_size); const unsigned char* pdb_bytes = mono_get_symfile_bytes_from_bundle (assembly_name, &symfile_size); + m_dbgprot_buffer_init (&buf, assembly_size + symfile_size); m_dbgprot_buffer_add_byte_array (&buf, (uint8_t *) assembly_bytes, assembly_size); m_dbgprot_buffer_add_byte_array (&buf, (uint8_t *) pdb_bytes, symfile_size); } } else + { + m_dbgprot_buffer_init (&buf, 128); error = mono_process_dbg_packet (id, command_set, command, &no_reply, data, data + size, &buf); + } mono_wasm_add_dbg_command_received (error == MDBGPROT_ERR_NONE, id, buf.buf, buf.p-buf.buf); - buffer_free (&buf); + m_dbgprot_buffer_free (&buf); result = TRUE; done: MONO_EXIT_GC_UNSAFE; @@ -454,9 +459,7 @@ mono_wasm_send_dbg_command (int id, MdbgProtCommandSet command_set, int command, static gboolean receive_debugger_agent_message (void *data, int len) { - mono_wasm_add_dbg_command_received(1, 0, data, len); - mono_wasm_save_thread_context(); - mono_wasm_fire_debugger_agent_message (); + mono_wasm_fire_debugger_agent_message_with_data ((const char*)data, len); return FALSE; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs index 731001be4fe3a..2ac407ec6056b 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs @@ -405,7 +405,22 @@ public ExecutionContext(MonoSDBHelper sdbAgent, int id, object auxData, PauseOnE SdbAgent = sdbAgent; PauseOnExceptions = pauseOnExceptions; } - + 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; @@ -416,6 +431,8 @@ public ExecutionContext(MonoSDBHelper sdbAgent, int id, object auxData, PauseOnE public bool IsResumedAfterBp { get; set; } public int ThreadId { get; set; } public int Id { get; set; } + public ExecutionContext ParentContext { get; private set; } + public SessionId SessionId { get; private set; } public bool PausedOnWasm { get; set; } @@ -431,8 +448,8 @@ public ExecutionContext(MonoSDBHelper sdbAgent, int id, object auxData, PauseOnE public string[] LoadedFiles { get; set; } internal DebugStore store; - internal MonoSDBHelper SdbAgent { get; init; } - public TaskCompletionSource Source { get; } = new TaskCompletionSource(); + internal MonoSDBHelper SdbAgent { get; private set; } + public TaskCompletionSource Source { get; private set; } = new TaskCompletionSource(); private Dictionary perScopeCaches { get; } = new Dictionary(); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs index 267e9bc0dd004..41a1ea5d058c9 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/Firefox/FirefoxMonoProxy.cs @@ -254,11 +254,11 @@ protected override async Task AcceptEvent(SessionId sessionId, JObject arg var topFunc = args["frame"]["displayName"].Value(); switch (topFunc) { - case "mono_wasm_fire_debugger_agent_message": - case "_mono_wasm_fire_debugger_agent_message": + 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, token); + return await OnReceiveDebuggerAgentEvent(sessionId, args, GetLastDebuggerAgentBuffer(args), token); } default: ctx.PausedOnWasm = false; @@ -694,15 +694,28 @@ internal override void SaveLastDebuggerAgentBufferReceivedToContext(SessionId se 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) + 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"]?["value"]?["value"]?.Value()); + byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?[0]?["value"]?["value"]?.Value()); using var retDebuggerCmdReader = new MonoBinaryReader(newBytes); retDebuggerCmdReader.ReadBytes(11); retDebuggerCmdReader.ReadByte(); @@ -817,7 +830,7 @@ internal override Task SendMonoCommand(SessionId id, MonoCommands cmd, C return SendCommand(id, "evaluateJSAsync", o, token); } - internal override async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, ExecutionContext context, CancellationToken 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; @@ -852,7 +865,8 @@ internal override async Task OnSourceFileAdded(SessionId sessionId, SourceFile s }); } await SendEvent(sessionId, "", sourcesJObj, token); - + if (!resolveBreakpoints) + return; foreach (var req in context.BreakpointRequests.Values) { if (req.TryResolve(source)) @@ -970,8 +984,8 @@ private async Task GetFrames(SessionId sessionId, ExecutionContext context 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" || - function_name == "_mono_wasm_fire_debugger_agent_message" || + 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); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index d6803096f8ec7..2ebf69bdcd605 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -13,6 +13,8 @@ using Newtonsoft.Json.Linq; using System.Net.Http; using BrowserDebugProxy; +using static System.Formats.Asn1.AsnWriter; +using System.Reflection; namespace Microsoft.WebAssembly.Diagnostics { @@ -152,9 +154,19 @@ protected override async Task AcceptEvent(SessionId sessionId, JObject par case "Debugger.paused": { - // Don't process events from sessions we aren't tracking - if (!contexts.ContainsKey(sessionId)) - return false; + if (args["asyncStackTraceId"] != null) + { + if (!contexts.TryGetValue(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(); @@ -175,17 +187,16 @@ protected override async Task AcceptEvent(SessionId sessionId, JObject par await ReloadSymbolsFromSymbolServer(sessionId, GetContext(sessionId), token); return true; } - case "mono_wasm_fire_debugger_agent_message": - case "_mono_wasm_fire_debugger_agent_message": + case "mono_wasm_fire_debugger_agent_message_with_data_to_pause": + case "_mono_wasm_fire_debugger_agent_message_with_data_to_pause": + try { - try { - return await OnReceiveDebuggerAgentEvent(sessionId, args, token); - } - catch (Exception) //if the page is refreshed maybe it stops here. - { - await SendResume(sessionId, token); - return true; - } + 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; } } break; @@ -198,8 +209,11 @@ protected override async Task AcceptEvent(SessionId sessionId, JObject par case "Target.attachedToTarget": { - if (args["targetInfo"]["type"]?.ToString() == "page") + var targetType = args["targetInfo"]["type"]?.ToString(); + if (targetType == "page") await AttachToTarget(new SessionId(args["sessionId"]?.ToString()), token); + else if (targetType == "worker") + CreateWorkerExecutionContext(new SessionId(args["sessionId"]?.ToString()), new SessionId(parms["sessionId"]?.ToString())); break; } @@ -212,6 +226,22 @@ protected override async Task AcceptEvent(SessionId sessionId, JObject par return false; } + + protected void CreateWorkerExecutionContext(SessionId workerSessionId, SessionId originSessionId) + { + if (!contexts.TryGetValue(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] = context.CreateChildAsyncExecutionContext(workerSessionId); + } + protected virtual async Task SendResume(SessionId id, CancellationToken token) { await SendCommand(id, "Debugger.resume", new JObject(), token); @@ -1077,6 +1107,8 @@ protected virtual async Task SendCallStack(SessionId sessionId, ExecutionC data, hitBreakpoints = bp_list, }); + if (args["asyncStackTraceId"] != null) + o["asyncStackTraceId"] = args["asyncStackTraceId"]; if (!await EvaluateCondition(sessionId, context, context.CallStack.First(), bp, token)) { context.ClearState(); @@ -1092,16 +1124,29 @@ internal virtual void SaveLastDebuggerAgentBufferReceivedToContext(SessionId ses { } - internal async Task OnReceiveDebuggerAgentEvent(SessionId sessionId, JObject args, CancellationToken token) + 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); - var res = await debuggerAgentBufferTask; - if (!res.IsOk) + if (!debuggerAgentBuffer.IsOk || debuggerAgentBuffer.Value?["result"].Value().Count == 0) + { + logger.LogTrace($"Unexpected DebuggerAgentBufferReceived {debuggerAgentBuffer}"); return false; - + } ExecutionContext context = GetContext(sessionId); - byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value()); + 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 @@ -1157,6 +1202,10 @@ internal async Task OnReceiveDebuggerAgentEvent(SessionId sessionId, JObje 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; @@ -1458,12 +1507,13 @@ private async Task SetMonoBreakpoint(SessionId sessionId, string req return bp; } - internal virtual async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, ExecutionContext context, CancellationToken token) + 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; foreach (var req in context.BreakpointRequests.Values) { if (req.TryResolve(source)) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 6861eef3ccbbc..e2f6d2dfd6729 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -839,6 +839,14 @@ public MonoSDBHelper(MonoProxy proxy, ILogger logger, SessionId sessionId) 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; diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/AsyncTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/AsyncTests.cs index 96dc8f25c07f9..ec2aa8a200f9c 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/AsyncTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/AsyncTests.cs @@ -22,7 +22,7 @@ public AsyncTests(ITestOutputHelper testOutput) : base(testOutput) // FIXME: check object properties.. //FIXME: function name - [ConditionalTheory(nameof(RunningOnChrome))] + [ConditionalTheory(nameof(WasmSingleThreaded), nameof(RunningOnChrome))] [InlineData("ContinueWithStaticAsync", "DebuggerTests.AsyncTests.ContinueWithTests.ContinueWithStaticAsync.AnonymousMethod__3_0")] [InlineData("ContinueWithInstanceAsync", "DebuggerTests.AsyncTests.ContinueWithTests.ContinueWithInstanceAsync.AnonymousMethod__5_0")] public async Task AsyncLocalsInContinueWith(string method_name, string expected_method_name) => await CheckInspectLocalsAtBreakpointSite( @@ -82,7 +82,7 @@ public async Task AsyncLocalsInNestedContinueWithStaticBlock() => await CheckIns }, "locals"); }); - [Theory] + [ConditionalTheory(nameof(WasmSingleThreaded), nameof(RunningOnChrome))] [InlineData("Run", 246, 16, 252, 16, "RunCSharpScope")] [InlineData("RunContinueWith", 277, 20, 283, 20, "RunContinueWithSameVariableName")] [InlineData("RunNestedContinueWith", 309, 24, 315, 24, "RunNestedContinueWithSameVariableName.AnonymousMethod__1")] diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs index 13abc58c2748a..4daa0be41c632 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/BreakpointTests.cs @@ -245,7 +245,7 @@ public async Task JSConditionalBreakpoint(string condition, int line_bp, int col await SetBreakpoint("/debugger-driver.html", line_bp, column_bp, condition: condition); await SetBreakpoint("/debugger-driver.html", 80, 11); - await EvaluateAndCheck( + var pause_location = await EvaluateAndCheck( "window.setTimeout(function() { conditional_breakpoint_test(5, 10, null); }, 1);", "debugger-driver.html", line_expected, column_expected, "conditional_breakpoint_test"); } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs index 55d59bd500508..c55c2b15efd7b 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/DebuggerTestBase.cs @@ -37,6 +37,11 @@ public static WasmHost RunningOn #else => WasmHost.Firefox; #endif + + public static bool WasmMultiThreaded => EnvironmentVariables.WasmTestsUsingVariant == "multithreaded"; + + public static bool WasmSingleThreaded => !WasmMultiThreaded; + public static bool RunningOnChrome => RunningOn == WasmHost.Chrome; public static bool RunningOnChromeAndLinux => RunningOn == WasmHost.Chrome && PlatformDetection.IsLinux; @@ -144,15 +149,17 @@ public virtual async Task InitializeAsync() { Func)>> fn = (client, token) => { - Func)> getInitCmdFn = (cmd) => (cmd, client.SendCommand(cmd, null, token)); + Func)> getInitCmdFn = (cmd, args) => (cmd, client.SendCommand(cmd, args, token)); var init_cmds = new List<(string, Task)> { - getInitCmdFn("Profiler.enable"), - getInitCmdFn("Runtime.enable"), - getInitCmdFn("Debugger.enable"), - getInitCmdFn("Runtime.runIfWaitingForDebugger") + getInitCmdFn("Profiler.enable", null), + getInitCmdFn("Runtime.enable", null), + getInitCmdFn("Debugger.enable", null), + getInitCmdFn("Runtime.runIfWaitingForDebugger", null), + getInitCmdFn("Debugger.setAsyncCallStackDepth", JObject.FromObject(new { maxDepth = 32 })), + getInitCmdFn("Target.setAutoAttach", JObject.FromObject(new { autoAttach = true, waitForDebuggerOnStart = true, flatten = true })) + //getInitCmdFn("ServiceWorker.enable", null) }; - return init_cmds; }; @@ -182,6 +189,7 @@ protected Task DefaultScriptParsedHandler(JObject ar { var script_id = args?["scriptId"]?.Value(); var url = args["url"]?.Value(); + script_id += args["sessionId"]?.Value(); if (script_id.StartsWith("dotnet://")) { var dbgUrl = args["dotNetUrl"]?.Value(); @@ -322,7 +330,7 @@ internal async Task CheckInspectLocalsAtBreakpointSite(string type, string metho internal virtual void CheckLocation(string script_loc, int line, int column, Dictionary scripts, JToken location) { - var loc_str = $"{ scripts[location["scriptId"].Value()] }" + + var loc_str = $"{ scripts[location["scriptId"].Value()+cli.CurrentSessionId.sessionId] }" + $"#{ location["lineNumber"].Value() }" + $"#{ location["columnNumber"].Value() }"; diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EnvironmentVariables.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EnvironmentVariables.cs index 0091ef0a02781..5149888049b7c 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EnvironmentVariables.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EnvironmentVariables.cs @@ -13,4 +13,5 @@ internal static class EnvironmentVariables public static readonly string? TestLogPath = Environment.GetEnvironmentVariable("TEST_LOG_PATH"); public static readonly bool SkipCleanup = Environment.GetEnvironmentVariable("SKIP_CLEANUP") == "1" || Environment.GetEnvironmentVariable("SKIP_CLEANUP") == "true"; + public static readonly string? WasmTestsUsingVariant = Environment.GetEnvironmentVariable("WASM_TESTS_USING_VARIANT"); } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests2.cs b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests2.cs index a03dced95ef70..e43a9f3e0974a 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests2.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/EvaluateOnCallFrameTests2.cs @@ -205,7 +205,7 @@ public async Task EvaluateStaticClassInvalidField() => await CheckInspectLocalsA AssertEqual("Failed to resolve member access for DebuggerTests.InvalidEvaluateStaticClass.StaticProperty2", res.Error["result"]?["description"]?.Value(), "wrong error message"); }); - [ConditionalFact(nameof(RunningOnChrome))] + [ConditionalFact(nameof(WasmSingleThreaded), nameof(RunningOnChrome))] public async Task AsyncLocalsInContinueWithBlock() => await CheckInspectLocalsAtBreakpointSite( "DebuggerTests.AsyncTests.ContinueWithTests", "ContinueWithStaticAsync", 4, "DebuggerTests.AsyncTests.ContinueWithTests.ContinueWithStaticAsync.AnonymousMethod__3_0", "window.setTimeout(function() { invoke_static_method('[debugger-test] DebuggerTests.AsyncTests.ContinueWithTests:RunAsync'); })", diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/FirefoxInspectorClient.cs b/src/mono/wasm/debugger/DebuggerTestSuite/FirefoxInspectorClient.cs index d0eff4c5e2c12..931c4f4b1f52c 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/FirefoxInspectorClient.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/FirefoxInspectorClient.cs @@ -125,7 +125,7 @@ public override async Task ProcessCommand(Result command, CancellationToken toke if (res["type"]?.Value() == "newSource") { var method = res["type"]?.Value(); - return onEvent(method, res, token); + return onEvent("", method, res, token); } if (res["type"]?.Value() == "target-available-form" && res["target"] is JObject target) @@ -192,7 +192,7 @@ public override async Task ProcessCommand(Result command, CancellationToken toke break; } } - return onEvent(method, res, token); + return onEvent("", method, res, token); } return null; } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs index 62462481685a6..b86838431448b 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Inspector.cs @@ -26,7 +26,7 @@ class Inspector ConcurrentDictionary> notifications = new (); ConcurrentDictionary>> eventListeners = new (); - + ConcurrentQueue<(string, JObject)> nextNotifications = new (); //in a multithreaded runtime we can receive more than one pause at same time public const string PAUSE = "pause"; public const string APP_READY = "app-ready"; public CancellationToken Token { get; } @@ -81,12 +81,22 @@ public Task WaitFor(string what) { if (tcs.Task.IsCompleted) { + Client.CurrentSessionId = new SessionId(tcs.Task.Result["sessionId"]?.Value()); notifications.Remove(what, out _); return tcs.Task; } throw new Exception($"Invalid internal state, waiting for {what} while another wait is already setup"); } + else if (nextNotifications.TryDequeue(out (string what, JObject args) notification)) + { + var n = new TaskCompletionSource(); + Client.CurrentSessionId = new SessionId(notification.args["sessionId"]?.Value()); + if (what != notification.what) + throw new Exception($"Unexpected different notification type"); + n.SetResult(notification.args); + return n.Task; + } else { var n = new TaskCompletionSource(); @@ -106,8 +116,12 @@ void NotifyOf(string what, JObject args) if (notifications.TryGetValue(what, out TaskCompletionSource? tcs)) { if (tcs.Task.IsCompleted) - throw new Exception($"Invalid internal state. Notifying for {what} again, but the previous one hasn't been read."); - + { + nextNotifications.Enqueue((what, args)); + return; + //throw new Exception($"Invalid internal state. Notifying for {what} again, but the previous one hasn't been read."); + } + Client.CurrentSessionId = new SessionId(args["sessionId"]?.Value()); notifications[what].SetResult(args); notifications.Remove(what, out _); } @@ -194,14 +208,28 @@ void FailAllWaiters(Exception? exception = null) return ($"console.{type}: {output}", type); } - async Task OnMessage(string method, JObject args, CancellationToken token) + async Task OnMessage(string sessionId, string method, JObject args, CancellationToken token) { bool fail = false; switch (method) { + case "Target.attachedToTarget": + { + var sessionIdNewTarget = new SessionId(args["sessionId"]?.Value()); + await Client.SendCommand(sessionIdNewTarget, "Profiler.enable", null, token); + await Client.SendCommand(sessionIdNewTarget, "Runtime.enable", null, token); + await Client.SendCommand(sessionIdNewTarget, "Debugger.enable", null, token); + await Client.SendCommand(sessionIdNewTarget, "Runtime.runIfWaitingForDebugger", null, token); + await Client.SendCommand(sessionIdNewTarget, "Debugger.setAsyncCallStackDepth", JObject.FromObject(new { maxDepth = 32}), token); + break; + } case "Debugger.paused": + { + if (sessionId != "") + args.Add("sessionId", sessionId); NotifyOf(PAUSE, args); break; + } case "Mono.runtimeReady": { _gotRuntimeReady = true; @@ -262,6 +290,8 @@ async Task OnMessage(string method, JObject args, CancellationToken token) if (eventListeners.TryGetValue(method, out Func>? listener) && listener != null) { + if (sessionId != "") + args.Add("sessionId", sessionId); ProtocolEventHandlerReturn result = await listener(args, token).ConfigureAwait(false); if (result is ProtocolEventHandlerReturn.RemoveHandler) eventListeners.Remove(method, out _); diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/InspectorClient.cs b/src/mono/wasm/debugger/DebuggerTestSuite/InspectorClient.cs index ad4aab960c3c7..795faceab9420 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/InspectorClient.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/InspectorClient.cs @@ -18,9 +18,11 @@ namespace DebuggerTests internal class InspectorClient : DevToolsClient { protected Dictionary> pending_cmds = new Dictionary>(); - protected Func onEvent; + protected Func onEvent; protected int next_cmd_id; + public SessionId CurrentSessionId { get; set; } = SessionId.Null; + public InspectorClient(ILogger logger) : base(logger) { } protected override async Task SetupConnection(Uri webserverUri, CancellationToken token) @@ -34,7 +36,7 @@ protected virtual Task HandleMessage(string msg, CancellationToken token) var res = JObject.Parse(msg); if (res["id"] == null) - return onEvent(res["method"].Value(), res["params"] as JObject, token); + return onEvent(res["sessionId"]?.Value(), res["method"].Value(), res["params"] as JObject, token); var id = res.ToObject(); if (!pending_cmds.Remove(id, out var item)) @@ -51,7 +53,7 @@ public virtual async Task ProcessCommand(Result command, CancellationToken token public virtual async Task Connect( Uri uri, - Func onEvent, + Func onEvent, CancellationTokenSource cts) { this.onEvent = onEvent; @@ -75,7 +77,7 @@ public virtual async Task Connect( } public Task SendCommand(string method, JObject args, CancellationToken token) - => SendCommand(new SessionId(null), method, args, token); + => SendCommand(CurrentSessionId, method, args, token); public virtual Task SendCommand(SessionId sessionId, string method, JObject args, CancellationToken token) { @@ -90,6 +92,8 @@ public virtual Task SendCommand(SessionId sessionId, string method, JObj @params = args }); + if (sessionId != SessionId.Null) + o.Add("sessionId", sessionId.sessionId); var tcs = new TaskCompletionSource(); pending_cmds[new MessageId(sessionId.sessionId, id)] = tcs; diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs index 737ca1a5431cc..fed9b6abb8d8e 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/MiscTests.cs @@ -858,7 +858,7 @@ public async Task GetSourceEmbeddedSource() Assert.False(source.Value["scriptSource"].Value().Contains("// Unable to read document")); } - [ConditionalFact(nameof(RunningOnChrome))] + [ConditionalFact(nameof(WasmSingleThreaded), nameof(RunningOnChrome))] public async Task InspectTaskAtLocals() => await CheckInspectLocalsAtBreakpointSite( "InspectTask", "RunInspectTask", @@ -1148,5 +1148,31 @@ await StepAndCheck(StepKind.Resume, "dotnet://debugger-test.dll/debugger-test.cs } ); } + + [ConditionalFact(nameof(WasmMultiThreaded))] + public async Task TestDebugUsingMultiThreadedRuntime() + { + var bp = await SetBreakpointInMethod("debugger-test.dll", "MultiThreadedTest", "Write", 2); + var expression = $"{{ invoke_static_method('[debugger-test] MultiThreadedTest:Run'); }}"; + + var pause_location = await EvaluateAndCheck( + "window.setTimeout(function() {" + expression + "; }, 1);", + "dotnet://debugger-test.dll/debugger-test.cs", 1598, 8, + "MultiThreadedTest.Write"); + + var locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + Assert.Equal(locals[1]["value"]["type"], "number"); + Assert.Equal(locals[1]["name"], "currentThread"); + + pause_location = await StepAndCheck(StepKind.Resume, "dotnet://debugger-test.dll/debugger-test.cs", 1598, 8, "MultiThreadedTest.Write"); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + Assert.Equal(locals[1]["value"]["type"], "number"); + Assert.Equal(locals[1]["name"], "currentThread"); + + pause_location = await StepAndCheck(StepKind.Resume, "dotnet://debugger-test.dll/debugger-test.cs", 1598, 8, "MultiThreadedTest.Write"); + locals = await GetProperties(pause_location["callFrames"][0]["callFrameId"].Value()); + Assert.Equal(locals[1]["value"]["type"], "number"); + Assert.Equal(locals[1]["name"], "currentThread"); + } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs index 868eb1d7006bf..a3f58e484ca71 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/SteppingTests.cs @@ -717,7 +717,7 @@ await EvaluateAndCheck( await StepAndCheck(StepKind.Out, source_file, 15, 4, "TestAsyncStepOut"); } - [Fact] + [ConditionalFact(nameof(WasmSingleThreaded))] public async Task ResumeOutOfAsyncMethodToAsyncCallerWithBreakpoint() { string source_file = "dotnet://debugger-test.dll/debugger-async-step.cs"; diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessOptions.cs b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessOptions.cs index c3b9bc6c58880..c22f0563747e7 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessOptions.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessOptions.cs @@ -14,6 +14,8 @@ public class TestHarnessOptions : ProxyOptions public string PagePath { get; set; } public string NodeApp { get; set; } public string BrowserParms { get; set; } + public bool WebServerUseCors { get; set; } + public bool WebServerUseCrossOriginPolicy { get; set; } public Func, Task> ExtractConnUrl { get; set; } } } diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs index 5124e349980fc..21cfe68664a5b 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessProxy.cs @@ -14,6 +14,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Microsoft.WebAssembly.Diagnostics; using Xunit.Abstractions; @@ -36,6 +37,15 @@ public class TestHarnessProxy public static Task Start(string appPath, string pagePath, string url, ITestOutputHelper testOutput) { + TestHarnessOptions options = new() + { + AppPath = appPath, + PagePath = pagePath, + DevToolsUrl = new Uri(url), + WebServerUseCors = false, + WebServerUseCrossOriginPolicy = true + }; + lock (proxyLock) { if (hostTask != null) @@ -76,13 +86,17 @@ public static Task Start(string appPath, string pagePath, string url, ITestOutpu }) .ConfigureServices((ctx, services) => { - services.Configure(ctx.Configuration); - services.Configure(options => + if (options.WebServerUseCors) { - options.AppPath = appPath; - options.PagePath = pagePath; - options.DevToolsUrl = new Uri(url); - }); + services.AddCors(o => o.AddPolicy("AnyCors", builder => + { + builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader() + .WithExposedHeaders("*"); + })); + } + services.AddSingleton(Options.Create(options)); }) .UseStartup() .UseUrls(Endpoint.ToString()) diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs index 01a3d4751c389..168c17d4a593b 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/TestHarnessStartup.cs @@ -75,15 +75,14 @@ async Task SendNodeList(HttpContext context) } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IOptionsMonitor optionsAccessor, IWebHostEnvironment env, ILogger logger, ILoggerFactory loggerFactory) + public void Configure(IApplicationBuilder app, IOptions optionsContainer, IWebHostEnvironment env, ILogger logger, ILoggerFactory loggerFactory) { this.Logger = logger; this._loggerFactory = loggerFactory; + TestHarnessOptions options = optionsContainer.Value; app.UseWebSockets(); - TestHarnessOptions options = optionsAccessor.CurrentValue; - var provider = new FileExtensionContentTypeProvider(); provider.Mappings[".wasm"] = "application/wasm"; @@ -92,9 +91,21 @@ public void Configure(IApplicationBuilder app, IOptionsMonitor { + if (options.WebServerUseCrossOriginPolicy) + { + context.Context.Response.Headers.Add("Cross-Origin-Embedder-Policy", "require-corp"); + context.Context.Response.Headers.Add("Cross-Origin-Opener-Policy", "same-origin"); + } + } }); + if (options.WebServerUseCors) + { + app.UseCors("AnyCors"); + } + app.UseRouter(router => { router.MapGet("launch-host-and-connect", async context => diff --git a/src/mono/wasm/debugger/Wasm.Debugger.Tests/Wasm.Debugger.Tests.csproj b/src/mono/wasm/debugger/Wasm.Debugger.Tests/Wasm.Debugger.Tests.csproj index 244686932557b..5d237210903dd 100644 --- a/src/mono/wasm/debugger/Wasm.Debugger.Tests/Wasm.Debugger.Tests.csproj +++ b/src/mono/wasm/debugger/Wasm.Debugger.Tests/Wasm.Debugger.Tests.csproj @@ -66,6 +66,7 @@ <_DotnetCommand Condition="'$(OS)' == 'Windows_NT'">dotnet.exe $(_DotnetCommand) test DebuggerTestSuite/DebuggerTestSuite.dll + $(RunScriptCommand) /e:WASM_TESTS_USING_VARIANT=multithreaded $(RunScriptCommand) "-l:trx%3BLogFileName=testResults.trx" $(RunScriptCommand) "-l:console%3BVerbosity=normal" diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs index b7d397d6d25c2..9dca7f3208a1b 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.cs @@ -1394,7 +1394,7 @@ public R2Sample2 (ref double d1) { public void Modify(double newDouble) { r1.d1 = newDouble; } - + public double Run() { return r1.d1; } @@ -1422,7 +1422,7 @@ public static void CheckArguments(ReadOnlySpan parameters) myR1.s2 = new S1(); myR1.s2.d1 = 30; myR1.s2.d2 = 40; - double xyz = 123.0; + double xyz = 123.0; R2Sample2 r2 = new R2Sample2(ref xyz); xyz = 456.0; System.Diagnostics.Debugger.Break(); @@ -1576,3 +1576,27 @@ public static void Run() array.Add(date); } } + +public class MultiThreadedTest +{ + public static void Run() + { + System.Collections.Generic.List myThreads = new(); + for (int i = 0 ; i < 3; i++) + { + var t = new System.Threading.Thread (() => Write("y")); + myThreads.Add(t); + t.Start(); + } + foreach (System.Threading.Thread curThread in myThreads) + { + curThread.Join(); + } + } + static void Write(string input) + { + var currentThread = System.Threading.Thread.CurrentThread.ManagedThreadId; + Console.WriteLine($"Thread:{currentThread} - {input}"); + } +} + diff --git a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj index 4d01709f69382..ca7622136601c 100644 --- a/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj +++ b/src/mono/wasm/debugger/tests/debugger-test/debugger-test.csproj @@ -8,6 +8,7 @@ true library true + <_WasmPThreadPoolSize Condition="'$(WasmEnableThreads)'=='true'">10 true diff --git a/src/mono/wasm/host/WebServerStartup.cs b/src/mono/wasm/host/WebServerStartup.cs index 26fe8e0b11f19..9285c83db6e6b 100644 --- a/src/mono/wasm/host/WebServerStartup.cs +++ b/src/mono/wasm/host/WebServerStartup.cs @@ -168,7 +168,6 @@ public void Configure(IApplicationBuilder app, }); }); - applicationLifetime.ApplicationStarted.Register(() => { TaskCompletionSource tcs = realUrlsAvailableTcs; diff --git a/src/mono/wasm/runtime/debug.ts b/src/mono/wasm/runtime/debug.ts index 66b8cecaa14a6..bb856bf45aa33 100644 --- a/src/mono/wasm/runtime/debug.ts +++ b/src/mono/wasm/runtime/debug.ts @@ -29,14 +29,21 @@ export function mono_wasm_runtime_ready(): void { debugger; } -export function mono_wasm_fire_debugger_agent_message(): void { +export function mono_wasm_fire_debugger_agent_message_with_data_to_pause(base64String: string): void { + //keep this console.assert, otherwise optimization will remove the assignments + console.assert(true, `mono_wasm_fire_debugger_agent_message_with_data ${base64String}`); // eslint-disable-next-line no-debugger debugger; } +export function mono_wasm_fire_debugger_agent_message_with_data(data: number, len: number): void { + const base64String = toBase64StringImpl(new Uint8Array(Module.HEAPU8.buffer, data, len)); + mono_wasm_fire_debugger_agent_message_with_data_to_pause(base64String); +} + export function mono_wasm_add_dbg_command_received(res_ok: boolean, id: number, buffer: number, buffer_len: number): void { - const assembly_data = new Uint8Array(Module.HEAPU8.buffer, buffer, buffer_len); - const base64String = toBase64StringImpl(assembly_data); + const dbg_command = new Uint8Array(Module.HEAPU8.buffer, buffer, buffer_len); + const base64String = toBase64StringImpl(dbg_command); const buffer_obj = { res_ok, res: { diff --git a/src/mono/wasm/runtime/dotnet-legacy.d.ts b/src/mono/wasm/runtime/dotnet-legacy.d.ts index b0cae052255bd..b0dfbc24084e6 100644 --- a/src/mono/wasm/runtime/dotnet-legacy.d.ts +++ b/src/mono/wasm/runtime/dotnet-legacy.d.ts @@ -27,8 +27,8 @@ interface MonoArray extends MonoObject { interface MonoObjectRef extends ManagedPointer { __brandMonoObjectRef: "MonoObjectRef"; } -declare type MemOffset = number | VoidPtr | NativePointer | ManagedPointer; -declare type NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer; +type MemOffset = number | VoidPtr | NativePointer | ManagedPointer; +type NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer; interface WasmRoot { get_address(): MonoObjectRef; get_address_32(): number; @@ -60,7 +60,7 @@ interface WasmRootBuffer { /** * @deprecated Please use methods in top level API object instead */ -declare type BINDINGType = { +type BINDINGType = { /** * @deprecated Please use [JSExportAttribute] instead */ @@ -137,7 +137,7 @@ declare type BINDINGType = { /** * @deprecated Please use methods in top level API object instead */ -declare type MONOType = { +type MONOType = { /** * @deprecated Please use setEnvironmentVariable() instead */ diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index 8eafb1d4deeb5..ed026d82b67c4 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -61,7 +61,7 @@ const linked_functions = [ // mini-wasm-debugger.c "mono_wasm_asm_loaded", - "mono_wasm_fire_debugger_agent_message", + "mono_wasm_fire_debugger_agent_message_with_data", "mono_wasm_debugger_log", "mono_wasm_add_dbg_command_received", "mono_wasm_set_entrypoint_breakpoint", diff --git a/src/mono/wasm/runtime/exports-linker.ts b/src/mono/wasm/runtime/exports-linker.ts index 8329035ae71bf..733bdd952c310 100644 --- a/src/mono/wasm/runtime/exports-linker.ts +++ b/src/mono/wasm/runtime/exports-linker.ts @@ -2,7 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. import MonoWasmThreads from "consts:monoWasmThreads"; -import { mono_wasm_fire_debugger_agent_message, mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint } from "./debug"; +import { mono_wasm_fire_debugger_agent_message_with_data, mono_wasm_fire_debugger_agent_message_with_data_to_pause, mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint } from "./debug"; + import { mono_wasm_release_cs_owned_object } from "./gc-handles"; import { mono_wasm_load_icu_data, mono_wasm_get_icudt_name } from "./icu"; import { mono_wasm_bind_cs_function } from "./invoke-cs"; @@ -48,10 +49,10 @@ export function export_linker(): any { // mini-wasm-debugger.c mono_wasm_asm_loaded, - mono_wasm_fire_debugger_agent_message, mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, - + mono_wasm_fire_debugger_agent_message_with_data, + mono_wasm_fire_debugger_agent_message_with_data_to_pause, // mono-threads-wasm.c schedule_background_exec, diff --git a/src/mono/wasm/runtime/rollup.config.js b/src/mono/wasm/runtime/rollup.config.js index 37f6dd6b9c4f8..299c2534fef55 100644 --- a/src/mono/wasm/runtime/rollup.config.js +++ b/src/mono/wasm/runtime/rollup.config.js @@ -27,7 +27,7 @@ const terserConfig = { mangle: { // because of stack walk at src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs // and unit test at src\libraries\System.Runtime.InteropServices.JavaScript\tests\System.Runtime.InteropServices.JavaScript.Legacy.UnitTests\timers.mjs - keep_fnames: /(mono_wasm_runtime_ready|mono_wasm_fire_debugger_agent_message|mono_wasm_set_timeout_exec)/, + keep_fnames: /(mono_wasm_runtime_ready|mono_wasm_fire_debugger_agent_message_with_data_to_pause|mono_wasm_set_timeout_exec)/, keep_classnames: /(ManagedObject|ManagedError|Span|ArraySegment|WasmRootBuffer|SessionOptionsBuilder)/, }, format: {