--- /dev/null
+// 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 System.IO;
+using System.Linq;
+using System.Text;
+using System.Xml;
+using Microsoft.Diagnostics.DebugServices;
+using Microsoft.Diagnostics.Runtime;
+
+namespace Microsoft.Diagnostics.ExtensionCommands
+{
+ [Command(Name = "traverseheap", Help = "Writes out heap information to a file in a format understood by the CLR Profiler.")]
+ public class TraverseHeapCommand : CommandBase
+ {
+ [ServiceImport]
+ public ClrRuntime Runtime { get; set; }
+
+ [ServiceImport]
+ public RootCacheService RootCache { get; set; }
+
+ [Option(Name = "-xml")]
+ public bool Xml { get; set; }
+
+ [Argument(Name = "filename")]
+ public string Filename { get; set; }
+
+ public override void Invoke()
+ {
+ if (string.IsNullOrWhiteSpace(Filename))
+ {
+ throw new ArgumentException($"Output filename cannot be empty.", nameof(Filename));
+ }
+
+ // create file early in case it throws
+ using StreamWriter output = File.CreateText(Filename);
+ using (XmlWriter xml = Xml ? XmlWriter.Create(output, new XmlWriterSettings() { Indent = true, OmitXmlDeclaration = true }) : null)
+ {
+ using StreamWriter text = Xml ? null : output;
+
+ // must be called first to initialize types
+ (MemoryStream rootObjectStream, Dictionary<ClrType, int> types) = WriteRootsAndObjects();
+
+ xml?.WriteStartElement("gcheap");
+ xml?.WriteStartElement("types");
+
+ foreach (KeyValuePair<ClrType, int> kv in types.OrderBy(kv => kv.Value))
+ {
+ string name = kv.Key?.Name ?? $"error-reading-type-name:{kv.Key.MethodTable:x}";
+ int typeId = kv.Value;
+
+ xml?.WriteStartElement("type");
+ xml?.WriteAttributeString("id", typeId.ToString());
+ xml?.WriteAttributeString("name", name);
+ xml?.WriteEndElement();
+
+ text?.WriteLine($"t {typeId} 0 {name}");
+ }
+ xml?.WriteEndElement();
+
+ xml?.Flush();
+ text?.Flush();
+
+ output.WriteLine();
+ output.Flush();
+
+ rootObjectStream.Position = 0;
+ rootObjectStream.CopyTo(output.BaseStream);
+
+ xml?.WriteEndElement();
+ }
+ }
+
+ private (MemoryStream Stream, Dictionary<ClrType, int> Types) WriteRootsAndObjects()
+ {
+ Dictionary<ClrType, int> types = new();
+ MemoryStream rootObjectStream = new();
+
+ using StreamWriter text = Xml ? null : new StreamWriter(rootObjectStream, Encoding.Default, 4096, leaveOpen: true);
+ using XmlWriter xml = Xml ? XmlWriter.Create(rootObjectStream, new XmlWriterSettings()
+ {
+ CloseOutput = false,
+ Indent = true,
+ OmitXmlDeclaration = true,
+ ConformanceLevel = ConformanceLevel.Fragment
+ }) : null;
+
+ int currObj = 1;
+ int currType = 1;
+
+ xml?.WriteStartElement("roots");
+ text?.Write("r");
+ foreach (ClrRoot root in RootCache.EnumerateRoots())
+ {
+ string kind = root switch
+ {
+ ClrStackRoot => "stack",
+ ClrHandle => "handle",
+ _ => "finalizer"
+ };
+
+ xml?.WriteStartElement("root");
+ xml?.WriteAttributeString("kind", kind);
+ xml?.WriteAttributeString("address", FormatHex(root.Address));
+ xml?.WriteEndElement();
+
+ text?.Write(" ");
+ text?.Write(FormatHex(root.Address));
+ }
+ xml?.WriteEndElement();
+ text?.WriteLine();
+
+ xml?.WriteStartElement("objects");
+ foreach (ClrObject obj in Runtime.Heap.EnumerateObjects())
+ {
+ if (!obj.IsValid)
+ {
+ continue;
+ }
+
+ ulong size = obj.Size;
+ int objId = currObj++;
+ if (!types.TryGetValue(obj.Type, out int typeId))
+ {
+ typeId = types[obj.Type] = currType++;
+ }
+
+ xml?.WriteStartElement("object");
+ xml?.WriteAttributeString("address", FormatHex(obj.Address));
+ xml?.WriteAttributeString("typeid", typeId.ToString());
+ xml?.WriteAttributeString("size", size.ToString());
+
+ text?.WriteLine($"n {objId} 1 {typeId} {size}");
+ text?.WriteLine($"! 1 {FormatHex(obj.Address)} {objId}");
+
+ text?.Write($"o {FormatHex(obj.Address)} {typeId} {size} "); // trailing space intentional
+
+ if (obj.ContainsPointers)
+ {
+ foreach (ClrObject objRef in obj.EnumerateReferences(considerDependantHandles: true))
+ {
+ xml?.WriteStartElement("member");
+ xml?.WriteAttributeString("address", FormatHex(objRef.Address));
+ xml?.WriteEndElement();
+
+ text?.Write($" ");
+ text?.Write(FormatHex(objRef.Address));
+ }
+ }
+
+ text?.WriteLine();
+ xml?.WriteEndElement();
+ }
+ xml?.WriteEndElement();
+
+ return (rootObjectStream, types);
+ }
+
+ private static string FormatHex(ulong address) => $"0x{address:x16}";
+ }
+}
}
-static const char *NameForHandle(unsigned int type)
-{
- switch (type)
- {
- case 0:
- return "weak short";
- case 1:
- return "weak long";
- case 2:
- return "strong";
- case 3:
- return "pinned";
- case 5:
- return "ref counted";
- case 6:
- return "dependent";
- case 7:
- return "async pinned";
- case 8:
- return "sized ref";
- case 9:
- return "weak WinRT";
- }
-
- return "unknown";
-}
-
///////////////////////////////////////////////////////////////////////////////
-void GetDependentHandleMap(std::unordered_map<TADDR, std::list<TADDR>>& map)
-{
- unsigned int type = HNDTYPE_DEPENDENT;
- ToRelease<ISOSHandleEnum> handles;
-
- HRESULT hr = g_sos->GetHandleEnumForTypes(&type, 1, &handles);
-
- if (FAILED(hr))
- {
- ExtOut("Failed to walk dependent handles. GCRoot may miss paths.\n");
- return;
- }
-
- SOSHandleData data[4];
- unsigned int fetched = 0;
-
- do
- {
- hr = handles->Next(ARRAY_SIZE(data), data, &fetched);
-
- if (FAILED(hr))
- {
- ExtOut("Error walking dependent handles. GCRoot may miss paths.\n");
- return;
- }
-
- for (unsigned int i = 0; i < fetched; ++i)
- {
- if (data[i].Secondary != 0)
- {
- TADDR obj = 0;
- TADDR target = TO_TADDR(data[i].Secondary);
-
- MOVE(obj, TO_TADDR(data[i].Handle));
-
- map[obj].push_back(target);
- }
- }
- } while (fetched == ARRAY_SIZE(data));
-}
-
UINT FindAllPinnedAndStrong(DWORD_PTR handlearray[], UINT arraySize)
{
unsigned int fetched = 0;
return VerifyObject(heap, seg, objAddr, MTAddr, objSize, bVerifyMember);
}
-////////////////////////////////////////////////////////////////////////////////
-////////////////////////////////////////////////////////////////////////////////
-typedef void (*TYPETREEVISIT)(size_t methodTable, size_t ID, LPVOID token);
-
-// TODO remove this. MethodTableCache already maps method tables to
-// various information. We don't need TypeTree to do this too.
-// Straightfoward to do, but low priority.
-class TypeTree
-{
-private:
- size_t methodTable;
- size_t ID;
- TypeTree *pLeft;
- TypeTree *pRight;
-
-public:
- TypeTree(size_t MT) : methodTable(MT),ID(0),pLeft(NULL),pRight(NULL) { }
-
- BOOL isIn(size_t MT, size_t *pID)
- {
- TypeTree *pCur = this;
-
- while (pCur)
- {
- if (MT == pCur->methodTable)
- {
- if (pID)
- *pID = pCur->ID;
- return TRUE;
- }
- else if (MT < pCur->methodTable)
- pCur = pCur->pLeft;
- else
- pCur = pCur->pRight;
- }
-
- return FALSE;
- }
-
- BOOL insert(size_t MT)
- {
- TypeTree *pCur = this;
-
- while (pCur)
- {
- if (MT == pCur->methodTable)
- return TRUE;
- else if ((MT < pCur->methodTable))
- {
- if (pCur->pLeft)
- pCur = pCur->pLeft;
- else
- break;
- }
- else if (pCur->pRight)
- pCur = pCur->pRight;
- else
- break;
- }
-
- // If we got here, we need to append at the current node.
- TypeTree *pNewNode = new TypeTree(MT);
- if (pNewNode == NULL)
- return FALSE;
-
- if (MT < pCur->methodTable)
- pCur->pLeft = pNewNode;
- else
- pCur->pRight = pNewNode;
-
- return TRUE;
- }
-
- static void destroy(TypeTree *pStart)
- {
- TypeTree *pCur = pStart;
-
- if (pCur)
- {
- destroy(pCur->pLeft);
- destroy(pCur->pRight);
- delete [] pCur;
- }
- }
-
- static void visit_inorder(TypeTree *pStart, TYPETREEVISIT pFunc, LPVOID token)
- {
- TypeTree *pCur = pStart;
-
- if (pCur)
- {
- visit_inorder(pCur->pLeft, pFunc, token);
- pFunc (pCur->methodTable, pCur->ID, token);
- visit_inorder(pCur->pRight, pFunc, token);
- }
- }
-
- static void setTypeIDs(TypeTree *pStart, size_t *pCurID)
- {
- TypeTree *pCur = pStart;
-
- if (pCur)
- {
- setTypeIDs(pCur->pLeft, pCurID);
- pCur->ID = *pCurID;
- (*pCurID)++;
- setTypeIDs(pCur->pRight, pCurID);
- }
- }
-
-};
-
-///////////////////////////////////////////////////////////////////////////////
-//
-
-HeapTraverser::HeapTraverser(bool verify)
-{
- m_format = 0;
- m_file = NULL;
- m_objVisited = 0;
- m_pTypeTree = NULL;
- m_curNID = 1;
- m_verify = verify;
-}
-
-HeapTraverser::~HeapTraverser()
-{
- if (m_pTypeTree) {
- TypeTree::destroy(m_pTypeTree);
- m_pTypeTree = NULL;
- }
-}
-
-BOOL HeapTraverser::Initialize()
-{
- if (!GCHeapsTraverse (HeapTraverser::GatherTypes, this, m_verify))
- {
- ExtOut("Error during heap traverse\n");
- return FALSE;
- }
-
- GetDependentHandleMap(mDependentHandleMap);
-
- size_t startID = 1;
- TypeTree::setTypeIDs(m_pTypeTree, &startID);
-
- return TRUE;
-}
-
-BOOL HeapTraverser::CreateReport (FILE *fp, int format)
-{
- if (fp == NULL || (format!=FORMAT_XML && format != FORMAT_CLRPROFILER))
- {
- return FALSE;
- }
-
- m_file = fp;
- m_format = format;
-
- PrintSection(TYPE_START,TRUE);
-
- PrintSection(TYPE_TYPES,TRUE);
- TypeTree::visit_inorder(m_pTypeTree, HeapTraverser::PrintOutTree, this);
- PrintSection(TYPE_TYPES,FALSE);
-
- ExtOut("tracing roots...\n");
- PrintSection(TYPE_ROOTS,TRUE);
- PrintRootHead();
-
- TraceHandles();
- FindGCRootOnStacks();
-
- PrintRootTail();
- PrintSection(TYPE_ROOTS,FALSE);
-
- // now print type tree
- PrintSection(TYPE_OBJECTS,TRUE);
- ExtOut("\nWalking heap...\n");
- m_objVisited = 0; // for UI updates
- GCHeapsTraverse (HeapTraverser::PrintHeap, this, FALSE); // Never verify on the second pass
- PrintSection(TYPE_OBJECTS,FALSE);
-
- PrintSection(TYPE_START,FALSE);
-
- m_file = NULL;
- return TRUE;
-}
-
-void HeapTraverser::insert(size_t mTable)
-{
- if (m_pTypeTree == NULL)
- {
- m_pTypeTree = new TypeTree(mTable);
- if (m_pTypeTree == NULL)
- {
- ReportOOM();
- return;
- }
- }
- else
- {
- m_pTypeTree->insert(mTable);
- }
-}
-
-size_t HeapTraverser::getID(size_t mTable)
-{
- if (m_pTypeTree == NULL)
- {
- return 0;
- }
- // IDs start at 1, so we can return 0 if not found.
- size_t ret;
- if (m_pTypeTree->isIn(mTable,&ret))
- {
- return ret;
- }
-
- return 0;
-}
-
-void replace(std::string &str, const char* toReplace, const char* replaceWith)
-{
- const size_t replaceLen = strlen(toReplace);
- const size_t replaceWithLen = strlen(replaceWith);
-
- size_t i = str.find(toReplace);
- while (i != std::wstring::npos)
- {
- str.replace(i, replaceLen, replaceWith);
- i = str.find(toReplace, i + replaceWithLen);
- }
-}
-
-void HeapTraverser::PrintType(size_t ID, LPCWSTR wname)
-{
- if (m_format==FORMAT_XML)
- {
- int len = (int)_wcslen(wname);
- int size = WideCharToMultiByte(CP_ACP, 0, wname, len, NULL, 0, NULL, NULL);
- char *buffer = (char*)_alloca(size + 1);
- WideCharToMultiByte(CP_ACP, 0, wname, len, buffer, size, NULL, NULL);
- buffer[size] = '\0';
-
- // Sanitize name based on XML spec.
- std::string name(buffer);
- replace(name, "&", "&");
- replace(name, "\"", """);
- replace(name, "'", "'");
- replace(name, "<", "<");
- replace(name, ">", ">");
-
- fprintf(m_file,
- "<type id=\"%d\" name=\"%s\"/>\n",
- ID, name.c_str());
- }
- else if (m_format==FORMAT_CLRPROFILER)
- {
- fprintf(m_file,
- "t %d 0 %S\n",
- ID, wname);
- }
-}
-
-void HeapTraverser::PrintObjectHead(size_t objAddr,size_t typeID,size_t Size)
-{
- if (m_format==FORMAT_XML)
- {
- fprintf(m_file,
- "<object address=\"0x%p\" typeid=\"%d\" size=\"%d\">\n",
- (PBYTE)objAddr,typeID, Size);
- }
- else if (m_format==FORMAT_CLRPROFILER)
- {
- fprintf(m_file,
- "n %d 1 %d %d\n",
- m_curNID,typeID,Size);
-
- fprintf(m_file,
- "! 1 0x%p %d\n",
- (PBYTE)objAddr,m_curNID);
-
- m_curNID++;
-
- fprintf(m_file,
- "o 0x%p %d %d ",
- (PBYTE)objAddr,typeID,Size);
- }
-}
-
-void HeapTraverser::PrintLoaderAllocator(size_t memberValue)
-{
- if (m_format == FORMAT_XML)
- {
- fprintf(m_file,
- " <loaderallocator address=\"0x%p\"/>\n",
- (PBYTE)memberValue);
- }
- else if (m_format == FORMAT_CLRPROFILER)
- {
- fprintf(m_file,
- " 0x%p",
- (PBYTE)memberValue);
- }
-}
-
-void HeapTraverser::PrintObjectMember(size_t memberValue, bool dependentHandle)
-{
- if (m_format==FORMAT_XML)
- {
- fprintf(m_file,
- " <member address=\"0x%p\"%s/>\n",
- (PBYTE)memberValue, dependentHandle ? " dependentHandle=\"1\"" : "");
- }
- else if (m_format==FORMAT_CLRPROFILER)
- {
- fprintf(m_file,
- " 0x%p",
- (PBYTE)memberValue);
- }
-}
-
-void HeapTraverser::PrintObjectTail()
-{
- if (m_format==FORMAT_XML)
- {
- fprintf(m_file,
- "</object>\n");
- }
- else if (m_format==FORMAT_CLRPROFILER)
- {
- fprintf(m_file,
- "\n");
- }
-}
-
-void HeapTraverser::PrintRootHead()
-{
- if (m_format==FORMAT_CLRPROFILER)
- {
- fprintf(m_file,
- "r ");
- }
-}
-
-void HeapTraverser::PrintRoot(LPCWSTR kind,size_t Value)
-{
- if (m_format==FORMAT_XML)
- {
- fprintf(m_file,
- "<root kind=\"%S\" address=\"0x%p\"/>\n",
- kind,
- (PBYTE)Value);
- }
- else if (m_format==FORMAT_CLRPROFILER)
- {
- fprintf(m_file,
- "0x%p ",
- (PBYTE)Value);
- }
-}
-
-void HeapTraverser::PrintRootTail()
-{
- if (m_format==FORMAT_CLRPROFILER)
- {
- fprintf(m_file,
- "\n");
- }
-}
-
-void HeapTraverser::PrintSection(int Type,BOOL bOpening)
-{
- const char *const pTypes[] = {"<gcheap>","<types>","<roots>","<objects>"};
- const char *const pTypeEnds[] = {"</gcheap>","</types>","</roots>","</objects>"};
-
- if (m_format==FORMAT_XML)
- {
- if ((Type >= 0) && (Type < TYPE_HIGHEST))
- {
- fprintf(m_file,"%s\n",bOpening ? pTypes[Type] : pTypeEnds[Type]);
- }
- else
- {
- ExtOut ("INVALID TYPE %d\n", Type);
- }
- }
- else if (m_format==FORMAT_CLRPROFILER)
- {
- if ((Type == TYPE_START) && !bOpening) // a final newline is needed
- {
- fprintf(m_file,"\n");
- }
- }
-}
-
-void HeapTraverser::FindGCRootOnStacks()
-{
- ArrayHolder<DWORD_PTR> threadList = NULL;
- int numThreads = 0;
-
- // GetThreadList calls ReportOOM so we don't need to do that here.
- HRESULT hr = GetThreadList(&threadList, &numThreads);
- if (FAILED(hr) || !threadList)
- {
- ExtOut("Failed to enumerate threads in the process.\n");
- return;
- }
-
- int total = 0;
- DacpThreadData vThread;
- for (int i = 0; i < numThreads; i++)
- {
- if (FAILED(vThread.Request(g_sos, threadList[i])))
- continue;
-
- if (vThread.osThreadId)
- {
- unsigned int refCount = 0;
- ArrayHolder<SOSStackRefData> refs = NULL;
-
- if (FAILED(::GetGCRefs(vThread.osThreadId, &refs, &refCount, NULL, NULL)))
- {
- ExtOut("Failed to walk thread %x\n", vThread.osThreadId);
- continue;
- }
-
- for (unsigned int i = 0; i < refCount; ++i)
- if (refs[i].Object)
- PrintRoot(W("stack"), TO_TADDR(refs[i].Object));
- }
- }
-
-}
-
-
-/* static */ void HeapTraverser::PrintOutTree(size_t methodTable, size_t ID,
- LPVOID token)
-{
- HeapTraverser *pHolder = (HeapTraverser *) token;
- NameForMT_s(methodTable, g_mdName, mdNameLen);
- pHolder->PrintType(ID,g_mdName);
-}
-
-
-/* static */ void HeapTraverser::PrintHeap(DWORD_PTR objAddr,size_t Size,
- DWORD_PTR methodTable, LPVOID token)
-{
- if (!IsMTForFreeObj (methodTable))
- {
- HeapTraverser *pHolder = (HeapTraverser *) token;
- pHolder->m_objVisited++;
- size_t ID = pHolder->getID(methodTable);
-
- pHolder->PrintObjectHead(objAddr, ID, Size);
- pHolder->PrintRefs(objAddr, methodTable, Size);
- pHolder->PrintObjectTail();
-
- if (pHolder->m_objVisited % 1024 == 0) {
- ExtOut(".");
- if (pHolder->m_objVisited % (1024*64) == 0)
- ExtOut("\r\n");
- }
- }
-}
-
-void HeapTraverser::TraceHandles()
-{
- unsigned int fetched = 0;
- SOSHandleData data[64];
-
- ToRelease<ISOSHandleEnum> handles;
- HRESULT hr = g_sos->GetHandleEnum(&handles);
- if (FAILED(hr))
- return;
-
- do
- {
- hr = handles->Next(ARRAY_SIZE(data), data, &fetched);
-
- if (FAILED(hr))
- break;
-
- for (unsigned int i = 0; i < fetched; ++i)
- PrintRoot(W("handle"), (size_t)data[i].Handle);
- } while (fetched == ARRAY_SIZE(data));
-}
-
-/* static */ void HeapTraverser::GatherTypes(DWORD_PTR objAddr,size_t Size,
- DWORD_PTR methodTable, LPVOID token)
-{
- if (!IsMTForFreeObj (methodTable))
- {
- HeapTraverser *pHolder = (HeapTraverser *) token;
- pHolder->insert(methodTable);
- }
-}
-
-void HeapTraverser::PrintRefs(size_t obj, size_t methodTable, size_t size)
-{
- DWORD_PTR dwAddr = methodTable;
-
- // TODO: pass info to callback having to lookup the MethodTableInfo again
- MethodTableInfo* info = g_special_mtCache.Lookup((DWORD_PTR)methodTable);
- _ASSERTE(info->IsInitialized()); // This is the second pass, so we should be initialized
-
- if (!info->bContainsPointers && !info->bCollectible)
- return;
-
- if (info->bContainsPointers)
- {
- // Fetch the GCInfo from the other process
- CGCDesc *map = info->GCInfo;
- if (map == NULL)
- {
- INT_PTR nEntries;
- move_xp (nEntries, dwAddr-sizeof(PVOID));
- bool arrayOfVC = false;
- if (nEntries<0)
- {
- arrayOfVC = true;
- nEntries = -nEntries;
- }
-
- size_t nSlots = 1+nEntries*sizeof(CGCDescSeries)/sizeof(DWORD_PTR);
- info->GCInfoBuffer = new DWORD_PTR[nSlots];
- if (info->GCInfoBuffer == NULL)
- {
- ReportOOM();
- return;
- }
-
- if (FAILED(rvCache->Read(TO_CDADDR(dwAddr - nSlots*sizeof(DWORD_PTR)),
- info->GCInfoBuffer, (ULONG) (nSlots*sizeof(DWORD_PTR)), NULL)))
- return;
-
- map = info->GCInfo = (CGCDesc*)(info->GCInfoBuffer+nSlots);
- info->ArrayOfVC = arrayOfVC;
- }
- }
-
- mCache.EnsureRangeInCache((TADDR)obj, (unsigned int)size);
- for (sos::RefIterator itr(obj, info->GCInfo, info->ArrayOfVC, &mCache); itr; ++itr)
- {
- if (*itr && (!m_verify || sos::IsObject(*itr)))
- {
- if (itr.IsLoaderAllocator())
- {
- PrintLoaderAllocator(*itr);
- }
- else
- {
- PrintObjectMember(*itr, false);
- }
- }
- }
-
- std::unordered_map<TADDR, std::list<TADDR>>::iterator itr = mDependentHandleMap.find((TADDR)obj);
- if (itr != mDependentHandleMap.end())
- {
- for (std::list<TADDR>::iterator litr = itr->second.begin(); litr != itr->second.end(); ++litr)
- {
- PrintObjectMember(*litr, true);
- }
- }
-}
-
void sos::ObjectIterator::BuildError(char *out, size_t count, const char *format, ...) const
{
if (out == NULL || count == 0)
int mMisses, mReads, mMisaligned;
};
-
-///////////////////////////////////////////////////////////////////////////////////////////
-//
-// Methods for creating a database out of the gc heap and it's roots in xml format or CLRProfiler format
-//
-
-#include <unordered_map>
-#include <unordered_set>
-#include <list>
-
-class TypeTree;
-enum { FORMAT_XML=0, FORMAT_CLRPROFILER=1 };
-enum { TYPE_START=0,TYPE_TYPES=1,TYPE_ROOTS=2,TYPE_OBJECTS=3,TYPE_HIGHEST=4};
-class HeapTraverser
-{
-private:
- TypeTree *m_pTypeTree;
- size_t m_curNID;
- FILE *m_file;
- int m_format; // from the enum above
- size_t m_objVisited; // for UI updates
- bool m_verify;
- LinearReadCache mCache;
-
- std::unordered_map<TADDR, std::list<TADDR>> mDependentHandleMap;
-
-public:
- HeapTraverser(bool verify);
- ~HeapTraverser();
-
- FILE *getFile() { return m_file; }
-
- BOOL Initialize();
- BOOL CreateReport (FILE *fp, int format);
-
-private:
- // First all types are added to a tree
- void insert(size_t mTable);
- size_t getID(size_t mTable);
-
- // Functions for writing to the output file.
- void PrintType(size_t ID,LPCWSTR name);
-
- void PrintObjectHead(size_t objAddr,size_t typeID,size_t Size);
- void PrintObjectMember(size_t memberValue, bool dependentHandle);
- void PrintLoaderAllocator(size_t memberValue);
- void PrintObjectTail();
-
- void PrintRootHead();
- void PrintRoot(LPCWSTR kind,size_t Value);
- void PrintRootTail();
-
- void PrintSection(int Type,BOOL bOpening);
-
- // Root and object member helper functions
- void FindGCRootOnStacks();
- void PrintRefs(size_t obj, size_t methodTable, size_t size);
-
- // Callback functions used during traversals
- static void GatherTypes(DWORD_PTR objAddr,size_t Size,DWORD_PTR methodTable, LPVOID token);
- static void PrintHeap(DWORD_PTR objAddr,size_t Size,DWORD_PTR methodTable, LPVOID token);
- static void PrintOutTree(size_t methodTable, size_t ID, LPVOID token);
- void TraceHandles();
-};
-
-//
-// Helper class used for type-safe bitflags
-// T - the enum type specifying the individual bit flags
-// U - the underlying/storage type
-// Requirement:
-// sizeof(T) <= sizeof(U)
-//
-template <typename T, typename U>
-struct Flags
-{
- typedef T UnderlyingType;
- typedef U BitFlagEnumType;
-
- static_assert_no_msg(sizeof(BitFlagEnumType) <= sizeof(UnderlyingType));
-
- Flags(UnderlyingType v)
- : m_val(v)
- { }
-
- Flags(BitFlagEnumType v)
- : m_val(v)
- { }
-
- Flags(const Flags& other)
- : m_val(other.m_val)
- { }
-
- Flags& operator = (const Flags& other)
- { m_val = other.m_val; return *this; }
-
- Flags operator | (Flags other) const
- { return Flags<T, U>(m_val | other._val); }
-
- void operator |= (Flags other)
- { m_val |= other.m_val; }
-
- Flags operator & (Flags other) const
- { return Flags<T, U>(m_val & other.m_val); }
-
- void operator &= (Flags other)
- { m_val &= other.m_val; }
-
- Flags operator ^ (Flags other) const
- { return Flags<T, U>(m_val ^ other._val); }
-
- void operator ^= (Flags other)
- { m_val ^= other.m_val; }
-
- BOOL operator == (Flags other) const
- { return m_val == other.m_val; }
-
- BOOL operator != (Flags other) const
- { return m_val != other.m_val; }
-
-
-private:
- UnderlyingType m_val;
-};
-
// Helper class used in ClrStackFromPublicInterface() to keep track of explicit EE Frames
// (i.e., "internal frames") on the stack. Call Init() with the appropriate
// ICorDebugThread3, and this class will initialize itself with the set of internal