Improve DumpAsync SOS command (#20845)
authorStephen Toub <stoub@microsoft.com>
Wed, 7 Nov 2018 20:14:00 +0000 (15:14 -0500)
committerGitHub <noreply@github.com>
Wed, 7 Nov 2018 20:14:00 +0000 (15:14 -0500)
* Help DumpDelegate to dump more cases

* Improve DumpAsync command

- Add a stats summary at the beginning of the output
- Improved single-line-per-entry default behavior for increased readability
- Add option to include all tasks, not just async state machine objects
- Include state value for each async object, the state field for async state machines or the state flags for other tasks
- Support following continuation chains, rendered as "async stacks"
- Support resolving delegate names so that Task.Run and related items have the associated method name displayed
- Optional (experimental) DGML rendering of the graph
- Optionally include completed tasks, by default filtering them out
- Optionally display the fields of state machines

* Decode task state flags

* Add addr option and associated DumpAsync DML

* Fix cast warnings in checked / fprintf_s on Unix

* Remove DGML from DumpAsync

Not enough value right now, and complications in compilation on Unix given sos's current set up.  Not worth it.

src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs
src/ToolBox/SOS/Strike/apollososdocs.txt
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

index 2adef9c..f78dcd0 100644 (file)
@@ -153,7 +153,7 @@ namespace System.Threading.Tasks
         internal object m_stateObject; // A state object that can be optionally supplied, passed to action.
         internal TaskScheduler m_taskScheduler; // The task scheduler this task runs under. 
 
-        internal volatile int m_stateFlags;
+        internal volatile int m_stateFlags; // SOS DumpAsync command depends on this name
 
         private Task ParentForDebugger => m_contingentProperties?.m_parent; // Private property used by a debugger to access this Task's parent
         private int StateFlagsForDebugger => m_stateFlags; // Private property used by a debugger to access this Task's state flags
@@ -185,7 +185,8 @@ namespace System.Threading.Tasks
         internal const int TASK_STATE_EXECUTIONCONTEXT_IS_NULL = 0x20000000;                   //bin: 0010 0000 0000 0000 0000 0000 0000 0000
         internal const int TASK_STATE_TASKSCHEDULED_WAS_FIRED = 0x40000000;                    //bin: 0100 0000 0000 0000 0000 0000 0000 0000
 
-        // A mask for all of the final states a task may be in
+        // A mask for all of the final states a task may be in.
+        // SOS DumpAsync command depends on these values.
         private const int TASK_STATE_COMPLETED_MASK = TASK_STATE_CANCELED | TASK_STATE_FAULTED | TASK_STATE_RAN_TO_COMPLETION;
 
         // Values for ContingentProperties.m_internalCancellationRequested.
index 5b3c352..6ed1dcc 100644 (file)
@@ -344,64 +344,20 @@ The arguments in detail:
 \\
 
 COMMAND: dumpasync.
-!DumpAsync [-mt <MethodTable address>] 
+!DumpAsync [-addr <Object Address>]
+           [-mt <MethodTable address>]
            [-type <partial type name>]
-           [-waiting]
-           [-roots]]
+           [-tasks]
+           [-completed]
+           [-fields]
+           [-stacks]
+           [-roots]
 
 !DumpAsync traverses the garbage collected heap, looking for objects representing
 async state machines as created when an async method's state is transferred to the
 heap.  This command recognizes async state machines defined as "async void", "async Task",
-"async Task<T>", "async ValueTask", and "async ValueTask<T>".
-
-The output includes a block of details for each async state machine object found.
-These details include:
-    - a line for the type of the async state machine object, including its MethodTable address,
-      its object address, its size, and its type name.
-    - a line for the state machine type name as contained in the object.
-    - a listing of each field on the state machine.
-    - a line for a continuation from this state machine object, if one or more has been registered.
-    - discovered GC roots for this async state machine object.
-  
-For example:
-
-    0:011> !DumpAsync -roots
-    #0
-    000001989f413de0 00007ff88c506ba8      112  System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
-    StateMachine: Program+<MethodD>d__4 (struct)
-                  MT    Field   Offset                 Type VT     Attr            Value Name
-    00007ff8d3df4b80  400000d        0         System.Int32  1 instance                0 <>1__state
-    00007ff8d3e082c0  400000e        8 ...TaskMethodBuilder  1 instance 000001989f413e38 <>t__builder
-    00007ff8d3dfea90  400000f       10 ...vices.TaskAwaiter  1 instance 000001989f413e40 <>u__1
-    Continuation: 000001989f413e50 (System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodC>d__3, test]])
-    GC roots:
-        Thread 2936c:
-            000000071a37e050 00007ff8d3ac1657 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [d:\repos\coreclr\src\System.Private.CoreLib\src\System\Threading\Tasks\Task.cs @ 2977]
-                rbp+10: 000000071a37e0c0
-                    ->  000001989f413fa0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<Main>d__0, test]]
-                    ->  000001989f413f30 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodA>d__1, test]]
-                    ->  000001989f413ec0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodB>d__2, test]]
-                    ->  000001989f413e50 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodC>d__3, test]]
-                    ->  000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
-        HandleTable:
-            000001989d8415f8 (pinned handle)
-            -> 00000198af3e1038 System.Object[]
-            -> 000001989f413410 System.Threading.TimerQueue[]
-            -> 000001989f413468 System.Threading.TimerQueue
-            -> 000001989f413330 System.Threading.TimerQueueTimer
-            -> 000001989f412e40 System.Threading.Tasks.Task+DelayPromise
-            -> 000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
-    ...
-
-
-The arguments in detail:
-
--mt       List only those state machine objects with the MethodTable given.
--type     List only those state machine objects whose type name is a
-          substring match of the string provided.
--waiting  List only those state machines that are currently at an await point.
--roots    Include GC root information for each state machine object.
-
+"async Task<T>", "async ValueTask", and "async ValueTask<T>".  It also optionally supports
+any other tasks.
 \\
 
 COMMAND: dumpstackobjects.
index 8b77865..cf3f079 100644 (file)
@@ -342,64 +342,20 @@ The arguments in detail:
 \\
 
 COMMAND: dumpasync.
-!DumpAsync [-mt <MethodTable address>] 
+!DumpAsync [-addr <Object Address>]
+           [-mt <MethodTable address>]
            [-type <partial type name>]
