Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/5.0-rc2] Backport wasm debugger improvements, and fixes #42057

Merged
merged 11 commits into from
Sep 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 193 additions & 81 deletions src/mono/mono/mini/mini-wasm-debugger.c

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/mono/netcore/sample/wasm/browser/WasmSample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
</ItemGroup>
<WasmAppBuilder
AppDir="$(AppDir)"
ExtraAssemblies="$(MicrosoftNetCoreAppRuntimePackDir)lib\$(NetCoreAppCurrent)\System.Runtime.InteropServices.JavaScript.dll"
ExtraAssemblies="$(MicrosoftNetCoreAppRuntimePackDir)lib\$(NetCoreAppCurrent)\System.Private.Runtime.InteropServices.JavaScript.dll"
MicrosoftNetCoreAppRuntimePackDir="$(MicrosoftNetCoreAppRuntimePackDir)"
MainAssembly="bin\WasmSample.dll"
MainJS="runtime.js"
Expand Down
25 changes: 23 additions & 2 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ public static MonoCommands GetScopeVariables(int scopeId, params VarInfo[] vars)
return new MonoCommands($"MONO.mono_wasm_get_variables({scopeId}, {JsonConvert.SerializeObject(var_ids)})");
}

public static MonoCommands EvaluateMemberAccess(int scopeId, string expr, params VarInfo[] vars)
{
var var_ids = vars.Select(v => new { index = v.Index, name = v.Name }).ToArray();
return new MonoCommands($"MONO.mono_wasm_eval_member_access({scopeId}, {JsonConvert.SerializeObject(var_ids)}, '', '{expr}')");
}

public static MonoCommands SetBreakpoint(string assemblyName, uint methodToken, int ilOffset) => new MonoCommands($"MONO.mono_wasm_set_breakpoint (\"{assemblyName}\", {methodToken}, {ilOffset})");

public static MonoCommands RemoveBreakpoint(int breakpointId) => new MonoCommands($"MONO.mono_wasm_remove_breakpoint({breakpointId})");
Expand Down Expand Up @@ -285,7 +291,7 @@ internal class ExecutionContext
internal DebugStore store;
public TaskCompletionSource<DebugStore> Source { get; } = new TaskCompletionSource<DebugStore>();

public Dictionary<string, JToken> LocalsCache = new Dictionary<string, JToken>();
Dictionary<int, PerScopeCache> perScopeCaches { get; } = new Dictionary<int, PerScopeCache>();

public DebugStore Store
{
Expand All @@ -298,11 +304,26 @@ public DebugStore Store
}
}

public PerScopeCache GetCacheForScope(int scope_id)
{
if (perScopeCaches.TryGetValue(scope_id, out var cache))
return cache;

cache = new PerScopeCache();
perScopeCaches[scope_id] = cache;
return cache;
}

public void ClearState()
{
CallStack = null;
LocalsCache.Clear();
perScopeCaches.Clear();
}
}

internal class PerScopeCache
{
public Dictionary<string, JObject> Locals { get; } = new Dictionary<string, JObject>();
public Dictionary<string, JObject> MemberReferences { get; } = new Dictionary<string, JObject>();
}
}
301 changes: 220 additions & 81 deletions src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.WebAssembly.Diagnostics
{
internal class MemberReferenceResolver
{
private MessageId messageId;
private int scopeId;
private MonoProxy proxy;
private ExecutionContext ctx;
private PerScopeCache scopeCache;
private VarInfo[] varIds;
private ILogger logger;
private bool locals_fetched = false;

public MemberReferenceResolver(MonoProxy proxy, ExecutionContext ctx, MessageId msg_id, int scope_id, ILogger logger)
{
messageId = msg_id;
scopeId = scope_id;
this.proxy = proxy;
this.ctx = ctx;
this.logger = logger;
scopeCache = ctx.GetCacheForScope(scope_id);
}

// Checks Locals, followed by `this`
public async Task<JObject> Resolve(string var_name, CancellationToken token)
{
if (scopeCache.Locals.Count == 0 && !locals_fetched)
{
var scope_res = await proxy.GetScopeProperties(messageId, scopeId, token);
if (scope_res.IsErr)
throw new Exception($"BUG: Unable to get properties for scope: {scopeId}. {scope_res}");
locals_fetched = true;
}

if (scopeCache.Locals.TryGetValue(var_name, out var obj))
{
return obj["value"]?.Value<JObject>();
}

if (scopeCache.MemberReferences.TryGetValue(var_name, out var ret))
return ret;

if (varIds == null)
{
var scope = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId);
varIds = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
}

var res = await proxy.SendMonoCommand(messageId, MonoCommands.EvaluateMemberAccess(scopeId, var_name, varIds), token);
if (res.IsOk)
{
ret = res.Value?["result"]?["value"]?["value"]?.Value<JObject>();
scopeCache.MemberReferences[var_name] = ret;
}
else
{
logger.LogDebug(res.Error.ToString());
}

return ret;
}

}
}
115 changes: 32 additions & 83 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J

