Reimplement !traverseheap (#3810)
authorLee Culver <leculver@microsoft.com>
Mon, 10 Apr 2023 17:22:32 +0000 (10:22 -0700)
committerGitHub <noreply@github.com>
Mon, 10 Apr 2023 17:22:32 +0000 (17:22 +0000)
* Add TraverseHeapCommand

* Remove HeapTraverser

* Remove TypeTree

* Remove Flags

src/Microsoft.Diagnostics.ExtensionCommands/TraverseHeapCommand.cs [new file with mode: 0644]
src/SOS/SOS.Hosting/Commands/SOSCommand.cs
src/SOS/Strike/gcroot.cpp
src/SOS/Strike/strike.cpp
src/SOS/Strike/util.h
src/SOS/lldbplugin/soscommand.cpp

diff --git a/src/Microsoft.Diagnostics.ExtensionCommands/TraverseHeapCommand.cs b/src/Microsoft.Diagnostics.ExtensionCommands/TraverseHeapCommand.cs
new file mode 100644 (file)
index 0000000..6464720
--- /dev/null
@@ -0,0 +1,163 @@
+// 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}";
+    }
+}
index 8a126eed95cd5ea896837307f053f4c392fdd806..355ccc311639a05a9ea3b65e27e82afa78503c06 100644 (file)
@@ -50,7 +50,6 @@ namespace SOS.Hosting
     [Command(Name = "syncblk",           DefaultOptions = "SyncBlk",             Help = "Displays the SyncBlock holder info.")]
     [Command(Name = "threadpool",        DefaultOptions = "ThreadPool",          Help = "Lists basic information about the thread pool.")]
     [Command(Name = "threadstate",       DefaultOptions = "ThreadState",         Help = "Pretty prints the meaning of a threads state.")]
-    [Command(Name = "traverseheap",      DefaultOptions = "TraverseHeap",        Help = "Writes out heap information to a file in a format understood by the CLR Profiler.")]
     [Command(Name = "verifyobj",         DefaultOptions = "VerifyObj",           Help = "Checks the object for signs of corruption.")]
     [Command(Name = "comstate",          DefaultOptions = "COMState",            Flags = CommandFlags.Windows, Help = "Lists the COM apartment model for each thread.")]
     [Command(Name = "dumprcw",           DefaultOptions = "DumpRCW",             Flags = CommandFlags.Windows, Help = "Displays information about a Runtime Callable Wrapper.")]
index d725339e5a8df2aedd0e1b996389dac35c6d5c16..34df3bc577d2791bfa48817aceb0ba43aa683ae4 100644 (file)
@@ -96,76 +96,8 @@ bool LinearReadCache::MoveToPage(TADDR addr, unsigned int size)
 }
 
 
-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;
@@ -610,573 +542,6 @@ BOOL VerifyObject(const GCHeapDetails &heap, DWORD_PTR objAddr, DWORD_PTR MTAddr
     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, "&", "&amp;");
-        replace(name, "\"", "&quot;");
-        replace(name, "'", "&apos;");
-        replace(name, "<", "&lt;");
-        replace(name, ">", "&gt;");
-
-        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)
index e1470e45b080a9307e5f4f358793e91adc08db61..08b39a1a23df8d4940fadd7618c1ba35fb86ffe4 100644 (file)
@@ -3647,80 +3647,9 @@ void PrintGCStat(HeapStat *inStat, const char* label=NULL)
 
 DECLARE_API(TraverseHeap)
 {
-    INIT_API();
+    INIT_API_EXT();
     MINIDUMP_NOT_SUPPORTED();
-    ONLY_SUPPORTED_ON_WINDOWS_TARGET();
-
-    BOOL bXmlFormat = FALSE;
-    BOOL bVerify = FALSE;
-    StringHolder Filename;
-
-    CMDOption option[] =
-    {   // name, vptr,        type, hasValue
-        {"-xml", &bXmlFormat, COBOOL, FALSE},
-        {"-verify", &bVerify, COBOOL, FALSE},
-    };
-    CMDValue arg[] =
-    {   // vptr, type
-        {&Filename.data, COSTRING},
-    };
-    size_t nArg;
-    if (!GetCMDOption(args, option, ARRAY_SIZE(option), arg, ARRAY_SIZE(arg), &nArg))
-    {
-        return Status;
-    }
-
-    if (nArg != 1)
-    {
-        ExtOut("usage: %straverseheap [-xml] filename\n", SOSPrefix);
-        return Status;
-    }
-
-    if (!g_snapshot.Build())
-    {
-        ExtOut("Unable to build snapshot of the garbage collector state\n");
-        return Status;
-    }
-
-    FILE* file = fopen(Filename.data, "w");
-    if (file == nullptr) {
-        ExtOut("Unable to open file %s (%d)\n", strerror(errno), errno);
-        return Status;
-    }
-
-    if (!bVerify)
-        ExtOut("Assuming a uncorrupted GC heap.  If this is a crash dump consider -verify option\n");
-
-    HeapTraverser traverser(bVerify != FALSE);
-
-    ExtOut("Writing %s format to file %s\n", bXmlFormat ? "Xml" : "CLRProfiler", Filename.data);
-    ExtOut("Gathering types...\n");
-
-    // TODO: there may be a canonical list of methodtables in the runtime that we can
-    // traverse instead of exploring the gc heap for that list. We could then simplify the
-    // tree structure to a sorted list of methodtables, and the index is the ID.
-
-    // TODO: "Traversing object members" code should be generalized and shared between
-    // gcroot and traverseheap. Also dumpheap can begin using GCHeapsTraverse.
-
-    if (!traverser.Initialize())
-    {
-        ExtOut("Error initializing heap traversal\n");
-        fclose(file);
-        return Status;
-    }
-
-    if (!traverser.CreateReport (file, bXmlFormat ? FORMAT_XML : FORMAT_CLRPROFILER))
-    {
-        ExtOut("Unable to write heap report\n");
-        fclose(file);
-        return Status;
-    }
-
-    fclose(file);
-    ExtOut("\nfile %s saved\n", Filename.data);
-
-    return Status;
+    return ExecuteCommand("traverseheap", args);
 }
 
 struct PrintRuntimeTypeArgs
index 74745734d26246df5893253f1a981711f5e0bc0d..6400aafa50b7dd7dd939abb26ab1d19869a5ee0b 100644 (file)
@@ -2891,130 +2891,6 @@ private:
     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
index 30164a4634680902ee126cec91346e77c5eb499d..6bd2fe04529edd58f51aeaef93a95e43b1637e19 100644 (file)
@@ -222,7 +222,6 @@ sosCommandInitialize(lldb::SBDebugger debugger)
     g_services->AddCommand("threadpool", new sosCommand("ThreadPool"), "Displays info about the runtime thread pool.");
     g_services->AddCommand("threadstate", new sosCommand("ThreadState"), "Pretty prints the meaning of a threads state.");
     g_services->AddCommand("token2ee", new sosCommand("token2ee"), "Displays the MethodTable structure and MethodDesc structure for the specified token and module.");
-    g_services->AddCommand("traverseheap", new sosCommand("TraverseHeap"), "Writes out heap information to a file in a format understood by the CLR Profiler.");
     g_services->AddCommand("verifyheap", new sosCommand("VerifyHeap"), "Checks the GC heap for signs of corruption.");
     g_services->AddCommand("verifyobj", new sosCommand("VerifyObj"), "Checks the object that is passed as an argument for signs of corruption.");
     return true;