-           [-waiting]
-           [-roots]]
+           [-tasks]
+           [-completed]
+           [-fields]
+           [-stacks]
+           [-roots]
 
 !DumpAsync traverses the garbage collected heap, looking for objects representing
 async state machines as created when an async method's state is transferred to the
 heap.  This command recognizes async state machines defined as "async void", "async Task",
-"async Task<T>", "async ValueTask", and "async ValueTask<T>".
-
-The output includes a block of details for each async state machine object found.
-These details include:
-    - a line for the type of the async state machine object, including its MethodTable address,
-      its object address, its size, and its type name.
-    - a line for the state machine type name as contained in the object.
-    - a listing of each field on the state machine.
-    - a line for a continuation from this state machine object, if one or more has been registered.
-    - discovered GC roots for this async state machine object.
-  
-For example:
-
-    0:011> !DumpAsync -roots
-    #0
-    000001989f413de0 00007ff88c506ba8      112  System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
-    StateMachine: Program+<MethodD>d__4 (struct)
-                  MT    Field   Offset                 Type VT     Attr            Value Name
-    00007ff8d3df4b80  400000d        0         System.Int32  1 instance                0 <>1__state
-    00007ff8d3e082c0  400000e        8 ...TaskMethodBuilder  1 instance 000001989f413e38 <>t__builder
-    00007ff8d3dfea90  400000f       10 ...vices.TaskAwaiter  1 instance 000001989f413e40 <>u__1
-    Continuation: 000001989f413e50 (System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodC>d__3, test]])
-    GC roots:
-        Thread 2936c:
-            000000071a37e050 00007ff8d3ac1657 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [d:\repos\coreclr\src\System.Private.CoreLib\src\System\Threading\Tasks\Task.cs @ 2977]
-                rbp+10: 000000071a37e0c0
-                    ->  000001989f413fa0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<Main>d__0, test]]
-                    ->  000001989f413f30 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodA>d__1, test]]
-                    ->  000001989f413ec0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodB>d__2, test]]
-                    ->  000001989f413e50 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodC>d__3, test]]
-                    ->  000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
-        HandleTable:
-            000001989d8415f8 (pinned handle)
-            -> 00000198af3e1038 System.Object[]
-            -> 000001989f413410 System.Threading.TimerQueue[]
-            -> 000001989f413468 System.Threading.TimerQueue
-            -> 000001989f413330 System.Threading.TimerQueueTimer
-            -> 000001989f412e40 System.Threading.Tasks.Task+DelayPromise
-            -> 000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
-    ...
-
-
-The arguments in detail:
-
--mt       List only those state machine objects with the MethodTable given.
--type     List only those state machine objects whose type name is a
-          substring match of the string provided.
--waiting  List only those state machines that are currently at an await point.
--roots    Include GC root information for each state machine object.
-
+"async Task<T>", "async ValueTask", and "async ValueTask<T>".  It also optionally supports
+any other tasks.
 \\
 
 COMMAND: dumpstackobjects.
index 5cd3a87..d014df7 100644 (file)
@@ -203,64 +203,20 @@ The arguments in detail:
 \\
 
 COMMAND: dumpasync.
-!DumpAsync [-mt <MethodTable address>] 
+!DumpAsync [-addr <Object Address>]
+           [-mt <MethodTable address>]
            [-type <partial type name>]
-           [-waiting]
-           [-roots]]
+           [-tasks]
+           [-completed]
+           [-fields]
+           [-stacks]
+           [-roots]
 
 !DumpAsync traverses the garbage collected heap, looking for objects representing
 async state machines as created when an async method's state is transferred to the
 heap.  This command recognizes async state machines defined as "async void", "async Task",
-"async Task<T>", "async ValueTask", and "async ValueTask<T>".
-
-The output includes a block of details for each async state machine object found.
-These details include:
-    - a line for the type of the async state machine object, including its MethodTable address,
-      its object address, its size, and its type name.
-    - a line for the state machine type name as contained in the object.
-    - a listing of each field on the state machine.
-    - a line for a continuation from this state machine object, if one or more has been registered.
-    - discovered GC roots for this async state machine object.
-  
-For example:
-
-    (lldb) dumpasync -roots
-    #0
-    000001989f413de0 00007ff88c506ba8      112  System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
-    StateMachine: Program+<MethodD>d__4 (struct)
-                  MT    Field   Offset                 Type VT     Attr            Value Name
-    00007ff8d3df4b80  400000d        0         System.Int32  1 instance                0 <>1__state
-    00007ff8d3e082c0  400000e        8 ...TaskMethodBuilder  1 instance 000001989f413e38 <>t__builder
-    00007ff8d3dfea90  400000f       10 ...vices.TaskAwaiter  1 instance 000001989f413e40 <>u__1
-    Continuation: 000001989f413e50 (System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodC>d__3, test]])
-    GC roots:
-        Thread 2936c:
-            000000071a37e050 00007ff8d3ac1657 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) [d:\repos\coreclr\src\System.Private.CoreLib\src\System\Threading\Tasks\Task.cs @ 2977]
-                rbp+10: 000000071a37e0c0
-                    ->  000001989f413fa0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<Main>d__0, test]]
-                    ->  000001989f413f30 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodA>d__1, test]]
-                    ->  000001989f413ec0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodB>d__2, test]]
-                    ->  000001989f413e50 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodC>d__3, test]]
-                    ->  000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
-        HandleTable:
-            000001989d8415f8 (pinned handle)
-            -> 00000198af3e1038 System.Object[]
-            -> 000001989f413410 System.Threading.TimerQueue[]
-            -> 000001989f413468 System.Threading.TimerQueue
-            -> 000001989f413330 System.Threading.TimerQueueTimer
-            -> 000001989f412e40 System.Threading.Tasks.Task+DelayPromise
-            -> 000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+<MethodD>d__4, test]]
-    ...
-
-
-The arguments in detail:
-
--mt       List only those state machine objects with the MethodTable given.
--type     List only those state machine objects whose type name is a
-          substring match of the string provided.
--waiting  List only those state machines that are currently at an await point.
--roots    Include GC root information for each state machine object.
-
+"async Task<T>", "async ValueTask", and "async ValueTask<T>".  It also optionally supports
+any other tasks.
 \\
 
 COMMAND: dumpstackobjects.
