Add DumpDelegate sos command (#20591)
authorStephen Toub <stoub@microsoft.com>
Thu, 25 Oct 2018 20:01:56 +0000 (13:01 -0700)
committerGitHub <noreply@github.com>
Thu, 25 Oct 2018 20:01:56 +0000 (13:01 -0700)
* Add DumpDelegate sos command

Finding out what method a delegate points to today with SOS is tedious.  This PR adds a DumpDelegate command that simplifies it: give it the delegate address, and it outputs a line for each delegate that makes up the input delegate, where each line includes the target object, the method descriptor, and a friendly name.

* Address PR feedback

And fix some warnings that were showing up in CI but not locally.

src/ToolBox/SOS/Strike/apollososdocs.txt
src/ToolBox/SOS/Strike/sos.def
src/ToolBox/SOS/Strike/sos_unixexports.src
src/ToolBox/SOS/Strike/sosdocs.txt
src/ToolBox/SOS/Strike/sosdocsunix.txt
src/ToolBox/SOS/Strike/strike.cpp
src/ToolBox/SOS/Strike/util.cpp
src/ToolBox/SOS/Strike/util.h
src/ToolBox/SOS/lldbplugin/soscommand.cpp

index 6af5f32..5b3c352 100644 (file)
@@ -26,16 +26,17 @@ Object Inspection                  Examining code and stacks
 DumpObj (do)                       Threads
 DumpArray (da)                     ThreadState
 DumpAsync                          IP2MD
-DumpStackObjects (dso)             U
-DumpHeap                           DumpStack
-DumpVC                             EEStack
-GCRoot                             CLRStack
-ObjSize                            GCInfo
-FinalizeQueue                      EHInfo
-PrintException (pe)                BPMD
-TraverseHeap                       COMState 
-Watch                              StopOnCatch
-                                   SuppressJitOptimization
+DumpDelegate                       U
+DumpStackObjects (dso)             DumpStack
+DumpHeap                           EEStack
+DumpVC                             CLRStack
+GCRoot                             GCInfo
+ObjSize                            EHInfo
+FinalizeQueue                      BPMD
+PrintException (pe)                COMState 
+TraverseHeap                       StopOnCatch
+Watch                              SuppressJitOptimization
+                                   
 
 Examining CLR data structures      Diagnostic Utilities
 -----------------------------      -----------------------------
@@ -419,6 +420,19 @@ interested in objects with invalid fields.
 The abbreviation !dso can be used for brevity.
 \\
 
+COMMAND: dumpdelegate.
+!DumpDelegate <delegate address>
+
+!DumpDelegate finds and outputs the one or more method descriptors associated with a delegate object.
+
+For example:
+
+       0:000> !dumpdelegate
+    Target           Method           Name
+    000001461bacb0d8 00007ffc5c894b80 ConsoleApp16.Program.InstanceMethod()
+    000001461bacb098 00007ffc5c894b68 ConsoleApp16.Program.StaticMethod()
+\\
+
 COMMAND: dumpheap.
 !DumpHeap [-stat] 
           [-strings] 
index 9ef5d2e..0c47279 100644 (file)
@@ -19,6 +19,8 @@ EXPORTS
     dumpasync=DumpAsync
     DumpClass
     dumpclass=DumpClass
+    DumpDelegate
+    dumpdelegate=DumpDelegate
     DumpDomain
     dumpdomain=DumpDomain
 #ifdef TRACE_GC
index 463c336..be8ad96 100644 (file)
@@ -9,6 +9,7 @@ DumpArray
 DumpAssembly
 DumpAsync
 DumpClass
+DumpDelegate
 DumpDomain
 DumpGCData
 DumpHeap
index 93c50ff..8b77865 100644 (file)
@@ -26,14 +26,15 @@ Object Inspection                  Examining code and stacks
 DumpObj (do)                       Threads
 DumpArray (da)                     ThreadState
 DumpAsync                          IP2MD
-DumpStackObjects (dso)             U
-DumpHeap                           DumpStack
-DumpVC                             EEStack
-GCRoot                             CLRStack
-ObjSize                            GCInfo
-FinalizeQueue                      EHInfo
-PrintException (pe)                BPMD
-TraverseHeap                       COMState
+DumpDelegate                       U
+DumpStackObjects (dso)             DumpStack
+DumpHeap                           EEStack
+DumpVC                             CLRStack
+GCRoot                             GCInfo
+ObjSize                            EHInfo
+FinalizeQueue                      BPMD
+PrintException (pe)                COMState
+TraverseHeap                       
 
 Examining CLR data structures      Diagnostic Utilities
 -----------------------------      -----------------------------
@@ -417,6 +418,19 @@ interested in objects with invalid fields.
 The abbreviation !dso can be used for brevity.
 \\
 
+COMMAND: dumpdelegate.
+!DumpDelegate <delegate address>
+
+!DumpDelegate finds and outputs the one or more method descriptors associated with a delegate object.
+
+For example:
+
+       0:000> !dumpdelegate
+    Target           Method           Name
+    000001461bacb0d8 00007ffc5c894b80 ConsoleApp16.Program.InstanceMethod()
+    000001461bacb098 00007ffc5c894b68 ConsoleApp16.Program.StaticMethod()
+\\
+
 COMMAND: dumpheap.
 !DumpHeap [-stat] 
           [-strings] 
index 1cc92fd..45e4c43 100644 (file)
@@ -26,13 +26,14 @@ Object Inspection                  Examining code and stacks
 DumpObj (dumpobj)                  Threads (clrthreads)
 DumpArray                          ThreadState
 DumpAsync (dumpasync)              IP2MD (ip2md)
-DumpStackObjects (dso)             u (clru)
-DumpHeap (dumpheap)                DumpStack (dumpstack)
-DumpVC                             EEStack (eestack)
-GCRoot (gcroot)                    CLRStack (clrstack)
-PrintException (pe)                GCInfo
-                                   EHInfo
+DumpDelegate (dumpdelegate)        u (clru)
+DumpStackObjects (dso)             DumpStack (dumpstack)
+DumpHeap (dumpheap)                EEStack (eestack)
+DumpVC                             CLRStack (clrstack)
+GCRoot (gcroot)                    GCInfo
+PrintException (pe)                EHInfo
                                    bpmd (bpmd)
+                                   
 
 Examining CLR data structures      Diagnostic Utilities
 -----------------------------      -----------------------------
@@ -278,6 +279,19 @@ interested in objects with invalid fields.
 The abbreviation dso can be used for brevity.
 \\
 
+COMMAND: dumpdelegate.
+!DumpDelegate <delegate address>
+
+!DumpDelegate finds and outputs the one or more method descriptors associated with a delegate object.
+
+For example:
+
+       0:000> !dumpdelegate
+    Target           Method           Name
+    000001461bacb0d8 00007ffc5c894b80 ConsoleApp16.Program.InstanceMethod()
+    000001461bacb098 00007ffc5c894b68 ConsoleApp16.Program.StaticMethod()
+\\
+
 COMMAND: dumpheap.
 DumpHeap [-stat] 
          [-strings] 
index 65e2aaf..b150848 100644 (file)
 
 #include <set>
 #include <algorithm>
-#include <vector>
 
 #include "tls.h"
 
@@ -147,6 +146,8 @@ const PROCESSINFOCLASS ProcessVmCounters = static_cast<PROCESSINFOCLASS>(3);
 
 #endif // !FEATURE_PAL
 
+#include <vector>
+
 BOOL CallStatus;
 BOOL ControlC = FALSE;
 
@@ -2057,6 +2058,134 @@ DECLARE_API(DumpObj)
     return Status;
 }
 
