private TimerInfo GetTimerInfo(ClrObject currentTimerQueueTimer) { var ti = new TimerInfo() { TimerQueueTimerAddress = currentTimerQueueTimer.Address }; ti.DueTime = currentTimerQueueTimer.ReadField <uint>("m_dueTime"); ti.Period = currentTimerQueueTimer.ReadField <uint>("m_period"); ti.Cancelled = currentTimerQueueTimer.ReadField <bool>("m_canceled"); var state = currentTimerQueueTimer.ReadObjectField("m_state"); ti.StateAddress = 0; if (state.IsValid) { ti.StateAddress = state.Address; var stateType = _heap.GetObjectType(ti.StateAddress); if (stateType != null) { ti.StateTypeName = stateType.Name; } } // decipher the callback details var timerCallback = currentTimerQueueTimer.ReadObjectField("m_timerCallback"); if (timerCallback.IsValid) { var elementType = timerCallback.Type; if (elementType != null) { if (elementType.Name == "System.Threading.TimerCallback") { ti.MethodName = BuildTimerCallbackMethodName(timerCallback); } else { ti.MethodName = "<" + elementType.Name + ">"; } } else { ti.MethodName = "{no callback type?}"; } } else { ti.MethodName = "???"; } return(ti); }
private Dictionary <string, string> GetParameters(ClrArray parameters) { Dictionary <string, string> parameterValues = new Dictionary <string, string>(); for (int i = 0; i < parameters.Length; i++) { ClrObject parameter = parameters.GetObjectValue(i); if (parameter.Address != 0) { string parameterName = parameter.ReadStringField("_parameterName"); string parameterValue = ""; if (parameter.ReadObjectField("_value").Address != 0) { switch (parameter.ReadObjectField("_value").Type.Name) { case "System.Int64": parameterValue = parameter.ReadField <Int64>("_value").ToString(); break; case "System.String": parameterValue = parameter.ReadObjectField("_value").AsString(); break; case "System.Int32": parameterValue = parameter.ReadField <Int32>("_value").ToString(); break; case "System.DateTime": parameterValue = parameter.ReadField <DateTime>("_value").ToString(); break; case "System.Double": parameterValue = parameter.ReadField <Double>("_value").ToString(); break; case "System.Boolean": parameterValue = parameter.ReadField <Boolean>("_value").ToString(); break; default: parameterValue = "cannot be read"; break; } } if (!string.IsNullOrEmpty(parameterValue) && !string.IsNullOrEmpty(parameterName)) { parameterValues.Add(parameterName, parameterValue); } } } return(parameterValues); }
private (string connectionString, int maxPoolSize, int minPoolSize) GetConnectionStringDetails(ClrObject connectionPool) { string connectionString; ClrObject poolGroup = connectionPool.ReadObjectField("_connectionPoolGroup"); ClrObject poolOptions = poolGroup.ReadObjectField("_connectionOptions"); connectionString = poolOptions.ReadStringField("_usersConnectionString"); int maxPoolSize = poolOptions.ReadField <int>("_maxPoolSize"); int minPoolSize = poolOptions.ReadField <int>("_minPoolSize"); return(connectionString, maxPoolSize, minPoolSize); }
private static TaskStatus GetStatus(ClrObject taskObject) { var stateFlags = taskObject.ReadField <int>("m_stateFlags"); if ((stateFlags & TaskStateFaulted) != 0) { return(TaskStatus.Faulted); } if ((stateFlags & TaskStateCanceled) != 0) { return(TaskStatus.Canceled); } if ((stateFlags & TaskStateRanToCompletion) != 0) { return(TaskStatus.RanToCompletion); } if ((stateFlags & TaskStateWaitingOnChildren) != 0) { return(TaskStatus.WaitingForChildrenToComplete); } if ((stateFlags & TaskStateDelegateInvoked) != 0) { return(TaskStatus.Running); } if ((stateFlags & TaskStateStarted) != 0) { return(TaskStatus.WaitingToRun); } if ((stateFlags & TaskStateWaitingForActivation) != 0) { return(TaskStatus.WaitingForActivation); } return(TaskStatus.Created); }
internal string BuildDelegateMethodName(ClrType targetType, ClrObject action) { var methodPtr = action.ReadField <ulong>("_methodPtr"); if (methodPtr != 0) { ClrMethod method = _clr.GetMethodByInstructionPointer(methodPtr); if (method == null) { // could happen in case of static method methodPtr = action.ReadField <ulong>("_methodPtrAux"); method = _clr.GetMethodByInstructionPointer(methodPtr); } if (method != null) { // anonymous method if (method.Type.Name == targetType.Name) { return($"{targetType.Name}.{method.Name}"); } else // method is implemented by an class inherited from targetType // ... or a simple delegate indirection to a static/instance method { if ( (string.CompareOrdinal(targetType.Name, "System.Threading.WaitCallback") == 0) || targetType.Name.StartsWith("System.Action<", StringComparison.Ordinal) ) { return($"{method.Type.Name}.{method.Name}"); } else { return($"({targetType.Name}){method.Type.Name}.{method.Name}"); } } } } return(""); }
static void Main(string[] args) { if (args.Length != 3) { Console.WriteLine("Usage: DumpDict [dump] [objref]"); Environment.Exit(1); } if (ulong.TryParse(args[2], System.Globalization.NumberStyles.HexNumber, null, out ulong objAddr)) { Console.WriteLine($"Could not parse object ref '{args[2]}'."); Environment.Exit(1); } string dumpFileName = args[0]; string dacPath = args[1]; using DataTarget dataTarget = DataTarget.LoadDump(dumpFileName); using ClrRuntime runtime = dataTarget.ClrVersions.Single().CreateRuntime(); ClrHeap heap = runtime.Heap; ClrObject obj = heap.GetObject(objAddr); if (!obj.IsValidObject) { Console.WriteLine("Invalid object {0:X}", obj); return; } if (!obj.Type.Name.StartsWith("System.Collections.Generic.Dictionary")) { Console.WriteLine("Error: Expected object {0:X} to be a dictionary, instead it's of type '{1}'."); return; } // Get the entries field. ClrArray entries = obj.ReadObjectField("entries").AsArray(); Console.WriteLine("{0,8} {1,16} : {2}", "hash", "key", "value"); for (int i = 0; i < entries.Length; ++i) { ClrObject entry = entries.GetObjectValue(i); // TODO: Need to handle non-object references int hashCode = entry.ReadField <int>("hashCode"); ClrObject key = entry.ReadObjectField("key"); ClrObject value = entry.ReadObjectField("value"); Console.WriteLine($"{hashCode:x} {key} -> {value}"); } }
private SQLConnectionInfo GetConnectionDetails(ClrObject connectionPool) { int maxPoolSize, minPoolSize; string connectionString; (connectionString, maxPoolSize, minPoolSize) = GetConnectionStringDetails(connectionPool); return(new SQLConnectionInfo { OpenConnectionCount = GetActiveConnectionCount(connectionPool), ConnectionString = connectionString, CurrentConnectionCount = connectionPool.ReadField <int>("_totalObjects"), MaxPoolSize = maxPoolSize, MinPoolSize = minPoolSize }); }
private static void MarkThreadingBlockTasks(DebuggerContext context, List <AsyncStateMachine> allStateMachines) { foreach (var thread in context.Runtime.Threads) { var stackFrame = thread.EnumerateStackTrace().Take(50).FirstOrDefault( f => f.Method is { } method && string.Equals(f.Method.Name, "CompleteOnCurrentThread", StringComparison.Ordinal) && string.Equals(f.Method.Type?.Name, "Microsoft.VisualStudio.Threading.JoinableTask", StringComparison.Ordinal)); if (stackFrame is object) { var visitedObjects = new HashSet <ulong>(); foreach (IClrStackRoot stackRoot in thread.EnumerateStackRoots()) { ClrObject stackObject = stackRoot.Object; if (string.Equals(stackObject.Type?.Name, "Microsoft.VisualStudio.Threading.JoinableTask", StringComparison.Ordinal) || string.Equals(stackObject.Type?.BaseType?.Name, "Microsoft.VisualStudio.Threading.JoinableTask", StringComparison.Ordinal)) { if (visitedObjects.Add(stackObject.Address)) { var joinableTaskObject = new ClrObject(stackObject.Address, stackObject.Type); int state = joinableTaskObject.ReadField <int>("state"); if ((state & 0x10) == 0x10) { // This flag indicates the JTF is blocking the thread var wrappedTask = joinableTaskObject.TryGetObjectField("wrappedTask"); if (!wrappedTask.IsNull) { var blockingStateMachine = allStateMachines .FirstOrDefault(s => s.Task.Address == wrappedTask.Address); if (blockingStateMachine is object) { blockingStateMachine.BlockedThread = thread.OSThreadId; blockingStateMachine.BlockedJoinableTask = joinableTaskObject; } } break; } } } } } } }
private unsafe long GetPerfCounterValue(ClrObject perfCounter) { if (perfCounter == null) { return(0); } ulong entryPoint = perfCounter.ReadField <ulong>("counterEntryPointer"); int v = sizeof(CounterEntry); Span <byte> buffer = new byte[v]; int read = Target.DataReader.Read(entryPoint, buffer); fixed(byte *ptr = buffer) { CounterEntry counterEntry = *(CounterEntry *)ptr; return(counterEntry.Value); } }
private string BuildTimerCallbackMethodName(ClrObject timerCallback) { var methodPtr = timerCallback.ReadField <ulong>("_methodPtr"); if (methodPtr != 0) { // NOTE: can't find a replacement for ClrMD 1.1 GetMethodByAddress // GetMethodByInstructionPointer always returns null var method = _clr.GetMethodByInstructionPointer(methodPtr); if (method != null) { // look for "this" to figure out the real callback implementor type var thisTypeName = "?"; var thisPtr = timerCallback.ReadObjectField("_target"); if (thisPtr.IsValid) { var thisRef = thisPtr.Address; var thisType = _heap.GetObjectType(thisRef); if (thisType != null) { thisTypeName = thisType.Name; } } else { thisTypeName = (method.Type != null) ? method.Type.Name : "?"; } return($"{thisTypeName}.{method.Name}"); } else { return(""); } } else { return(""); } }
private TimerInfo GetTimerInfo(ClrObject currentTimerQueueTimer) { var ti = new TimerInfo() { TimerQueueTimerAddress = currentTimerQueueTimer.Address }; // field names prefix changes from "m_" to "_" in .NET Core 3.0 var is30Format = currentTimerQueueTimer.Type.GetFieldByName("_dueTime") != null; ClrObject state; if (is30Format) { ti.DueTime = currentTimerQueueTimer.ReadField <uint>("_dueTime"); ti.Period = currentTimerQueueTimer.ReadField <uint>("_period"); ti.Cancelled = currentTimerQueueTimer.ReadField <bool>("_canceled"); state = currentTimerQueueTimer.ReadObjectField("_state"); } else { ti.DueTime = currentTimerQueueTimer.ReadField <uint>("m_dueTime"); ti.Period = currentTimerQueueTimer.ReadField <uint>("m_period"); ti.Cancelled = currentTimerQueueTimer.ReadField <bool>("m_canceled"); state = currentTimerQueueTimer.ReadObjectField("m_state"); } ti.StateAddress = 0; if (state.IsValid) { ti.StateAddress = state.Address; var stateType = _heap.GetObjectType(ti.StateAddress); if (stateType != null) { ti.StateTypeName = stateType.Name; } } // decipher the callback details var timerCallback = is30Format ? currentTimerQueueTimer.ReadObjectField("_timerCallback") : currentTimerQueueTimer.ReadObjectField("m_timerCallback"); if (timerCallback.IsValid) { var elementType = timerCallback.Type; if (elementType != null) { if (elementType.Name == "System.Threading.TimerCallback") { ti.MethodName = BuildTimerCallbackMethodName(timerCallback); } else { ti.MethodName = "<" + elementType.Name + ">"; } } else { ti.MethodName = "{no callback type?}"; } } else { ti.MethodName = "???"; } return(ti); }
private static void GetAllStateMachines(DebuggerContext context, ClrHeap heap, List <AsyncStateMachine> allStateMachines, Dictionary <ulong, AsyncStateMachine> knownStateMachines) { foreach (ClrObject obj in heap.GetObjectsOfType("System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner")) { try { ClrObject stateMachine = obj.ReadObjectField("m_stateMachine"); if (!knownStateMachines.ContainsKey(stateMachine.Address)) { try { var state = stateMachine.ReadField <int>("<>1__state"); if (state >= -1) { ClrObject taskField = default(ClrObject); ClrValueType?asyncBuilder = stateMachine.TryGetValueClassField("<>t__builder"); if (asyncBuilder.HasValue) { while (asyncBuilder.HasValue) { taskField = asyncBuilder.TryGetObjectField("m_task"); if (!taskField.IsNull) { break; } ClrValueType?nextAsyncBuilder = asyncBuilder.TryGetValueClassField("m_builder"); if (nextAsyncBuilder is null) { asyncBuilder = asyncBuilder.TryGetValueClassField("_methodBuilder"); } else { asyncBuilder = nextAsyncBuilder; } } } else { // CLR debugger may not be able to access t__builder, when NGEN assemblies are being used, and the type of the field could be lost. // Our workaround is to pick up the first Task object referenced by the state machine, which seems to be correct. // That function works with the raw data structure (like how GC scans the object, so it doesn't depend on symbols. // // However, one problem of that is we can pick up tasks from other reference fields of the same structure. So, we go through fields which we have symbols // and remember references encounted, and we skip them when we go through GC references. // Note: we can do better by going through other value structures, and extract references from them here, which we can consider when we have a real scenario. var previousReferences = new Dictionary <ulong, int>(); if (stateMachine.Type?.GetFieldByName("<>t__builder") is not null) { foreach (ClrInstanceField field in stateMachine.Type.Fields) { if (string.Equals(field.Name, "<>t__builder", StringComparison.Ordinal)) { break; } if (field.IsObjectReference) { ClrObject referencedValue = field.ReadObject(stateMachine.Address, interior: false); if (!referencedValue.IsNull) { if (previousReferences.TryGetValue(referencedValue.Address, out int refCount)) { previousReferences[referencedValue.Address] = refCount + 1; } else { previousReferences[referencedValue.Address] = 1; } } } } } foreach (ClrObject referencedObject in stateMachine.EnumerateReferences(true)) { if (!referencedObject.IsNull) { if (previousReferences.TryGetValue(referencedObject.Address, out int refCount) && refCount > 0) { if (refCount == 1) { previousReferences.Remove(referencedObject.Address); } else { previousReferences[referencedObject.Address] = refCount - 1; } continue; } else if (previousReferences.Count > 0) { continue; } if (referencedObject.Type is object && (string.Equals(referencedObject.Type.Name, "System.Threading.Tasks.Task", StringComparison.Ordinal) || string.Equals(referencedObject.Type.BaseType?.Name, "System.Threading.Tasks.Task", StringComparison.Ordinal))) { taskField = referencedObject; break; } } } } var asyncState = new AsyncStateMachine(state, stateMachine, taskField); allStateMachines.Add(asyncState); knownStateMachines.Add(stateMachine.Address, asyncState); if (stateMachine.Type is object) { foreach (ClrMethod?method in stateMachine.Type.Methods) { if (method.Name == "MoveNext" && method.NativeCode != ulong.MaxValue) { asyncState.CodeAddress = method.NativeCode; } } } } } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { context.Output.WriteLine($"Fail to process state machine {stateMachine.Address:x} Type:'{stateMachine.Type?.Name}' Module:'{stateMachine.Type?.Module?.Name}' Error: {ex.Message}"); } } } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { context.Output.WriteLine($"Fail to process AsyncStateMachine Runner {obj.Address:x} Error: {ex.Message}"); } } }
private static int GetStateFlags(ClrObject taskObject) { return(taskObject.ReadField <int>("m_stateFlags")); }
private static void GetAllStateMachines(DebuggerContext context, ClrHeap heap, List <AsyncStateMachine> allStateMachines, Dictionary <ulong, AsyncStateMachine> knownStateMachines) { foreach (ClrObject obj in heap.GetObjectsOfType("System.Runtime.CompilerServices.AsyncMethodBuilderCore+MoveNextRunner")) { try { ClrObject stateMachine = obj.ReadObjectField("m_stateMachine"); if (!knownStateMachines.ContainsKey(stateMachine.Address)) { try { var state = stateMachine.ReadField <int>("<>1__state"); if (state >= -1) { ClrObject taskField = default(ClrObject); ClrValueType?asyncBuilder = stateMachine.TryGetValueClassField("<>t__builder"); if (asyncBuilder.HasValue) { while (asyncBuilder.HasValue) { taskField = asyncBuilder.TryGetObjectField("m_task"); if (!taskField.IsNull) { break; } asyncBuilder = asyncBuilder.TryGetValueClassField("m_builder"); } } else { // CLR debugger may not be able to access t__builder, when NGEN assemblies are being used, and the type of the field could be lost. // Our workaround is to pick up the first Task object referenced by the state machine, which seems to be correct. // That function works with the raw data structure (like how GC scans the object, so it doesn't depend on symbols. foreach (ClrObject referencedObject in stateMachine.EnumerateReferences(true)) { if (!referencedObject.IsNull && referencedObject.Type is object) { if (string.Equals(referencedObject.Type.Name, "System.Threading.Tasks.Task", StringComparison.Ordinal) || string.Equals(referencedObject.Type.BaseType?.Name, "System.Threading.Tasks.Task", StringComparison.Ordinal)) { taskField = referencedObject; break; } } } } var asyncState = new AsyncStateMachine(state, stateMachine, taskField); allStateMachines.Add(asyncState); knownStateMachines.Add(stateMachine.Address, asyncState); if (stateMachine.Type is object) { foreach (ClrMethod?method in stateMachine.Type.Methods) { if (method.Name == "MoveNext" && method.NativeCode != ulong.MaxValue) { asyncState.CodeAddress = method.NativeCode; } } } } } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { context.Output.WriteLine($"Fail to process state machine {stateMachine.Address:x} Type:'{stateMachine.Type?.Name}' Module:'{stateMachine.Type?.Module?.Name}' Error: {ex.Message}"); } } } #pragma warning disable CA1031 // Do not catch general exception types catch (Exception ex) #pragma warning restore CA1031 // Do not catch general exception types { context.Output.WriteLine($"Fail to process AsyncStateMachine Runner {obj.Address:x} Error: {ex.Message}"); } } }