Skip to content

Commit

Permalink
[release/6.0-staging][mono][debugger] Showing "Frame not in module" a…
Browse files Browse the repository at this point in the history
…fter vscode-js-debug bump on VS (#88351)

* Backport #87154 #87870 #87979

* fix compilation error
  • Loading branch information
thaystg authored Jul 10, 2023
1 parent 71b40e2 commit 9df36d8
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 47 deletions.
68 changes: 68 additions & 0 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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;
Expand Down Expand Up @@ -288,6 +289,7 @@ internal class ExecutionContext
public object AuxData { get; set; }

public PauseOnExceptionsKind PauseOnExceptions { get; set; }
internal bool Destroyed { get; set; }

public List<Frame> CallStack { get; set; }

Expand Down Expand Up @@ -341,4 +343,70 @@ public PerScopeCache()
{
}
}

internal sealed class ConcurrentExecutionContextDictionary
{
private ConcurrentDictionary<SessionId, ConcurrentBag<ExecutionContext>> contexts = new ConcurrentDictionary<SessionId, ConcurrentBag<ExecutionContext>>();
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<ExecutionContext> contextBag))
return false;
if (contextBag.IsEmpty)
return false;
IEnumerable<ExecutionContext> validContexts = null;
if (ignoreDestroyedContext)
validContexts = contextBag.Where(context => context.Destroyed == false);
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<string, BreakpointRequest> 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<ExecutionContext> bag = contexts.GetOrAdd(sessionId, _ => new ConcurrentBag<ExecutionContext>());
bag.Add(newExecutionContext);
return hasExisting;
}

public void DestroyContext(SessionId sessionId, int id)
{
if (!contexts.TryGetValue(sessionId, out ConcurrentBag<ExecutionContext> 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<ExecutionContext> contextBag))
return;
foreach (ExecutionContext context in contextBag)
context.Destroyed = true;
}

public bool ContainsKey(SessionId sessionId) => contexts.ContainsKey(sessionId);
}
}
89 changes: 43 additions & 46 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal class MonoProxy : DevToolsProxy
private IList<string> urlSymbolServerList;
private static HttpClient client = new HttpClient();
private HashSet<SessionId> sessions = new HashSet<SessionId>();
private Dictionary<SessionId, ExecutionContext> contexts = new Dictionary<SessionId, ExecutionContext>();
internal ConcurrentExecutionContextDictionary Contexts = new ConcurrentExecutionContextDictionary();
private const string sPauseOnUncaught = "pause_on_uncaught";
private const string sPauseOnCaught = "pause_on_caught";

Expand All @@ -32,21 +32,6 @@ public MonoProxy(ILoggerFactory loggerFactory, IList<string> urlSymbolServerList
SdbHelper = new MonoSDBHelper(this, logger);
}

internal ExecutionContext GetContext(SessionId sessionId)
{
if (contexts.TryGetValue(sessionId, out ExecutionContext context))
return context;

throw new ArgumentException($"Invalid Session: \"{sessionId}\"", nameof(sessionId));
}

private bool UpdateContext(SessionId sessionId, ExecutionContext executionContext, out ExecutionContext previousExecutionContext)
{
bool previous = contexts.TryGetValue(sessionId, out previousExecutionContext);
contexts[sessionId] = executionContext;
return previous;
}

internal Task<Result> SendMonoCommand(SessionId id, MonoCommands cmd, CancellationToken token) => SendCommand(id, "Runtime.evaluate", JObject.FromObject(cmd), token);

protected override async Task<bool> AcceptEvent(SessionId sessionId, string method, JObject args, CancellationToken token)
Expand All @@ -56,7 +41,7 @@ protected override async Task<bool> AcceptEvent(SessionId sessionId, string meth
case "Runtime.consoleAPICalled":
{
// Don't process events from sessions we aren't tracking
if (!contexts.TryGetValue(sessionId, out ExecutionContext context))
if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context))
return false;
string type = args["type"]?.ToString();
if (type == "debug")
Expand Down Expand Up @@ -129,7 +114,7 @@ protected override async Task<bool> AcceptEvent(SessionId sessionId, string meth
case "Runtime.exceptionThrown":
{
// Don't process events from sessions we aren't tracking
if (!contexts.TryGetValue(sessionId, out ExecutionContext context))
if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context))
return false;