+/**********************************************************************\
+* Routine Description:                                                 *
+*                                                                      *
+*    This function is called to dump the contents of a delegate from a *
+*    given address.                                                    *
+*                                                                      *
+\**********************************************************************/
+
+DECLARE_API(DumpDelegate)
+{
+    INIT_API();
+    MINIDUMP_NOT_SUPPORTED();
+
+    try
+    {
+        BOOL dml = FALSE;
+        DWORD_PTR dwAddr = 0;
+
+        CMDOption option[] =
+        {   // name, vptr, type, hasValue
+            {"/d", &dml, COBOOL, FALSE}
+        };
+        CMDValue arg[] =
+        {   // vptr, type
+            {&dwAddr, COHEX}
+        };
+        size_t nArg;
+        if (!GetCMDOption(args, option, _countof(option), arg, _countof(arg), &nArg))
+        {
+            return Status;
+        }
+        if (nArg != 1)
+        {
+            ExtOut("Usage: !DumpDelegate <delegate object address>\n");
+            return Status;
+        }
+
+        EnableDMLHolder dmlHolder(dml);
+        CLRDATA_ADDRESS delegateAddr = TO_CDADDR(dwAddr);
+
+        if (!sos::IsObject(delegateAddr))
+        {
+            ExtOut("Invalid object.\n");
+        }
+        else
+        {
+            sos::Object delegateObj = TO_TADDR(delegateAddr);
+            if (!IsDerivedFrom(TO_CDADDR(delegateObj.GetMT()), W("System.Delegate")))
+            {
+                ExtOut("Object of type '%S' is not a delegate.", delegateObj.GetTypeName());
+            }
+            else
+            {
+                ExtOut("Target           Method           Name\n");
+
+                std::vector<CLRDATA_ADDRESS> delegatesRemaining;
+                delegatesRemaining.push_back(delegateAddr);
+                while (delegatesRemaining.size() > 0)
+                {
+                    delegateAddr = delegatesRemaining.back();
+                    delegatesRemaining.pop_back();
+                    delegateObj = TO_TADDR(delegateAddr);
+
+                    int offset;
+                    if ((offset = GetObjFieldOffset(delegateObj.GetAddress(), delegateObj.GetMT(), W("_target"))) != 0)
+                    {
+                        CLRDATA_ADDRESS target;
+                        MOVE(target, delegateObj.GetAddress() + offset);
+
+                        if ((offset = GetObjFieldOffset(delegateObj.GetAddress(), delegateObj.GetMT(), W("_invocationList"))) != 0)
+                        {
+                            CLRDATA_ADDRESS invocationList;
+                            MOVE(invocationList, delegateObj.GetAddress() + offset);
+
+                            if ((offset = GetObjFieldOffset(delegateObj.GetAddress(), delegateObj.GetMT(), W("_invocationCount"))) != 0)
+                            {
+                                int invocationCount;
+                                MOVE(invocationCount, delegateObj.GetAddress() + offset);
+
+                                if (invocationList == NULL)
+                                {
+                                    CLRDATA_ADDRESS md;
+                                    DMLOut("%s ", DMLObject(target));
+                                    if (TryGetMethodDescriptorForDelegate(delegateAddr, &md))
+                                    {
+                                        DMLOut("%s ", DMLMethodDesc(md));
+                                        NameForMD_s((DWORD_PTR)md, g_mdName, mdNameLen);
+                                        ExtOut("%S\n", g_mdName);
+                                    }
+                                    else
+                                    {
+                                        ExtOut("(unknown)\n");
+                                    }
+                                }
+                                else if (sos::IsObject(invocationList, false))
+                                {
+                                    DacpObjectData objData;
+                                    if (objData.Request(g_sos, invocationList) == S_OK &&
+                                        objData.ObjectType == OBJ_ARRAY &&
+                                        invocationCount <= objData.dwNumComponents)
+                                    {
+                                        for (int i = 0; i < invocationCount; i++)
+                                        {
+                                            CLRDATA_ADDRESS elementPtr;
+                                            MOVE(elementPtr, TO_CDADDR(objData.ArrayDataPtr + (i * objData.dwComponentSize)));
+                                            if (elementPtr != NULL && sos::IsObject(elementPtr, false))
+                                            {
+                                                delegatesRemaining.push_back(elementPtr);
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return S_OK;
+    }
+    catch (const sos::Exception &e)
+    {
+        ExtOut("%s\n", e.what());
+        return E_FAIL;
+    }
+}
+
 CLRDATA_ADDRESS isExceptionObj(CLRDATA_ADDRESS mtObj)
 {
     // We want to follow back until we get the mt for System.Exception
@@ -10511,27 +10640,6 @@ DECLARE_API(GCHandles)
     return Status;
 }
 
-BOOL derivedFrom(CLRDATA_ADDRESS mtObj, __in_z LPWSTR baseString)
-{
-    // We want to follow back until we get the mt for System.Exception
-    DacpMethodTableData dmtd;
-    CLRDATA_ADDRESS walkMT = mtObj;
-    while(walkMT != NULL)
-    {
-        if (dmtd.Request(g_sos, walkMT) != S_OK)
-        {
-            break;            
-        }
-        NameForMT_s (TO_TADDR(walkMT), g_mdName, mdNameLen);                
-        if (_wcscmp (baseString, g_mdName) == 0)
-        {
-            return TRUE;
-        }
-        walkMT = dmtd.ParentMethodTable;
-    }
-    return FALSE;
-}
-
 // This is an experimental and undocumented SOS API that attempts to step through code
 // stopping once jitted code is reached. It currently has some issues - it can take arbitrarily long
 // to reach jitted code and canceling it is terrible. At best it doesn't cancel, at worst it
@@ -10849,7 +10957,7 @@ DECLARE_API(StopOnException)
         {            
             NameForMT_s (taMT, g_mdName, mdNameLen);
             if ((_wcscmp(g_mdName,typeNameWide) == 0) ||
-                (fDerived && derivedFrom(taMT, typeNameWide)))
+                (fDerived && IsDerivedFrom(taMT, typeNameWide)))
             {
                 sprintf_s(buffer,_countof (buffer),
                     "r$t%d=1",
index 9e124c4..27e8665 100644 (file)
@@ -2468,6 +2468,62 @@ BOOL IsStringObject (size_t obj)
     return FALSE;
 }
 
+BOOL IsDerivedFrom(CLRDATA_ADDRESS mtObj, __in_z LPCWSTR baseString)
+{
+    DacpMethodTableData dmtd;
+    CLRDATA_ADDRESS walkMT = mtObj;
+    while (walkMT != NULL)
+    {
+        if (dmtd.Request(g_sos, walkMT) != S_OK)
+        {
+            break;
+        }
+
+        NameForMT_s(TO_TADDR(walkMT), g_mdName, mdNameLen);
+        if (_wcscmp(baseString, g_mdName) == 0)
+        {
+            return TRUE;
+        }
+
+        walkMT = dmtd.ParentMethodTable;
+    }
+
+    return FALSE;
+}
+
+BOOL TryGetMethodDescriptorForDelegate(CLRDATA_ADDRESS delegateAddr, CLRDATA_ADDRESS* pMD)
+{
+    if (!sos::IsObject(delegateAddr, false))
+    {
+        return FALSE;
+    }
+
+    sos::Object delegateObj = TO_TADDR(delegateAddr);
+    int offset;
+
+    if ((offset = GetObjFieldOffset(delegateObj.GetAddress(), delegateObj.GetMT(), W("_methodPtrAux"))) != 0)
+    {
+        CLRDATA_ADDRESS methodPtrAux;
+        MOVE(methodPtrAux, delegateObj.GetAddress() + offset);
+        if (methodPtrAux != NULL && g_sos->GetMethodDescPtrFromIP(methodPtrAux, pMD) == S_OK)
+        {
+            return TRUE;
+        }
+    }
+
+    if ((offset = GetObjFieldOffset(delegateObj.GetAddress(), delegateObj.GetMT(), W("_methodPtr"))) != 0)
+    {
+        CLRDATA_ADDRESS methodPtr;
+        MOVE(methodPtr, delegateObj.GetAddress() + offset);
+        if (methodPtr != NULL && g_sos->GetMethodDescPtrFromIP(methodPtr, pMD) == S_OK)
+        {
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
 void DumpStackObjectsOutput(const char *location, DWORD_PTR objAddr, BOOL verifyFields)
 {
     // rule out pointers that are outside of the gc heap.
index 1a4cd12..a519559 100644 (file)
@@ -1846,6 +1846,8 @@ BOOL IsMethodTable (DWORD_PTR value);
 BOOL IsStringObject (size_t obj);
 BOOL IsObjectArray (DWORD_PTR objPointer);
 BOOL IsObjectArray (DacpObjectData *pData);
+BOOL IsDerivedFrom(CLRDATA_ADDRESS mtObj, __in_z LPCWSTR baseString);
+BOOL TryGetMethodDescriptorForDelegate(CLRDATA_ADDRESS delegateAddr, CLRDATA_ADDRESS* pMD);
 
 /* Returns a list of all modules in the process.
  * Params:
index bf29122..0a54f63 100644 (file)
@@ -129,6 +129,7 @@ sosCommandInitialize(lldb::SBDebugger debugger)
     interpreter.AddCommand("clru", new sosCommand("u"), "Displays an annotated disassembly of a managed method.");
     interpreter.AddCommand("dumpasync", new sosCommand("DumpAsync"), "Displays info about async state machines on the garbage-collected heap.");
     interpreter.AddCommand("dumpclass", new sosCommand("DumpClass"), "Displays information about a EE class structure at the specified address.");
+    interpreter.AddCommand("dumpdelegate", new sosCommand("DumpDelegate"), "Displays information about a delegate.");
     interpreter.AddCommand("dumpheap", new sosCommand("DumpHeap"), "Displays info about the garbage-collected heap and collection statistics about objects.");
     interpreter.AddCommand("dumpil", new sosCommand("DumpIL"), "Displays the Microsoft intermediate language (MSIL) that is associated with a managed method.");
     interpreter.AddCommand("dumplog", new sosCommand("DumpLog"), "Writes the contents of an in-memory stress log to the specified file.");