| File: Contracts\StackWalk\GC\GcScanContext.cs | Web Access |
| Project: src\runtime\src\native\managed\cdac\Microsoft.Diagnostics.DataContractReader.Contracts\Microsoft.Diagnostics.DataContractReader.Contracts.csproj (Microsoft.Diagnostics.DataContractReader.Contracts) |
// 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.Extensions; namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; internal class GcScanContext { private readonly Target _target; private readonly IGC _gc; private readonly IRuntimeTypeSystem _rts; private readonly LinearReadCache _cache; private readonly uint _numComponentsOffsetArray; private readonly uint _numComponentsOffsetString; private readonly ulong _methodTableOffset; private readonly byte _objectToMethodTableUnmask; public bool ResolveInteriorPointers { get; } public List<StackRefData> StackRefs { get; } = []; public TargetPointer StackPointer { get; private set; } public TargetCodePointer InstructionPointer { get; private set; } public TargetPointer Frame { get; private set; } // When set, overrides the default IP/Frame source-type classification for reported roots. // Used to mark roots reported outside the frame walk (GCFrame/GCPROTECT and ExInfo chains) // as StackSourceOther, since their Source is a node address rather than a capital-F Frame. private StackRefData.SourceTypes? _sourceTypeOverride; public GcScanContext(Target target, bool resolveInteriorPointers) { _target = target; ResolveInteriorPointers = resolveInteriorPointers; _gc = target.Contracts.GC; _rts = target.Contracts.RuntimeTypeSystem; _cache = new LinearReadCache(target); _numComponentsOffsetArray = (uint)target.GetTypeInfo(DataType.Array).Fields[Constants.FieldNames.Array.NumComponents].Offset; _numComponentsOffsetString = (uint)target.GetTypeInfo(DataType.String).Fields["m_StringLength"].Offset; _methodTableOffset = (ulong)target.GetTypeInfo(DataType.Object).Fields["m_pMethTab"].Offset; _objectToMethodTableUnmask = target.ReadGlobal<byte>(Constants.Globals.ObjectToMethodTableUnmask); } public void UpdateScanContext(TargetPointer sp, TargetCodePointer ip, TargetPointer frame, StackRefData.SourceTypes? sourceTypeOverride = null) { StackPointer = sp; InstructionPointer = ip; Frame = frame; _sourceTypeOverride = sourceTypeOverride; } private void SetSource(StackRefData data) { if (_sourceTypeOverride is StackRefData.SourceTypes sourceType) { data.SourceType = sourceType; data.Source = Frame; } else if (Frame != TargetPointer.Null) { data.SourceType = StackRefData.SourceTypes.StackSourceFrame; data.Source = Frame; } else { data.SourceType = StackRefData.SourceTypes.StackSourceIP; data.Source = CodePointerUtils.AddressFromCodePointer(InstructionPointer, _target); } } public void RecordDeferredFrame(TargetPointer frameAddress) { StackRefs.Add(new StackRefData { HasRegisterInformation = false, IsInteriorPointer = false, Register = 0, Offset = 0, Address = 0, Object = 0, Flags = GcScanFlags.CDAC_DEFERRED_FRAME, SourceType = StackRefData.SourceTypes.StackSourceFrame, Source = frameAddress, StackPointer = StackPointer, }); } public void GCEnumCallback(TargetPointer pObject, GcScanFlags flags, GcScanSlotLocation loc) { TargetPointer addr; TargetPointer obj; if (loc.TargetPtr) { addr = pObject; obj = _target.ReadPointer(addr); } else { addr = 0; obj = pObject; } if (flags.HasFlag(GcScanFlags.GC_CALL_INTERIOR) && ResolveInteriorPointers) { TargetPointer interiorObj = GetInteriorPointer(obj); if (interiorObj == TargetPointer.Null) return; obj = interiorObj; } StackRefData data = new() { HasRegisterInformation = true, IsInteriorPointer = flags.HasFlag(GcScanFlags.GC_CALL_INTERIOR), Register = loc.Reg, Offset = loc.RegOffset, Address = addr, Object = obj, Flags = flags, StackPointer = StackPointer, }; SetSource(data); StackRefs.Add(data); } private TargetPointer GetInteriorPointer(TargetPointer obj) { TargetPointer outerObj = TargetPointer.Null; foreach ((GCHeapSegmentInfo seg, GCHeapData _) in _gc.EnumerateAllSegments()) { if (obj.Value < seg.Start.Value || obj.Value >= seg.End.Value) continue; TargetPointer currentObj = _gc.GetPotentialNextObjectAddress(seg.Start, 0, seg); ulong size = 0; while (currentObj.Value <= obj.Value) { // Replicate IObject.GetMethodTableAddress in fast path with linear read cache if (!_cache.TryReadPointer(currentObj.Value + _methodTableOffset, out TargetPointer mt)) { return TargetPointer.Null; } mt = mt.Value & (ulong)~_objectToMethodTableUnmask; // Replicate IObject.GetSize in fast path with linear read cache if (!TryGetObjectSize(currentObj, mt, out size) || size == 0) { return TargetPointer.Null; } size = _gc.AlignObjectSize(size, seg.Generation); if (currentObj.Value + size > seg.End.Value || size == 0) { return TargetPointer.Null; } outerObj = currentObj; currentObj = _gc.GetPotentialNextObjectAddress(currentObj, size, seg); } return outerObj + size > obj ? outerObj : TargetPointer.Null; } return outerObj; } 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; } } public void GCReportCallback(TargetPointer ppObj, GcScanFlags flags) { // Read the object pointer from the stack slot. TargetPointer obj = _target.ReadPointer(ppObj); if (flags.HasFlag(GcScanFlags.GC_CALL_INTERIOR) && ResolveInteriorPointers) { TargetPointer interiorObj = GetInteriorPointer(obj); if (interiorObj != TargetPointer.Null) obj = interiorObj; } StackRefData data = new() { HasRegisterInformation = false, IsInteriorPointer = flags.HasFlag(GcScanFlags.GC_CALL_INTERIOR), Register = 0, Offset = 0, Address = ppObj, Object = obj, Flags = flags, StackPointer = StackPointer, }; SetSource(data); StackRefs.Add(data); } }