index d7794f4..8ff0c0d 100644 (file)
 #include "ExpressionNode.h"
 #include "WatchCmd.h"
 
-#include <set>
 #include <algorithm>
 
 #include "tls.h"
@@ -146,7 +145,9 @@ const PROCESSINFOCLASS ProcessVmCounters = static_cast<PROCESSINFOCLASS>(3);
 
 #endif // !FEATURE_PAL
 
+#include <set>
 #include <vector>
+#include <map>
 
 BOOL CallStatus;
 BOOL ControlC = FALSE;
@@ -156,7 +157,6 @@ WCHAR g_mdName[mdNameLen];
 
 #ifndef FEATURE_PAL
 HMODULE g_hInstance = NULL;
-#include <vector>
 #include <algorithm>
 #endif // !FEATURE_PAL
 
@@ -4234,11 +4234,169 @@ private:
 /**********************************************************************\
 * Routine Description:                                                 *
 *                                                                      *
-*    This function dumps async state machines on GC heap,          *
+*    This function dumps async state machines on GC heap,              *
 *    displaying details about each async operation found.              *
 *    (May not work if GC is in progress.)                              *
 *                                                                      *
 \**********************************************************************/
+
+void ResolveContinuation(CLRDATA_ADDRESS* contAddr)
+{
+    // Ideally this continuation is itself an async method box.
+    sos::Object contObj = TO_TADDR(*contAddr);
+    if (GetObjFieldOffset(contObj.GetAddress(), contObj.GetMT(), W("StateMachine")) == 0)
+    {
+        // It was something else.
+
+        // If it's a standard task continuation, get its task field.
+        int offset;
+        if ((offset = GetObjFieldOffset(contObj.GetAddress(), contObj.GetMT(), W("m_task"))) != 0)
+        {
+            MOVE(*contAddr, contObj.GetAddress() + offset);
+            if (sos::IsObject(*contAddr, false))
+            {
+                contObj = TO_TADDR(*contAddr);
+            }
+        }
+        else
+        {
+            // If it's storing an action wrapper, try to follow to that action's target.
+            if ((offset = GetObjFieldOffset(contObj.GetAddress(), contObj.GetMT(), W("m_action"))) != 0)
+            {
+                MOVE(*contAddr, contObj.GetAddress() + offset);
+                if (sos::IsObject(*contAddr, false))
+                {
+                    contObj = TO_TADDR(*contAddr);
+                }
+            }
+
+            // If it was, or if it's storing an action, try to follow through to the action's target.
+            if ((offset = GetObjFieldOffset(contObj.GetAddress(), contObj.GetMT(), W("_target"))) != 0)
+            {
+                MOVE(*contAddr, contObj.GetAddress() + offset);
+                if (sos::IsObject(*contAddr, false))
+                {
+                    contObj = TO_TADDR(*contAddr);
+                }
+            }
+        }
+
+        // Use whatever object we ended with.
+        *contAddr = contObj.GetAddress();
+    }
+}
+
+bool TryGetContinuation(CLRDATA_ADDRESS addr, CLRDATA_ADDRESS mt, CLRDATA_ADDRESS* contAddr)
+{
+    // Get the continuation field from the task.
+    int offset = GetObjFieldOffset(addr, mt, W("m_continuationObject"));
+    if (offset != 0)
+    {
+        DWORD_PTR contObjPtr;
+        MOVE(contObjPtr, addr + offset);
+        if (sos::IsObject(contObjPtr, false))
+        {
+            *contAddr = TO_CDADDR(contObjPtr);
+            ResolveContinuation(contAddr);
+            return true;
+        }
+    }
+
+    return false;
+}
+
+struct AsyncRecord
+{
+    CLRDATA_ADDRESS Address;
+    CLRDATA_ADDRESS MT;
+    DWORD Size;
+    CLRDATA_ADDRESS StateMachineAddr;
+    CLRDATA_ADDRESS StateMachineMT;
+    BOOL FilteredByOptions;
+    BOOL IsStateMachine;
+    BOOL IsValueType;
+    BOOL IsTopLevel;
+    int TaskStateFlags;
+    int StateValue;
+    std::vector<CLRDATA_ADDRESS> Continuations;
+};
+
+bool AsyncRecordIsCompleted(AsyncRecord& ar)
+{
+    const int TASK_STATE_COMPLETED_MASK = 0x1600000;
+    return (ar.TaskStateFlags & TASK_STATE_COMPLETED_MASK) != 0;
+}
+
+const char* GetAsyncRecordStatusDescription(AsyncRecord& ar)
+{
+    const int TASK_STATE_RAN_TO_COMPLETION = 0x1000000;
+    const int TASK_STATE_FAULTED = 0x200000;
+    const int TASK_STATE_CANCELED = 0x400000;
+
+    if ((ar.TaskStateFlags & TASK_STATE_RAN_TO_COMPLETION) != 0) return "Success";
+    if ((ar.TaskStateFlags & TASK_STATE_FAULTED) != 0) return "Failed";
+    if ((ar.TaskStateFlags & TASK_STATE_CANCELED) != 0) return "Canceled";
+    return "Pending";
+}
+
+void ExtOutTaskDelegateMethod(sos::Object& obj)
+{
+    DacpFieldDescData actionField;
+    int offset = GetObjFieldOffset(obj.GetAddress(), obj.GetMT(), W("m_action"), TRUE, &actionField);
+    if (offset != 0)
+    {
+        CLRDATA_ADDRESS actionAddr;
+        MOVE(actionAddr, obj.GetAddress() + offset);
+        CLRDATA_ADDRESS actionMD;
+        if (actionAddr != NULL && TryGetMethodDescriptorForDelegate(actionAddr, &actionMD))
+        {
+            NameForMD_s((DWORD_PTR)actionMD, g_mdName, mdNameLen);
+            ExtOut("(%S) ", g_mdName);
+        }
+    }
+}
+
+void ExtOutTaskStateFlagsDescription(int stateFlags)
+{
+    if (stateFlags == 0) return;
+
+    ExtOut("State Flags: ");
+
+    // TaskCreationOptions.*
+    if ((stateFlags & 0x01) != 0) ExtOut("PreferFairness ");
+    if ((stateFlags & 0x02) != 0) ExtOut("LongRunning ");
+    if ((stateFlags & 0x04) != 0) ExtOut("AttachedToParent ");
+    if ((stateFlags & 0x08) != 0) ExtOut("DenyChildAttach ");
+    if ((stateFlags & 0x10) != 0) ExtOut("HideScheduler ");
+    if ((stateFlags & 0x40) != 0) ExtOut("RunContinuationsAsynchronously ");
+
+    // InternalTaskOptions.*
+    if ((stateFlags & 0x0200) != 0) ExtOut("ContinuationTask ");
+    if ((stateFlags & 0x0400) != 0) ExtOut("PromiseTask ");
+    if ((stateFlags & 0x1000) != 0) ExtOut("LazyCancellation ");
+    if ((stateFlags & 0x2000) != 0) ExtOut("QueuedByRuntime ");
+    if ((stateFlags & 0x4000) != 0) ExtOut("DoNotDispose ");
+
+    // TASK_STATE_*
+    if ((stateFlags & 0x10000) != 0) ExtOut("STARTED ");
+    if ((stateFlags & 0x20000) != 0) ExtOut("DELEGATE_INVOKED ");
+    if ((stateFlags & 0x40000) != 0) ExtOut("DISPOSED ");
+    if ((stateFlags & 0x80000) != 0) ExtOut("EXCEPTIONOBSERVEDBYPARENT ");
+    if ((stateFlags & 0x100000) != 0) ExtOut("CANCELLATIONACKNOWLEDGED ");
+    if ((stateFlags & 0x200000) != 0) ExtOut("FAULTED ");
+    if ((stateFlags & 0x400000) != 0) ExtOut("CANCELED ");
+    if ((stateFlags & 0x800000) != 0) ExtOut("WAITING_ON_CHILDREN ");
+    if ((stateFlags & 0x1000000) != 0) ExtOut("RAN_TO_COMPLETION ");
+    if ((stateFlags & 0x2000000) != 0) ExtOut("WAITINGFORACTIVATION ");
+    if ((stateFlags & 0x4000000) != 0) ExtOut("COMPLETION_RESERVED ");
+    if ((stateFlags & 0x8000000) != 0) ExtOut("THREAD_WAS_ABORTED ");
+    if ((stateFlags & 0x10000000) != 0) ExtOut("WAIT_COMPLETION_NOTIFICATION ");
+    if ((stateFlags & 0x20000000) != 0) ExtOut("EXECUTIONCONTEXT_IS_NULL ");
+    if ((stateFlags & 0x40000000) != 0) ExtOut("TASKSCHEDULED_WAS_FIRED ");
+
+    ExtOut("\n");
+}
+
 DECLARE_API(DumpAsync)
 {
     INIT_API();
@@ -4253,40 +4411,47 @@ DECLARE_API(DumpAsync)
     {
         // Process command-line arguments.
         size_t nArg = 0;
-        TADDR mt = NULL;
+        TADDR mt = NULL, addr = NULL;
         ArrayHolder<char> ansiType = NULL;
         ArrayHolder<WCHAR> type = NULL;
-        BOOL dml = FALSE, waiting = FALSE, roots = FALSE;
+        BOOL dml = FALSE, includeCompleted = FALSE, includeStacks = FALSE, includeRoots = FALSE, includeAllTasks = FALSE, dumpFields = FALSE;
         CMDOption option[] =
         {   // name, vptr, type, hasValue
-            { "-mt", &mt, COHEX, TRUE },             // dump state machines only with a given MethodTable
-            { "-type", &ansiType, COSTRING, TRUE },  // dump state machines only that contain the specified type substring
-            { "-waiting", &waiting, COBOOL, FALSE }, // dump state machines only when they're in a waiting state
-            { "-roots", &roots, COBOOL, FALSE },     // gather GC root information
+            { "-addr", &addr, COHEX, TRUE },                // dump only the async object at the specified address
+            { "-mt", &mt, COHEX, TRUE },                        // dump only async objects with a given MethodTable
+            { "-type", &ansiType, COSTRING, TRUE },             // dump only async objects that contain the specified type substring
+            { "-tasks", &includeAllTasks, COBOOL, FALSE },      // include all tasks that can be found on the heap, not just async methods
+            { "-completed", &includeCompleted, COBOOL, FALSE }, // include async objects that are in a completed state
+            { "-fields", &dumpFields, COBOOL, FALSE },          // show relevant fields of found async objects
+            { "-stacks", &includeStacks, COBOOL, FALSE },       // gather and output continuation/stack information
+            { "-roots", &includeRoots, COBOOL, FALSE },         // gather and output GC root information
 #ifndef FEATURE_PAL
-            { "/d", &dml, COBOOL, FALSE },            // Debugger Markup Language
+            { "/d", &dml, COBOOL, FALSE },                      // Debugger Markup Language
 #endif
         };
-        if (!GetCMDOption(args, option, _countof(option), NULL, 0, &nArg))
-        {
-            sos::Throw<sos::Exception>("Usage: DumpAsync [-mt MethodTableAddr] [-type TypeName] [-waiting] [-roots]");
-        }
-        if (nArg != 0)
-        {
-            sos::Throw<sos::Exception>("Unexpected command-line arguments.");
+        if (!GetCMDOption(args, option, _countof(option), NULL, 0, &nArg) || nArg != 0)
+        {
+            sos::Throw<sos::Exception>(
+                "Usage: DumpAsync [-addr ObjectAddr] [-mt MethodTableAddr] [-type TypeName] [-tasks] [-completed] [-fields] [-stacks] [-roots]\n"
+                "[-addr ObjectAddr]     => Only display the async object at the specified address.\n"
+                "[-mt MethodTableAddr]  => Only display top-level async objects with the specified method table address.\n"
+                "[-type TypeName]       => Only display top-level async objects whose type name includes the specified substring.\n"
+                "[-tasks]               => Include Task and Task-derived objects, in addition to any state machine objects found.\n"
+                "[-completed]           => Include async objects that represent completed operations but that are still on the heap.\n"
+                "[-fields]              => Show the fields of state machines.\n"
+                "[-stacks]              => Gather, output, and consolidate based on continuation chains / async stacks for discovered async objects.\n"
+                "[-roots]               => Perform a gcroot on each rendered async object.\n"
+                );
         }
         if (ansiType != NULL)
         {
-            if (mt != NULL)
-            {
-                sos::Throw<sos::Exception>("Cannot specify both -mt and -type");
-            }
-
             size_t ansiTypeLen = strlen(ansiType) + 1;
             type = new WCHAR[ansiTypeLen];
             MultiByteToWideChar(CP_ACP, 0, ansiType, -1, type, (int)ansiTypeLen);
         }
+        
         EnableDMLHolder dmlHolder(dml);
+        BOOL hasTypeFilter = mt != NULL || ansiType != NULL || addr != NULL;
 
         // Display a message if the heap isn't verified.
         sos::GCHeap gcheap;
@@ -4295,160 +4460,321 @@ DECLARE_API(DumpAsync)
             DisplayInvalidStructuresMessage();
         }
 
-        // Print out header for the main line of each async state machine object.
-        ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %8s %s\n", "Address", "MT", "Size", "Name");
-
-        // Walk each heap object looking for async state machine objects.
-        BOOL missingStateFieldWarning = FALSE;
-        int numStateMachines = 0;
+        // Walk each heap object looking for async state machine objects.  As we're targeting .NET Core 2.1+, all such objects
+        // will be Task or Task-derived types.
+        std::map<CLRDATA_ADDRESS, AsyncRecord> asyncRecords;
         for (sos::ObjectIterator itr = gcheap.WalkHeap(); !IsInterrupt() && itr != NULL; ++itr)
         {
-            // Skip objects we know to be too small to possibly be a state machine.
-            // This helps filter out some caching data structures generated by the compiler.
-            if (itr->GetSize() <= 24)
-            {
-                continue;
-            }
-
-            // Match only MTs the user requested.
-            if (mt != NULL && mt != itr->GetMT())
+            // Skip objects too small to be state machines or tasks, avoiding some compiler-generated caching data structures.
+            if (itr->GetSize() <= 24) 
             {
                 continue;
             }
 
-            // Match only type name substrings the user requested.
-            if (type != NULL && _wcsstr(itr->GetTypeName(), type) == NULL)
+            // Match only async objects.
+            if (includeAllTasks)
             {
-                continue;
+                // If the user has selected to include all tasks and not just async state machine boxes, we simply need to validate
+                // that this is Task or Task-derived, and if it's not, skip it.
+                if (!IsDerivedFrom(itr->GetMT(), W("System.Threading.Tasks.Task")))
+                {
+                    continue;
+                }
             }
-
-            // Match only the two known state machine class name prefixes.
-            if (_wcsncmp(itr->GetTypeName(), W("System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1"), 79) != 0 &&               // Normal box.
-                _wcsncmp(itr->GetTypeName(), W("System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+DebugFinalizableAsyncStateMachineBox`1"), 95) != 0) // Used when certain ETW events enabled.
+            else
             {
-                continue;
+                // Otherwise, we only care about AsyncStateMachineBox`1 as well as the DebugFinalizableAsyncStateMachineBox`1
+                // that's used when certain ETW events are set.
+                if (_wcsncmp(itr->GetTypeName(), W("System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1"), 79) != 0 &&
+                    _wcsncmp(itr->GetTypeName(), W("System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+DebugFinalizableAsyncStateMachineBox`1"), 95) != 0)
+                {
+                    continue;
+                }
             }
 
-            // Get the async state machine object's StateMachine field.  If we can't, it's not
-            // an async state machine we can handle.
+            // Create an AsyncRecord to store the state for this instance.  We're likely going to keep the object at this point,
+            // though we may still discard/skip it with a few checks later; to do that, though, we'll need some of the info
+            // gathered here, so we construct the record to store the data.
+            AsyncRecord ar;
+            ar.Address = itr->GetAddress();
+            ar.MT = itr->GetMT();
+            ar.Size = (DWORD)itr->GetSize();
+            ar.StateMachineAddr = itr->GetAddress();
+            ar.StateMachineMT = itr->GetMT();
+            ar.IsValueType = false;
+            ar.IsTopLevel = true;
+            ar.IsStateMachine = false;
+            ar.TaskStateFlags = 0;
+            ar.StateValue = 0;
+            ar.FilteredByOptions = // we process all objects to support forming proper chains, but then only display ones that match the user's request
+                (mt == NULL || mt == itr->GetMT()) && // Match only MTs the user requested.
+                (type == NULL || _wcsstr(itr->GetTypeName(), type) != NULL) && // Match only type name substrings the user requested.
+                (addr == NULL || addr == itr->GetAddress()); // Match only the object at the specified address.
+
+            // Get the state flags for the task.  This is used to determine whether async objects are completed (and thus should
+            // be culled by default).  It avoids our needing to depend on interpreting the compiler's "<>1__state" field, and also lets
+            // us display state information for non-async state machine objects.
+            DacpFieldDescData stateFlagsField;
+            int offset = GetObjFieldOffset(ar.Address, ar.MT, W("m_stateFlags"), TRUE, &stateFlagsField);
+            if (offset != 0)
+            {
+                sos::Object obj = TO_TADDR(ar.Address);
+                MOVE(ar.TaskStateFlags, obj.GetAddress() + offset);
+            }
+
+            // Get the async state machine object's StateMachine field.
             DacpFieldDescData stateMachineField;
             int stateMachineFieldOffset = GetObjFieldOffset(TO_CDADDR(itr->GetAddress()), itr->GetMT(), W("StateMachine"), TRUE, &stateMachineField);
-            if (stateMachineFieldOffset <= 0)
+            if (stateMachineFieldOffset != 0)
             {
-                continue;
+                ar.IsStateMachine = true;
+                ar.IsValueType = stateMachineField.Type == ELEMENT_TYPE_VALUETYPE;
+
+                // Get the address and method table of the state machine.  While it'll generally be a struct, it is valid for it to be a
+                // class (the C# compiler generates a class in debug builds to better support Edit-And-Continue), so we accommodate both.
+                DacpFieldDescData stateField;
+                int stateFieldOffset = -1;
+                if (ar.IsValueType)
+                {
+                    ar.StateMachineAddr = itr->GetAddress() + stateMachineFieldOffset;
+                    ar.StateMachineMT = stateMachineField.MTOfType;
+                    stateFieldOffset = GetValueFieldOffset(ar.StateMachineMT, W("<>1__state"), &stateField);
+                }
+                else
+                {
+                    MOVE(ar.StateMachineAddr, itr->GetAddress() + stateMachineFieldOffset);
+                    DacpObjectData objData;
+                    if (objData.Request(g_sos, ar.StateMachineAddr) == S_OK)
+                    {
+                        ar.StateMachineMT = objData.MethodTable; // update from Canon to actual type
+                        stateFieldOffset = GetObjFieldOffset(ar.StateMachineAddr, ar.StateMachineMT, W("<>1__state"), TRUE, &stateField);
+                    }
+                }
+
+                if (stateFieldOffset >= 0 && (ar.IsValueType || stateFieldOffset != 0))
+                {
+                    MOVE(ar.StateValue, ar.StateMachineAddr + stateFieldOffset);
+                }
             }
 
-            // Get the address and method table of the state machine.  While it'll generally be a struct,
-            // it is valid for it to be a class, so we accommodate both.
-            BOOL bStateMachineIsValueType = stateMachineField.Type == ELEMENT_TYPE_VALUETYPE;
-            CLRDATA_ADDRESS stateMachineAddr;
-            CLRDATA_ADDRESS stateMachineMT;
-            if (bStateMachineIsValueType)
+            // If we only want to include incomplete async objects, skip this one if it's completed.
+            if (!includeCompleted && AsyncRecordIsCompleted(ar))
             {
-                stateMachineAddr = itr->GetAddress() + stateMachineFieldOffset;
-                stateMachineMT = stateMachineField.MTOfType;
+                continue;
             }
-            else
+
+            // If the user has asked to include "async stacks" information, resolve any continuation
+            // that might be registered with it.  This could be a single continuation, or it could
+            // be a list of continuations in the case of the same task being awaited multiple times.
+            CLRDATA_ADDRESS nextAddr;
+            if (includeStacks && TryGetContinuation(itr->GetAddress(), itr->GetMT(), &nextAddr))
             {
-                MOVE(stateMachineAddr, itr->GetAddress() + stateMachineFieldOffset);
-                DacpObjectData objData;
-                if (objData.Request(g_sos, stateMachineAddr) != S_OK)
+                sos::Object contObj = TO_TADDR(nextAddr);
+                if (_wcsncmp(contObj.GetTypeName(), W("System.Collections.Generic.List`1"), 33) == 0)
                 {
-                    // Couldn't get the class-based object; just skip this state machine.
-                    continue;
+                    // The continuation is a List<object>.  Iterate through its internal object[]
+                    // looking for non-null objects, and adding each one as a continuation.
+                    int itemsOffset = GetObjFieldOffset(contObj.GetAddress(), contObj.GetMT(), W("_items"));
+                    if (itemsOffset != 0)
+                    {
+                        DWORD_PTR listItemsPtr;
+                        MOVE(listItemsPtr, contObj.GetAddress() + itemsOffset);
+                        if (sos::IsObject(listItemsPtr, false))
+                        {
+                            DacpObjectData objData;
+                            if (objData.Request(g_sos, TO_CDADDR(listItemsPtr)) == S_OK && objData.ObjectType == OBJ_ARRAY)
+                            {
+                                for (int i = 0; i < objData.dwNumComponents; i++)
+                                {
+                                    CLRDATA_ADDRESS elementPtr;
+                                    MOVE(elementPtr, TO_CDADDR(objData.ArrayDataPtr + (i * objData.dwComponentSize)));
+                                    if (elementPtr != NULL && sos::IsObject(elementPtr, false))
+                                    {
+                                        ResolveContinuation(&elementPtr);
+                                        ar.Continuations.push_back(elementPtr);
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    ar.Continuations.push_back(contObj.GetAddress());
                 }
-                stateMachineMT = objData.MethodTable; // update from Canon to actual type
             }
 
-            // Get the current state value of the state machine. If the user has requested to filter down
-            // to only those state machines that are currently at an await, compare it against the expected
-            // waiting values.  This value can also be used in later analysis.
-            int stateValue = -2;
-            DacpFieldDescData stateField;
-            int stateFieldOffset = bStateMachineIsValueType ?
-                GetValueFieldOffset(stateMachineMT, W("<>1__state"), &stateField) :
-                GetObjFieldOffset(stateMachineAddr, stateMachineMT, W("<>1__state"), TRUE, &stateField);
-            if (stateFieldOffset < 0 || (!bStateMachineIsValueType && stateFieldOffset == 0))
+            // We've gathered all of the needed information for this heap object.  Add it to our list of async records.
+            asyncRecords.insert(std::pair<CLRDATA_ADDRESS, AsyncRecord>(ar.Address, ar));
+        }
+
+        // As with DumpHeap, output a summary table about all of the objects we found.  In contrast, though, his is based on the filtered
+        // list of async records we gathered rather than everything on the heap.
+        if (addr == NULL) // no point in stats if we're only targeting a single object
+        {
+            HeapStat stats;
+            for (std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt)
             {
-                missingStateFieldWarning = TRUE;
-                if (waiting)
+                if (!hasTypeFilter || arIt->second.FilteredByOptions)
                 {
-                    // waiting was specified and we couldn't find the field to satisfy the query,
-                    // so skip this object.
-                    continue;
+                    stats.Add((DWORD_PTR)arIt->second.MT, (DWORD)arIt->second.Size);
                 }
             }
-            else
+            stats.Sort();
+            stats.Print();
+        }
+
+        // If the user has asked for "async stacks" and if there's not MT/type name filter, look through all of our async records
+        // to find the "top-level" nodes that start rather than that are a part of a continuation chain.  When we then iterate through
+        // async records, we only print ones out that are still classified as top-level.  We don't do this if there's a type filter
+        // because in that case we consider those and only those objects to be top-level.
+        if (includeStacks && !hasTypeFilter)
+        {
+            size_t uniqueChains = asyncRecords.size();
+            for (std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt)
             {
-                MOVE(stateValue, stateMachineAddr + stateFieldOffset);
-                if (waiting && stateValue < 0)
+                for (std::vector<CLRDATA_ADDRESS>::iterator contIt = arIt->second.Continuations.begin(); contIt != arIt->second.Continuations.end(); ++contIt)
                 {
-                    // 0+ values correspond to the await in linear sequence in the method, so a non-negative
-                    // value indicates the state machine is at an await.  Since we're filtering for waiting,
-                    // anything else should be skipped.
-                    continue;
+                    std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator found = asyncRecords.find(*contIt);
+                    if (found != asyncRecords.end())
+                    {
+                        if (found->second.IsTopLevel)
+                        {
+                            found->second.IsTopLevel = false;
+                            uniqueChains--;
+                        }
+                    }
                 }
             }
 
-            // We now have a state machine that's passed all of our criteria.  Print out its details.
+            ExtOut("In %d chains.\n", uniqueChains);
+        }
 
-            // Print out top level description of the state machine object.
-            ExtOut("#%d\n", numStateMachines);
-            numStateMachines++;
-            DMLOut("%s %s %8d", DMLObject(itr->GetAddress()), DMLDumpHeapMT(itr->GetMT()), itr->GetSize());
-            ExtOut("  %S\n", itr->GetTypeName());
+        // Print out header for the main line of each result.
+        ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %8s ", "Address", "MT", "Size");
+        if (includeCompleted) ExtOut("%8s ", "Status");
+        ExtOut("%10s %s\n", "State", "Description");
 
-            // Output the state machine's name and fields.
+        // Output each top-level async record.
+        int counter = 0;
+        for (std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt)
+        {
+            if (!arIt->second.IsTopLevel || (hasTypeFilter && !arIt->second.FilteredByOptions))
+            {
+                continue;
+            }
+
+            // Output the state machine's details as a single line.
+            sos::Object obj = TO_TADDR(arIt->second.Address);
             DacpMethodTableData mtabledata;
             DacpMethodTableFieldData vMethodTableFields;
-            if (mtabledata.Request(g_sos, stateMachineMT) == S_OK &&
-                vMethodTableFields.Request(g_sos, stateMachineMT) == S_OK &&
+            if (arIt->second.IsStateMachine &&
+                mtabledata.Request(g_sos, arIt->second.StateMachineMT) == S_OK &&
+                vMethodTableFields.Request(g_sos, arIt->second.StateMachineMT) == S_OK &&
                 vMethodTableFields.wNumInstanceFields + vMethodTableFields.wNumStaticFields > 0)
             {
-                sos::MethodTable mt = (TADDR)stateMachineMT;
-                ExtOut("StateMachine: %S (%s)\n", mt.GetName(), bStateMachineIsValueType ? "struct" : "class");
-                DisplayFields(stateMachineMT, &mtabledata, &vMethodTableFields, (DWORD_PTR)stateMachineAddr, TRUE, bStateMachineIsValueType);
+                // This has a StateMachine.  Output its details.
+                sos::MethodTable mt = TO_TADDR(arIt->second.StateMachineMT);
+                DMLOut("%s %s %8d ", DMLAsync(obj.GetAddress()), DMLDumpHeapMT(obj.GetMT()), obj.GetSize());
+                if (includeCompleted) ExtOut("%8s ", GetAsyncRecordStatusDescription(arIt->second));
+                ExtOut("%10d %S\n", arIt->second.StateValue, mt.GetName());
+                if (dumpFields) DisplayFields(arIt->second.StateMachineMT, &mtabledata, &vMethodTableFields, (DWORD_PTR)arIt->second.StateMachineAddr, TRUE, arIt->second.IsValueType);
+            }
+            else
+            {
+                // This does not have a StateMachine.  Output the details of the Task itself.
+                DMLOut("%s %s %8d ", DMLAsync(obj.GetAddress()), DMLDumpHeapMT(obj.GetMT()), obj.GetSize());
+                if (includeCompleted) ExtOut("%8s ", GetAsyncRecordStatusDescription(arIt->second));
+                ExtOut("[%08x] %S ", arIt->second.TaskStateFlags, obj.GetTypeName());
+                ExtOutTaskDelegateMethod(obj);
+                ExtOut("\n");
+                if (dumpFields) ExtOutTaskStateFlagsDescription(arIt->second.TaskStateFlags);
             }
 
-            // If the object already has a registered continuation, output it.
-            int iContOffset = GetObjFieldOffset(TO_CDADDR(itr->GetAddress()), itr->GetMT(), W("m_continuationObject"));
-            if (iContOffset > 0)
+            // If we gathered any continuations for this record, output the chains now.
+            if (includeStacks && arIt->second.Continuations.size() > 0)
             {
-                DWORD_PTR ContObjPtr;
-                MOVE(ContObjPtr, itr->GetAddress() + iContOffset);
-                DMLOut("Continuation: %s", DMLObject(ContObjPtr));
-                if (sos::IsObject(ContObjPtr, false))
+                ExtOut(includeAllTasks ? "Continuation chains:\n" : "Async \"stack\":\n");
+                std::vector<std::pair<int, CLRDATA_ADDRESS>> continuationChainToExplore;
+                continuationChainToExplore.push_back(std::pair<int, CLRDATA_ADDRESS>(1, obj.GetAddress()));
+
+                // Do a depth-first traversal of continuations, outputting each continuation found and then
+                // looking in our gathered objects list for its continuations.
+                std::set<CLRDATA_ADDRESS> seen;
+                while (continuationChainToExplore.size() > 0)
                 {
-                    sos::Object contObj = ContObjPtr;
-                    ExtOut(" (%S)", contObj.GetTypeName());
+                    // Pop the next continuation from the stack.
+                    std::pair<int, CLRDATA_ADDRESS> cur = continuationChainToExplore.back();
+                    continuationChainToExplore.pop_back();
+
+                    // Get the async record for this continuation.  It should be one we already know about.
+                    std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator curAsyncRecord = asyncRecords.find(cur.second);
+                    if (curAsyncRecord == asyncRecords.end())
+                    {
+                        continue;
+                    }
+
+                    // Make sure to avoid cycles in the rare case where async records may refer to each other.
+                    if (seen.find(cur.second) != seen.end())
+                    {
+                        continue;
+                    }
+                    seen.insert(cur.second);
+
+                    // Iterate through all continuations from this object.
+                    for (std::vector<CLRDATA_ADDRESS>::iterator contIt = curAsyncRecord->second.Continuations.begin(); contIt != curAsyncRecord->second.Continuations.end(); ++contIt)
+                    {
+                        sos::Object cont = TO_TADDR(*contIt);
+
+                        // Print out the depth of the continuation with dots, then its address.
+                        for (int i = 0; i < cur.first; i++) ExtOut(".");
+                        DMLOut("%s ", DMLObject(cont.GetAddress()));
+
+                        // Print out the name of the method for this task's delegate if it has one (state machines won't, but others tasks may).
+                        ExtOutTaskDelegateMethod(cont);
+
+                        // Find the async record for this continuation, and output its name.  If it's a state machine,
+                        // also output its current state value so that a user can see at a glance its status.
+                        std::map<CLRDATA_ADDRESS, AsyncRecord>::iterator contAsyncRecord = asyncRecords.find(cont.GetAddress());
+                        if (contAsyncRecord != asyncRecords.end())
+                        {
+                            sos::MethodTable contMT = TO_TADDR(contAsyncRecord->second.StateMachineMT);
+                            if (contAsyncRecord->second.IsStateMachine) ExtOut("(%d) ", contAsyncRecord->second.StateValue);
+                            ExtOut("%S\n", contMT.GetName());
+                        }
+                        else
+                        {
+                            ExtOut("%S\n", cont.GetTypeName());
+                        }
+
+                        // Add this continuation to the stack to explore.
+                        continuationChainToExplore.push_back(std::pair<int, CLRDATA_ADDRESS>(cur.first + 1, *contIt));
+                    }
                 }
-                ExtOut("\n");
             }
 
-            // Finally, output gcroots, as they can serve as call stacks, and also help to highlight
-            // state machines that aren't being kept alive.
-            if (roots)
+            // Finally, output gcroots, as they can serve as alternative/more detailed "async stacks", and also help to highlight
+            // state machines that aren't being kept alive.  However, they're more expensive to compute, so they're opt-in.
+            if (includeRoots)
             {
                 ExtOut("GC roots:\n");
                 IncrementIndent();
                 GCRootImpl gcroot;
-                int numRoots = gcroot.PrintRootsForObject(*itr, FALSE, FALSE);
+                int numRoots = gcroot.PrintRootsForObject(obj.GetAddress(), FALSE, FALSE);
                 DecrementIndent();
-
-                if (stateValue >= 0 && numRoots == 0)
+                if (numRoots == 0 && !AsyncRecordIsCompleted(arIt->second))
                 {
-                    ExtOut("Incomplete state machine (<>1__state == %d) with 0 roots.\n", stateValue);
+                    ExtOut("Incomplete state machine or task with 0 roots.\n");
                 }
             }
 
-            ExtOut("\n");
+            // If we're rendering more than one line per entry, output a separator to help distinguish the entries.
+            if (dumpFields || includeStacks || includeRoots)
+            {
+                ExtOut("--------------------------------------------------------------------------------\n");
+            }
         }
 
-        ExtOut("\nFound %d state machines.\n", numStateMachines);
-        if (missingStateFieldWarning)
-        {
-            ExtOut("Warning: Could not find a state machine's <>1__state field.\n");
-        }
         return S_OK;
     }
     catch (const sos::Exception &e)
index 27e8665..27f9c58 100644 (file)
@@ -2499,25 +2499,28 @@ BOOL TryGetMethodDescriptorForDelegate(CLRDATA_ADDRESS delegateAddr, CLRDATA_ADD
     }
 
     sos::Object delegateObj = TO_TADDR(delegateAddr);
-    int offset;
 
-    if ((offset = GetObjFieldOffset(delegateObj.GetAddress(), delegateObj.GetMT(), W("_methodPtrAux"))) != 0)
+    for (int i = 0; i < 2; i++)
     {
-        CLRDATA_ADDRESS methodPtrAux;
-        MOVE(methodPtrAux, delegateObj.GetAddress() + offset);
-        if (methodPtrAux != NULL && g_sos->GetMethodDescPtrFromIP(methodPtrAux, pMD) == S_OK)
+        int offset;
+        if ((offset = GetObjFieldOffset(delegateObj.GetAddress(), delegateObj.GetMT(), i == 0 ? W("_methodPtrAux") : W("_methodPtr"))) != 0)
         {
-            return TRUE;
-        }
-    }
+            CLRDATA_ADDRESS methodPtr;
+            MOVE(methodPtr, delegateObj.GetAddress() + offset);
+            if (methodPtr != NULL)
+            {
+                if (g_sos->GetMethodDescPtrFromIP(methodPtr, 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;
+                DacpCodeHeaderData codeHeaderData;
+                if (codeHeaderData.Request(g_sos, methodPtr) == S_OK)
+                {
+                    *pMD = codeHeaderData.MethodDescPtr;
+                    return TRUE;
+                }
+            }
         }
     }
 
@@ -5510,6 +5513,7 @@ const char * const DMLFormats[] =
     "<exec cmd=\"!DumpRCW /d %s\">%s</exec>",       // DML_RCWrapper
     "<exec cmd=\"!DumpCCW /d %s\">%s</exec>",       // DML_CCWrapper
     "<exec cmd=\"!ClrStack -i %S %d\">%S</exec>",   // DML_ManagedVar
+    "<exec cmd=\"!DumpAsync -addr %s -tasks -completed -fields -stacks -roots\">%s</exec>", // DML_Async
 };
 
 void ConvertToLower(__out_ecount(len) char *buffer, size_t len)
index a519559..88a5cc7 100644 (file)
@@ -356,6 +356,7 @@ namespace Output
         DML_RCWrapper,
         DML_CCWrapper,
         DML_ManagedVar,
+        DML_Async,
     };
 
     /**********************************************************************\
@@ -457,6 +458,7 @@ inline void ExtOutIndent()  { WhitespaceOut(Output::g_Indent << 2); }
 #define DMLRCWrapper(addr) Output::BuildHexValue(addr, Output::DML_RCWrapper).GetPtr()
 #define DMLCCWrapper(addr) Output::BuildHexValue(addr, Output::DML_CCWrapper).GetPtr()
 #define DMLManagedVar(expansionName,frame,simpleName) Output::BuildManagedVarValue(expansionName, frame, simpleName, Output::DML_ManagedVar).GetPtr()
+#define DMLAsync(addr) Output::BuildHexValue(addr, Output::DML_Async).GetPtr()
 
 bool IsDMLEnabled();