in src/HeapDump/GCHeapDumper.cs [1083:1432]
private void DumpDotNetHeapDataWorker(ClrHeap heap, ref ICorDebugProcess debugProcess, bool isDump, double retryScale)
{
#if DEPENDENT_HANDLE
m_handles = new Dictionary<Address, NodeIndex>(100);
#endif
m_children = new GrowableArray<NodeIndex>(2000);
m_graphTypeIdxForArrayType = new Dictionary<string, NodeTypeIndex>(100);
m_typeIdxToGraphIdx = new GrowableArray<int>();
m_typeMayHaveHandles = new GrowableArray<bool>();
m_gotDotNetData = true;
m_copyOfLog.GetStringBuilder().Length = 0; // Restart the copy
m_log.WriteLine("Dumping GC heap, This process is a {0} bit process on a {1} bit OS",
EnvironmentUtilities.Is64BitProcess ? "64" : "32",
EnvironmentUtilities.Is64BitOperatingSystem ? "64" : "32");
m_log.WriteLine("{0,5:f1}s: Starting heap dump {1}", m_sw.Elapsed.TotalSeconds, DateTime.Now);
m_dotNetHeap = heap;
if (debugProcess != null && Freeze && !isDump)
{
int isRunning;
debugProcess.IsRunning(out isRunning);
if (isRunning != 0)
{
m_log.WriteLine("freezing process.");
debugProcess.Stop(5000);
}
}
ulong totalGCSize = m_dotNetHeap.TotalHeapSize;
if (MaxDumpCountK != 0 && MaxDumpCountK < 10) // Having fewer than 10K is probably wrong.
{
MaxDumpCountK = 10;
}
m_log.WriteLine("{0,5:f1}s: Size of heap = {1:f3} GB", m_sw.Elapsed.TotalSeconds, ((double)totalGCSize) / 1000000000.0);
// We have an overhead of about 52 bytes per object (24 for the hash table, 28 for the rest)
// we have 1GB in a 32 bit process
m_maxNodeCount = 1000000000 / 52; // 20 Meg objects;
if (EnvironmentUtilities.Is64BitOperatingSystem)
{
m_maxNodeCount *= 3; // We have 4GB instead of 2GB, so we 3GB instead of 1GB available for us to use in 32 bit processes = 60Meg objects
}
// On 64 bit process we are limited by the fact that the graph node is in a MemoryStream and its byte array is limited to 2 gig. Most objects will
// be represented by 10 bytes in this array and we round this up to 16 = 128Meg
if (EnvironmentUtilities.Is64BitProcess)
{
m_maxNodeCount = int.MaxValue / 16 - 11; // Limited to 128Meg objects. (We are limited by the size of the stream)
m_log.WriteLine("In a 64 bit process. Increasing the max node count to {0:f1} Meg", m_maxNodeCount / 1000000.0);
}
m_log.WriteLine("Implicitly limit the number of nodes to {0:f1} Meg to avoid arrays that are too large", m_maxNodeCount / 1000000.0);
// Can force it smaller in case our estimate is not good enough.
var explicitMax = MaxNodeCountK * 1000;
if (0 < explicitMax)
{
m_maxNodeCount = Math.Min(m_maxNodeCount, explicitMax);
m_log.WriteLine("Explicit object count maximum {0:n0}, resulting max {1:n0}", explicitMax, m_maxNodeCount);
}
if (retryScale != 1)
{
m_maxNodeCount = (int)(m_maxNodeCount / retryScale);
m_log.WriteLine("We are retrying the dump so we scale the max by {0} to the value {1}", retryScale, m_maxNodeCount);
}
// We assume that object on average are 8 object pointers.
int estimatedObjectCount = (int)(totalGCSize / ((uint)(8 * IntPtr.Size)));
m_log.WriteLine("Estimated number of objects = {0:n0}", estimatedObjectCount);
// We force the node count to be this max node count if we are within a factor of 2.
// This ensures that we don't have an issue where growing algorithms overshoot the amount
// of memory available and fail. Note we do this on 64 bit too
if (estimatedObjectCount >= m_maxNodeCount / 2)
{
m_log.WriteLine("Limiting object count to {0:n0}", m_maxNodeCount);
estimatedObjectCount = m_maxNodeCount + 2;
}
// Allocate a memory graph if we have not already.
if (m_gcHeapDump.MemoryGraph == null)
{
m_gcHeapDump.MemoryGraph = new MemoryGraph(estimatedObjectCount);
}
m_gcHeapDump.MemoryGraph.Is64Bit = EnvironmentUtilities.Is64BitProcess;
var dotNetRoot = new MemoryNodeBuilder(m_gcHeapDump.MemoryGraph, "[.NET Roots]");
ulong total = 0;
var ccwChildren = new GrowableArray<NodeIndex>();
m_log.WriteLine("DumpDotNetHeapDataWorker: Heap Size of dumper {0:n0} MB", GC.GetTotalMemory(false) / 1000000.0);
m_log.WriteLine("A total of {0} segments.", m_dotNetHeap.Segments.Count);
// Get the GC Segments to dump
var gcHeapDumpSegments = new List<GCHeapDumpSegment>(m_dotNetHeap.Segments.Count);
foreach (var seg in m_dotNetHeap.Segments)
{
var gcHeapDumpSegment = new GCHeapDumpSegment();
gcHeapDumpSegment.Start = seg.Start;
gcHeapDumpSegment.End = seg.End;
if (seg.IsLarge)
{
// Everything is Gen3 (large objects)
gcHeapDumpSegment.Gen0End = seg.End;
gcHeapDumpSegment.Gen1End = seg.End;
gcHeapDumpSegment.Gen2End = seg.End;
gcHeapDumpSegment.Gen3End = seg.End;
}
else
{
gcHeapDumpSegment.Gen0End = seg.End;
gcHeapDumpSegment.Gen1End = seg.Gen0Start;
gcHeapDumpSegment.Gen2End = seg.Gen1Start;
gcHeapDumpSegment.Gen3End = seg.Start;
}
gcHeapDumpSegments.Add(gcHeapDumpSegment);
total += seg.Length;
m_log.WriteLine("Segment: Start {0,16:x} Length: {1,16:x} {2,11:n3}M LOH:{3}", seg.Start, seg.Length, seg.Length / 1000000.0, seg.IsLarge);
}
m_log.WriteLine("Segment: Total {0,16} Length: {1,16:x} {2,11:n3}M", "", total, total / 1000000.0);
try
{
m_log.WriteLine("{0,5:f1}s: Scanning Named GC roots", m_sw.Elapsed.TotalSeconds);
// AddStaticAndLocalRoots(rootNode, debugProcess);
// From here we don't use proc unless we are frozen, so we can detach aggressively
if (debugProcess != null && !Freeze && !isDump)
{
m_log.WriteLine("{0,5:f1}s: Not frozen, Finished with roots. Detaching the process", m_sw.Elapsed.TotalSeconds);
TryDetach(ref debugProcess);
}
m_log.WriteLine("{0,5:f1}s: Scanning UNNAMED GC roots", m_sw.Elapsed.TotalSeconds);
var rootsStartTimeMSec = m_sw.Elapsed.TotalMilliseconds;
var getCCWDataNotImplemented = false; // THis happens on silverlight.
// Do the roots that don't have good names
int numRoots = 0;
foreach (ClrRoot root in m_dotNetHeap.EnumerateRoots(true))
{
// If there is a named root already then we assume that that root is the interesting one and we drop this one.
if (m_gcHeapDump.MemoryGraph.IsInGraph(root.Object))
{
continue;
}
// Skip weak roots.
if (root.Kind == Microsoft.Diagnostics.Runtime.GCRootKind.Weak)
{
continue;
}
numRoots++;
if (numRoots % 1024 == 0)
{
m_log.WriteLine("{0,5:f1}s: Scanned {1} roots.", m_sw.Elapsed.TotalSeconds, numRoots);
}
string name = root.Name;
if (name == "RefCount handle")
{
name = "COM/WinRT Objects";
}
else if (name == "local var" || name.EndsWith(" handle", StringComparison.OrdinalIgnoreCase))
{
name += "s";
}
MemoryNodeBuilder nodeToAddRootTo = dotNetRoot;
var type = heap.GetObjectType(root.Object);
if (type != null && !getCCWDataNotImplemented)
{
// TODO FIX NOW, try clause is a hack because ccwInfo.* methods sometime throw.
// Also GetCCWData fails for silverlight
try
{
var ccwInfo = type.GetCCWData(root.Object);
if (ccwInfo != null)
{
// TODO FIX NOW for some reason IUnknown is always 0
ulong comPtr = ccwInfo.IUnknown;
if (comPtr == 0)
{
var intfs = ccwInfo.Interfaces;
if (intfs != null && intfs.Count > 0)
{
comPtr = intfs[0].InterfacePointer;
}
}
// Create a CCW node that represents the COM object that has one child that points at the managed object.
var ccwNode = m_gcHeapDump.MemoryGraph.GetNodeIndex(ccwInfo.Handle);
var typeName = "[CCW";
var targetType = m_dotNetHeap.GetObjectType(root.Object);
if (targetType != null)
{
typeName += " for " + targetType.Name;
}
// typeName += " " + comPtrName + ":[0x" + comPtr.ToString("x");
typeName += " RefCnt: " + ccwInfo.RefCount + "]";
var ccwTypeIndex = GetTypeIndexForName(typeName, null, 200);
ccwChildren.Clear();
ccwChildren.Add(m_gcHeapDump.MemoryGraph.GetNodeIndex(root.Object));
if (comPtr != 0)
{
m_gcHeapDump.MemoryGraph.SetNode(ccwNode, ccwTypeIndex, 200, ccwChildren);
}
nodeToAddRootTo = nodeToAddRootTo.FindOrCreateChild("[COM/WinRT Objects]");
nodeToAddRootTo.AddChild(ccwNode);
continue;
}
}
catch (NotImplementedException)
{
// getCCWDataNoImplemented not implemented on Silverlight. don't keep trying.
getCCWDataNotImplemented = true;
}
catch (Exception e)
{
m_log.WriteLine("Caught exception {0} while fetching CCW information, treating as unknown root",
e.GetType().Name);
}
}
if (name.StartsWith("static var"))
{
nodeToAddRootTo = nodeToAddRootTo.FindOrCreateChild("[static vars]");
}
if (root.IsPinned && root.Kind == Microsoft.Diagnostics.Runtime.GCRootKind.LocalVar)
{
// Add pinned local vars to their own node
nodeToAddRootTo = nodeToAddRootTo.FindOrCreateChild("[Pinned local vars]");
}
else
{
nodeToAddRootTo = nodeToAddRootTo.FindOrCreateChild("[" + name + "]");
}
nodeToAddRootTo.AddChild(m_gcHeapDump.MemoryGraph.GetNodeIndex(root.Object));
}
#if DEPENDENT_HANDLE
var runtime = m_dotNetHeap.Runtime;
// Special logic to allow Dependent handles to look like nodes from source to target.
NodeTypeIndex typeIdxForDependentHandlePseudoNode = NodeTypeIndex.Invalid;
// Get all the dependent handles
foreach (var handle in runtime.EnumerateHandles())
{
if (handle.HandleType == HandleType.Dependent)
{
var dependentHandle = handle.Address;
Debug.Assert(dependentHandle != 0);
var source = handle.Object;
if (source != 0)
{
m_children.Clear();
m_children.Add(m_gcHeapDump.MemoryGraph.GetNodeIndex(handle.DependentTarget));
if (typeIdxForDependentHandlePseudoNode == NodeTypeIndex.Invalid)
{
typeIdxForDependentHandlePseudoNode = GetTypeIndexForName("Dependent Handle", null, 0);
}
NodeIndex nodeIdxForDependentHandlePseudoNode = m_gcHeapDump.MemoryGraph.GetNodeIndex(dependentHandle);
m_gcHeapDump.MemoryGraph.SetNode(nodeIdxForDependentHandlePseudoNode, typeIdxForDependentHandlePseudoNode, runtime.PointerSize * 2, m_children);
m_handles[dependentHandle] = nodeIdxForDependentHandlePseudoNode;
// Add the dependent handle node to a table so that we can create links from the source to the dependent handle.
if (m_dependentHandles == null)
{
m_dependentHandles = new Dictionary<NodeIndex, List<NodeIndex>>();
}
var sourceNodeIdx = m_gcHeapDump.MemoryGraph.GetNodeIndex(source);
List<NodeIndex> dependentHandlesForAddress;
if (!m_dependentHandles.TryGetValue(sourceNodeIdx, out dependentHandlesForAddress))
{
m_dependentHandles[sourceNodeIdx] = dependentHandlesForAddress = new List<NodeIndex>();
}
dependentHandlesForAddress.Add(nodeIdxForDependentHandlePseudoNode);
}
}
}
#endif
var rootDuration = m_sw.Elapsed.TotalMilliseconds - rootsStartTimeMSec;
m_log.WriteLine("Scanning UNNAMED GC roots took {0:n1} msec", rootDuration);
}
catch (Exception e) when (!(e is OutOfMemoryException))
{
m_log.WriteLine("[ERROR while processing roots: {0}", e.Message);
m_log.WriteLine("Continuing without complete root information");
}
m_log.Flush();
m_log.WriteLine("{0,5:f1}s: Starting GC Graph Traversal. This can take a while...", m_sw.Elapsed.TotalSeconds);
double heapTravseralStartSec = m_sw.Elapsed.TotalSeconds;
// If we are want to dump the whole heap, do it now, this is much more efficient.
long startSize = m_gcHeapDump.MemoryGraph.TotalSize;
DumpAllSegments();
Debug.Assert(m_gcHeapDump.MemoryGraph.TotalSize - startSize < (long)totalGCSize);
if (m_runTime != null)
{
m_log.Write("{0,5:f1}s: Dump RCW/CCW information", m_sw.Elapsed.TotalSeconds);
m_gcHeapDump.InteropInfo = new InteropInfo();
try
{
DumpCCWRCW();
}
catch (Exception e)
{
m_log.Write("Error: dumping CCW/RCW information\r\n{0}", e);
m_gcHeapDump.InteropInfo = new InteropInfo(); // Clear the info
}
}
// If we have been asked to free, we only need to freeze while gathering data. We are done here so we can unfreeze
if (debugProcess != null && Freeze && !isDump)
{
TryDetach(ref debugProcess);
}
m_log.WriteLine("{0,5:f1}s: Done collecting data.", m_sw.Elapsed.TotalSeconds);
var dotNetInfo = m_gcHeapDump.DotNetHeapInfo = new DotNetHeapInfo();
// Write out the dump (TODO we should do this incrementally).
dotNetInfo.SizeOfAllSegments = (long)totalGCSize;
dotNetInfo.Segments = gcHeapDumpSegments;
m_gcHeapDump.MemoryGraph.RootIndex = dotNetRoot.Build();
m_log.WriteLine("Number of bad objects during trace {0:n0}", BadObjectCount);
m_log.WriteLine("{0,5:f1}s: Finished heap dump {1}", m_sw.Elapsed.TotalSeconds, DateTime.Now);
return;
}