public override ClrObject GetNextObject(ClrObject obj) { if (obj.IsNull) { return(obj); } uint minObjSize = (uint)_clr.PointerSize * 3; ClrType type = obj.Type; if (type == null) { return(new ClrObject(0, _heap.NullType)); } ulong size = obj.Size; size = Align(size, _large); if (size < minObjSize) { size = minObjSize; } // Move to the next object ulong addr = obj.Address + size; // Check to make sure a GC didn't cause "count" to be invalid, leading to too large // of an object if (addr >= End) { return(new ClrObject(0, _heap.NullType)); } // Ensure we aren't at the start of an alloc context ulong tmp; while (!IsLarge && _subHeap.AllocPointers.TryGetValue(addr, out tmp)) { tmp += Align(minObjSize, _large); // Only if there's data corruption: if (addr >= tmp) { return(new ClrObject(0, _heap.NullType)); } // Otherwise: addr = tmp; if (addr >= End) { return(new ClrObject(0, _heap.NullType)); } } type = _heap.GetObjectType(addr); return(new ClrObject(addr, type)); }
public ClrStackRoot(ulong address, ClrObject obj, ClrStackFrame stackFrame, bool interior, bool pinned) { Address = address; Object = obj; StackFrame = stackFrame; IsInterior = interior; IsPinned = pinned; }
internal static ClrObject Create(ulong address, ClrType type) { ClrObject obj = new ClrObject(); obj._address = address; obj._type = type; return(obj); }
/// <summary> /// Converts the specified o. /// </summary> /// <param name="o">The o.</param> /// <returns>IClrObject.</returns> public IClrObject Convert(ClrMd.ClrObject o) { if (o == null) { return(null); } var item = new ClrObjectAdapter(this, o); return(Cache.GetOrAdd <IClrObject>(o, () => item, () => item.Setup())); }
/// <summary> /// Constructor. /// </summary> /// <param name="del">The parent delgate that this target came from.</param> /// <param name="target">The "target" of this delegate.</param> /// <param name="method">The method this delegate will call.</param> public ClrDelegateTarget(ClrDelegate del, ClrObject target, ClrMethod method) { if (method is null) { throw new ArgumentNullException(nameof(method)); } Parent = del; TargetObject = target; Method = method; }
public ClrException(IExceptionHelpers helpers, ClrThread?thread, ClrObject obj) { if (obj.IsNull) { throw new InvalidOperationException($"Cannot construct a ClrException from a null object."); } _helpers = helpers ?? throw new ArgumentNullException(nameof(helpers)); _object = obj; Thread = thread; DebugOnly.Assert(obj.IsException); }
private Task <Tuple <LinkedList <ClrObject>, ClrRoot> > PathToParallel(ObjectSet seen, Dictionary <ulong, LinkedListNode <ClrObject> > knownEndPoints, ClrHandle handle, ulong target, bool unique, CancellationToken cancelToken) { Debug.Assert(IsFullyCached); Task <Tuple <LinkedList <ClrObject>, ClrRoot> > t = new Task <Tuple <LinkedList <ClrObject>, ClrRoot> >(() => { LinkedList <ClrObject> path = PathTo(seen, knownEndPoints, ClrObject.Create(handle.Object, handle.Type), target, unique, true, cancelToken).FirstOrDefault(); return(new Tuple <LinkedList <ClrObject>, ClrRoot>(path, path != null ? GetHandleRoot(handle) : null)); }); t.Start(); return(t); }
public override IEnumerable <ClrObject> EnumerateObjects() { RevisionValidator.Validate(Revision, GetRuntimeRevision()); for (int i = 0; i < _segments.Length; ++i) { ClrSegment seg = _segments[i]; for (ulong obj = seg.GetFirstObject(out ClrType type); obj != 0; obj = seg.NextObject(obj, out type)) { _lastSegmentIdx = i; yield return(ClrObject.Create(obj, type)); } } }
/// <summary> /// Enumerates all delegate targets of this delegate. If called on a MulitcastDelegate, this will enumerate all /// targets that will be called when this delegate is invoked. If called on a non-MulticastDelegate, this will /// enumerate the value of GetDelegateTarget. /// </summary> /// <returns></returns> public IEnumerable <ClrDelegateTarget> EnumerateDelegateTargets() { ClrDelegateTarget?first = GetDelegateTarget(); if (first != null) { yield return(first); } // The call to GetDelegateMethod will validate that we are a valid object and a subclass of System.Delegate if (!Object.TryReadField("_invocationCount", out int count) || count == 0 || !Object.TryReadObjectField("_invocationList", out ClrObject invocationList) || !invocationList.IsArray) { yield break; } ClrArray invocationArray = invocationList.AsArray(); count = Math.Min(count, invocationArray.Length); ClrHeap heap = Object.Type !.Heap; UIntPtr[]? pointers = invocationArray.ReadValues <UIntPtr>(0, count); if (pointers is not null) { foreach (UIntPtr ptr in pointers) { if (ptr == UIntPtr.Zero) { continue; } ClrObject delegateObj = heap.GetObject(ptr.ToUInt64()); if (delegateObj.IsDelegate) { ClrDelegateTarget?delegateTarget = new ClrDelegate(delegateObj).GetDelegateTarget(); if (delegateTarget is not null) { yield return(delegateTarget); } } } } }
public override IEnumerable <ClrObject> EnumerateObjects() { if (Revision != GetRuntimeRevision()) { ClrDiagnosticsException.ThrowRevisionError(Revision, GetRuntimeRevision()); } for (int i = 0; i < _segments.Length; ++i) { var seg = _segments[i]; for (ClrObject obj = seg.FirstObject; !obj.IsNull; obj = seg.GetNextObject(obj)) { _lastSegmentIdx = i; yield return(obj); } } }
/// <summary> /// Creates a ClrFieldReference from an actual field. /// </summary> /// <param name="reference">The object referenced.</param> /// <param name="containingType">The type of the object which points to <paramref name="reference"/>.</param> /// <param name="offset">The offset within the source object where <paramref name="reference"/> was located.</param> public static ClrReference CreateFromFieldOrArray(ClrObject reference, ClrType containingType, int offset) { if (containingType == null) { throw new ArgumentNullException(nameof(containingType)); } offset -= IntPtr.Size; DebugOnly.Assert(offset >= 0); ClrInstanceField?field = containingType.IsArray ? null : containingType.Fields.First(f => f.Offset <= offset && offset < f.Offset + f.Size); unchecked { return(new ClrReference(reference, field, OffsetFlag | (uint)offset)); } }
/// <summary> /// Gets a string field from the object. Note that the type must match exactly, as this method /// will not do type coercion. /// </summary> /// <param name="fieldName">The name of the field to get the value for.</param> /// <param name="maxLength">The maximum length of the string returned. Warning: If the DataTarget /// being inspected has corrupted or an inconsistent heap state, the length of a string may be /// incorrect, leading to OutOfMemory and other failures.</param> /// <returns>The value of the given field.</returns> /// <exception cref="ArgumentException">No field matches the given name.</exception> /// <exception cref="InvalidOperationException">The field is not a string.</exception> /// <exception cref="MemoryReadException">There was an error reading the value of this field out of the data target.</exception> public string?GetStringField(string fieldName, int maxLength = 4096) { ulong address = GetFieldAddress(fieldName, ClrElementType.String, "string"); if (!DataReader.ReadPointer(address, out ulong str)) { throw new MemoryReadException(address); } if (str == 0) { return(null); } ClrObject obj = new ClrObject(str, Type.Heap.StringType); return(obj.AsString(maxLength)); }
public override IEnumerable <ClrObject> EnumerateObjects() { if (Revision != GetRuntimeRevision()) { ClrDiagnosticsException.ThrowRevisionError(Revision, GetRuntimeRevision()); } for (int i = 0; i < _segments.Length; ++i) { ClrSegment seg = _segments[i]; for (ulong obj = seg.GetFirstObject(out ClrType type); obj != 0; obj = seg.NextObject(obj, out type)) { _lastSegmentIdx = i; yield return(ClrObject.Create(obj, type)); } } }
/// <summary> /// Creates a ClrFieldReference from an actual field. /// </summary> /// <param name="reference">The object referenced.</param> /// <param name="containingType">The type of the object which points to <paramref name="reference"/>.</param> /// <param name="offset">The offset within the source object where <paramref name="reference"/> was located. This offset /// should start from where the object's data starts (IE this offset should NOT contain the MethodTable in the offset /// calculation.</param> public static ClrReference CreateFromFieldOrArray(ClrObject reference, ClrType containingType, int offset) { if (containingType == null) { throw new ArgumentNullException(nameof(containingType)); } if (offset < 0) { throw new ArgumentOutOfRangeException($"{nameof(offset)} must be >= 0."); } ClrInstanceField?field = FindField(containingType.Fields, offset); unchecked { return(new ClrReference(reference, field, OffsetFlag | (uint)offset)); } }
/// <summary> /// Creates a ClrFieldReference from an actual field. /// </summary> /// <param name="reference">The object referenced.</param> /// <param name="containingType">The type of the object which points to <paramref name="reference"/>.</param> /// <param name="offset">The offset within the source object where <paramref name="reference"/> was located.</param> public static ClrReference CreateFromFieldOrArray(ClrObject reference, ClrType containingType, int offset) { if (containingType == null) { throw new ArgumentNullException(nameof(containingType)); } offset -= IntPtr.Size; DebugOnly.Assert(offset >= 0); ClrInstanceField?field = null; foreach (ClrInstanceField curr in containingType.Fields) { // If we found the correct field, stop searching if (curr.Offset <= offset && offset <= curr.Offset + curr.Size) { field = curr; break; } // Sometimes .Size == 0 if we failed to properly determine the type of the field, // instead search for the field closest to the offset we are searching for. if (curr.Offset <= offset) { if (field == null) { field = curr; } else if (field.Offset < curr.Offset) { field = curr; } } } unchecked { return(new ClrReference(reference, field, OffsetFlag | (uint)offset)); } }
public ClrObject?AsObject() { if (_object.HasValue) { return(_object.Value); } // It's possible that ObjectPointer points the the beginning of an object, though that's rare. Check that first. ClrType?type = _segment.Heap.GetObjectType(ObjectPointer); if (!(type is null)) { _object = new ClrObject(ObjectPointer, type); return(_object.Value); } // ObjectPointer is pointing in the middle of an object, get the previous object for the address. ulong obj = _segment.GetPreviousObjectAddress(ObjectPointer); if (obj == 0) { return(null); } type = _segment.Heap.GetObjectType(obj); if (type is null) { // This is heap corruption, or an inconsistent dump. We should have found a real object here. return(null); } ClrObject result = new ClrObject(obj, type); _object = result; return(result); }
/// <summary> /// Constructs a <see cref="ClrDelegate"/> from a <see cref="ClrObject"/>. Note that obj.IsDelegate /// must be true. /// </summary> /// <param name="obj">A delegate object</param> public ClrDelegate(ClrObject obj) { DebugOnly.Assert(obj.IsDelegate); Object = obj; }
private Stack <ClrObject> GetRefs(ObjectSet seen, Dictionary <ulong, LinkedListNode <ClrObject> > knownEndPoints, ClrObject obj, ulong target, bool unique, bool parallel, CancellationToken cancelToken, out bool foundTarget, out LinkedListNode <ClrObject> foundEnding) { // These asserts slow debug down by a lot, but it's important to ensure consistency in retail. //Debug.Assert(obj.Type != null); //Debug.Assert(obj.Type == _heap.GetObjectType(obj.Address)); Stack <ClrObject> result = s_emptyStack; bool found = false; LinkedListNode <ClrObject> ending = null; if (obj.ContainsPointers) { foreach (ClrObject reference in obj.EnumerateObjectReferences(true)) { cancelToken.ThrowIfCancellationRequested(); if (ending == null && knownEndPoints != null) { lock (knownEndPoints) { if (unique) { if (knownEndPoints.ContainsKey(reference.Address)) { continue; } } else { knownEndPoints.TryGetValue(reference.Address, out ending); } } } if (!seen.Contains(reference.Address)) { if (result == s_emptyStack) { result = new Stack <ClrObject>(); } result.Push(reference); if (reference.Address == target) { found = true; } } } } foundTarget = found; foundEnding = ending; return(result); }
private IEnumerable <LinkedList <ClrObject> > PathTo(ObjectSet seen, Dictionary <ulong, LinkedListNode <ClrObject> > knownEndPoints, ClrObject source, ulong target, bool unique, bool parallel, CancellationToken cancelToken) { seen.Add(source.Address); bool foundTarget; LinkedListNode <ClrObject> foundEnding; if (source.Type == null) { yield break; } LinkedList <PathEntry> path = new LinkedList <PathEntry>(); if (source.Address == target) { path.AddLast(new PathEntry() { Object = source }); yield return(GetResult(knownEndPoints, path, null, target)); yield break; } path.AddLast(new PathEntry() { Object = source, Todo = GetRefs(seen, knownEndPoints, source, target, unique, parallel, cancelToken, out foundTarget, out foundEnding) }); // Did the 'start' object point directly to 'end'? If so, early out. if (foundTarget) { path.AddLast(new PathEntry() { Object = ClrObject.Create(target, _heap.GetObjectType(target)) }); yield return(GetResult(knownEndPoints, path, null, target)); } else if (foundEnding != null) { yield return(GetResult(knownEndPoints, path, foundEnding, target)); } while (path.Count > 0) { cancelToken.ThrowIfCancellationRequested(); TraceFullPath(null, path); var last = path.Last.Value; if (last.Todo.Count == 0) { // We've exhausted all children and didn't find the target. Remove this node // and continue. path.RemoveLast(); } else { // We loop here in case we encounter an object we've already processed (or if // we can't get an object's type...inconsistent heap happens sometimes). do { cancelToken.ThrowIfCancellationRequested(); ClrObject next = last.Todo.Pop(); // Now that we are in the process of adding 'next' to the path, don't ever consider // this object in the future. if (!seen.Add(next.Address)) { continue; } // We should never reach the 'end' here, as we always check if we found the target // value when adding refs below. Debug.Assert(next.Address != target); PathEntry nextPathEntry = new PathEntry() { Object = next, Todo = GetRefs(seen, knownEndPoints, next, target, unique, parallel, cancelToken, out foundTarget, out foundEnding) }; path.AddLast(nextPathEntry); // If we found the target object while enumerating refs of the current object, we are done. if (foundTarget) { path.AddLast(new PathEntry() { Object = ClrObject.Create(target, _heap.GetObjectType(target)) }); TraceFullPath("FoundTarget", path); yield return(GetResult(knownEndPoints, path, null, target)); path.RemoveLast(); path.RemoveLast(); } else if (foundEnding != null) { TraceFullPath(path, foundEnding); yield return(GetResult(knownEndPoints, path, foundEnding, target)); path.RemoveLast(); } // Now that we've added a new entry to 'path', break out of the do/while that's looping through Todo. break; }while (last.Todo.Count > 0); } } }
private Task <Tuple <LinkedList <ClrObject>, ClrRoot> > PathToParallel(ObjectSet seen, Dictionary <ulong, LinkedListNode <ClrObject> > knownEndPoints, ClrRoot root, ulong target, bool unique, CancellationToken cancelToken) { Debug.Assert(IsFullyCached); Task <Tuple <LinkedList <ClrObject>, ClrRoot> > t = new Task <Tuple <LinkedList <ClrObject>, ClrRoot> >(() => new Tuple <LinkedList <ClrObject>, ClrRoot>(PathTo(seen, knownEndPoints, ClrObject.Create(root.Object, root.Type), target, unique, true, cancelToken).FirstOrDefault(), root)); t.Start(); return(t); }
private ClrReference(ClrObject obj, ClrInstanceField?field, ulong offsetOrHandleValue) { _offsetOrHandle = offsetOrHandleValue; Object = obj; Field = field; }
private IEnumerable <LinkedList <ClrObject> > PathTo(ObjectSet seen, Dictionary <ulong, LinkedListNode <ClrObject> > knownEndPoints, ClrObject source, ulong target, bool unique, bool parallel, CancellationToken cancelToken) { seen.Add(source.Address); if (source.Type == null) { yield break; } LinkedList <PathEntry> path = new LinkedList <PathEntry>(); if (source.Address == target) { path.AddLast(new PathEntry() { Object = source }); yield return(GetResult(knownEndPoints, path, null, target)); yield break; } path.AddLast(new PathEntry() { Object = source, Todo = GetRefs(seen, knownEndPoints, source, target, unique, parallel, cancelToken, out bool foundTarget, out LinkedListNode <ClrObject> foundEnding) });
/// <summary> /// Create a field reference from a dependent handle value. We do not keep track of the dependent handle it came from /// so we don't accept the value here. /// </summary> /// <param name="reference">The object referenced.</param> public static ClrReference CreateFromDependentHandle(ClrObject reference) => new ClrReference(reference, null, DependentFlag);
/// <summary> /// Gets a <see cref="ClrObject"/> for the given address on this heap. /// </summary> /// <remarks> /// The returned object will have a <c>null</c> <see cref="ClrObject.Type"/> if objRef does not point to /// a valid managed object. /// </remarks> /// <param name="objRef"></param> /// <returns></returns> public ClrObject GetObject(ulong objRef) => ClrObject.Create(objRef, GetObjectType(objRef));
public static dynamic AsDynamic(this ClrObject clrObject) { var heap = clrObject.Type.Heap; return(heap.GetProxy(clrObject.Address)); }
private IEnumerable <LinkedList <ClrObject> > PathsTo( ObjectSet deadEnds, Dictionary <ulong, LinkedListNode <ClrObject> >?knownEndPoints, ClrObject source, ulong target, bool unique, CancellationToken cancellationToken) { /* knownEndPoints: A set of objects that are known to point to the target, once we see one of * these, we can end the search early and return the current path plus the * nodes there. * * deadEnds When unique == false, these are Objects we've fully processed and know doesn't * point to the target object. When unique == true, deadEnds are updated to include * objects that other threads are processing, ensuring we don't yield duplicate * roots. Note that we must be careful to ONLY add objects to this list when we are * sure we've exhausted all potential paths because parallel threads use this same * set. * * path: The current path we are considering. As we walk through objects in the rooting chain, * we will add a new node to this list with the object in question and create a stack of * all objects it points to. This is a depth-first search, so we will search as deeply * as we can, and pop off objects from path as soon as the stack of references is empty. * If we find that the reference stack is empty and we didn't find the target/known end * point, then we'll mark the object in deadEnds. * * processing: The list of objects we are currently considering on this thread. Since objects * can contain circular references, we need to know we shouldn't re-consider objects * we encounter while walking objects. * * It's important to note that multiple threads may race and consider the same path at the same * time. This is expected (and similar to how the GC operates). The thing we have to make sure * of is that we only add objects to knownEndPoints/deadEnds when we are 100% sure of the result * because it will directly affect other threads walking these paths. */ HashSet <ulong> processing = new HashSet <ulong>() { source.Address }; LinkedList <PathEntry> path = new LinkedList <PathEntry>(); if (knownEndPoints != null) { lock (knownEndPoints) { if (knownEndPoints.TryGetValue(source.Address, out LinkedListNode <ClrObject>?ending)) { if (!unique || ending.Value.Address == target) { yield return(GetResult(ending)); } yield break; } } } if (unique && !deadEnds.Add(source.Address)) { yield break; } if (source.Type is null) { yield break; } if (source.Address == target) { path.AddLast(new PathEntry { Object = source }); yield return(GetResult()); yield break; } path.AddLast( new PathEntry { Object = source, Todo = GetRefs(source, out bool foundTarget, out LinkedListNode <ClrObject>?foundEnding) });
public override Address NextObject(Address addr) { ClrObject obj = Heap.GetObject(addr); return(GetNextObject(obj).Address); }
/// <summary> /// Returns the next object given an object on this segment. Returns an object with /// IsNull set to true when reaching the end of this segment. /// </summary> public abstract ClrObject GetNextObject(ClrObject obj);