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 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}"); } } }