if (!context.IsRuntimeReady)
Expand All @@ -141,10 +126,22 @@ protected override async Task<bool> AcceptEvent(SessionId sessionId, string meth
break;
}

case "Runtime.executionContextDestroyed":
{
Contexts.DestroyContext(sessionId, args["executionContextId"].Value<int>());
return false;
}

case "Runtime.executionContextsCleared":
{
Contexts.ClearContexts(sessionId);
return false;
}

case "Debugger.paused":
{
// Don't process events from sessions we aren't tracking
if (!contexts.TryGetValue(sessionId, out ExecutionContext context))
if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context))
return false;

if (args?["callFrames"]?.Value<JArray>()?.Count == 0)
Expand Down Expand Up @@ -238,7 +235,7 @@ protected override async Task<bool> AcceptEvent(SessionId sessionId, string meth

private async Task<bool> IsRuntimeAlreadyReadyAlready(SessionId sessionId, CancellationToken token)
{
if (contexts.TryGetValue(sessionId, out ExecutionContext context) && context.IsRuntimeReady)
if (Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context) && context.IsRuntimeReady)
return true;

Result res = await SendMonoCommand(sessionId, MonoCommands.IsRuntimeReady(), token);
Expand All @@ -252,7 +249,7 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J
if (id == SessionId.Null)
await AttachToTarget(id, token);

if (!contexts.TryGetValue(id, out ExecutionContext context))
if (!Contexts.TryGetCurrentExecutionContextValue(id, out ExecutionContext context))
{
// for Dotnetdebugger.* messages, treat them as handled, thus not passing them on to the browser
return method.StartsWith("DotnetDebugger.", StringComparison.OrdinalIgnoreCase);
Expand Down Expand Up @@ -607,7 +604,7 @@ private async Task<bool> CallOnFunction(MessageId id, JObject args, Cancellation

private async Task<bool> OnSetVariableValue(MessageId id, int scopeId, string varName, JToken varValue, CancellationToken token)
{
ExecutionContext ctx = GetContext(id);
ExecutionContext ctx = Contexts.GetCurrentContext(id);
Frame scope = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId);
if (scope == null)
return false;
Expand Down Expand Up @@ -844,7 +841,7 @@ private async Task<bool> OnReceiveDebuggerAgentEvent(SessionId sessionId, JObjec
if (res.IsErr)
return false;

ExecutionContext context = GetContext(sessionId);
ExecutionContext context = Contexts.GetCurrentContext(sessionId);
byte[] newBytes = Convert.FromBase64String(res.Value?["result"]?["value"]?["value"]?.Value<string>());
var retDebuggerCmd = new MemoryStream(newBytes);
var retDebuggerCmdReader = new MonoBinaryReader(retDebuggerCmd);
Expand Down Expand Up @@ -910,7 +907,7 @@ private async Task<bool> OnReceiveDebuggerAgentEvent(SessionId sessionId, JObjec

internal async Task<MethodInfo> LoadSymbolsOnDemand(AssemblyInfo asm, int method_token, SessionId sessionId, CancellationToken token)
{
ExecutionContext context = GetContext(sessionId);
ExecutionContext context = Contexts.GetCurrentContext(sessionId);
if (urlSymbolServerList.Count == 0)
return null;
if (asm.TriedToLoadSymbolsOnDemand)
Expand Down Expand Up @@ -961,22 +958,15 @@ internal async Task<MethodInfo> LoadSymbolsOnDemand(AssemblyInfo asm, int method
private async Task OnDefaultContext(SessionId sessionId, ExecutionContext context, CancellationToken token)
{
Log("verbose", "Default context created, clearing state and sending events");
if (UpdateContext(sessionId, context, out ExecutionContext previousContext))
{
foreach (KeyValuePair<string, BreakpointRequest> kvp in previousContext.BreakpointRequests)
{
context.BreakpointRequests[kvp.Key] = kvp.Value.Clone();
}
context.PauseOnExceptions = previousContext.PauseOnExceptions;
}
Contexts.OnDefaultContextUpdate(sessionId, context);

if (await IsRuntimeAlreadyReadyAlready(sessionId, token))
await RuntimeReady(sessionId, token);
}

private async Task OnResume(MessageId msg_id, CancellationToken token)
{
ExecutionContext ctx = GetContext(msg_id);
ExecutionContext ctx = Contexts.GetCurrentContext(msg_id);
if (ctx.CallStack != null)
{
// Stopped on managed code
Expand All @@ -985,12 +975,12 @@ private async Task OnResume(MessageId msg_id, CancellationToken token)

//discard managed frames
SdbHelper.ClearCache();
GetContext(msg_id).ClearState();
Contexts.GetCurrentContext(msg_id).ClearState();
}

private async Task<bool> Step(MessageId msg_id, StepKind kind, CancellationToken token)
{
ExecutionContext context = GetContext(msg_id);
ExecutionContext context = Contexts.GetCurrentContext(msg_id);
if (context.CallStack == null)
return false;

Expand Down Expand Up @@ -1061,7 +1051,7 @@ private async Task<bool> OnAssemblyLoadedJSEvent(SessionId sessionId, JObject ev
var assembly_data = Convert.FromBase64String(assembly_b64);
var pdb_data = string.IsNullOrEmpty(pdb_b64) ? null : Convert.FromBase64String(pdb_b64);

var context = GetContext(sessionId);
var context = Contexts.GetCurrentContext(sessionId);
foreach (var source in store.Add(sessionId, assembly_data, pdb_data))
{
await OnSourceFileAdded(sessionId, source, context, token);
Expand All @@ -1080,7 +1070,7 @@ private async Task<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scopeId, st
{
try
{
ExecutionContext context = GetContext(msg_id);
ExecutionContext context = Contexts.GetCurrentContext(msg_id);
if (context.CallStack == null)
return false;

Expand Down Expand Up @@ -1121,7 +1111,7 @@ internal async Task<Result> GetScopeProperties(SessionId msg_id, int scopeId, Ca
{
try
{
ExecutionContext ctx = GetContext(msg_id);
ExecutionContext ctx = Contexts.GetCurrentContext(msg_id);
Frame scope = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId);
if (scope == null)
return Result.Err(JObject.FromObject(new { message = $"Could not find scope with id #{scopeId}" }));
Expand Down Expand Up @@ -1180,14 +1170,21 @@ private async Task OnSourceFileAdded(SessionId sessionId, SourceFile source, Exe
{
if (req.TryResolve(source))
{
await SetBreakpoint(sessionId, context.store, req, true, token);
try
{
await SetBreakpoint(sessionId, context.store, req, true, token);
}
catch (Exception e)
{
logger.LogDebug($"Unexpected error on OnSourceFileAdded {e}");
}
}
}
}

internal async Task<DebugStore> LoadStore(SessionId sessionId, CancellationToken token)
{
ExecutionContext context = GetContext(sessionId);
ExecutionContext context = Contexts.GetCurrentContext(sessionId);

if (Interlocked.CompareExchange(ref context.store, new DebugStore(logger), null) != null)
return await context.Source.Task;
Expand Down Expand Up @@ -1239,7 +1236,7 @@ async Task<string[]> GetLoadedFiles(SessionId sessionId, ExecutionContext contex

private async Task<DebugStore> RuntimeReady(SessionId sessionId, CancellationToken token)
{
ExecutionContext context = GetContext(sessionId);
ExecutionContext context = Contexts.GetCurrentContext(sessionId);
if (Interlocked.CompareExchange(ref context.ready, new TaskCompletionSource<DebugStore>(), null) != null)
return await context.ready.Task;

Expand All @@ -1263,7 +1260,7 @@ private async Task<DebugStore> RuntimeReady(SessionId sessionId, CancellationTok

private async Task ResetBreakpoint(SessionId msg_id, MethodInfo method, CancellationToken token)
{
ExecutionContext context = GetContext(msg_id);
ExecutionContext context = Contexts.GetCurrentContext(msg_id);
foreach (var req in context.BreakpointRequests.Values)
{
if (req.Method != null)
Expand All @@ -1279,7 +1276,7 @@ private async Task RemoveBreakpoint(SessionId msg_id, JObject args, bool isEnCRe
{
string bpid = args?["breakpointId"]?.Value<string>();

ExecutionContext context = GetContext(msg_id);
ExecutionContext context = Contexts.GetCurrentContext(msg_id);
if (!context.BreakpointRequests.TryGetValue(bpid, out BreakpointRequest breakpointRequest))
return;

Expand All @@ -1303,7 +1300,7 @@ private async Task RemoveBreakpoint(SessionId msg_id, JObject args, bool isEnCRe

private async Task SetBreakpoint(SessionId sessionId, DebugStore store, BreakpointRequest req, bool sendResolvedEvent, CancellationToken token)
{
ExecutionContext context = GetContext(sessionId);
ExecutionContext context = Contexts.GetCurrentContext(sessionId);
if (req.Locations.Any())
{
Log("debug", $"locations already loaded for {req.Id}");
Expand All @@ -1319,7 +1316,7 @@ private async Task SetBreakpoint(SessionId sessionId, DebugStore store, Breakpoi
.OrderBy(l => l.Column)
.GroupBy(l => l.Id);

logger.LogDebug("BP request for '{req}' runtime ready {context.RuntimeReady}", req, GetContext(sessionId).IsRuntimeReady);
logger.LogDebug("BP request for '{req}' runtime ready {context.RuntimeReady}", req, Contexts.GetCurrentContext(sessionId).IsRuntimeReady);

var breakpoints = new List<Breakpoint>();

Expand Down Expand Up @@ -1415,7 +1412,7 @@ private async Task AttachToTarget(SessionId sessionId, CancellationToken token)
//we only need this check if it's a non-vs debugging
if (sessionId == SessionId.Null)
{
if (!contexts.TryGetValue(sessionId, out ExecutionContext context) || context.PauseOnExceptions == PauseOnExceptionsKind.Unset)
if (!Contexts.TryGetCurrentExecutionContextValue(sessionId, out ExecutionContext context) || context.PauseOnExceptions == PauseOnExceptionsKind.Unset)
{
checkUncaughtExceptions = $"throw \"{sPauseOnUncaught}\";";
checkCaughtExceptions = $"try {{throw \"{sPauseOnCaught}\";}} catch {{}}";
Expand Down
2 changes: 1 addition & 1 deletion src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1408,7 +1408,7 @@ public async Task<string> GetValueFromDebuggerDisplayAttribute(SessionId session

var stringId = getCAttrsRetReader.ReadInt32();
var dispAttrStr = await GetStringValue(sessionId, stringId, token);
ExecutionContext context = proxy.GetContext(sessionId);
ExecutionContext context = proxy.Contexts.GetCurrentContext(sessionId);
JArray objectValues = await GetObjectValues(sessionId, objectId, GetObjectCommandOptions.WithProperties | GetObjectCommandOptions.ForDebuggerDisplayAttribute, token);

var thisObj = CreateJObject<string>(value: "", type: "object", description: "", writable: false, objectId: $"dotnet:object:{objectId}");
Expand Down

0 comments on commit 9df36d8

Please sign in to comment.