in src/SosThreadingTools/DumpAsyncCommand.cs [33:170]
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}");
}
}
}