src/Microsoft.Diagnostics.Runtime/DacImplementation/DacHeap.cs (396 lines of code) (raw):

// 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.Buffers; using System.Collections.Generic; using System.Linq; using Microsoft.Diagnostics.Runtime.AbstractDac; using Microsoft.Diagnostics.Runtime.DacInterface; using Microsoft.Diagnostics.Runtime.Extensions; using Microsoft.Diagnostics.Runtime.Utilities; using GCKind = Microsoft.Diagnostics.Runtime.AbstractDac.GCKind; namespace Microsoft.Diagnostics.Runtime.DacImplementation { internal sealed class DacHeap : IAbstractHeap { private readonly SOSDac _sos; private readonly SOSDac8? _sos8; private readonly SosDac12? _sos12; private readonly ISOSDac16? _sos16; private readonly IMemoryReader _memoryReader; private readonly GCState _gcState; private HashSet<ulong>? _validMethodTables; private const uint SyncBlockRecLevelMask = 0x0000FC00; private const int SyncBlockRecLevelShift = 10; private const uint SyncBlockThreadIdMask = 0x000003FF; private const uint SyncBlockSpinLock = 0x10000000; private const uint SyncBlockHashOrSyncBlockIndex = 0x08000000; public DacHeap(SOSDac sos, SOSDac8? sos8, SosDac12? sos12, ISOSDac16? sos16, IMemoryReader reader, in GCInfo gcInfo, in CommonMethodTables commonMethodTables) { _sos = sos; _sos8 = sos8; _sos12 = sos12; _sos16 = sos16; _memoryReader = reader; _gcState = new() { Kind = gcInfo.ServerMode != 0 ? GCKind.Server : GCKind.Workstation, AreGCStructuresValid = gcInfo.GCStructuresValid != 0, HeapCount = gcInfo.HeapCount, MaxGeneration = gcInfo.MaxGeneration, ExceptionMethodTable = commonMethodTables.ExceptionMethodTable, FreeMethodTable = commonMethodTables.FreeMethodTable, ObjectMethodTable = commonMethodTables.ObjectMethodTable, StringMethodTable = commonMethodTables.StringMethodTable, }; } public ref readonly GCState State { get => ref _gcState; } public IEnumerable<MemoryRange> EnumerateThreadAllocationContexts() { if (_sos12 is not null && _sos12.GetGlobalAllocationContext(out ulong allocPointer, out ulong allocLimit)) { if (allocPointer < allocLimit) yield return new(allocPointer, allocLimit); } if (!_sos.GetThreadStoreData(out ThreadStoreData threadStore)) yield break; ulong address = threadStore.FirstThread; for (int i = 0; i < threadStore.ThreadCount && address != 0; i++) { if (!_sos.GetThreadData(address, out ThreadData thread)) break; if (thread.AllocationContextPointer < thread.AllocationContextLimit) yield return new(thread.AllocationContextPointer, thread.AllocationContextLimit); address = thread.NextThread; } } public IEnumerable<(ulong Source, ulong Target)> EnumerateDependentHandles() { using SOSHandleEnum? handleEnum = _sos.EnumerateHandles(ClrHandleKind.Dependent); if (handleEnum is null) yield break; foreach (HandleData handle in handleEnum.ReadHandles()) { if (handle.Type == (int)ClrHandleKind.Dependent) { ulong obj = _memoryReader.ReadPointer(handle.Handle); if (obj != 0) yield return (obj, handle.Secondary); } } } public IEnumerable<SyncBlockInfo> EnumerateSyncBlocks() { HResult hr = _sos.GetSyncBlockData(1, out SyncBlockData data); if (!hr || data.TotalSyncBlockCount == 0) yield break; int max = data.TotalSyncBlockCount >= int.MaxValue ? int.MaxValue : (int)data.TotalSyncBlockCount; int curr = 1; do { if (data.Free == 0) { yield return new() { Index = curr, Address = data.Address, Object = data.Object, AppDomain = data.AppDomain, AdditionalThreadCount = data.AdditionalThreadCount.ToSigned(), COMFlags = (SyncBlockComFlags)data.COMFlags, HoldingThread = data.HoldingThread, MonitorHeldCount = data.MonitorHeld.ToSigned(), Recursion = data.Recursion.ToSigned() }; } curr++; if (curr > max) break; hr = _sos.GetSyncBlockData(curr, out data); } while (hr); } public IEnumerable<SubHeapInfo> EnumerateSubHeaps() { if (_gcState.Kind == AbstractDac.GCKind.Server) { ClrDataAddress[] heapAddresses = _sos.GetHeapList(_gcState.HeapCount); for (int i = 0; i < heapAddresses.Length; i++) { if (_sos.GetServerHeapDetails(heapAddresses[i], out HeapDetails heapData)) { SubHeapInfo subHeapInfo = CreateSubHeapInfo(heapAddresses[i], i, heapData); yield return subHeapInfo; } } } else { if (_sos.GetWksHeapDetails(out HeapDetails heapData)) { SubHeapInfo subHeapInfo = CreateSubHeapInfo(0, 0, heapData); yield return subHeapInfo; } } } private SubHeapInfo CreateSubHeapInfo(ulong address, int i, HeapDetails heapData) { GenerationData[] genData = heapData.GenerationTable; IEnumerable<ClrDataAddress> finalization = heapData.FinalizationFillPointers.Take(6); if (_sos8 is not null) { genData = (address != 0 ? _sos8.GetGenerationTable(address) : _sos8.GetGenerationTable()) ?? genData; finalization = (address != 0 ? _sos8.GetFinalizationFillPointers(address) : _sos8.GetFinalizationFillPointers()) ?? finalization; } SubHeapInfo subHeapInfo = new() { Address = address, HeapIndex = i, Allocated = heapData.Allocated, MarkArray = heapData.MarkArray, State = (HeapMarkState)(ulong)heapData.CurrentGCState, CurrentSweepPosition = heapData.NextSweepObj, SavedSweepEphemeralSegment = heapData.SavedSweepEphemeralSeg, SavedSweepEphemeralStart = heapData.SavedSweepEphemeralStart, BackgroundSavedLowestAddress = heapData.BackgroundSavedLowestAddress, BackgroundSavedHighestAddress = heapData.BackgroundSavedHighestAddress, EphemeralHeapSegment = heapData.EphemeralHeapSegment, LowestAddress = heapData.LowestAddress, HighestAddress = heapData.HighestAddress, CardTable = heapData.CardTable, EphemeralAllocContextPointer = heapData.EphemeralAllocContextPtr, EphemeralAllocContextLimit = heapData.EphemeralAllocContextLimit, FinalizationPointers = finalization.Select(r => (ulong)r).ToArray(), Generations = ConvertGenerations(genData), }; subHeapInfo.Segments = EnumerateSegments(subHeapInfo).ToArray(); return subHeapInfo; } private static GenerationInfo[] ConvertGenerations(GenerationData[] genData) { var result = new GenerationInfo[genData.Length]; for (int i = 0; i < result.Length; i++) result[i] = new() { AllocationStart = genData[i].AllocationStart, StartSegment = genData[i].StartSegment, AllocationContextLimit = genData[i].AllocationContextLimit, AllocationContextPointer = genData[i].AllocationContextPointer, }; return result; } private IEnumerable<SegmentInfo> EnumerateSegments(in SubHeapInfo heap) { HashSet<ulong> seen = new() { 0 }; IEnumerable<SegmentInfo> segments = EnumerateSegments(heap, 3, seen); segments = segments.Concat(EnumerateSegments(heap, 2, seen)); if (heap.HasRegions) { segments = segments.Concat(EnumerateSegments(heap, 1, seen)); segments = segments.Concat(EnumerateSegments(heap, 0, seen)); } if (heap.Generations.Length > 4) segments = segments.Concat(EnumerateSegments(heap, 4, seen)); return segments; } private IEnumerable<SegmentInfo> EnumerateSegments(SubHeapInfo heap, int generation, HashSet<ulong> seen) { ulong address = heap.Generations[generation].StartSegment; while (address != 0 && seen.Add(address)) { if (!TryCreateSegment(heap, address, generation, out SegmentInfo segInfo)) break; yield return segInfo; address = segInfo.Next; } } private bool TryCreateSegment(SubHeapInfo subHeap, ulong address, int generation, out SegmentInfo segInfo) { if (!_sos.GetSegmentData(address, out SegmentData data)) { segInfo = default; return false; } var flags = (ClrSegmentFlags)data.Flags; GCSegmentKind kind = GCSegmentKind.Generation2; if ((flags & ClrSegmentFlags.ReadOnly) == ClrSegmentFlags.ReadOnly) { kind = GCSegmentKind.Frozen; } else if (generation == 3) { kind = GCSegmentKind.Large; } else if (generation == 4) { kind = GCSegmentKind.Pinned; } else { // We are not a Frozen, Large, or Pinned segment/region: if (subHeap.HasRegions) { if (generation == 0) kind = GCSegmentKind.Generation0; else if (generation == 1) kind = GCSegmentKind.Generation1; else if (generation == 2) kind = GCSegmentKind.Generation2; } else { if (subHeap.EphemeralHeapSegment == address) kind = GCSegmentKind.Ephemeral; else kind = GCSegmentKind.Generation2; } } // The range of memory occupied by allocated objects MemoryRange allocated = new(data.Start, subHeap.EphemeralHeapSegment == address ? subHeap.Allocated : (ulong)data.Allocated); // There's a bit of calculation involved with finding the committed start. // For regions, it's "allocated.Start - sizeof(aligned_plug_and_gap)". // For segments, it's adjusted by segment_info_size which can be different based // on whether background GC is enabled. Since we don't have that information, we'll // use a heuristic here and hope for the best. ulong committedStart; if (kind == GCSegmentKind.Frozen) committedStart = allocated.Start - (uint)IntPtr.Size; else if ((allocated.Start & 0x1ffful) == 0x1000) committedStart = allocated.Start - 0x1000; else committedStart = allocated.Start & ~0xffful; MemoryRange committed, gen0, gen1, gen2; if (subHeap.HasRegions) { committed = new(committedStart, data.Committed); gen0 = default; gen1 = default; gen2 = default; switch (generation) { case 0: gen0 = new(allocated.Start, allocated.End); break; case 1: gen1 = new(allocated.Start, allocated.End); break; default: gen2 = new(allocated.Start, allocated.End); break; } } else { committed = new(committedStart, data.Committed); if (kind == GCSegmentKind.Ephemeral) { gen0 = new(subHeap.Generations[0].AllocationStart, allocated.End); gen1 = new(subHeap.Generations[1].AllocationStart, gen0.Start); gen2 = new(allocated.Start, gen1.Start); } else { gen0 = default; gen1 = default; gen2 = allocated; } } // The range of memory reserved MemoryRange reserved = new(committed.End, data.Reserved); segInfo = new() { Address = data.Address, Kind = kind, ObjectRange = allocated, CommittedMemory = committed, ReservedMemory = reserved, Generation0 = gen0, Generation1 = gen1, Generation2 = gen2, Flags = flags, Next = data.Next, BackgroundAllocated = data.BackgroundAllocated, }; return true; } public (ulong Thread, int Recursion) GetThinLock(uint header) { if (!HasThinlock(header)) return default; (uint threadId, uint recursion) = GetThinlockData(header); ulong threadAddress = _sos.GetThreadFromThinlockId(threadId); if (threadAddress == 0) return default; return (threadAddress, recursion.ToSigned()); } private static bool HasThinlock(uint header) { return (header & (SyncBlockHashOrSyncBlockIndex | SyncBlockSpinLock)) == 0 && (header & SyncBlockThreadIdMask) != 0; } private static (uint ThreadId, uint Recursion) GetThinlockData(uint header) { uint threadId = header & SyncBlockThreadIdMask; uint recursion = (header & SyncBlockRecLevelMask) >> SyncBlockRecLevelShift; return (threadId, recursion); } public bool IsValidMethodTable(ulong mt) { // clear the mark bit mt &= ~1ul; HashSet<ulong> validMts = _validMethodTables ??= new(); lock (validMts) if (validMts.Contains(mt)) return true; bool verified = _sos.GetMethodTableData(mt, out _); if (verified) { lock (validMts) validMts.Add(mt); } return verified; } public MemoryRange GetInternalRootArray(ulong subHeapAddress) { DacHeapAnalyzeData analyzeData; if (subHeapAddress != 0) _sos.GetHeapAnalyzeData(subHeapAddress, out analyzeData); else _sos.GetHeapAnalyzeData(out analyzeData); if (analyzeData.InternalRootArray == 0 || analyzeData.InternalRootArrayIndex == 0) return default; ulong end = analyzeData.InternalRootArray + (uint)_memoryReader.PointerSize * analyzeData.InternalRootArrayIndex; return new(analyzeData.InternalRootArray, end); } public bool GetOOMInfo(ulong subHeapAddress, out OomInfo oomInfo) { DacOOMData oomData; if (subHeapAddress != 0) { if (!_sos.GetOOMData(subHeapAddress, out oomData) || oomData.Reason == OutOfMemoryReason.None && oomData.GetMemoryFailure == GetMemoryFailureReason.None) { oomInfo = default; return false; } } else { if (!_sos.GetOOMData(out oomData) || oomData.Reason == OutOfMemoryReason.None && oomData.GetMemoryFailure == GetMemoryFailureReason.None) { oomInfo = default; return false; } } oomInfo = new() { AllocSize = oomData.AllocSize, AvailablePageFileMB = oomData.AvailablePageFileMB, GCIndex = oomData.GCIndex, GetMemoryFailure = oomData.GetMemoryFailure, IsLOH = oomData.IsLOH != 0, Reason = oomData.Reason, Size = oomData.Size, }; return true; } public int? GetDynamicAdaptationMode() { if (_sos16 != null) { return _sos16.GetDynamicAdaptationMode(); } else { return null; } } } }