private void AssertPathIsCorrect(ClrHeap heap, IReadOnlyList <ClrObject> path, ulong source, ulong target) { Assert.NotNull(path); Assert.True(path.Count > 0); ClrObject first = path.First(); Assert.Equal(source, first.Address); for (int i = 0; i < path.Count - 1; i++) { ClrObject curr = path[i]; Assert.Equal(curr.Type, heap.GetObjectType(curr.Address)); IEnumerable <ClrObject> refs = curr.EnumerateReferences(); ClrObject next = path[i + 1]; Assert.Contains(next, refs); } ClrObject last = path.Last(); Assert.Equal(last.Type, heap.GetObjectType(last.Address)); Assert.Equal(target, last.Address); }
public void EnumerateGCRefs() { using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump(); using ClrRuntime runtime = dataTarget.ClrVersions.Single().CreateRuntime(); ClrHeap heap = runtime.Heap; ClrObject obj = heap.GetObjectsOfType("DoubleRef").Single(); Assert.False(obj.IsNull); ValidateRefs(obj.EnumerateReferences().ToArray()); }
public void EnumerateGCRefsArray() { using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump(); using ClrRuntime runtime = dataTarget.ClrVersions.Single().CreateRuntime(); ClrHeap heap = runtime.Heap; ClrModule module = heap.Runtime.GetMainModule(); ClrType mainType = module.GetTypeByName("GCRootTarget"); ClrObject obj = mainType.GetStaticObjectValue("TheRoot"); obj = obj.GetObjectField("Item1"); Assert.Equal("System.Object[]", obj.Type.Name); ClrObject[] refs = obj.EnumerateReferences(false).ToArray(); Assert.Single(refs); Assert.Equal("DoubleRef", refs[0].Type.Name); }
public void EnumerateGCRefs() { using DataTarget dataTarget = TestTargets.GCRoot.LoadFullDump(); using ClrRuntime runtime = dataTarget.ClrVersions.Single().CreateRuntime(); ClrHeap heap = runtime.Heap; ClrObject doubleRef = heap.GetObjectsOfType("DoubleRef").Single(); Assert.False(doubleRef.IsNull); ClrObject[] refs = doubleRef.EnumerateReferences().ToArray(); // Should contain one SingleRef and one TripleRef object. Assert.Equal(2, refs.Length); Assert.Equal(1, refs.Count(r => r.Type.Name == "SingleRef")); Assert.Equal(1, refs.Count(r => r.Type.Name == "TripleRef")); foreach (ClrObject obj in refs) { Assert.NotEqual(0ul, obj.Address); Assert.Equal(obj.Type.Heap.GetObjectType(obj.Address), obj.Type); } }
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 ChainStateMachineBasedOnTaskContinuations(Dictionary <ulong, AsyncStateMachine> knownStateMachines, AsyncStateMachine stateMachine, ClrObject continuationObject) { ClrObject continuationAction = continuationObject.TryGetObjectField("m_action"); // case 1 ClrObject continuationTarget = continuationAction.TryGetObjectField("_target"); if (continuationTarget.IsNull) { // case 2 continuationTarget = continuationObject.TryGetObjectField("_target"); if (continuationTarget.IsNull) { // case 3 continuationTarget = continuationObject.TryGetObjectField("m_task").TryGetObjectField("m_stateObject").TryGetObjectField("_target"); } } while (!continuationTarget.IsNull) { // now get the continuation from the target ClrObject continuationTargetStateMachine = continuationTarget.TryGetObjectField("m_stateMachine"); if (!continuationTargetStateMachine.IsNull) { AsyncStateMachine targetAsyncState; if (knownStateMachines.TryGetValue(continuationTargetStateMachine.Address, out targetAsyncState) && targetAsyncState != stateMachine) { stateMachine.Next = targetAsyncState; stateMachine.DependentCount++; targetAsyncState.Previous = stateMachine; } break; } else { ClrObject nextContinuation = continuationTarget.TryGetObjectField("m_continuation"); continuationTarget = nextContinuation.TryGetObjectField("_target"); } } ClrObject items = continuationObject.TryGetObjectField("_items"); if (!items.IsNull && items.IsArray && items.ContainsPointers) { foreach (ClrObject promise in items.EnumerateReferences(true)) { if (!promise.IsNull) { ClrObject innerContinuationObject = promise.TryGetObjectField("m_continuationObject"); if (!innerContinuationObject.IsNull) { ChainStateMachineBasedOnTaskContinuations(knownStateMachines, stateMachine, innerContinuationObject); } else { ChainStateMachineBasedOnTaskContinuations(knownStateMachines, stateMachine, promise); } } } } }
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}"); } } }