case "Debugger.enable":
{
System.Console.WriteLine("recebi o Debugger.enable");
var resp = await SendCommand(id, method, args, token);

context.DebuggerId = resp.Value["debuggerId"]?.ToString();
Expand Down Expand Up @@ -426,7 +425,9 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J
async Task<Result> RuntimeGetProperties(MessageId id, DotnetObjectId objectId, JToken args, CancellationToken token)
{
if (objectId.Scheme == "scope")
{
return await GetScopeProperties(id, int.Parse(objectId.Value), token);
}

var res = await SendMonoCommand(id, MonoCommands.GetDetails(objectId, args), token);
if (res.IsErr)
Expand Down Expand Up @@ -456,7 +457,6 @@ async Task<Result> RuntimeGetProperties(MessageId id, DotnetObjectId objectId, J
return res;
}

//static int frame_id=0;
async Task<bool> OnPause(SessionId sessionId, JObject args, CancellationToken token)
{
//FIXME we should send release objects every now and then? Or intercept those we inject and deal in the runtime
Expand Down Expand Up @@ -517,12 +517,11 @@ async Task<bool> OnPause(SessionId sessionId, JObject args, CancellationToken to
}

var frames = new List<Frame>();
int frame_id = 0;
var the_mono_frames = res.Value?["result"]?["value"]?["frames"]?.Values<JObject>();

foreach (var mono_frame in the_mono_frames)
{
++frame_id;
int frame_id = mono_frame["frame_id"].Value<int>();
var il_pos = mono_frame["il_pos"].Value<int>();
var method_token = mono_frame["method_token"].Value<uint>();
var assembly_name = mono_frame["assembly_name"].Value<string>();
Expand Down Expand Up @@ -559,12 +558,12 @@ async Task<bool> OnPause(SessionId sessionId, JObject args, CancellationToken to

Log("info", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}");
Log("info", $"\tmethod {method_name} location: {location}");
frames.Add(new Frame(method, location, frame_id - 1));
frames.Add(new Frame(method, location, frame_id));

callFrames.Add(new
{
functionName = method_name,
callFrameId = $"dotnet:scope:{frame_id - 1}",
callFrameId = $"dotnet:scope:{frame_id}",
functionLocation = method.StartLocation.AsLocation(),

location = location.AsLocation(),
Expand All @@ -581,7 +580,7 @@ async Task<bool> OnPause(SessionId sessionId, JObject args, CancellationToken to
@type = "object",
className = "Object",
description = "Object",
objectId = $"dotnet:scope:{frame_id-1}",
objectId = $"dotnet:scope:{frame_id}",
},
name = method_name,
startLocation = method.StartLocation.AsLocation(),
Expand Down Expand Up @@ -673,64 +672,6 @@ async Task<bool> Step(MessageId msg_id, StepKind kind, CancellationToken token)
return true;
}

internal bool TryFindVariableValueInCache(ExecutionContext ctx, string expression, bool only_search_on_this, out JToken obj)
{
if (ctx.LocalsCache.TryGetValue(expression, out obj))
{
if (only_search_on_this && obj["fromThis"] == null)
return false;
return true;
}
return false;
}

internal async Task<JToken> TryGetVariableValue(MessageId msg_id, int scope_id, string expression, bool only_search_on_this, CancellationToken token)
{
JToken thisValue = null;
var context = GetContext(msg_id);
if (context.CallStack == null)
return null;

if (TryFindVariableValueInCache(context, expression, only_search_on_this, out JToken obj))
return obj;

var scope = context.CallStack.FirstOrDefault(s => s.Id == scope_id);
var live_vars = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
//get_this
var res = await SendMonoCommand(msg_id, MonoCommands.GetScopeVariables(scope.Id, live_vars), token);

var scope_values = res.Value?["result"]?["value"]?.Values<JObject>()?.ToArray();
thisValue = scope_values?.FirstOrDefault(v => v["name"]?.Value<string>() == "this");

if (!only_search_on_this)
{
if (thisValue != null && expression == "this")
return thisValue;

var value = scope_values.SingleOrDefault(sv => sv["name"]?.Value<string>() == expression);
if (value != null)
return value;
}

//search in scope
if (thisValue != null)
{
if (!DotnetObjectId.TryParse(thisValue["value"]["objectId"], out var objectId))
return null;

res = await SendMonoCommand(msg_id, MonoCommands.GetDetails(objectId), token);
scope_values = res.Value?["result"]?["value"]?.Values<JObject>().ToArray();
var foundValue = scope_values.FirstOrDefault(v => v["name"].Value<string>() == expression);
if (foundValue != null)
{
foundValue["fromThis"] = true;
context.LocalsCache[foundValue["name"].Value<string>()] = foundValue;
return foundValue;
}
}
return null;
}

async Task<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, string expression, CancellationToken token)
{
try
Expand All @@ -739,35 +680,40 @@ async Task<bool> OnEvaluateOnCallFrame(MessageId msg_id, int scope_id, string ex
if (context.CallStack == null)
return false;

var varValue = await TryGetVariableValue(msg_id, scope_id, expression, false, token);
var resolver = new MemberReferenceResolver(this, context, msg_id, scope_id, logger);

if (varValue != null)
JObject retValue = await resolver.Resolve(expression, token);
if (retValue == null)
{
retValue = await EvaluateExpression.CompileAndRunTheExpression(expression, resolver, token);
}

if (retValue != null)
{
SendResponse(msg_id, Result.OkFromObject(new
{
result = varValue["value"]
result = retValue
}), token);
return true;
}

string retValue = await EvaluateExpression.CompileAndRunTheExpression(this, msg_id, scope_id, expression, token);
SendResponse(msg_id, Result.OkFromObject(new
else
{
result = new
{
value = retValue
}
}), token);
return true;
SendResponse(msg_id, Result.Err($"Unable to evaluate '{expression}'"), token);
}
}
catch (ReturnAsErrorException ree)
{
SendResponse(msg_id, ree.Error, token);
}
catch (Exception e)
{
logger.LogDebug(e, $"Error in EvaluateOnCallFrame for expression '{expression}.");
logger.LogDebug($"Error in EvaluateOnCallFrame for expression '{expression}' with '{e}.");
SendResponse(msg_id, Result.Exception(e), token);
}
return false;

return true;
}

async Task<Result> GetScopeProperties(MessageId msg_id, int scope_id, CancellationToken token)
internal async Task<Result> GetScopeProperties(MessageId msg_id, int scope_id, CancellationToken token)
{
try
{
Expand All @@ -788,8 +734,11 @@ async Task<Result> GetScopeProperties(MessageId msg_id, int scope_id, Cancellati
if (values == null || values.Length == 0)
return Result.OkFromObject(new { result = Array.Empty<object>() });

var frameCache = ctx.GetCacheForScope(scope_id);
foreach (var value in values)
ctx.LocalsCache[value["name"]?.Value<string>()] = value;
{
frameCache.Locals[value["name"]?.Value<string>()] = value;
}

return Result.OkFromObject(new { result = values });
}
Expand Down Expand Up @@ -902,7 +851,7 @@ async Task RemoveBreakpoint(MessageId msg_id, JObject args, CancellationToken to
bp.State = BreakpointState.Disabled;
}
}
breakpointRequest.Locations.Clear();
context.BreakpointRequests.Remove(bpid);
}

async Task SetBreakpoint(SessionId sessionId, DebugStore store, BreakpointRequest req, bool sendResolvedEvent, CancellationToken token)
Expand Down
Loading