/// <summary> /// Reads the Nodes Element /// </summary> private static void ReadNodesFromXml(XmlReader reader, MemoryGraph graph) { Debug.Assert(reader.NodeType == XmlNodeType.Element); var inputDepth = reader.Depth; reader.Read(); // Advance to children var children = new GrowableArray <NodeIndex>(1000); var typeStorage = graph.AllocTypeNodeStorage(); while (inputDepth < reader.Depth) { if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case "Node": { NodeIndex readNodeIndex = (NodeIndex)FetchInt(reader, "Index", -1); NodeTypeIndex typeIndex = (NodeTypeIndex)FetchInt(reader, "TypeIndex", -1); int size = FetchInt(reader, "Size"); if (readNodeIndex == NodeIndex.Invalid) { throw new ApplicationException("Node element does not have a Index attribute."); } if (typeIndex == NodeTypeIndex.Invalid) { throw new ApplicationException("Node element does not have a TypeIndex attribute."); } // TODO FIX NOW very inefficient. Use ReadValueChunk and FastStream to make more efficient. children.Clear(); var body = reader.ReadElementContentAsString(); foreach (var num in Regex.Split(body, @"\s+")) { if (num.Length > 0) { children.Add((NodeIndex)int.Parse(num)); } } if (size == 0) { size = graph.GetType(typeIndex, typeStorage).Size; } // TODO should probably just reserve node index 0 to be an undefined object? NodeIndex nodeIndex = 0; if (readNodeIndex != 0) { nodeIndex = graph.CreateNode(); } if (readNodeIndex != nodeIndex) { throw new ApplicationException("Node Indexes do not start at 0 or 1 and increase consecutively."); } graph.SetNode(nodeIndex, typeIndex, size, children); } break; default: Debug.WriteLine("Skipping unknown element {0}", reader.Name); reader.Skip(); break; } } else if (!reader.Read()) { break; } } }
public PdbScopeMemoryGraph(string pdbScopeFile) : base(10000) { var children = new GrowableArray <NodeIndex>(1000); Dictionary <string, NodeTypeIndex> knownTypes = new Dictionary <string, NodeTypeIndex>(1000); XmlReaderSettings settings = new XmlReaderSettings() { IgnoreWhitespace = true, IgnoreComments = true }; using (XmlReader reader = XmlReader.Create(pdbScopeFile, settings)) { int foundBestRoot = int.MaxValue; // it is zero when when we find the best root. Address imageBase = 0; uint sizeOfImageHeader = 0; Address lastAddress = 0; int badValues = 0; Queue <Section> sections = new Queue <Section>(); Address prevAddr = 0; Address expectedAddr = 0; RootIndex = NodeIndex.Invalid; NodeIndex firstNodeIndex = NodeIndex.Invalid; while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { switch (reader.Name) { case "Section": { Section section = new Section(); section.Start = Address.Parse(reader.GetAttribute("Start")); section.Size = uint.Parse(reader.GetAttribute("Size")); section.Name = reader.GetAttribute("Name"); sections.Enqueue(section); lastAddress = Math.Max(lastAddress, section.EndRoundedUpToPage); } break; case "Module": if (imageBase == 0) { imageBase = Address.Parse(reader.GetAttribute("Base")); sizeOfImageHeader = 1024; // We are using the file size number NodeIndex nodeIndex = GetNodeIndex(imageBase); NodeTypeIndex typeIndex = CreateType("Image Header"); children.Clear(); SetNode(nodeIndex, typeIndex, (int)sizeOfImageHeader, children); expectedAddr = imageBase + 0x1000; DebugWriteLine("Loading Module Map table used to decode $N symbol prefixes."); string dllFilePath = reader.GetAttribute("FilePath"); if (dllFilePath != null) { LoadModuleMap(dllFilePath, pdbScopeFile); } else { DebugWriteLine("Could not find path to original DLL being analyzed."); } if (m_moduleMap != null) { DebugWriteLine("Loaded Module Map of " + m_moduleMap.Count + " Project N style IL modules to unmangled $N_ prefixes."); } else { DebugWriteLine("Warning: No Module Map Found: $N_ prefixes will not be unmangled."); } } break; case "ObjectTypes": case "Type": case "Dicectory": case "Sections": case "PdbscopeReport": case "Symbols": case "SourceFiles": case "File": break; case "Symbol": string addrStr = reader.GetAttribute("addr"); Address addr; if (addrStr != null && Address.TryParse(addrStr, NumberStyles.AllowHexSpecifier, null, out addr)) { if (addr < lastAddress) { // Get Size string sizeStr = reader.GetAttribute("size"); uint size = 0; if (sizeStr != null) { uint.TryParse(sizeStr, out size); } // Get Children children.Clear(); string to = reader.GetAttribute("to"); if (to != null) { GetChildrenForAddresses(ref children, to); } // Get Name, make a type out of it string name; NodeTypeIndex typeIndex = GetTypeForXmlElement(knownTypes, reader, size, out name); // Currently PdbScope files have extra information lines where it shows the different generic instantiations associated // with a given symbol. These ways have the same address as the previous entry and have no size (size will be 0) so // we filter these lines out with the following condition. if (prevAddr != addr || size != 0) { prevAddr = addr; if (addr < expectedAddr) { DebugWriteLine(string.Format("Got Address {0:x} which is less than the expected address {1:x}. Discarding {2}", addr, expectedAddr, name)); badValues++; if (50 < badValues) { throw new ApplicationException("Too many cases where the addresses were not ascending in the file"); } continue; // discard } /*** We want to make sure we account for all bytes, so log when we see gaps ***/ // If we don't match see if it is because of section boundary. if (addr != expectedAddr) { EmitNodesForGaps(sections, expectedAddr, addr); } expectedAddr = addr + size; NodeIndex nodeIndex = GetNodeIndex((Address)addr); SetNode(nodeIndex, typeIndex, (int)size, children); // See if this is a good root if (foundBestRoot != 0 && name != null) { if (name == "RHBinder__ShimExeMain") { RootIndex = nodeIndex; foundBestRoot = 0; } else if (0 < foundBestRoot && name.Contains("ILT$Main")) { RootIndex = nodeIndex; foundBestRoot = 1; } else if (1 < foundBestRoot & name.Contains("DllMainCRTStartup")) { RootIndex = nodeIndex; foundBestRoot = 1; } else if (2 < foundBestRoot & name.Contains("Main")) { RootIndex = nodeIndex; foundBestRoot = 2; } } // Remember first node. if (firstNodeIndex == NodeIndex.Invalid) { firstNodeIndex = nodeIndex; } } } else { DebugWriteLine(string.Format("Warning Discarding Symbol node {0:x} outside the last address in the image {1:x}", addr, lastAddress)); } } else { DebugWriteLine("Error: symbol without addr"); } break; default: DebugWriteLine(string.Format("Skipping unknown element {0}", reader.Name)); break; } } } EmitNodesForGaps(sections, expectedAddr, lastAddress); if (RootIndex == NodeIndex.Invalid) { RootIndex = firstNodeIndex; } DebugWriteLine(string.Format("Image Base {0:x} LastAddress {1:x}", imageBase, lastAddress)); DebugWriteLine(string.Format("Total Virtual Size {0} ({0:x})", lastAddress - imageBase)); DebugWriteLine(string.Format("Total File Size {0} ({0:x})", TotalSize)); } AllowReading(); }
/// <summary> /// This demo shows you how to create a a graph of memory and turn it into a stackSource with MemoryGraphStackSource. /// </summary> public void DemoMemoryGraph() { // Make a custom stack source that was created out of nothing. InternStackSouce is your friend here. MemoryGraph myGraph = new MemoryGraph(1000); // Create a memory graph out of 'nothing'. In the example below we create a graph where the root which points at // 'base' which has a bunch of children, each of which have a child that points back to the base node (thus it has lots of cycles) var baseIdx = myGraph.GetNodeIndex(0); // Define the NAME (index) for the of the graph (but we have not defined what is in it) GrowableArray <NodeIndex> children = new GrowableArray <NodeIndex>(1); // This array is reused again and again for each child. GrowableArray <NodeIndex> rootChildren = new GrowableArray <NodeIndex>(100); // This is used to create the children of the root; //Here I make up a graph of memory addresses at made up locations for (Address objAddr = 0x100000; objAddr < 0x200000; objAddr += 0x10000) { NodeIndex nodeIdx = myGraph.GetNodeIndex(objAddr); // Create the name (node index) for the child // Make a type for the child. In this case we make a new type for each node, normally you keep these in a interning table so that // every distinct type name has exactly one type index. Interning is not STRICTLLY needed, but the representation assumes that // there are many more nodes than there are types. NodeTypeIndex nodeTypeIdx = myGraph.CreateType("MyType_" + objAddr.ToString(), "MyModule"); // Create a list of children for this node in this case, each node has exactly one child, which is the root node. children.Clear(); children.Add(baseIdx); // Actually define the node with the given name (nodeIdx), type, size (100 in our case) and children; myGraph.SetNode(nodeIdx, nodeTypeIdx, 100, children); rootChildren.Add(nodeIdx); // Remember this node name as belonging to the things that the root points at. } // At this point we have everything we need to define the base node, do it here. myGraph.SetNode(baseIdx, myGraph.CreateType("[Base]"), 0, rootChildren); // Create a root node that points at the base node. myGraph.RootIndex = myGraph.GetNodeIndex(1); children.Clear(); children.Add(baseIdx); myGraph.SetNode(myGraph.RootIndex, myGraph.CreateType("[ROOT]"), 0, children); // Note that the raw graph APIs force you to know all the children of a particular node before you can define // the node. There is a class call MemoryNodeBuilder which makes this a bit nicer in that you can incrementally // add children to nodes and then 'finalize' them all at once at the end. Ideally, however you don't have many // nodes that need MemoryNodeBuilder as they are more expensive to build. // So far, the graph is in 'write mode' where only creation APIs are allowed. Change to 'Read Mode' which no writes // are allowed by read APIs are allowed. (We may lift this restriction at some point). myGraph.AllowReading(); // I can dump it as XML to look at it. using (var writer = File.CreateText("MyGraph.dump.xml")) { myGraph.WriteXml(writer); } // I can write the graph out as a file and read it back in later. // myGraph.WriteAsBinaryFile("myGraph.gcGraph"); // var roundTrip = MemoryGraph.ReadFromBinaryFile("myGraph.gcGraph"); // OK we have a graph, turn it into a stack source so that I can view it. MemoryGraphStackSource myStackSource = new MemoryGraphStackSource(myGraph, LogFile); // Create a Stacks class (which remembers all the Filters, Scaling policy and other things the viewer wants. Stacks stacks = new Stacks(myStackSource); // If you wanted to, you can change the filtering ... stacks.Filter.GroupRegExs = ""; stacks.Filter.MinInclusiveTimePercent = ""; // And view it. OpenStackViewer(stacks); }
void Read(TextReader reader) { var framePattern = new Regex(@"\b(\w+?)\!(\S\(?[\S\s]*\)?)"); var stackStart = new Regex(@"Call Site"); // the call stack from the debugger kc command looksl like this //Call Site //coreclr!JIT_MonEnterWorker_Portable //System_Windows_ni!MS.Internal.ManagedPeerTable.TryGetManagedPeer(IntPtr, Boolean, System.Object ByRef) //System_Windows_ni!MS.Internal.ManagedPeerTable.EnsureManagedPeer(IntPtr, Int32, System.Type, Boolean) //System_Windows_ni!MS.Internal.FrameworkCallbacks.CheckPeerType(IntPtr, System.String, Boolean) //System_Windows_ni!DomainBoundILStubClass.IL_STUB_ReversePInvoke(Int32, IntPtr, Int32) //coreclr!UM2MThunk_WrapperHelper //coreclr!UMThunkStubWorker //coreclr!UMThunkStub //agcore!CParser::StartObjectElement //agcore!CParser::Attribute //agcore!CParser::LoadXaml var stack = new GrowableArray <DebuggerCallStackFrame>(); bool newCallStackFound = false; var sample = new StackSourceSample(this); float time = 0; for (; ;) { var line = reader.ReadLine(); if (line == null) { break; } var match = framePattern.Match(line); if (match.Success && newCallStackFound) { var module = match.Groups[1].Value; var methodName = match.Groups[2].Value; // trim the methodName if it has file name info (if the trace is collected with kv instead of kc) int index = methodName.LastIndexOf(")+"); if (index != -1) { methodName = methodName.Substring(0, index + 1); } var moduleIndex = Interner.ModuleIntern(module); var frameIndex = Interner.FrameIntern(methodName, moduleIndex); DebuggerCallStackFrame frame = new DebuggerCallStackFrame(); frame.frame = frameIndex; stack.Add(frame); } else { var stackStartMatch = stackStart.Match(line); if (stackStartMatch.Success) { // start a new sample. // add the previous sample // clear the stack if (stack.Count != 0) { StackSourceCallStackIndex parent = StackSourceCallStackIndex.Invalid; for (int i = stack.Count - 1; i >= 0; --i) { parent = Interner.CallStackIntern(stack[i].frame, parent); } stack.Clear(); sample.StackIndex = parent; sample.TimeRelativeMSec = time; time++; AddSample(sample); } newCallStackFound = true; } } } Interner.DoneInterning(); }
/// <summary> /// After reading the events the graph is not actually created, you need to post process the information we gathered /// from the events. This is where that happens. Thus 'SetupCallbacks, Process(), ConvertHeapDataToGraph()' is how /// you dump a heap. /// </summary> internal unsafe void ConvertHeapDataToGraph() { int maxNodeCount = 10_000_000; if (m_converted) { return; } m_converted = true; if (!m_seenStart) { if (m_processName != null) { throw new ApplicationException("ETL file did not include a Heap Dump for process " + m_processName); } throw new ApplicationException("ETL file did not include a Heap Dump for process ID " + m_processId); } if (!m_ignoreEvents) { throw new ApplicationException("ETL file shows the start of a heap dump but not its completion."); } m_log.WriteLine("Processing Heap Data, BulkTypeEventCount:{0} BulkNodeEventCount:{1} BulkEdgeEventCount:{2}", m_typeBlocks.Count, m_nodeBlocks.Count, m_edgeBlocks.Count); // Process the type information (we can't do it on the fly because we need the module information, which may be // at the end of the trace. while (m_typeBlocks.Count > 0) { GCBulkTypeTraceData data = m_typeBlocks.Dequeue(); for (int i = 0; i < data.Count; i++) { GCBulkTypeValues typeData = data.Values(i); var typeName = typeData.TypeName; if (IsProjectN) { // For project N we only log the type ID and module base address. Debug.Assert(typeName.Length == 0); Debug.Assert((typeData.Flags & TypeFlags.ModuleBaseAddress) != 0); var moduleBaseAddress = typeData.TypeID - (ulong)typeData.TypeNameID; // Tricky way of getting the image base. Debug.Assert((moduleBaseAddress & 0xFFFF) == 0); // Image loads should be on 64K boundaries. Module module = GetModuleForImageBase(moduleBaseAddress); if (module.Path == null) { m_log.WriteLine("Error: Could not find DLL name for imageBase 0x{0:x} looking up typeID 0x{1:x} with TypeNameID {2:x}", moduleBaseAddress, typeData.TypeID, typeData.TypeNameID); } m_typeID2TypeIndex[typeData.TypeID] = m_graph.CreateType(typeData.TypeNameID, module); } else { if (typeName.Length == 0) { if ((typeData.Flags & TypeFlags.Array) != 0) { typeName = "ArrayType(0x" + typeData.TypeNameID.ToString("x") + ")"; } else { typeName = "Type(0x" + typeData.TypeNameID.ToString("x") + ")"; } } // TODO FIX NOW these are kind of hacks typeName = Regex.Replace(typeName, @"`\d+", ""); typeName = typeName.Replace("[", "<"); typeName = typeName.Replace("]", ">"); typeName = typeName.Replace("<>", "[]"); string moduleName; if (!m_moduleID2Name.TryGetValue(typeData.ModuleID, out moduleName)) { moduleName = "Module(0x" + typeData.ModuleID.ToString("x") + ")"; m_moduleID2Name[typeData.ModuleID] = moduleName; } // Is this type a an RCW? If so mark the type name that way. if ((typeData.Flags & TypeFlags.ExternallyImplementedCOMObject) != 0) { typeName = "[RCW " + typeName + "]"; } m_typeID2TypeIndex[typeData.TypeID] = CreateType(typeName, moduleName); // Trace.WriteLine(string.Format("Type 0x{0:x} = {1}", typeData.TypeID, typeName)); } } } // Process all the ccw root information (which also need the type information complete) var ccwRoot = m_root.FindOrCreateChild("[COM/WinRT Objects]"); while (m_ccwBlocks.Count > 0) { GCBulkRootCCWTraceData data = m_ccwBlocks.Dequeue(); GrowableArray <NodeIndex> ccwChildren = new GrowableArray <NodeIndex>(1); for (int i = 0; i < data.Count; i++) { unsafe { GCBulkRootCCWValues ccwInfo = data.Values(i); // TODO Debug.Assert(ccwInfo.IUnknown != 0); if (ccwInfo.IUnknown == 0) { // TODO currently there are times when a CCWs IUnknown pointer is not set (it is set lazily). // m_log.WriteLine("Warning seen a CCW with IUnknown == 0"); continue; } // Create a CCW node that represents the COM object that has one child that points at the managed object. var ccwNode = m_graph.GetNodeIndex(ccwInfo.IUnknown); var ccwTypeIndex = GetTypeIndex(ccwInfo.TypeID, 200); var ccwType = m_graph.GetType(ccwTypeIndex, m_typeStorage); var typeName = "[CCW 0x" + ccwInfo.IUnknown.ToString("x") + " for type " + ccwType.Name + "]"; ccwTypeIndex = CreateType(typeName); ccwChildren.Clear(); ccwChildren.Add(m_graph.GetNodeIndex(ccwInfo.ObjectID)); m_graph.SetNode(ccwNode, ccwTypeIndex, 200, ccwChildren); ccwRoot.AddChild(ccwNode); } } } // Process all the static variable root information (which also need the module information complete var staticVarsRoot = m_root.FindOrCreateChild("[static vars]"); while (m_staticVarBlocks.Count > 0) { GCBulkRootStaticVarTraceData data = m_staticVarBlocks.Dequeue(); for (int i = 0; i < data.Count; i++) { GCBulkRootStaticVarValues staticVarData = data.Values(i); var rootToAddTo = staticVarsRoot; if ((staticVarData.Flags & GCRootStaticVarFlags.ThreadLocal) != 0) { rootToAddTo = m_root.FindOrCreateChild("[thread static vars]"); } // Get the type name. NodeTypeIndex typeIdx; string typeName; if (m_typeID2TypeIndex.TryGetValue(staticVarData.TypeID, out typeIdx)) { var type = m_graph.GetType(typeIdx, m_typeStorage); typeName = type.Name; } else { typeName = "Type(0x" + staticVarData.TypeID.ToString("x") + ")"; } string fullFieldName = typeName + "." + staticVarData.FieldName; rootToAddTo = rootToAddTo.FindOrCreateChild("[static var " + fullFieldName + "]"); var nodeIdx = m_graph.GetNodeIndex(staticVarData.ObjectID); rootToAddTo.AddChild(nodeIdx); } } // var typeStorage = m_graph.AllocTypeNodeStorage(); GCBulkNodeUnsafeNodes nodeStorage = new GCBulkNodeUnsafeNodes(); // Process all the node and edge nodes we have collected. bool doCompletionCheck = true; for (; ;) { GCBulkNodeUnsafeNodes *node = GetNextNode(&nodeStorage); if (node == null) { break; } // Get the node index var nodeIdx = m_graph.GetNodeIndex((Address)node->Address); var objSize = (int)node->Size; Debug.Assert(node->Size < 0x1000000000); var typeIdx = GetTypeIndex(node->TypeID, objSize); // TODO FIX NOW REMOVE // var type = m_graph.GetType(typeIdx, typeStorage); // Trace.WriteLine(string.Format("Got Object 0x{0:x} Type {1} Size {2} #children {3} nodeIdx {4}", (Address)node->Address, type.Name, objSize, node->EdgeCount, nodeIdx)); // Process the edges (which can add children) m_children.Clear(); for (int i = 0; i < node->EdgeCount; i++) { Address edge = GetNextEdge(); var childIdx = m_graph.GetNodeIndex(edge); m_children.Add(childIdx); // Trace.WriteLine(string.Format(" Child 0x{0:x}", edge)); } // TODO we can use the nodes type to see if this is an RCW before doing this lookup which may be a bit more efficient. RCWInfo info; if (m_objectToRCW.TryGetValue((Address)node->Address, out info)) { // Add the COM object this RCW points at as a child of this node. m_children.Add(m_graph.GetNodeIndex(info.IUnknown)); // We add 1000 to account for the overhead of the RCW that is NOT on the GC heap. objSize += 1000; } Debug.Assert(!m_graph.IsDefined(nodeIdx)); m_graph.SetNode(nodeIdx, typeIdx, objSize, m_children); if (m_graph.NodeCount >= maxNodeCount) { doCompletionCheck = false; var userMessage = string.Format("Exceeded max node count {0}", maxNodeCount); m_log.WriteLine("[WARNING: ]", userMessage); break; } } if (doCompletionCheck && m_curEdgeBlock != null && m_curEdgeBlock.Count != m_curEdgeIdx) { throw new ApplicationException("Error: extra edge data. Giving up on heap dump."); } m_root.Build(); m_graph.RootIndex = m_root.Index; }
public GCHeapGraph(GCHeap heap) : base((int)heap.NumberOfObjects) { // TODO is basically nodes that have not had 'SetNode' done to them. Thus the node index has been // allocated, but we have not processed the defintion of the node. // This list should NEVER contain duplicates (you should test if you already processed the node before // adding to this queue. Queue <NodeIndex> toDo = new Queue <NodeIndex>(); // Create the root node. var root = new MemoryNodeBuilder(this, "[root]"); // Create categories for the roots. var nodeForKind = new MemoryNodeBuilder[((int)GCRootKind.Max) + 1]; var otherRoots = root.FindOrCreateChild("[other roots]"); nodeForKind[(int)GCRootKind.LocalVar] = otherRoots.FindOrCreateChild("[unknown local vars]"); for (int i = 0; i < nodeForKind.Length; i++) { if (nodeForKind[i] == null) { nodeForKind[i] = otherRoots.FindOrCreateChild("[" + ((GCRootKind)i).ToString().ToLower() + " handles]"); } } var unreachable = root.FindOrCreateChild("[unreachable but not yet collected]"); foreach (var gcRoot in heap.Roots) { if (gcRoot.HeapReference == 0) { continue; } // TODO FIX NOW condition should not be needed, should be able to assert it. if (!heap.IsInHeap(gcRoot.HeapReference)) { continue; } Debug.Assert(heap.IsInHeap(gcRoot.HeapReference)); // Make a node for it, and notice if the root is already in the heap. var lastLimit = NodeIndexLimit; var newNodeIdx = GetNodeIndex(gcRoot.HeapReference); var nodeForGcRoot = nodeForKind[(int)gcRoot.Kind]; if (gcRoot.Type != null) { var prefix = "other"; if (gcRoot.Kind == GCRootKind.StaticVar) { prefix = "static"; } else if (gcRoot.Kind == GCRootKind.LocalVar) { prefix = "local"; } var appDomainNode = root.FindOrCreateChild("[appdomain " + gcRoot.AppDomainName + "]"); var varKindNode = appDomainNode.FindOrCreateChild("[" + prefix + " vars]"); var moduleName = Path.GetFileNameWithoutExtension(gcRoot.Type.ModuleFilePath); var moduleNode = varKindNode.FindOrCreateChild("[" + prefix + " vars " + moduleName + "]"); var typeNode = moduleNode.FindOrCreateChild("[" + prefix + " vars " + gcRoot.Type.Name + "]"); var name = gcRoot.Type.Name + "+" + gcRoot.Name + " [" + prefix + " var]"; nodeForGcRoot = typeNode.FindOrCreateChild(name, gcRoot.Type.ModuleFilePath); // TODO REMOVE // if (nodeForGcRoot.Size == 0) // nodeForGcRoot.Size = 4; } // Give the user a bit more information about runtime internal handles if (gcRoot.Kind == GCRootKind.Pinning) { var pinnedType = heap.GetObjectType(gcRoot.HeapReference); if (pinnedType.Name == "System.Object []") { // Traverse pinned object arrays a bit 'manually' here so we tell the users they are likely handles. var handleArray = new MemoryNodeBuilder( this, "[likely runtime object array handle table]", pinnedType.ModuleFilePath, newNodeIdx); handleArray.Size = pinnedType.GetSize(gcRoot.HeapReference); nodeForGcRoot.AddChild(handleArray); pinnedType.EnumerateRefsOfObjectCarefully(gcRoot.HeapReference, delegate(Address childRef, int childFieldOffset) { // Poor man's visited bit. If we did not need to add a node, then we have visited it. var childLastLimit = NodeIndexLimit; var childNodeIdx = GetNodeIndex(childRef); if (childNodeIdx < childLastLimit) // Already visited, simply add the child and move one { handleArray.AddChild(childNodeIdx); return; } var childType = heap.GetObjectType(childRef); if (childType.Name == "System.String") { // TODO FIX NOW: Only want to morph the name if something else does not point at it. var literal = new MemoryNodeBuilder( this, "[likely string literal]", childType.ModuleFilePath, childNodeIdx); literal.Size = childType.GetSize(childRef); handleArray.AddChild(literal); } else { handleArray.AddChild(childNodeIdx); toDo.Enqueue(childNodeIdx); } }); continue; // we are done processing this node. } } nodeForGcRoot.AddChild(newNodeIdx); if (newNodeIdx >= lastLimit) // have not been visited. { toDo.Enqueue(newNodeIdx); } } root.AllocateTypeIndexes(); // Create the necessary types. int memoryTypesStart = (int)NodeTypeIndexLimit; foreach (var type in heap.Types) { NodeTypeIndex nodeType = CreateType(type.Name, type.ModuleFilePath); Debug.Assert((int)nodeType == (int)type.Index + memoryTypesStart); } var children = new GrowableArray <NodeIndex>(); while (toDo.Count > 0) { var nodeIndex = toDo.Dequeue(); var objRef = GetAddress(nodeIndex); children.Clear(); GCHeapType objType = heap.GetObjectType(objRef); objType.EnumerateRefsOfObjectCarefully(objRef, delegate(Address childRef, int childFieldOffset) { Debug.Assert(heap.IsInHeap(childRef)); var lastLimit = NodeIndexLimit; var childNodeIdx = GetNodeIndex(childRef); children.Add(childNodeIdx); // Poor man's visited bit. If the index we just asked for is a new node, put it in the work queue if (childNodeIdx >= lastLimit) { toDo.Enqueue(childNodeIdx); } }); int objSize = objType.GetSize(objRef); Debug.Assert(objSize > 0); SetNode(nodeIndex, (NodeTypeIndex)((int)objType.Index + memoryTypesStart), objSize, children); } long unreachableSize = (heap.TotalSize - TotalSize); unreachable.Size = (int)unreachableSize; if (unreachable.Size != unreachableSize) { unreachable.Size = int.MaxValue; // TODO not correct on overflow } // We are done! RootIndex = root.Build(); AllowReading(); }