| File: Dbi\Helpers\HeapWalk.cs | Web Access |
| Project: src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Legacy\Microsoft.Diagnostics.DataContractReader.Legacy.csproj (Microsoft.Diagnostics.DataContractReader.Legacy) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using Microsoft.Diagnostics.DataContractReader.Contracts; using Microsoft.Diagnostics.DataContractReader.Contracts.Extensions; namespace Microsoft.Diagnostics.DataContractReader.Legacy; internal sealed class HeapWalk : IEnum<COR_HEAPOBJECT> { private readonly IGC _gc; private readonly IRuntimeTypeSystem _rts; private readonly TargetPointer _freeObjectMT; private readonly LinearReadCache _cache; private readonly uint _numComponentsOffsetArray; private readonly uint _numComponentsOffsetString; private readonly uint _methodTableOffset; private readonly ulong _methodTableMask; public IEnumerator<COR_HEAPOBJECT> Enumerator { get; } public nuint LegacyHandle { get; set; } = 0; public HeapWalk(Target target) { _gc = target.Contracts.GC; _rts = target.Contracts.RuntimeTypeSystem; _freeObjectMT = _rts.GetWellKnownMethodTable(WellKnownMethodTable.Free); _cache = new LinearReadCache(target); // use these fields directly instead of through RuntimeTypeSystem so that we can use our cache that we really only need for heap walking _numComponentsOffsetArray = (uint)target.GetTypeInfo(DataType.Array).Fields[Constants.FieldNames.Array.NumComponents].Offset; _numComponentsOffsetString = (uint)target.GetTypeInfo(DataType.String).Fields["m_StringLength"].Offset; _methodTableOffset = (uint)target.GetTypeInfo(DataType.Object).Fields["m_pMethTab"].Offset; _methodTableMask = (ulong)~target.ReadGlobal<byte>(Constants.Globals.ObjectToMethodTableUnmask); Enumerator = Walk().GetEnumerator(); } private IEnumerable<COR_HEAPOBJECT> Walk() { bool pendingFailure = false; foreach ((GCHeapSegmentInfo seg, GCHeapData _) in _gc.EnumerateAllSegments()) { if (seg.Start.Value >= seg.End.Value) continue; TargetPointer currentObj = _gc.GetPotentialNextObjectAddress(seg.Start, 0, seg); while (currentObj.Value < seg.End.Value) { // Replicate IObject.GetMethodTableAddress in fast path with linear read cache if (!_cache.TryReadPointer(currentObj.Value + _methodTableOffset, out TargetPointer mt)) { pendingFailure = true; break; } mt = new TargetPointer(mt.Value & _methodTableMask); // Replicate IObject.GetSize in fast path with linear read cache if (!TryGetObjectSize(currentObj, mt, out ulong size) || size == 0) { pendingFailure = true; break; } size = _gc.AlignObjectSize(size, seg.Generation); if (currentObj.Value + size > seg.End.Value || size == 0) { pendingFailure = true; break; } // Advance past this object (and any allocation-context tail) in a single call. TargetPointer nextObj = _gc.GetPotentialNextObjectAddress(currentObj, size, seg); if (nextObj.Value > seg.End.Value) { break; } TargetPointer reportedAddr = currentObj; bool isFree = mt == _freeObjectMT; currentObj = nextObj; if (isFree) continue; if (pendingFailure) { // sentinel, show that we have a read failure and then yield the next valid object yield return default; pendingFailure = false; } yield return new COR_HEAPOBJECT { address = reportedAddr.Value, size = size, type = new COR_TYPEID { token1 = mt.Value, token2 = 0 }, }; } } // Trailing corruption: if validation failed after the last successful yield (or // before any yield at all), show a sentinel now. if (pendingFailure) yield return default; } private bool TryGetObjectSize(TargetPointer objAddr, TargetPointer mt, out ulong size) { size = 0; try { TypeHandle handle = _rts.GetTypeHandle(mt); ulong baseSize = _rts.GetBaseSize(handle); uint componentSize = _rts.GetComponentSize(handle); uint numComponentsOffset = 0; if (componentSize != 0) { if (_rts.IsArray(handle, out _) || _rts.IsFreeObjectMethodTable(handle)) numComponentsOffset = _numComponentsOffsetArray; else if (_rts.IsString(handle)) numComponentsOffset = _numComponentsOffsetString; else return false; // unrecognized component type if (!_cache.TryReadUInt32(objAddr.Value + numComponentsOffset, out uint numComponents)) return false; baseSize += (ulong)componentSize * numComponents; } size = baseSize; return true; } catch { // The MT may be corrupt — surface as a read failure. return false; } } }