From d6b35bb52564d9c434732cba1803bc7e34bddf79 Mon Sep 17 00:00:00 2001 From: Thays Grazia Date: Tue, 17 Aug 2021 12:22:09 -0300 Subject: [PATCH] [wasm] [debugger] Cache debugger information on Proxy side to avoid going 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 * Addressing @radical comments. * Adding cache for generic type. * Fix merge. * Fixing test behavior. * Fixing debuggerproxy. * Fixing merge Fixing tests. Co-authored-by: Ankit Jain --- .../wasm/debugger/BrowserDebugProxy/DebugStore.cs | 25 +- .../debugger/BrowserDebugProxy/DevToolsHelper.cs | 6 +- .../BrowserDebugProxy/EvaluateExpression.cs | 6 +- .../BrowserDebugProxy/MemberReferenceResolver.cs | 5 +- .../wasm/debugger/BrowserDebugProxy/MonoProxy.cs | 90 ++---- .../debugger/BrowserDebugProxy/MonoSDBHelper.cs | 347 +++++++++++++++++---- src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs | 1 - 7 files changed, 330 insertions(+), 150 deletions(-) diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs index c398ca8..be7d729 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DebugStore.cs @@ -317,21 +317,22 @@ namespace Microsoft.WebAssembly.Diagnostics 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()); @@ -456,7 +457,7 @@ namespace Microsoft.WebAssembly.Diagnostics internal AssemblyInfo assembly; private TypeDefinition type; private List methods; - public int Token { get; } + internal int Token { get; } public TypeInfo(AssemblyInfo assembly, TypeDefinitionHandle typeHandle, TypeDefinition type) { @@ -483,6 +484,12 @@ namespace Microsoft.WebAssembly.Diagnostics FullName = namespaceName + Name; } + public TypeInfo(AssemblyInfo assembly, string name) + { + Name = name; + FullName = name; + } + public string Name { get; } public string FullName { get; } public List Methods => methods; @@ -498,7 +505,6 @@ namespace Microsoft.WebAssembly.Diagnostics private readonly ILogger logger; private Dictionary methods = new Dictionary(); private Dictionary sourceLinkMappings = new Dictionary(); - private Dictionary typesByName = new Dictionary(); private readonly List sources = new List(); internal string Url { get; } internal MetadataReader asmMetadataReader { get; } @@ -508,6 +514,7 @@ namespace Microsoft.WebAssembly.Diagnostics internal PEReader peReader; internal MemoryStream asmStream; internal MemoryStream pdbStream; + public int DebugId { get; set; } public bool TriedToLoadSymbolsOnDemand { get; set; } @@ -597,7 +604,8 @@ namespace Microsoft.WebAssembly.Diagnostics 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()) @@ -675,7 +683,8 @@ namespace Microsoft.WebAssembly.Diagnostics public IEnumerable Sources => this.sources; public Dictionary Methods => this.methods; - public Dictionary TypesByName => this.typesByName; + public Dictionary TypesByName { get; } = new(); + public Dictionary TypesByToken { get; } = new(); public int Id => id; public string Name { get; } public bool HasSymbols => pdbMetadataReader != null; @@ -696,7 +705,7 @@ namespace Microsoft.WebAssembly.Diagnostics public TypeInfo GetTypeByName(string name) { - typesByName.TryGetValue(name, out TypeInfo res); + TypesByName.TryGetValue(name, out TypeInfo res); return res; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs index de40842..bc0f4fa 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/DevToolsHelper.cs @@ -214,14 +214,14 @@ namespace Microsoft.WebAssembly.Diagnostics 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; } } @@ -269,7 +269,7 @@ namespace Microsoft.WebAssembly.Diagnostics internal class ExecutionContext { - public string DebuggerId { get; set; } + public string DebugId { get; set; } public Dictionary BreakpointRequests { get; } = new Dictionary(); public TaskCompletionSource ready; diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs index 4a2abbc..f14aabb 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/EvaluateExpression.cs @@ -286,13 +286,17 @@ namespace Microsoft.WebAssembly.Diagnostics internal static async Task 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); diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs index e5823ea..5589667 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MemberReferenceResolver.cs @@ -94,7 +94,7 @@ namespace Microsoft.WebAssembly.Diagnostics 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()) @@ -124,8 +124,7 @@ namespace Microsoft.WebAssembly.Diagnostics 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); } } } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs index f9710ef..95aa6de 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoProxy.cs @@ -18,7 +18,7 @@ namespace Microsoft.WebAssembly.Diagnostics { internal class MonoProxy : DevToolsProxy { - internal MonoSDBHelper SdbHelper { get; } + internal MonoSDBHelper SdbHelper { get; set; } private IList urlSymbolServerList; private static HttpClient client = new HttpClient(); private HashSet sessions = new HashSet(); @@ -255,7 +255,7 @@ namespace Microsoft.WebAssembly.Diagnostics { 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); @@ -584,7 +584,7 @@ namespace Microsoft.WebAssembly.Diagnostics 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); @@ -601,7 +601,7 @@ namespace Microsoft.WebAssembly.Diagnostics 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); @@ -714,30 +714,15 @@ namespace Microsoft.WebAssembly.Diagnostics private async Task 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); } @@ -762,47 +747,10 @@ namespace Microsoft.WebAssembly.Diagnostics 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 @@ -813,15 +761,15 @@ namespace Microsoft.WebAssembly.Diagnostics 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(), @@ -839,9 +787,9 @@ namespace Microsoft.WebAssembly.Diagnostics 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(), } } }); @@ -949,7 +897,7 @@ namespace Microsoft.WebAssembly.Diagnostics return false; } - private async Task LoadSymbolsOnDemand(AssemblyInfo asm, int method_token, SessionId sessionId, CancellationToken token) + internal async Task LoadSymbolsOnDemand(AssemblyInfo asm, int method_token, SessionId sessionId, CancellationToken token) { ExecutionContext context = GetContext(sessionId); if (urlSymbolServerList.Count == 0) @@ -1166,7 +1114,7 @@ namespace Microsoft.WebAssembly.Diagnostics 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) @@ -1282,9 +1230,9 @@ namespace Microsoft.WebAssembly.Diagnostics 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; } diff --git a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs index 1b31923..d804421 100644 --- a/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs +++ b/src/mono/wasm/debugger/BrowserDebugProxy/MonoSDBHelper.cs @@ -357,6 +357,25 @@ namespace Microsoft.WebAssembly.Diagnostics Line } + internal record MethodInfoWithDebugInformation(MethodInfo Info, int DebugId, string Name); + + internal class TypeInfoWithDebugInformation + { + public TypeInfo Info { get; } + public int DebugId { get; } + public string Name { get; } + public List FieldsList { get; set; } + public MonoBinaryReader PropertiesBinaryReader { get; set; } + public List TypeParamsOrArgsForGenericType { get; set; } + + public TypeInfoWithDebugInformation(TypeInfo typeInfo, int debugId, string name) + { + Info = typeInfo; + DebugId = debugId; + Name = name; + } + } + internal class MonoBinaryReader : BinaryReader { public MonoBinaryReader(Stream stream) : base(stream) {} @@ -583,11 +602,13 @@ namespace Microsoft.WebAssembly.Diagnostics public int Id { get; } public string Name { get; } public int TypeId { get; } - public FieldTypeClass(int id, string name, int typeId) + public bool IsPublic { get; } + public FieldTypeClass(int id, string name, int typeId, bool isPublic) { Id = id; Name = name; TypeId = typeId; + IsPublic = isPublic; } } internal class ValueTypeClass @@ -627,14 +648,22 @@ namespace Microsoft.WebAssembly.Diagnostics } internal class MonoSDBHelper { + private static int debuggerObjectId; + private static int cmdId; + private static int GetId() {return cmdId++;} + private static int MINOR_VERSION = 61; + private static int MAJOR_VERSION = 2; + + private Dictionary methods = new(); + private Dictionary assemblies = new(); + private Dictionary types = new(); + internal Dictionary valueTypes = new Dictionary(); internal Dictionary pointerValues = new Dictionary(); - private static int debugger_object_id; - private static int cmd_id; - private static int GetId() {return cmd_id++;} + private MonoProxy proxy; - private static int MINOR_VERSION = 61; - private static int MAJOR_VERSION = 2; + private DebugStore store; + private readonly ILogger logger; private Regex regexForAsyncLocals = new Regex(@"\<([^)]*)\>", RegexOptions.Singleline); @@ -642,6 +671,115 @@ namespace Microsoft.WebAssembly.Diagnostics { this.proxy = proxy; this.logger = logger; + this.store = null; + } + + public void SetStore(DebugStore store) + { + this.store = store; + } + + public async Task GetAssemblyInfo(SessionId sessionId, int assemblyId, CancellationToken token) + { + AssemblyInfo asm = null; + if (assemblies.TryGetValue(assemblyId, out asm)) + { + return asm; + } + var assemblyName = await GetAssemblyName(sessionId, assemblyId, token); + + asm = store.GetAssemblyByName(assemblyName); + + if (asm == null) + { + assemblyName = await GetAssemblyFileNameFromId(sessionId, assemblyId, token); //maybe is a lazy loaded assembly + asm = store.GetAssemblyByName(assemblyName); + if (asm == null) + { + logger.LogDebug($"Unable to find assembly: {assemblyName}"); + return null; + } + } + asm.DebugId = assemblyId; + assemblies[assemblyId] = asm; + return asm; + } + + public async Task GetMethodInfo(SessionId sessionId, int methodId, CancellationToken token) + { + MethodInfoWithDebugInformation methodDebugInfo = null; + if (methods.TryGetValue(methodId, out methodDebugInfo)) + { + return methodDebugInfo; + } + var methodToken = await GetMethodToken(sessionId, methodId, token); + var assemblyId = await GetAssemblyIdFromMethod(sessionId, methodId, token); + + var asm = await GetAssemblyInfo(sessionId, assemblyId, token); + + if (asm == null) + { + logger.LogDebug($"Unable to find assembly: {assemblyId}"); + return null; + } + + var method = asm.GetMethodByToken(methodToken); + + if (method == null && !asm.HasSymbols) + { + try + { + method = await proxy.LoadSymbolsOnDemand(asm, methodToken, sessionId, token); + } + catch (Exception e) + { + logger.LogDebug($"Unable to find method token: {methodToken} assembly name: {asm.Name} exception: {e}"); + return null; + } + } + + if (method == null) + { + logger.LogDebug($"Unable to find method token: {methodToken} assembly name: {asm.Name}"); + return null; + } + + string methodName = await GetMethodName(sessionId, methodId, token); + methods[methodId] = new MethodInfoWithDebugInformation(method, methodId, methodName); + return methods[methodId]; + } + + public async Task GetTypeInfo(SessionId sessionId, int typeId, CancellationToken token) + { + TypeInfoWithDebugInformation typeDebugInfo = null; + if (types.TryGetValue(typeId, out typeDebugInfo)) + { + return typeDebugInfo; + } + + TypeInfo type = null; + + var typeToken = await GetTypeToken(sessionId, typeId, token); + var typeName = await GetTypeName(sessionId, typeId, token); + var assemblyId = await GetAssemblyFromType(sessionId, typeId, token); + var asm = await GetAssemblyInfo(sessionId, assemblyId, token); + + if (asm == null) + { + logger.LogDebug($"Unable to find assembly: {assemblyId}"); + return null; + } + + asm.TypesByToken.TryGetValue(typeToken, out type); + + if (type == null) + { + logger.LogDebug($"Unable to find type token: {typeName} assembly name: {asm.Name}"); + return null; + } + + types[typeId] = new TypeInfoWithDebugInformation(type, typeId, typeName); + return types[typeId]; } public void ClearCache() @@ -782,12 +920,20 @@ namespace Microsoft.WebAssembly.Diagnostics return retDebuggerCmdReader.ReadInt32(); } - public async Task> GetTypeParamsOrArgsForGenericType(SessionId sessionId, int type_id, CancellationToken token) + public async Task> GetTypeParamsOrArgsForGenericType(SessionId sessionId, int typeId, CancellationToken token) { + var typeInfo = await GetTypeInfo(sessionId, typeId, token); + + if (typeInfo == null) + return null; + + if (typeInfo.TypeParamsOrArgsForGenericType != null) + return typeInfo.TypeParamsOrArgsForGenericType; + var ret = new List(); var commandParams = new MemoryStream(); var commandParamsWriter = new MonoBinaryWriter(commandParams); - commandParamsWriter.Write(type_id); + commandParamsWriter.Write(typeId); commandParamsWriter.Write((int) MonoTypeNameFormat.FormatReflection); var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetInfo, commandParams, token); @@ -813,6 +959,9 @@ namespace Microsoft.WebAssembly.Diagnostics { ret.Add(retDebuggerCmdReader.ReadInt32()); //generic type } + + typeInfo.TypeParamsOrArgsForGenericType = ret; + return ret; } @@ -892,6 +1041,10 @@ namespace Microsoft.WebAssembly.Diagnostics public async Task MethodIsStatic(SessionId sessionId, int methodId, CancellationToken token) { + var methodInfo = await GetMethodInfo(sessionId, methodId, token); + if (methodInfo != null) + return methodInfo.Info.IsStatic(); + var commandParams = new MemoryStream(); var commandParamsWriter = new MonoBinaryWriter(commandParams); commandParamsWriter.Write(methodId); @@ -1025,7 +1178,7 @@ namespace Microsoft.WebAssembly.Diagnostics commandParamsWriter.Write(fieldId); var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetValues, commandParams, token); - return await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, "", false, -1, token); + return await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, "", false, -1, false, token); } public async Task TypeIsInitialized(SessionId sessionId, int typeId, CancellationToken token) @@ -1050,25 +1203,53 @@ namespace Microsoft.WebAssembly.Diagnostics return retDebuggerCmdReader.ReadInt32(); } - public async Task> GetTypeFields(SessionId sessionId, int type_id, bool onlyPublic, CancellationToken token) + public async Task GetTypePropertiesReader(SessionId sessionId, int typeId, CancellationToken token) { + var typeInfo = await GetTypeInfo(sessionId, typeId, token); + + if (typeInfo == null) + return null; + + if (typeInfo.PropertiesBinaryReader != null) + { + typeInfo.PropertiesBinaryReader.BaseStream.Seek(0, SeekOrigin.Begin); + return typeInfo.PropertiesBinaryReader; + } + + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(typeId); + + typeInfo.PropertiesBinaryReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, commandParams, token); + return typeInfo.PropertiesBinaryReader; + } + + public async Task> GetTypeFields(SessionId sessionId, int typeId, CancellationToken token) + { + var typeInfo = await GetTypeInfo(sessionId, typeId, token); + + if (typeInfo.FieldsList != null) { + return typeInfo.FieldsList; + } + var ret = new List(); var commandParams = new MemoryStream(); var commandParamsWriter = new MonoBinaryWriter(commandParams); - commandParamsWriter.Write(type_id); + commandParamsWriter.Write(typeId); var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetFields, commandParams, token); var nFields = retDebuggerCmdReader.ReadInt32(); for (int i = 0 ; i < nFields; i++) { + bool isPublic = false; int fieldId = retDebuggerCmdReader.ReadInt32(); //fieldId string fieldNameStr = retDebuggerCmdReader.ReadString(); - int typeId = retDebuggerCmdReader.ReadInt32(); //typeId + int fieldTypeId = retDebuggerCmdReader.ReadInt32(); //typeId int attrs = retDebuggerCmdReader.ReadInt32(); //attrs int isSpecialStatic = retDebuggerCmdReader.ReadInt32(); //is_special_static - if (onlyPublic && ((attrs & (int)MethodAttributes.Public) == 0)) - continue; + if (((attrs & (int)MethodAttributes.Public) != 0)) + isPublic = true; if (isSpecialStatic == 1) continue; if (fieldNameStr.Contains("k__BackingField")) @@ -1077,8 +1258,9 @@ namespace Microsoft.WebAssembly.Diagnostics fieldNameStr = fieldNameStr.Replace("<", ""); fieldNameStr = fieldNameStr.Replace(">", ""); } - ret.Add(new FieldTypeClass(fieldId, fieldNameStr, typeId)); + ret.Add(new FieldTypeClass(fieldId, fieldNameStr, fieldTypeId, isPublic)); } + typeInfo.FieldsList = ret; return ret; } @@ -1093,6 +1275,7 @@ namespace Microsoft.WebAssembly.Diagnostics className = className.Replace("System.Byte", "byte"); return className; } + internal async Task GetCAttrsFromType(SessionId sessionId, int objectId, int typeId, string attrName, CancellationToken token) { var invokeParams = new MemoryStream(); @@ -1124,13 +1307,28 @@ namespace Microsoft.WebAssembly.Diagnostics for (int j = 0; j < parmCount; j++) { //to typed_args - await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, "varName", false, -1, token); + await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, "varName", false, -1, false, token); } } } return null; } + public async Task GetAssemblyFromType(SessionId sessionId, int type_id, CancellationToken token) + { + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(type_id); + commandParamsWriter.Write((int) MonoTypeNameFormat.FormatReflection); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetInfo, commandParams, token); + + retDebuggerCmdReader.ReadString(); + retDebuggerCmdReader.ReadString(); + retDebuggerCmdReader.ReadString(); + + return retDebuggerCmdReader.ReadInt32(); + } + public async Task GetValueFromDebuggerDisplayAttribute(SessionId sessionId, int objectId, int typeId, CancellationToken token) { string expr = ""; @@ -1166,10 +1364,15 @@ namespace Microsoft.WebAssembly.Diagnostics { dispAttrStr = dispAttrStr.Replace(", nq", ""); } + if (dispAttrStr.Contains(",nq")) + { + dispAttrStr = dispAttrStr.Replace(",nq", ""); + } expr = "$\"" + dispAttrStr + "\""; JObject retValue = await resolver.Resolve(expr, token); if (retValue == null) retValue = await EvaluateExpression.CompileAndRunTheExpression(expr, resolver, token); + return retValue?["value"]?.Value(); } catch (Exception) @@ -1205,6 +1408,23 @@ namespace Microsoft.WebAssembly.Diagnostics return retDebuggerCmdReader.ReadString(); //class name formatted } + public async Task GetTypeToken(SessionId sessionId, int typeId, CancellationToken token) + { + var commandParams = new MemoryStream(); + var commandParamsWriter = new MonoBinaryWriter(commandParams); + commandParamsWriter.Write(typeId); + commandParamsWriter.Write((int) MonoTypeNameFormat.FormatReflection); + var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetInfo, commandParams, token); + retDebuggerCmdReader.ReadString(); //namespace + retDebuggerCmdReader.ReadString(); //class name + retDebuggerCmdReader.ReadString(); //class name formatted + retDebuggerCmdReader.ReadInt32(); //assemblyid + retDebuggerCmdReader.ReadInt32(); //moduleId + retDebuggerCmdReader.ReadInt32(); //parent typeId + retDebuggerCmdReader.ReadInt32(); //array typeId + return retDebuggerCmdReader.ReadInt32(); //token + } + public async Task GetStringValue(SessionId sessionId, int string_id, CancellationToken token) { var commandParams = new MemoryStream(); @@ -1331,16 +1551,15 @@ namespace Microsoft.WebAssembly.Diagnostics commandParamsWriter.Write(0); var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdVM.InvokeMethod, parms, token); retDebuggerCmdReader.ReadByte(); //number of objects returned. - return await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, varName, false, -1, token); + return await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, varName, false, -1, false, token); } public async Task GetPropertyMethodIdByName(SessionId sessionId, int typeId, string propertyName, CancellationToken token) { - var commandParams = new MemoryStream(); - var commandParamsWriter = new MonoBinaryWriter(commandParams); - commandParamsWriter.Write(typeId); + var retDebuggerCmdReader = await GetTypePropertiesReader(sessionId, typeId, token); + if (retDebuggerCmdReader == null) + return -1; - var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, commandParams, token); var nProperties = retDebuggerCmdReader.ReadInt32(); for (int i = 0 ; i < nProperties; i++) { @@ -1360,11 +1579,10 @@ namespace Microsoft.WebAssembly.Diagnostics public async Task CreateJArrayForProperties(SessionId sessionId, int typeId, byte[] object_buffer, JArray attributes, bool isAutoExpandable, string objectId, bool isOwn, CancellationToken token) { JArray ret = new JArray(); - var commandParams = new MemoryStream(); - var commandParamsWriter = new MonoBinaryWriter(commandParams); - commandParamsWriter.Write(typeId); + var retDebuggerCmdReader = await GetTypePropertiesReader(sessionId, typeId, token); + if (retDebuggerCmdReader == null) + return null; - var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, commandParams, token); var nProperties = retDebuggerCmdReader.ReadInt32(); for (int i = 0 ; i < nProperties; i++) { @@ -1420,8 +1638,9 @@ namespace Microsoft.WebAssembly.Diagnostics var varName = pointerValues[pointerId].varName; if (int.TryParse(varName, out _)) varName = $"[{varName}]"; - return await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, "*" + varName, false, -1, token); + return await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, "*" + varName, false, -1, false, token); } + public async Task GetPropertiesValuesOfValueType(SessionId sessionId, int valueTypeId, CancellationToken token) { JArray ret = new JArray(); @@ -1523,7 +1742,7 @@ namespace Microsoft.WebAssembly.Diagnostics int pointerId = 0; if (valueAddress != 0 && className != "(void*)") { - pointerId = Interlocked.Increment(ref debugger_object_id); + pointerId = Interlocked.Increment(ref debuggerObjectId); type = "object"; value = className; pointerValues[pointerId] = new PointerValue(valueAddress, typeId, name); @@ -1551,13 +1770,15 @@ namespace Microsoft.WebAssembly.Diagnostics return CreateJObject(null, "object", $"{value.ToString()}({length})", false, value.ToString(), "dotnet:array:" + objectId, null, "array"); } - public async Task CreateJObjectForObject(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, int typeIdFromAttribute, CancellationToken token) + public async Task CreateJObjectForObject(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token) { var objectId = retDebuggerCmdReader.ReadInt32(); var className = ""; var type_id = await GetTypeIdFromObject(sessionId, objectId, false, token); className = await GetTypeName(sessionId, type_id[0], token); - var debuggerDisplayAttribute = await GetValueFromDebuggerDisplayAttribute(sessionId, objectId, type_id[0], token); + string debuggerDisplayAttribute = null; + if (!forDebuggerDisplayAttribute) + debuggerDisplayAttribute = await GetValueFromDebuggerDisplayAttribute(sessionId, objectId, type_id[0], token); var description = className.ToString(); if (debuggerDisplayAttribute != null) @@ -1588,13 +1809,13 @@ namespace Microsoft.WebAssembly.Diagnostics var className = await GetTypeName(sessionId, typeId, token); var description = className; var numFields = retDebuggerCmdReader.ReadInt32(); - var fields = await GetTypeFields(sessionId, typeId, onlyPublic: false, token); + var fields = await GetTypeFields(sessionId, typeId, token); JArray valueTypeFields = new JArray(); if (className.IndexOf("System.Nullable<") == 0) //should we call something on debugger-agent to check??? { retDebuggerCmdReader.ReadByte(); //ignoring the boolean type var isNull = retDebuggerCmdReader.ReadInt32(); - var value = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, name, false, -1, token); + var value = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, name, false, -1, false, token); if (isNull != 0) return value; else @@ -1602,12 +1823,12 @@ namespace Microsoft.WebAssembly.Diagnostics } for (int i = 0; i < numFields ; i++) { - fieldValueType = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, fields.ElementAt(i).Name, true, fields.ElementAt(i).TypeId, token); + fieldValueType = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, fields.ElementAt(i).Name, true, fields.ElementAt(i).TypeId, false, token); valueTypeFields.Add(fieldValueType); } long endPos = retDebuggerCmdReader.BaseStream.Position; - var valueTypeId = Interlocked.Increment(ref debugger_object_id); + var valueTypeId = Interlocked.Increment(ref debuggerObjectId); retDebuggerCmdReader.BaseStream.Position = initialPos; byte[] valueTypeBuffer = new byte[endPos - initialPos]; @@ -1663,7 +1884,7 @@ namespace Microsoft.WebAssembly.Diagnostics return CreateJObject(null, "object", className, false, className, null, null, "null"); } - public async Task CreateJObjectForVariableValue(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, string name, bool isOwn, int typeIdFromAttribute, CancellationToken token) + public async Task CreateJObjectForVariableValue(SessionId sessionId, MonoBinaryReader retDebuggerCmdReader, string name, bool isOwn, int typeIdFromAttribute, bool forDebuggerDisplayAttribute, CancellationToken token) { long initialPos = retDebuggerCmdReader == null ? 0 : retDebuggerCmdReader.BaseStream.Position; ElementType etype = (ElementType)retDebuggerCmdReader.ReadByte(); @@ -1771,7 +1992,7 @@ namespace Microsoft.WebAssembly.Diagnostics case ElementType.Class: case ElementType.Object: { - ret = await CreateJObjectForObject(sessionId, retDebuggerCmdReader, typeIdFromAttribute, token); + ret = await CreateJObjectForObject(sessionId, retDebuggerCmdReader, typeIdFromAttribute, forDebuggerDisplayAttribute, token); break; } case ElementType.ValueType: @@ -1806,12 +2027,19 @@ namespace Microsoft.WebAssembly.Diagnostics public async Task IsAsyncMethod(SessionId sessionId, int methodId, CancellationToken token) { + var methodInfo = await GetMethodInfo(sessionId, methodId, token); + if (methodInfo != null && methodInfo.Info.IsAsync != -1) + { + return methodInfo.Info.IsAsync == 1; + } + var commandParams = new MemoryStream(); var commandParamsWriter = new MonoBinaryWriter(commandParams); commandParamsWriter.Write(methodId); var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdMethod.AsyncDebugInfo, commandParams, token); - return retDebuggerCmdReader.ReadByte() == 1 ; //token + methodInfo.Info.IsAsync = retDebuggerCmdReader.ReadByte(); + return methodInfo.Info.IsAsync == 1; } private bool IsClosureReferenceField (string fieldName) @@ -1870,7 +2098,7 @@ namespace Microsoft.WebAssembly.Diagnostics return asyncLocalsFull; } - public async Task StackFrameGetValues(SessionId sessionId, MethodInfo method, int thread_id, int frame_id, VarInfo[] varIds, CancellationToken token) + public async Task StackFrameGetValues(SessionId sessionId, MethodInfoWithDebugInformation method, int thread_id, int frame_id, VarInfo[] varIds, CancellationToken token) { var commandParams = new MemoryStream(); var commandParamsWriter = new MonoBinaryWriter(commandParams); @@ -1883,20 +2111,13 @@ namespace Microsoft.WebAssembly.Diagnostics commandParamsWriter.Write(var.Index); } - if (await IsAsyncMethod(sessionId, method.DebuggerId, token)) + if (await IsAsyncMethod(sessionId, method.DebugId, token)) { retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetThis, commandParams, token); retDebuggerCmdReader.ReadByte(); //ignore type var objectId = retDebuggerCmdReader.ReadInt32(); var asyncLocals = await GetObjectValues(sessionId, objectId, GetObjectCommandOptions.WithProperties, token); - asyncLocals = new JArray(asyncLocals.Where( asyncLocal => !asyncLocal["name"].Value().Contains("<>") || asyncLocal["name"].Value().EndsWith("__this"))); - foreach (var asyncLocal in asyncLocals) - { - if (asyncLocal["name"].Value().EndsWith("__this", StringComparison.Ordinal)) - asyncLocal["name"] = "this"; - else if (asyncLocal["name"].Value().Contains('<')) - asyncLocal["name"] = Regex.Match(asyncLocal["name"].Value(), @"\<([^)]*)\>").Groups[1].Value; - } + asyncLocals = await GetHoistedLocalVariables(sessionId, objectId, asyncLocals, token); return asyncLocals; } @@ -1904,13 +2125,13 @@ namespace Microsoft.WebAssembly.Diagnostics retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetValues, commandParams, token); foreach (var var in varIds) { - var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, var.Name, false, -1, token); + var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, var.Name, false, -1, false, token); locals.Add(var_json); } - if (!method.IsStatic()) + if (!method.Info.IsStatic()) { retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdFrame.GetThis, commandParams, token); - var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, "this", false, -1, token); + var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, "this", false, -1, false, token); var_json.Add("fieldOffset", -1); locals.Add(var_json); } @@ -1936,11 +2157,10 @@ namespace Microsoft.WebAssembly.Diagnostics return valueTypes[valueTypeId].valueTypeProxy; valueTypes[valueTypeId].valueTypeProxy = new JArray(valueTypes[valueTypeId].valueTypeJson); - var commandParams = new MemoryStream(); - var commandParamsWriter = new MonoBinaryWriter(commandParams); - commandParamsWriter.Write(valueTypes[valueTypeId].typeId); + var retDebuggerCmdReader = await GetTypePropertiesReader(sessionId, valueTypes[valueTypeId].typeId, token); + if (retDebuggerCmdReader == null) + return null; - var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, commandParams, token); var nProperties = retDebuggerCmdReader.ReadInt32(); for (int i = 0 ; i < nProperties; i++) @@ -1983,7 +2203,7 @@ namespace Microsoft.WebAssembly.Diagnostics JArray array = new JArray(); for (int i = 0 ; i < length ; i++) { - var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, i.ToString(), false, -1, token); + var var_json = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, i.ToString(), false, -1, false, token); array.Add(var_json); } return array; @@ -2115,7 +2335,10 @@ namespace Microsoft.WebAssembly.Diagnostics { if (!getCommandType.HasFlag(GetObjectCommandOptions.AccessorPropertiesOnly)) { - var fields = await GetTypeFields(sessionId, typeId[i], onlyPublic: getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute), token); + className = await GetTypeName(sessionId, typeId[i], token); + var fields = await GetTypeFields(sessionId, typeId[i], token); + if (getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerProxyAttribute)) + fields = fields.Where(field => field.IsPublic).ToList(); JArray objectFields = new JArray(); var commandParams = new MemoryStream(); @@ -2124,7 +2347,7 @@ namespace Microsoft.WebAssembly.Diagnostics commandParamsWriter.Write(fields.Count); foreach (var field in fields) { - commandParamsWriter.Write(field.Id); + commandParamsWriter.Write(field.Id); } var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdObject.RefGetValues, commandParams, token); @@ -2134,8 +2357,7 @@ namespace Microsoft.WebAssembly.Diagnostics long initialPos = retDebuggerCmdReader.BaseStream.Position; int valtype = retDebuggerCmdReader.ReadByte(); retDebuggerCmdReader.BaseStream.Position = initialPos; - var fieldValue = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, field.Name, i == 0, field.TypeId, token); - + var fieldValue = await CreateJObjectForVariableValue(sessionId, retDebuggerCmdReader, field.Name, i == 0, field.TypeId, getCommandType.HasFlag(GetObjectCommandOptions.ForDebuggerDisplayAttribute), token); if (ret.Where(attribute => attribute["name"].Value().Equals(fieldValue["name"].Value())).Any()) { continue; } @@ -2182,7 +2404,7 @@ namespace Microsoft.WebAssembly.Diagnostics List> allFields = new List>(); for (int i = 0; i < typeId.Count; i++) { - var fields = await GetTypeFields(sessionId, typeId[i], onlyPublic: false, token); + var fields = await GetTypeFields(sessionId, typeId[i], token); allFields.Add(fields); } foreach (var item in ret) @@ -2216,11 +2438,10 @@ namespace Microsoft.WebAssembly.Diagnostics var typeIds = await GetTypeIdFromObject(sessionId, objectId, true, token); foreach (var typeId in typeIds) { - var commandParams = new MemoryStream(); - var commandParamsWriter = new MonoBinaryWriter(commandParams); - commandParamsWriter.Write(typeId); + var retDebuggerCmdReader = await GetTypePropertiesReader(sessionId, typeId, token); + if (retDebuggerCmdReader == null) + return null; - var retDebuggerCmdReader = await SendDebuggerAgentCommand(sessionId, CmdType.GetProperties, commandParams, token); var nProperties = retDebuggerCmdReader.ReadInt32(); for (int i = 0 ; i < nProperties; i++) { diff --git a/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs b/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs index 91f42ab..548b012 100644 --- a/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs +++ b/src/mono/wasm/debugger/DebuggerTestSuite/Tests.cs @@ -841,7 +841,6 @@ namespace DebuggerTests var t_props = await GetObjectOnLocals(locals, "t"); await CheckProps(t_props, new { - s_taskIdCounter = TNumber(0), Status = TGetter("Status") }, "t_props", num_fields: 53); }); -- 2.7.4