Skip to content

Commit

Permalink
[wasm] [debugger] Cache debugger information on Proxy side to avoid g…
Browse files Browse the repository at this point in the history
…oing to debugger-agent everytime. (#57404)

* Caching some debugger information to avoid going to debugger-agent everytime.

* Apply suggestions from code review

Co-authored-by: Ankit Jain <radical@gmail.com>

* Addressing @radical comments.

* Adding cache for generic type.

* Fix merge.

* Fixing test behavior.

* Fixing debuggerproxy.

* Fixing merge
Fixing tests.

Co-authored-by: Ankit Jain <radical@gmail.com>
  • Loading branch information
thaystg and radical authored Aug 17, 2021
1 parent f7633f4 commit d6b35bb
Show file tree
Hide file tree
Showing 7 changed files with 330 additions and 150 deletions.
25 changes: 17 additions & 8 deletions src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,21 +317,22 @@ internal class MethodInfo

public SourceId SourceId => source.SourceId;

public int DebuggerId { get; set; }
public string Name { get; }
public MethodDebugInformation DebugInformation;
public MethodDefinitionHandle methodDefHandle;
private MetadataReader pdbMetadataReader;

public SourceLocation StartLocation { get; set;}
public SourceLocation EndLocation { get; set;}
public SourceLocation StartLocation { get; set; }
public SourceLocation EndLocation { get; set; }
public AssemblyInfo Assembly { get; }
public int Token { get; }
internal bool IsEnCMethod;
internal LocalScopeHandleCollection localScopes;
public bool IsStatic() => (methodDef.Attributes & MethodAttributes.Static) != 0;
public int IsAsync { get; set; }
public MethodInfo(AssemblyInfo assembly, MethodDefinitionHandle methodDefHandle, int token, SourceFile source, TypeInfo type, MetadataReader asmMetadataReader, MetadataReader pdbMetadataReader)
{
this.IsAsync = -1;
this.Assembly = assembly;
this.methodDef = asmMetadataReader.GetMethodDefinition(methodDefHandle);
this.DebugInformation = pdbMetadataReader.GetMethodDebugInformation(methodDefHandle.ToDebugInformationHandle());
Expand Down Expand Up @@ -456,7 +457,7 @@ internal class TypeInfo
internal AssemblyInfo assembly;
private TypeDefinition type;
private List<MethodInfo> methods;
public int Token { get; }
internal int Token { get; }

public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefinition type)
{
Expand All @@ -483,6 +484,12 @@ public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefi
FullName = namespaceName + Name;
}

public TypeInfo(AssemblyInfo assembly, string name)
{
Name = name;
FullName = name;
}

public string Name { get; }
public string FullName { get; }
public List<MethodInfo> Methods => methods;
Expand All @@ -498,7 +505,6 @@ internal class AssemblyInfo
private readonly ILogger logger;
private Dictionary<int, MethodInfo> methods = new Dictionary<int, MethodInfo>();
private Dictionary<string, string> sourceLinkMappings = new Dictionary<string, string>();
private Dictionary<string, TypeInfo> typesByName = new Dictionary<string, TypeInfo>();
private readonly List<SourceFile> sources = new List<SourceFile>();
internal string Url { get; }
internal MetadataReader asmMetadataReader { get; }
Expand All @@ -508,6 +514,7 @@ internal class AssemblyInfo
internal PEReader peReader;
internal MemoryStream asmStream;
internal MemoryStream pdbStream;
public int DebugId { get; set; }

public bool TriedToLoadSymbolsOnDemand { get; set; }

Expand Down Expand Up @@ -597,7 +604,8 @@ SourceFile FindSource(DocumentHandle doc, int rowid, string documentName)
var typeDefinition = asmMetadataReader.GetTypeDefinition(type);

var typeInfo = new TypeInfo(this, type, typeDefinition);
typesByName[typeInfo.FullName] = typeInfo;
TypesByName[typeInfo.FullName] = typeInfo;
TypesByToken[typeInfo.Token] = typeInfo;
if (pdbMetadataReader != null)
{
foreach (MethodDefinitionHandle method in typeDefinition.GetMethods())
Expand Down Expand Up @@ -675,7 +683,8 @@ private Uri GetSourceLinkUrl(string document)
public IEnumerable<SourceFile> Sources => this.sources;
public Dictionary<int, MethodInfo> Methods => this.methods;

public Dictionary<string, TypeInfo> TypesByName => this.typesByName;
public Dictionary<string, TypeInfo> TypesByName { get; } = new();
public Dictionary<int, TypeInfo> TypesByToken { get; } = new();
public int Id => id;
public string Name { get; }
public bool HasSymbols => pdbMetadataReader != null;
Expand All @@ -696,7 +705,7 @@ public MethodInfo GetMethodByToken(int token)

public TypeInfo GetTypeByName(string name)
{
typesByName.TryGetValue(name, out TypeInfo res);
TypesByName.TryGetValue(name, out TypeInfo res);
return res;
}

Expand Down
6 changes: 3 additions & 3 deletions src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,14 +214,14 @@ internal static class MonoConstants

internal class Frame
{
public Frame(MethodInfo method, SourceLocation location, int id)
public Frame(MethodInfoWithDebugInformation method, SourceLocation location, int id)
{
this.Method = method;
this.Location = location;
this.Id = id;
}

public MethodInfo Method { get; private set; }
public MethodInfoWithDebugInformation Method { get; private set; }
public SourceLocation Location { get; private set; }
public int Id { get; private set; }
}
Expand Down Expand Up @@ -269,7 +269,7 @@ internal enum StepKind

internal class ExecutionContext
{
public string DebuggerId { get; set; }
public string DebugId { get; set; }
public Dictionary<string, BreakpointRequest> BreakpointRequests { get; } = new Dictionary<string, BreakpointRequest>();

public TaskCompletionSource<DebugStore> ready;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,13 +286,17 @@ private static async Task<IList<JObject>> ResolveMethodCalls(IEnumerable<Invocat
internal static async Task<JObject> CompileAndRunTheExpression(string expression, MemberReferenceResolver resolver, CancellationToken token)
{
expression = expression.Trim();
if (!expression.StartsWith('('))
{
expression = "(" + expression + ")";
}
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(@"
using System;
public class CompileAndRunTheExpression
{
public static object Evaluate()
{
return (" + expression + @");
return " + expression + @";
}
}", cancellationToken: token);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public async Task<JObject> TryToRunOnLoadedClasses(string varName, CancellationT
classNameToFind += part.Trim();
if (typeId != -1)
{
var fields = await sdbHelper.GetTypeFields(sessionId, typeId, onlyPublic: false, token);
var fields = await sdbHelper.GetTypeFields(sessionId, typeId, token);
foreach (var field in fields)
{
if (field.Name == part.Trim())
Expand Down Expand Up @@ -124,8 +124,7 @@ public async Task<JObject> TryToRunOnLoadedClasses(string varName, CancellationT
var type = asm.GetTypeByName(classNameToFind);
if (type != null)
{
var assemblyId = await sdbHelper.GetAssemblyId(sessionId, type.assembly.Name, token);
typeId = await sdbHelper.GetTypeIdFromToken(sessionId, assemblyId, type.Token, token);
typeId = await sdbHelper.GetTypeIdFromToken(sessionId, asm.DebugId, type.Token, token);
}
}
}
Expand Down
90 changes: 19 additions & 71 deletions src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Microsoft.WebAssembly.Diagnostics
{
internal class MonoProxy : DevToolsProxy
{
internal MonoSDBHelper SdbHelper { get; }
internal MonoSDBHelper SdbHelper { get; set; }
private IList<string> urlSymbolServerList;
private static HttpClient client = new HttpClient();
private HashSet<SessionId> sessions = new HashSet<SessionId>();
Expand Down Expand Up @@ -255,7 +255,7 @@ protected override async Task<bool> AcceptCommand(MessageId id, string method, J
{
Result resp = await SendCommand(id, method, args, token);

context.DebuggerId = resp.Value["debuggerId"]?.ToString();
context.DebugId = resp.Value["DebugId"]?.ToString();

if (await IsRuntimeAlreadyReadyAlready(id, token))
await RuntimeReady(id, token);
Expand Down Expand Up @@ -584,7 +584,7 @@ private async Task<bool> CallOnFunction(MessageId id, JObject args, Cancellation
var retDebuggerCmd = new MemoryStream(newBytes);
var retDebuggerCmdReader = new MonoBinaryReader(retDebuggerCmd);
retDebuggerCmdReader.ReadByte(); //number of objects returned.
var obj = await SdbHelper.CreateJObjectForVariableValue(id, retDebuggerCmdReader, "ret", false, -1, token);
var obj = await SdbHelper.CreateJObjectForVariableValue(id, retDebuggerCmdReader, "ret", false, -1, false, token);
/*JTokenType? res_value_type = res.Value?["result"]?["value"]?.Type;*/
res = Result.OkFromObject(new { result = obj["value"]});
SendResponse(id, res, token);
Expand All @@ -601,7 +601,7 @@ private async Task<bool> OnSetVariableValue(MessageId id, int scopeId, string va
Frame scope = ctx.CallStack.FirstOrDefault(s => s.Id == scopeId);
if (scope == null)
return false;
var varIds = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
var varIds = scope.Method.Info.GetLiveVarsAt(scope.Location.CliLocation.Offset);
if (varIds == null)
return false;
var varToSetValue = varIds.FirstOrDefault(v => v.Name == varName);
Expand Down Expand Up @@ -714,30 +714,15 @@ private async Task<bool> ProcessEnC(SessionId sessionId, ExecutionContext contex

private async Task<bool> SendBreakpointsOfMethodUpdated(SessionId sessionId, ExecutionContext context, MonoBinaryReader retDebuggerCmdReader, CancellationToken token)
{
var method_id = retDebuggerCmdReader.ReadInt32();
var method_token = await SdbHelper.GetMethodToken(sessionId, method_id, token);
var assembly_id = await SdbHelper.GetAssemblyIdFromMethod(sessionId, method_id, token);
var assembly_name = await SdbHelper.GetAssemblyName(sessionId, assembly_id, token);
var method_name = await SdbHelper.GetMethodName(sessionId, method_id, token);
DebugStore store = await LoadStore(sessionId, token);
AssemblyInfo asm = store.GetAssemblyByName(assembly_name);
if (asm == null)
{
assembly_name = await SdbHelper.GetAssemblyFileNameFromId(sessionId, assembly_id, token);
asm = store.GetAssemblyByName(assembly_name);
if (asm == null)
{
return true;
}
}
MethodInfo method = asm.GetMethodByToken(method_token);
var methodId = retDebuggerCmdReader.ReadInt32();
var method = await SdbHelper.GetMethodInfo(sessionId, methodId, token);
if (method == null)
{
return true;
}
foreach (var req in context.BreakpointRequests.Values)
{
if (req.Method != null && req.Method.Assembly.Id == method.Assembly.Id && req.Method.Token == method.Token)
if (req.Method != null && req.Method.Assembly.Id == method.Info.Assembly.Id && req.Method.Token == method.Info.Token)
{
await SetBreakpoint(sessionId, context.store, req, true, token);
}
Expand All @@ -762,47 +747,10 @@ private async Task<bool> SendCallStack(SessionId sessionId, ExecutionContext con
var methodId = retDebuggerCmdReader.ReadInt32();
var il_pos = retDebuggerCmdReader.ReadInt32();
var flags = retDebuggerCmdReader.ReadByte();
var method_token = await SdbHelper.GetMethodToken(sessionId, methodId, token);
var assembly_id = await SdbHelper.GetAssemblyIdFromMethod(sessionId, methodId, token);
var assembly_name = await SdbHelper.GetAssemblyName(sessionId, assembly_id, token);
var method_name = await SdbHelper.GetMethodName(sessionId, methodId, token);
DebugStore store = await LoadStore(sessionId, token);
AssemblyInfo asm = store.GetAssemblyByName(assembly_name);
if (asm == null)
{
assembly_name = await SdbHelper.GetAssemblyFileNameFromId(sessionId, assembly_id, token); //maybe is a lazy loaded assembly
asm = store.GetAssemblyByName(assembly_name);
if (asm == null)
{
Log("debug", $"Unable to find assembly: {assembly_name}");
continue;
}
}

MethodInfo method = asm.GetMethodByToken(method_token);

if (method == null && !asm.HasSymbols)
{
try
{
method = await LoadSymbolsOnDemand(asm, method_token, sessionId, token);
}
catch (Exception e)
{
Log("info", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name} exception: {e}");
continue;
}
}
var method = await SdbHelper.GetMethodInfo(sessionId, methodId, token);

if (method == null)
{
Log("debug", $"Unable to find il offset: {il_pos} in method token: {method_token} assembly name: {assembly_name}");
continue;
}

method.DebuggerId = methodId;

SourceLocation location = method?.GetLocationByIl(il_pos);
SourceLocation location = method?.Info.GetLocationByIl(il_pos);

// When hitting a breakpoint on the "IncrementCount" method in the standard
// Blazor project template, one of the stack frames is inside mscorlib.dll
Expand All @@ -813,15 +761,15 @@ private async Task<bool> SendCallStack(SessionId sessionId, ExecutionContext con
continue;
}

Log("debug", $"frame il offset: {il_pos} method token: {method_token} assembly name: {assembly_name}");
Log("debug", $"\tmethod {method_name} location: {location}");
Log("debug", $"frame il offset: {il_pos} method token: {method.Info.Token} assembly name: {method.Info.Assembly.Name}");
Log("debug", $"\tmethod {method.Name} location: {location}");
frames.Add(new Frame(method, location, frame_id));

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

location = location.AsLocation(),

Expand All @@ -839,9 +787,9 @@ private async Task<bool> SendCallStack(SessionId sessionId, ExecutionContext con
description = "Object",
objectId = $"dotnet:scope:{frame_id}",
},
name = method_name,
startLocation = method.StartLocation.AsLocation(),
endLocation = method.EndLocation.AsLocation(),
name = method.Name,
startLocation = method.Info.StartLocation.AsLocation(),
endLocation = method.Info.EndLocation.AsLocation(),
}
}
});
Expand Down Expand Up @@ -949,7 +897,7 @@ private async Task<bool> OnReceiveDebuggerAgentEvent(SessionId sessionId, JObjec
return false;
}

private async Task<MethodInfo> LoadSymbolsOnDemand(AssemblyInfo asm, int method_token, SessionId sessionId, CancellationToken token)
internal async Task<MethodInfo> LoadSymbolsOnDemand(AssemblyInfo asm, int method_token, SessionId sessionId, CancellationToken token)
{
ExecutionContext context = GetContext(sessionId);
if (urlSymbolServerList.Count == 0)
Expand Down Expand Up @@ -1166,7 +1114,7 @@ internal async Task<Result> GetScopeProperties(SessionId msg_id, int scopeId, Ca
if (scope == null)
return Result.Err(JObject.FromObject(new { message = $"Could not find scope with id #{scopeId}" }));

VarInfo[] varIds = scope.Method.GetLiveVarsAt(scope.Location.CliLocation.Offset);
VarInfo[] varIds = scope.Method.Info.GetLiveVarsAt(scope.Location.CliLocation.Offset);

var values = await SdbHelper.StackFrameGetValues(msg_id, scope.Method, ctx.ThreadId, scopeId, varIds, token);
if (values != null)
Expand Down Expand Up @@ -1282,9 +1230,9 @@ private async Task<DebugStore> RuntimeReady(SessionId sessionId, CancellationTok
await SdbHelper.EnableReceiveRequests(sessionId, EventKind.MethodUpdate, token);

DebugStore store = await LoadStore(sessionId, token);

context.ready.SetResult(store);
SendEvent(sessionId, "Mono.runtimeReady", new JObject(), token);
SdbHelper.SetStore(store);
return store;
}

Expand Down
Loading

0 comments on commit d6b35bb

Please sign in to comment.