diff --git a/documentation/api/definitions.md b/documentation/api/definitions.md index dff57e617e4..a779a5b2689 100644 --- a/documentation/api/definitions.md +++ b/documentation/api/definitions.md @@ -35,8 +35,11 @@ First Available: 8.0 Preview 7 | Name | Type | Description | |---|---|---| | `methodName` | string | Name of the method for this frame. This includes generic parameters. | +| `methodToken` | int | TypeDef token for the method. | +| `parameterTypes` | string[] | Array of parameter types. Empty array if none. | | `typeName` | string | Name of the class for this frame. This includes generic parameters. | | `moduleName` | string | Name of the module for this frame. | +| `moduleVersionId` | guid | Unique identifier used to distinguish between two versions of the same module. An empty value: `00000000-0000-0000-0000-000000000000`. | ## CallStackResult diff --git a/documentation/api/exceptions.md b/documentation/api/exceptions.md index 764000cdb28..904e83f2733 100644 --- a/documentation/api/exceptions.md +++ b/documentation/api/exceptions.md @@ -126,8 +126,54 @@ Accept: application/x-ndjson HTTP/1.1 200 OK Content-Type: application/x-ndjson -{"id":2,"timestamp":"2023-07-13T21:45:11.8056355Z","typeName":"System.InvalidOperationException","moduleName":"System.Private.CoreLib.dll","message":"Operation is not valid due to the current state of the object.","innerExceptions":[],"stack":{"threadId":4768,"threadName":null,"frames":[{"methodName":"MoveNext","parameterTypes":[],"typeName":"WebApplication3.Pages.IndexModel\u002B\u003CGetData\u003Ed__3","moduleName":"WebApplication3.dll"},{"methodName":"RunInternal","parameterTypes":["System.Threading.ExecutionContext","System.Threading.ContextCallback","System.Object"],"typeName":"System.Threading.ExecutionContext","moduleName":"System.Private.CoreLib.dll"},{"methodName":"MoveNext","parameterTypes":["System.Threading.Thread"],"typeName":"System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601[System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601\u002BTResult,System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601\u002BTStateMachine]","moduleName":"System.Private.CoreLib.dll"},{"methodName":"\u003COutputCorrelationEtwEvent\u003Eb__6_0","parameterTypes":["System.Action","System.Threading.Tasks.Task"],"typeName":"System.Runtime.CompilerServices.YieldAwaitable\u002BYieldAwaiter\u002B\u003C\u003Ec","moduleName":"System.Private.CoreLib.dll"},{"methodName":"Dispatch","parameterTypes":[],"typeName":"System.Threading.ThreadPoolWorkQueue","moduleName":"System.Private.CoreLib.dll"},{"methodName":"WorkerThreadStart","parameterTypes":[],"typeName":"System.Threading.PortableThreadPool\u002BWorkerThread","moduleName":"System.Private.CoreLib.dll"}]}} -{"id":3,"timestamp":"2023-07-13T21:46:18.7530773Z","typeName":"System.ObjectDisposedException","moduleName":"System.Private.CoreLib.dll","message":"Cannot access a disposed object.\r\nObject name: \u0027System.Net.Sockets.NetworkStream\u0027.","innerExceptions":[],"stack":{"threadId":15912,"threadName":null,"frames":[{"methodName":"ThrowObjectDisposedException","parameterTypes":["System.Object"],"typeName":"System.ThrowHelper","moduleName":"System.Private.CoreLib.dll"},{"methodName":"ThrowIf","parameterTypes":["System.Boolean","System.Object"],"typeName":"System.ObjectDisposedException","moduleName":"System.Private.CoreLib.dll"},{"methodName":"ReadAsync","parameterTypes":["System.Memory\u00601[[System.Byte, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]][System.Byte]","System.Threading.CancellationToken"],"typeName":"System.Net.Sockets.NetworkStream","moduleName":"System.Net.Sockets.dll"},{"methodName":"MoveNext","parameterTypes":[],"typeName":"System.Net.Http.HttpConnection\u002B\u003C\u003CEnsureReadAheadTaskHasStarted\u003Eg__ReadAheadWithZeroByteReadAsync|43_0\u003Ed","moduleName":"System.Net.Http.dll"},{"methodName":"ExecutionContextCallback","parameterTypes":["System.Object"],"typeName":"System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601[System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601\u002BTResult,System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601\u002BTStateMachine]","moduleName":"System.Private.CoreLib.dll"},{"methodName":"RunInternal","parameterTypes":["System.Threading.ExecutionContext","System.Threading.ContextCallback","System.Object"],"typeName":"System.Threading.ExecutionContext","moduleName":"System.Private.CoreLib.dll"},{"methodName":"MoveNext","parameterTypes":["System.Threading.Thread"],"typeName":"System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601[System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601\u002BTResult,System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601\u002BTStateMachine]","moduleName":"System.Private.CoreLib.dll"},{"methodName":"MoveNext","parameterTypes":[],"typeName":"System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601[System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601\u002BTResult,System.Runtime.CompilerServices.AsyncTaskMethodBuilder\u00601\u002BAsyncStateMachineBox\u00601\u002BTStateMachine]","moduleName":"System.Private.CoreLib.dll"},{"methodName":"\u003C.cctor\u003Eb__176_0","parameterTypes":["System.UInt32","System.UInt32","System.Threading.NativeOverlapped*"],"typeName":"System.Net.Sockets.SocketAsyncEventArgs\u002B\u003C\u003Ec","moduleName":"System.Net.Sockets.dll"},{"methodName":"Invoke","parameterTypes":["System.Threading.PortableThreadPool\u002BIOCompletionPoller\u002BEvent"],"typeName":"System.Threading.PortableThreadPool\u002BIOCompletionPoller\u002BCallback","moduleName":"System.Private.CoreLib.dll"},{"methodName":"System.Threading.IThreadPoolWorkItem.Execute","parameterTypes":[],"typeName":"System.Threading.ThreadPoolTypedWorkItemQueue\u00602[System.Threading.ThreadPoolTypedWorkItemQueue\u00602\u002BT,System.Threading.ThreadPoolTypedWorkItemQueue\u00602\u002BTCallback]","moduleName":"System.Private.CoreLib.dll"},{"methodName":"Dispatch","parameterTypes":[],"typeName":"System.Threading.ThreadPoolWorkQueue","moduleName":"System.Private.CoreLib.dll"},{"methodName":"WorkerThreadStart","parameterTypes":[],"typeName":"System.Threading.PortableThreadPool\u002BWorkerThread","moduleName":"System.Private.CoreLib.dll"}]}} +{ + "id": 2, + "timestamp": "2023-07-13T21:45:11.8056355Z", + "typeName": "System.InvalidOperationException", + "moduleName": "System.Private.CoreLib.dll", + "message": "Operation is not valid due to the current state of the object.", + "innerExceptions": [], + "stack": { + "threadId": 4768, + "threadName": null, + "frames": [ + { + "methodName": "MoveNext", + "methodToken": 100663639, + "parameterTypes": [], + "typeName": "WebApplication3.Pages.IndexModel\u002B\u003CGetData\u003Ed__3", + "moduleName": "WebApplication3.dll", + "moduleVersionId": "bf769014-c2e2-496a-93b7-76fbbcd04be5" + }, + ... // see stacks.md + ] + } +} +{ + "id": 3, + "timestamp": "2023-07-13T21:46:18.7530773Z", + "typeName": "System.ObjectDisposedException", + "moduleName": "System.Private.CoreLib.dll", + "message": "Cannot access a disposed object.\r\nObject name: \u0027System.Net.Sockets.NetworkStream\u0027.", + "innerExceptions": [], + "stack": { + "threadId": 15912, + "threadName": null, + "frames": [ + { + "methodName": "ThrowObjectDisposedException", + "methodToken": 100663639, + "parameterTypes": [ + "System.Object" + ], + "typeName": "System.ThrowHelper", + "moduleName": "System.Private.CoreLib.dll", + "moduleVersionId": "bf769014-c2e2-496a-93b7-76fbbcd04be5" + }, + ... // see stacks.md + ] + } +} ``` ## Supported Runtimes diff --git a/documentation/api/stacks.md b/documentation/api/stacks.md index 4147e7f57a4..eeec2510332 100644 --- a/documentation/api/stacks.md +++ b/documentation/api/stacks.md @@ -81,19 +81,33 @@ Location: localhost:52323/operations/67f07e40-5cca-4709-9062-26302c484f18 "frames": [ { "methodName": "GetQueuedCompletionStatus", + "methodToken": 100663634, + "parameterTypes": [], "typeName": "Interop\u002BKernel32", "moduleName": "System.Private.CoreLib.dll", + "moduleVersionId": "194ddabd-a802-4520-90ef-854e2f1cd606" }, { "methodName": "WaitForSignal", + "methodToken": 100663639, + "parameterTypes": [ + "System.Threading.ExecutionContext", + "System.Threading.ContextCallback", + "System.Object" + ], "typeName": "System.Threading.LowLevelLifoSemaphore", "moduleName": "System.Private.CoreLib.dll", + "moduleVersionId": "194ddabd-a802-4520-90ef-854e2f1cd606" }, { "methodName": "Wait", + "methodToken": 100663643, + "parameterTypes": [], "typeName": "System.Threading.LowLevelLifoSemaphore", "moduleName": "System.Private.CoreLib.dll", + "moduleVersionId": "194ddabd-a802-4520-90ef-854e2f1cd606" } + ] } ``` diff --git a/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Eventing/ExceptionsEventSource.cs b/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Eventing/ExceptionsEventSource.cs index 3bcb89b13da..efa325bb9a9 100644 --- a/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Eventing/ExceptionsEventSource.cs +++ b/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Eventing/ExceptionsEventSource.cs @@ -97,6 +97,7 @@ internal sealed class ExceptionsEventSource : AbstractMonitorEventSource [Event(ExceptionEvents.EventIds.FunctionDescription)] public void FunctionDescription( ulong FunctionId, + uint MethodToken, ulong ClassId, uint ClassToken, ulong ModuleId, @@ -104,7 +105,7 @@ internal sealed class ExceptionsEventSource : AbstractMonitorEventSource ulong[] TypeArgs, ulong[] ParameterTypes) { - Span data = stackalloc EventData[7]; + Span data = stackalloc EventData[8]; using PinnedData namePinned = PinnedData.Create(Name); Span typeArgsSpan = stackalloc byte[GetArrayDataSize(TypeArgs)]; FillArrayData(typeArgsSpan, TypeArgs); @@ -112,6 +113,7 @@ internal sealed class ExceptionsEventSource : AbstractMonitorEventSource FillArrayData(parameterTypesSpan, ParameterTypes); SetValue(ref data[NameIdentificationEvents.FunctionDescPayloads.FunctionId], FunctionId); + SetValue(ref data[NameIdentificationEvents.FunctionDescPayloads.MethodToken], MethodToken); SetValue(ref data[NameIdentificationEvents.FunctionDescPayloads.ClassId], ClassId); SetValue(ref data[NameIdentificationEvents.FunctionDescPayloads.ClassToken], ClassToken); SetValue(ref data[NameIdentificationEvents.FunctionDescPayloads.ModuleId], ModuleId); @@ -125,12 +127,14 @@ internal sealed class ExceptionsEventSource : AbstractMonitorEventSource [Event(ExceptionEvents.EventIds.ModuleDescription)] public void ModuleDescription( ulong ModuleId, + Guid ModuleVersionId, string Name) { - Span data = stackalloc EventData[2]; + Span data = stackalloc EventData[3]; using PinnedData namePinned = PinnedData.Create(Name); SetValue(ref data[NameIdentificationEvents.ModuleDescPayloads.ModuleId], ModuleId); + SetValue(ref data[NameIdentificationEvents.ModuleDescPayloads.ModuleVersionId], ModuleVersionId); SetValue(ref data[NameIdentificationEvents.ModuleDescPayloads.Name], namePinned); WriteEventWithFlushing(ExceptionEvents.EventIds.ModuleDescription, data); diff --git a/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Eventing/ExceptionsEventSourceIdentifierCacheCallback.cs b/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Eventing/ExceptionsEventSourceIdentifierCacheCallback.cs index f59bd20b5b2..5a76d8ea968 100644 --- a/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Eventing/ExceptionsEventSourceIdentifierCacheCallback.cs +++ b/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Eventing/ExceptionsEventSourceIdentifierCacheCallback.cs @@ -38,8 +38,9 @@ public override void OnFunctionData(ulong functionId, FunctionData data) { _source.FunctionDescription( functionId, + data.MethodToken, data.ParentClass, - data.ParentToken, + data.ParentClassToken, data.ModuleId, data.Name, data.TypeArgs, @@ -50,6 +51,7 @@ public override void OnModuleData(ulong moduleId, ModuleData data) { _source.ModuleDescription( moduleId, + data.ModuleVersionId, data.Name); } diff --git a/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Identification/ExceptionGroupIdentifierCache.cs b/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Identification/ExceptionGroupIdentifierCache.cs index 9b93c7e6f22..99ef3d1f5e6 100644 --- a/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Identification/ExceptionGroupIdentifierCache.cs +++ b/src/Microsoft.Diagnostics.Monitoring.StartupHook/Exceptions/Identification/ExceptionGroupIdentifierCache.cs @@ -83,13 +83,19 @@ public ulong GetOrAdd(MethodBase method) return methodId; // Dynamic methods do not have metadata tokens - uint metadataToken = 0; + uint methodToken = 0; try { - metadataToken = Convert.ToUInt32(method.MetadataToken); + methodToken = Convert.ToUInt32(method.MetadataToken); } catch (Exception) { } + uint parentClassToken = 0; + if (null != method.DeclaringType) + { + parentClassToken = Convert.ToUInt32(method.DeclaringType.MetadataToken); + } + // RTDynamicMethod does not implement GetGenericArguments. Type[] genericArguments = Array.Empty(); try @@ -100,8 +106,9 @@ public ulong GetOrAdd(MethodBase method) FunctionData data = new( method.Name, + methodToken, AddOrDefault(method.DeclaringType), - metadataToken, + parentClassToken, GetOrAdd(method.Module), GetOrAdd(genericArguments), GetOrAdd(method.GetParameters()) @@ -128,7 +135,7 @@ public ulong GetOrAdd(Module module) if (!GetOrCreateIdentifier(_moduleIds, module, ref _nextModuleId, out ulong moduleId)) return moduleId; - ModuleData data = new(module.Name); + ModuleData data = new(module.Name, module.ModuleVersionId); if (_nameCache.ModuleData.TryAdd(moduleId, data)) { @@ -212,16 +219,16 @@ public ulong GetOrAdd(Type type) ModuleScopedToken key = new(moduleId, typeToken); if (!_nameCache.TokenData.ContainsKey(key)) { - uint parentToken = 0; + uint parentClassToken = 0; if (null != type.DeclaringType) { - parentToken = Convert.ToUInt32(type.DeclaringType.MetadataToken); + parentClassToken = Convert.ToUInt32(type.DeclaringType.MetadataToken); } TokenData tokenData = new( type.Name, null == type.DeclaringType ? type.Namespace ?? string.Empty : string.Empty, - parentToken); + parentClassToken); if (!_nameCache.TokenData.TryAdd(key, tokenData)) break; diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Models/CallStackResults.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Models/CallStackResults.cs index a93e0f2ecb9..3d410000a49 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Models/CallStackResults.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Models/CallStackResults.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; using System.Text.Json.Serialization; @@ -11,12 +12,18 @@ public class CallStackFrame [JsonPropertyName("methodName")] public string MethodName { get; set; } + [JsonPropertyName("methodToken")] + public uint MethodToken { get; set; } + [JsonPropertyName("typeName")] public string TypeName { get; set; } [JsonPropertyName("moduleName")] public string ModuleName { get; set; } + [JsonPropertyName("moduleVersionId")] + public Guid ModuleVersionId { get; set; } + [JsonIgnore] internal IList SimpleGenericArgTypes { get; set; } = new List(); diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/CallStackData.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/CallStackData.cs index 5e8acce7503..47e4f73b5ae 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/CallStackData.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/CallStackData.cs @@ -1,6 +1,7 @@ // 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.Collections.Generic; namespace Microsoft.Diagnostics.Monitoring.WebApi.Stacks @@ -20,6 +21,10 @@ internal sealed class CallStackFrame { public ulong FunctionId { get; set; } + public uint MethodToken { get; set; } + + public Guid ModuleVersionId { get; set; } + public ulong Offset { get; set; } } diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/EventStacksPipeline.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/EventStacksPipeline.cs index ec280574838..50508001b1e 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/EventStacksPipeline.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/EventStacksPipeline.cs @@ -85,7 +85,22 @@ private void Callback(TraceEvent action) { for (int i = 0; i < functionIds.Length; i++) { - stack.Frames.Add(new CallStackFrame { FunctionId = functionIds[i], Offset = offsets[i] }); + CallStackFrame stackFrame = new CallStackFrame + { + FunctionId = functionIds[i], + Offset = offsets[i] + }; + + if (_result.NameCache.FunctionData.TryGetValue(stackFrame.FunctionId, out FunctionData functionData)) + { + stackFrame.MethodToken = functionData.MethodToken; + if (_result.NameCache.ModuleData.TryGetValue(functionData.ModuleId, out ModuleData moduleData)) + { + stackFrame.ModuleVersionId = moduleData.ModuleVersionId; + } + } + + stack.Frames.Add(stackFrame); } } } @@ -94,6 +109,7 @@ private void Callback(TraceEvent action) ulong id = action.GetPayload(NameIdentificationEvents.FunctionDescPayloads.FunctionId); var functionData = new FunctionData( action.GetPayload(NameIdentificationEvents.FunctionDescPayloads.Name), + action.GetPayload(NameIdentificationEvents.FunctionDescPayloads.MethodToken), action.GetPayload(NameIdentificationEvents.FunctionDescPayloads.ClassId), action.GetPayload(NameIdentificationEvents.FunctionDescPayloads.ClassToken), action.GetPayload(NameIdentificationEvents.FunctionDescPayloads.ModuleId), @@ -119,7 +135,8 @@ private void Callback(TraceEvent action) { ulong id = action.GetPayload(NameIdentificationEvents.ModuleDescPayloads.ModuleId); var moduleData = new ModuleData( - action.GetPayload(NameIdentificationEvents.ModuleDescPayloads.Name) + action.GetPayload(NameIdentificationEvents.ModuleDescPayloads.Name), + action.GetPayload(NameIdentificationEvents.ModuleDescPayloads.ModuleVersionId) ); _result.NameCache.ModuleData.TryAdd(id, moduleData); diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameCache.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameCache.cs index 9fb10cfe8e2..6207e0837a5 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameCache.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameCache.cs @@ -1,6 +1,7 @@ // 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.Collections.Concurrent; using System.Diagnostics; @@ -43,20 +44,22 @@ internal sealed record class ClassData(uint Token, ulong ModuleId, ClassFlags Fl internal sealed record class TokenData(string Name, string Namespace, uint OuterToken); /// The name of the function. + /// The method token of the function (methodDef token). /// The parent class identifier of the function. - /// The parent metadata token of the function. + /// The parent metadata token of the function (typeDef token). /// The identifier of the module that contains the function. /// The class identifiers of the generic type arguments of the function. /// The class identifiers of the parameter types of the function. /// - /// If is 0, then use . + /// If is 0, then use . /// [DebuggerDisplay("{Name}")] - internal sealed record class FunctionData(string Name, ulong ParentClass, uint ParentToken, ulong ModuleId, ulong[] TypeArgs, ulong[] ParameterTypes); + internal sealed record class FunctionData(string Name, uint MethodToken, ulong ParentClass, uint ParentClassToken, ulong ModuleId, ulong[] TypeArgs, ulong[] ParameterTypes); /// The name of the module. + /// The version identifier of the module. [DebuggerDisplay("{Name}")] - internal sealed record class ModuleData(string Name); + internal sealed record class ModuleData(string Name, Guid ModuleVersionId); internal sealed record class ModuleScopedToken(ulong ModuleId, uint Token); } diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameFormatter.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameFormatter.cs index 3d5d9031076..050ab763dc8 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameFormatter.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameFormatter.cs @@ -42,7 +42,7 @@ public static void BuildTypeName(StringBuilder builder, NameCache cache, Functio } else { - BuildTypeName(builder, cache, functionData.ModuleId, functionData.ParentToken, TypeFormat.Full); + BuildTypeName(builder, cache, functionData.ModuleId, functionData.ParentClassToken, TypeFormat.Full); } } @@ -80,11 +80,11 @@ public static void BuildTypeName(StringBuilder builder, NameCache cache, ulong c } } - private static void BuildTypeName(StringBuilder builder, NameCache cache, ulong moduleId, uint token, TypeFormat typeFormat) + private static void BuildTypeName(StringBuilder builder, NameCache cache, ulong moduleId, uint classToken, TypeFormat typeFormat) { var typeNames = new Stack(); - uint currentToken = token; + uint currentToken = classToken; while (currentToken != 0 && cache.TokenData.TryGetValue(new ModuleScopedToken(moduleId, currentToken), out TokenData? tokenData)) { string typeName = tokenData.Name; diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameIdentificationEvents.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameIdentificationEvents.cs index 5991b064d66..91394002118 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameIdentificationEvents.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Stacks/NameIdentificationEvents.cs @@ -12,12 +12,13 @@ internal static class NameIdentificationEvents public static class FunctionDescPayloads { public const int FunctionId = 0; - public const int ClassId = 1; - public const int ClassToken = 2; - public const int ModuleId = 3; - public const int Name = 4; - public const int TypeArgs = 5; - public const int ParameterTypes = 6; + public const int MethodToken = 1; + public const int ClassId = 2; + public const int ClassToken = 3; + public const int ModuleId = 4; + public const int Name = 5; + public const int TypeArgs = 6; + public const int ParameterTypes = 7; } public static class ClassDescPayloads @@ -32,7 +33,8 @@ public static class ClassDescPayloads public static class ModuleDescPayloads { public const int ModuleId = 0; - public const int Name = 1; + public const int ModuleVersionId = 1; + public const int Name = 2; } public static class TokenDescPayloads diff --git a/src/Microsoft.Diagnostics.Monitoring.WebApi/Utilities/StackUtilities.cs b/src/Microsoft.Diagnostics.Monitoring.WebApi/Utilities/StackUtilities.cs index 269b2a55dad..c20962c5fad 100644 --- a/src/Microsoft.Diagnostics.Monitoring.WebApi/Utilities/StackUtilities.cs +++ b/src/Microsoft.Diagnostics.Monitoring.WebApi/Utilities/StackUtilities.cs @@ -48,9 +48,11 @@ internal static Models.CallStackFrame CreateFrameModel(CallStackFrame frame, Nam { TypeName = NameFormatter.UnknownClass, MethodName = StacksFormatter.UnknownFunction, + MethodToken = 0, //TODO Bring this back once we have a useful offset value //Offset = frame.Offset, - ModuleName = NameFormatter.UnknownModule + ModuleName = NameFormatter.UnknownModule, + ModuleVersionId = Guid.Empty }; if (frame.FunctionId == 0) { @@ -60,8 +62,14 @@ internal static Models.CallStackFrame CreateFrameModel(CallStackFrame frame, Nam } else if (cache.FunctionData.TryGetValue(frame.FunctionId, out FunctionData functionData)) { + frameModel.MethodToken = functionData.MethodToken; frameModel.ModuleName = NameFormatter.GetModuleName(cache, functionData.ModuleId); + if (cache.ModuleData.TryGetValue(functionData.ModuleId, out ModuleData moduleData)) + { + frameModel.ModuleVersionId = moduleData.ModuleVersionId; + } + builder.Clear(); builder.Append(functionData.Name); diff --git a/src/Profilers/MonitorProfiler/Stacks/StacksEventProvider.cpp b/src/Profilers/MonitorProfiler/Stacks/StacksEventProvider.cpp index d3f69b4599a..5f148ee4520 100644 --- a/src/Profilers/MonitorProfiler/Stacks/StacksEventProvider.cpp +++ b/src/Profilers/MonitorProfiler/Stacks/StacksEventProvider.cpp @@ -53,6 +53,7 @@ HRESULT StacksEventProvider::WriteFunctionData(FunctionID functionId, const Func { return _functionEvent->WritePayload( static_cast(functionId), + functionData.GetMethodToken(), static_cast(functionData.GetClass()), functionData.GetClassToken(), static_cast(functionData.GetModuleId()), @@ -63,7 +64,10 @@ HRESULT StacksEventProvider::WriteFunctionData(FunctionID functionId, const Func HRESULT StacksEventProvider::WriteModuleData(ModuleID moduleId, const ModuleData& moduleData) { - return _moduleEvent->WritePayload(moduleId, moduleData.GetName()); + return _moduleEvent->WritePayload( + moduleId, + moduleData.GetMvid(), + moduleData.GetName()); } HRESULT StacksEventProvider::WriteTokenData(ModuleID moduleId, mdTypeDef typeDef, const TokenData& tokenData) diff --git a/src/Profilers/MonitorProfiler/Stacks/StacksEventProvider.h b/src/Profilers/MonitorProfiler/Stacks/StacksEventProvider.h index 4a66cfdc0d1..e2d6af6ce45 100644 --- a/src/Profilers/MonitorProfiler/Stacks/StacksEventProvider.h +++ b/src/Profilers/MonitorProfiler/Stacks/StacksEventProvider.h @@ -42,8 +42,8 @@ class StacksEventProvider std::unique_ptr, std::vector>> _callstackEvent; //Note we will either send a ClassId or a ClassToken. For Shared generic functions, there is no ClassID. - const WCHAR* FunctionPayloads[7] = { _T("FunctionId"), _T("ClassId"), _T("ClassToken"), _T("ModuleId"), _T("Name"), _T("TypeArgs"), _T("ParameterTypes") }; - std::unique_ptr, std::vector>> _functionEvent; + const WCHAR* FunctionPayloads[8] = { _T("FunctionId"), _T("MethodToken"), _T("ClassId"), _T("ClassToken"), _T("ModuleId"), _T("Name"), _T("TypeArgs"), _T("ParameterTypes") }; + std::unique_ptr, std::vector>> _functionEvent; //We cannot retrieve detailed information for some ClassIds. Flags is used to indicate these conditions. const WCHAR* ClassPayloads[5] = { _T("ClassId"), _T("ModuleId"), _T("Token"), _T("Flags"), _T("TypeArgs") }; @@ -52,8 +52,8 @@ class StacksEventProvider const WCHAR* TokenPayloads[5] = { _T("ModuleId"), _T("Token"), _T("OuterToken"), _T("Name"), _T("Namespace") }; std::unique_ptr> _tokenEvent; - const WCHAR* ModulePayloads[2] = { _T("ModuleId"), _T("Name") }; - std::unique_ptr> _moduleEvent; + const WCHAR* ModulePayloads[3] = { _T("ModuleId"), _T("ModuleVersionId"), _T("Name") }; + std::unique_ptr> _moduleEvent; //TODO Once ProfilerEvent supports it, use an event with no payload. const WCHAR* EndPayloads[1] = { _T("Unused") }; diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Eventing/ExceptionsEventListener.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Eventing/ExceptionsEventListener.cs index 3d1458faffb..c9444ab823d 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Eventing/ExceptionsEventListener.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Eventing/ExceptionsEventListener.cs @@ -68,6 +68,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) ToUInt64(eventData.Payload[NameIdentificationEvents.FunctionDescPayloads.FunctionId]), new FunctionData( ToString(eventData.Payload[NameIdentificationEvents.FunctionDescPayloads.Name]), + ToUInt32(eventData.Payload[NameIdentificationEvents.FunctionDescPayloads.MethodToken]), ToUInt64(eventData.Payload[NameIdentificationEvents.FunctionDescPayloads.ClassId]), ToUInt32(eventData.Payload[NameIdentificationEvents.FunctionDescPayloads.ClassToken]), ToUInt64(eventData.Payload[NameIdentificationEvents.FunctionDescPayloads.ModuleId]), @@ -78,7 +79,9 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) NameCache.ModuleData.TryAdd( ToUInt64(eventData.Payload[NameIdentificationEvents.ModuleDescPayloads.ModuleId]), new ModuleData( - ToString(eventData.Payload[NameIdentificationEvents.ModuleDescPayloads.Name]))); + ToString(eventData.Payload[NameIdentificationEvents.ModuleDescPayloads.Name]), + ToGuid(eventData.Payload[NameIdentificationEvents.ModuleDescPayloads.ModuleVersionId]) + )); break; case ExceptionEvents.EventIds.StackFrameDescription: StackFrameIdentifiers.TryAdd( @@ -104,6 +107,11 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) } } + private static Guid ToGuid(object? value) + { + return ToType(value); + } + private static int ToInt32(object? value) { return ToType(value); diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Eventing/ExceptionsEventSourceTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Eventing/ExceptionsEventSourceTests.cs index 685da3af35a..64b9ba75129 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Eventing/ExceptionsEventSourceTests.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Eventing/ExceptionsEventSourceTests.cs @@ -142,6 +142,58 @@ public void ExceptionsEventSource_WriteStackFrame_Event(ulong id, ulong methodId Assert.Equal(ilOffset, frameIdentifier.ILOffset); } + [Theory] + [InlineData(0, 0, 0, 0, 0, "", new ulong[0], new ulong[0])] + [InlineData(1, 100663639, 128, 256, 512, "ThrowObjectDisposedException", new ulong[1] { 1024 }, new ulong[2] { 2048, 4096 })] + public void ExceptionsEventSource_WriteFunction_Event( + ulong functionId, + uint methodToken, + ulong classId, + uint classToken, + ulong moduleId, + string name, + ulong[] typeArgs, + ulong[] parameterTypes) + { + using ExceptionsEventSource source = new(); + + using ExceptionsEventListener listener = new(); + listener.EnableEvents(source, EventLevel.Informational); + + source.FunctionDescription(functionId, methodToken, classId, classToken, moduleId, name, typeArgs, parameterTypes); + + Assert.True(listener.NameCache.FunctionData.TryGetValue(functionId, out FunctionData? function)); + Assert.Equal(methodToken, function.MethodToken); + Assert.Equal(classId, function.ParentClass); + Assert.Equal(classToken, function.ParentClassToken); + Assert.Equal(moduleId, function.ModuleId); + Assert.Equal(name, function.Name); + // We would normally expect the following to return an array of the stack frame IDs + // but in-process listener doesn't decode non-byte arrays correctly. + Assert.Equal(Array.Empty(), function.TypeArgs); + Assert.Equal(Array.Empty(), function.ParameterTypes); + } + + [Theory] + [InlineData(0, "00000000-0000-0000-0000-000000000000", "")] + [InlineData(1, NonEmptyGuidString, "Module")] + public void ExceptionsEventSource_WriteModule_Event( + ulong moduleId, + Guid moduleVersionId, + string name) + { + using ExceptionsEventSource source = new(); + + using ExceptionsEventListener listener = new(); + listener.EnableEvents(source, EventLevel.Informational); + + source.ModuleDescription(moduleId, moduleVersionId, name); + + Assert.True(listener.NameCache.ModuleData.TryGetValue(moduleId, out ModuleData? module)); + Assert.Equal(moduleVersionId, module.ModuleVersionId); + Assert.Equal(name, module.Name); + } + private static string CoalesceNull(string? value) { return value ?? ExceptionsEventListener.NullValue; diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Identification/ExceptionGroupIdentifierCacheTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Identification/ExceptionGroupIdentifierCacheTests.cs index 798086c179d..9a5917eb2b2 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Identification/ExceptionGroupIdentifierCacheTests.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.StartupHook.UnitTests/Exceptions/Identification/ExceptionGroupIdentifierCacheTests.cs @@ -178,7 +178,7 @@ public void ExceptionGroupIdentifierCache_ThrownException() // Validate throwing method remaining properties Assert.Equal(nameof(ExceptionGroupIdentifierCache_ThrownException), throwingMethodData.Name); Assert.NotEqual(InvalidId, throwingMethodData.ParentClass); - Assert.NotEqual(InvalidToken, throwingMethodData.ParentToken); + Assert.NotEqual(InvalidToken, throwingMethodData.ParentClassToken); Assert.Empty(throwingMethodData.TypeArgs); // Validate stack frame data diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/ExceptionsTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/ExceptionsTests.cs index 207eef01c35..7e70cf70c16 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/ExceptionsTests.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/ExceptionsTests.cs @@ -15,6 +15,7 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Reflection; using System.Runtime.InteropServices; using System.Text.Json; using System.Threading.Tasks; @@ -130,12 +131,20 @@ private async Task ExceptionsJsonTest(Architecture targetArchitecture, bool enab var topFrame = callStackResultsRootElement.GetProperty("frames").EnumerateArray().FirstOrDefault(); + MethodInfo methodInfo = typeof(Microsoft.Diagnostics.Monitoring.UnitTestApp.Scenarios.ExceptionsScenario).GetMethod( + FrameMethodName, + BindingFlags.Static | BindingFlags.NonPublic, + [typeof(bool), typeof(bool)]); + Assert.NotNull(methodInfo); + Assert.Equal(FrameMethodName, topFrame.GetProperty("methodName").ToString()); + Assert.Equal((uint)methodInfo.MetadataToken, topFrame.GetProperty("methodToken").GetUInt32()); Assert.Equal(2, topFrame.GetProperty("parameterTypes").GetArrayLength()); Assert.Equal(FrameParameterType, topFrame.GetProperty("parameterTypes")[0].ToString()); Assert.Equal(FrameParameterType, topFrame.GetProperty("parameterTypes")[1].ToString()); Assert.Equal(FrameTypeName, topFrame.GetProperty("typeName").ToString()); Assert.Equal(UnitTestAppModule, topFrame.GetProperty("moduleName").ToString()); + Assert.Equal(methodInfo.Module.ModuleVersionId, topFrame.GetProperty("moduleVersionId").GetGuid()); }, configureApp: runner => { diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.csproj b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.csproj index c2373b90b27..352877b0203 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.csproj +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests.csproj @@ -94,6 +94,7 @@ TargetFramework=net8.0 + diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/StacksTests.cs b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/StacksTests.cs index 100f281177c..0a8631cbcae 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/StacksTests.cs +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.Tool.FunctionalTests/StacksTests.cs @@ -40,6 +40,23 @@ public class StacksTests private const string NativeFrame = "[NativeFrame]"; private const string ExpectedThreadName = "TestThread"; + private static MethodInfo GetMethodInfo(string methodName) + { + // Strip off any generic type information. + if (methodName.Contains('[')) + { + methodName = methodName[..methodName.IndexOf('[')]; + } + + // Return null on psuedo frames (e.g. [NativeFrame]) + if (methodName.Length == 0) + { + return null; + } + + return typeof(Microsoft.Diagnostics.Monitoring.UnitTestApp.Scenarios.StacksWorker.StacksWorkerNested).GetMethod(methodName); + } + public StacksTests(ITestOutputHelper outputHelper, ServiceProviderFixture serviceProviderFixture) { _httpClientFactory = serviceProviderFixture.ServiceProvider.GetService(); @@ -452,8 +469,17 @@ private void CollectStacksActionConfigureTool(MonitorCollectRunner runner, out T private static string FormatFrame(string module, string @class, string function) => FormattableString.Invariant($"{module}!{@class}.{function}"); - private static bool AreFramesEqual(WebApi.Models.CallStackFrame left, WebApi.Models.CallStackFrame right) => - (left.ModuleName == right.ModuleName) && (left.TypeName == right.TypeName) && (left.MethodName == right.MethodName); + private static bool AreFramesEqual(WebApi.Models.CallStackFrame expected, WebApi.Models.CallStackFrame actual) + { + MethodInfo expectedMethodInfo = GetMethodInfo(expected.MethodName); + + return (expected.ModuleName == actual.ModuleName) && + (expected.TypeName == actual.TypeName) && + (expected.MethodName == actual.MethodName) && + ((expectedMethodInfo?.MetadataToken ?? 0) == actual.MethodToken) && + ((expectedMethodInfo?.Module.ModuleVersionId ?? Guid.Empty) == actual.ModuleVersionId); + + } private static bool AreFramesEqual(WebApi.Models.ProfileEvent left, WebApi.Models.ProfileEvent right) => (left.Frame == right.Frame) && (left.At == right.At) && (left.Type == right.Type); @@ -535,24 +561,24 @@ private static (WebApi.Models.CallStack, IList) Ge private static WebApi.Models.CallStackFrame[] ExpectedFrames() => new WebApi.Models.CallStackFrame[] { - new WebApi.Models.CallStackFrame - { - ModuleName = ExpectedModule, - TypeName = ExpectedClass, - MethodName = ExpectedCallbackFunction - }, - new WebApi.Models.CallStackFrame - { - ModuleName = NativeFrame, - TypeName = NativeFrame, - MethodName = NativeFrame - }, - new WebApi.Models.CallStackFrame - { - ModuleName = ExpectedModule, - TypeName = ExpectedClass, - MethodName = ExpectedFunction - } + new WebApi.Models.CallStackFrame + { + ModuleName = ExpectedModule, + TypeName = ExpectedClass, + MethodName = ExpectedCallbackFunction, + }, + new WebApi.Models.CallStackFrame + { + ModuleName = NativeFrame, + TypeName = NativeFrame, + MethodName = NativeFrame, + }, + new WebApi.Models.CallStackFrame + { + ModuleName = ExpectedModule, + TypeName = ExpectedClass, + MethodName = ExpectedFunction, + } }; } } diff --git a/src/Tests/Microsoft.Diagnostics.Monitoring.UnitTestApp/Microsoft.Diagnostics.Monitoring.UnitTestApp.csproj b/src/Tests/Microsoft.Diagnostics.Monitoring.UnitTestApp/Microsoft.Diagnostics.Monitoring.UnitTestApp.csproj index cd3ab6d95da..185ac82e527 100644 --- a/src/Tests/Microsoft.Diagnostics.Monitoring.UnitTestApp/Microsoft.Diagnostics.Monitoring.UnitTestApp.csproj +++ b/src/Tests/Microsoft.Diagnostics.Monitoring.UnitTestApp/Microsoft.Diagnostics.Monitoring.UnitTestApp.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,6 +7,10 @@ true + + + + diff --git a/src/Tools/dotnet-monitor/Exceptions/EventExceptionsPipeline.cs b/src/Tools/dotnet-monitor/Exceptions/EventExceptionsPipeline.cs index c98d758d853..d302e5d6c02 100644 --- a/src/Tools/dotnet-monitor/Exceptions/EventExceptionsPipeline.cs +++ b/src/Tools/dotnet-monitor/Exceptions/EventExceptionsPipeline.cs @@ -94,6 +94,7 @@ private void Callback(TraceEvent traceEvent) case "FunctionDescription": _cache.AddFunction( traceEvent.GetPayload(NameIdentificationEvents.FunctionDescPayloads.FunctionId), + traceEvent.GetPayload(NameIdentificationEvents.FunctionDescPayloads.MethodToken), traceEvent.GetPayload(NameIdentificationEvents.FunctionDescPayloads.ClassId), traceEvent.GetPayload(NameIdentificationEvents.FunctionDescPayloads.ClassToken), traceEvent.GetPayload(NameIdentificationEvents.FunctionDescPayloads.ModuleId), @@ -105,6 +106,7 @@ private void Callback(TraceEvent traceEvent) case "ModuleDescription": _cache.AddModule( traceEvent.GetPayload(NameIdentificationEvents.ModuleDescPayloads.ModuleId), + traceEvent.GetPayload(NameIdentificationEvents.ModuleDescPayloads.ModuleVersionId), traceEvent.GetPayload(NameIdentificationEvents.ModuleDescPayloads.Name) ); break; diff --git a/src/Tools/dotnet-monitor/Exceptions/EventExceptionsPipelineNameCache.cs b/src/Tools/dotnet-monitor/Exceptions/EventExceptionsPipelineNameCache.cs index 8f761b50c40..3660d68925b 100644 --- a/src/Tools/dotnet-monitor/Exceptions/EventExceptionsPipelineNameCache.cs +++ b/src/Tools/dotnet-monitor/Exceptions/EventExceptionsPipelineNameCache.cs @@ -26,9 +26,9 @@ public void AddExceptionGroup(ulong id, ulong exceptionClassId, ulong throwingMe _exceptionGroupMap.Add(id, new ExceptionGroup(exceptionClassId, throwingMethodId, ilOffset)); } - public void AddFunction(ulong id, ulong classId, uint classToken, ulong moduleId, string name, ulong[] typeArgs, ulong[] parameterTypes) + public void AddFunction(ulong id, uint methodToken, ulong classId, uint classToken, ulong moduleId, string name, ulong[] typeArgs, ulong[] parameterTypes) { - _nameCache.FunctionData.TryAdd(id, new FunctionData(name, classId, classToken, moduleId, typeArgs ?? Array.Empty(), parameterTypes ?? Array.Empty())); + _nameCache.FunctionData.TryAdd(id, new FunctionData(name, methodToken, classId, classToken, moduleId, typeArgs ?? Array.Empty(), parameterTypes ?? Array.Empty())); } public void AddStackFrame(ulong id, ulong functionId, int ilOffset) @@ -36,9 +36,9 @@ public void AddStackFrame(ulong id, ulong functionId, int ilOffset) _stackFrames.Add(id, new StackFrameInstance(functionId, ilOffset)); } - public void AddModule(ulong id, string moduleName) + public void AddModule(ulong id, Guid moduleVersionId, string moduleName) { - _nameCache.ModuleData.TryAdd(id, new ModuleData(moduleName)); + _nameCache.ModuleData.TryAdd(id, new ModuleData(moduleName, moduleVersionId)); } public void AddToken(ulong moduleId, uint token, uint outerToken, string name, string @namespace) diff --git a/src/Tools/dotnet-monitor/Exceptions/ExceptionsOperation.cs b/src/Tools/dotnet-monitor/Exceptions/ExceptionsOperation.cs index f8063f2a13f..bd1ee3eb0cc 100644 --- a/src/Tools/dotnet-monitor/Exceptions/ExceptionsOperation.cs +++ b/src/Tools/dotnet-monitor/Exceptions/ExceptionsOperation.cs @@ -195,6 +195,7 @@ await using (Utf8JsonWriter writer = new(stream, new JsonWriterOptions() { Inden assembledMethodName += builder.ToString(); } writer.WriteString("methodName", assembledMethodName); + writer.WriteNumber("methodToken", frame.MethodToken); writer.WriteStartArray("parameterTypes"); foreach (string parameterType in frame.FullParameterTypes) { @@ -203,6 +204,7 @@ await using (Utf8JsonWriter writer = new(stream, new JsonWriterOptions() { Inden writer.WriteEndArray(); // end parameterTypes writer.WriteString("typeName", frame.TypeName); writer.WriteString("moduleName", frame.ModuleName); + writer.WriteString("moduleVersionId", frame.ModuleVersionId.ToString("D")); writer.WriteEndObject(); }