From c8956caab6cb21adc0d7ed2b092dcc2e4f15fd90 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 29 May 2018 11:54:38 -0400 Subject: [PATCH] Add sos DumpAsync command Debugging async methods with sos can be time consuming and relies on knowing how to use dumpheap, dumpvc, gcroot, and other commands to get the desired information, while often digging through a myriad of objects on the heap to find the desired info. This commit adds a new DumpAsync command, which finds the async state machine objects on the GC heap and outputs relevant information about each, including the fields of its state machine, any registered continuation, and GC roots for the state machine object (as they often serve as a valid substitute for call stacks). Example program used as a test: ```C# using System.Threading.Tasks; class Program { static async Task Main() => await MethodA(); static async Task MethodA() => await MethodB(); static async Task MethodB() => await MethodC(); static async Task MethodC() => await MethodD(); static async Task MethodD() => await Task.Delay(int.MaxValue); } ``` and example command output: ``` 0:011> !DumpAsync -type MethodD Address MT Size Name 000001989f413de0 00007ff88c506ba8 112 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__4, test]] StateMachine: Program+d__4 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+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+
d__0, test]] -> 000001989f413f30 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__1, test]] -> 000001989f413ec0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__2, test]] -> 000001989f413e50 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__3, test]] -> 000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+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+d__4, test]] Found 1 state machines. ``` --- .../CompilerServices/AsyncMethodBuilder.cs | 6 +- .../src/System/Threading/Tasks/Task.cs | 2 +- src/ToolBox/SOS/Strike/apollososdocs.txt | 76 +++++++- src/ToolBox/SOS/Strike/sos.def | 2 + src/ToolBox/SOS/Strike/sos_unixexports.src | 1 + src/ToolBox/SOS/Strike/sosdocs.txt | 74 ++++++- src/ToolBox/SOS/Strike/sosdocsunix.txt | 68 ++++++- src/ToolBox/SOS/Strike/strike.cpp | 182 ++++++++++++++++++ src/ToolBox/SOS/Strike/util.cpp | 8 +- src/ToolBox/SOS/Strike/util.h | 2 +- src/ToolBox/SOS/lldbplugin/soscommand.cpp | 1 + 11 files changed, 390 insertions(+), 32 deletions(-) diff --git a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs index d411609e365f..ec8af8c086b9 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs @@ -498,7 +498,7 @@ private IAsyncStateMachineBox GetStateMachineBox( /// event about the state machine if it's being finalized without having been completed. /// /// Specifies the type of the state machine. - private sealed class DebugFinalizableAsyncStateMachineBox : + private sealed class DebugFinalizableAsyncStateMachineBox : // SOS DumpAsync command depends on this name AsyncStateMachineBox where TStateMachine : IAsyncStateMachine { @@ -517,7 +517,7 @@ private sealed class DebugFinalizableAsyncStateMachineBox : /// A strongly-typed box for Task-based async state machines. /// Specifies the type of the state machine. /// Specifies the type of the Task's result. - private class AsyncStateMachineBox : + private class AsyncStateMachineBox : // SOS DumpAsync command depends on this name Task, IAsyncStateMachineBox where TStateMachine : IAsyncStateMachine { @@ -527,7 +527,7 @@ private class AsyncStateMachineBox : /// A delegate to the method. private Action _moveNextAction; /// The state machine itself. - public TStateMachine StateMachine; // mutable struct; do not make this readonly + public TStateMachine StateMachine; // mutable struct; do not make this readonly. SOS DumpAsync command depends on this name. /// Captured ExecutionContext with which to invoke ; may be null. public ExecutionContext Context; diff --git a/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs b/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs index 4b98612ab0fc..b9455611c892 100644 --- a/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -193,7 +193,7 @@ public class Task : IThreadPoolWorkItem, IAsyncResult, IDisposable // Can be null, a single continuation, a list of continuations, or s_taskCompletionSentinel, // in that order. The logic arround this object assumes it will never regress to a previous state. - private volatile object m_continuationObject = null; + private volatile object m_continuationObject = null; // SOS DumpAsync command depends on this name // m_continuationObject is set to this when the task completes. private static readonly object s_taskCompletionSentinel = new object(); diff --git a/src/ToolBox/SOS/Strike/apollososdocs.txt b/src/ToolBox/SOS/Strike/apollososdocs.txt index 29dd8b62629e..34e7e4d84644 100644 --- a/src/ToolBox/SOS/Strike/apollososdocs.txt +++ b/src/ToolBox/SOS/Strike/apollososdocs.txt @@ -25,16 +25,16 @@ Object Inspection Examining code and stacks ----------------------------- ----------------------------- DumpObj (do) Threads DumpArray (da) ThreadState -DumpStackObjects (dso) IP2MD -DumpHeap U -DumpVC DumpStack -GCRoot EEStack -ObjSize CLRStack -FinalizeQueue GCInfo -PrintException (pe) EHInfo -TraverseHeap BPMD -Watch COMState - StopOnCatch +DumpAsync IP2MD +DumpStackObjects (dso) U +DumpHeap DumpStack +DumpVC EEStack +GCRoot CLRStack +ObjSize GCInfo +FinalizeQueue EHInfo +PrintException (pe) BPMD +TraverseHeap COMState +Watch StopOnCatch SuppressJitOptimization Examining CLR data structures Diagnostic Utilities @@ -340,6 +340,62 @@ The arguments in detail: 5b9a628c 4000002 4 System.Int32 instance 8 y 5b9a628c 4000003 8 System.Int32 instance 12 z +\\ + +COMMAND: dumpasync. +!DumpAsync [-mt ] + [-type ]] + +!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", "async ValueTask", and "async ValueTask". + +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 + #0 + 000001989f413de0 00007ff88c506ba8 112 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__4, test]] + StateMachine: Program+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+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+
d__0, test]] + -> 000001989f413f30 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__1, test]] + -> 000001989f413ec0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__2, test]] + -> 000001989f413e50 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__3, test]] + -> 000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+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+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. \\ diff --git a/src/ToolBox/SOS/Strike/sos.def b/src/ToolBox/SOS/Strike/sos.def index c8d08e73195a..9ef5d2e7ed44 100644 --- a/src/ToolBox/SOS/Strike/sos.def +++ b/src/ToolBox/SOS/Strike/sos.def @@ -15,6 +15,8 @@ EXPORTS dumparray=DumpArray DumpAssembly dumpassembly=DumpAssembly + DumpAsync + dumpasync=DumpAsync DumpClass dumpclass=DumpClass DumpDomain diff --git a/src/ToolBox/SOS/Strike/sos_unixexports.src b/src/ToolBox/SOS/Strike/sos_unixexports.src index a8cc71228b25..463c33697666 100644 --- a/src/ToolBox/SOS/Strike/sos_unixexports.src +++ b/src/ToolBox/SOS/Strike/sos_unixexports.src @@ -7,6 +7,7 @@ ClrStack CreateDump DumpArray DumpAssembly +DumpAsync DumpClass DumpDomain DumpGCData diff --git a/src/ToolBox/SOS/Strike/sosdocs.txt b/src/ToolBox/SOS/Strike/sosdocs.txt index 924d5f51b9fc..cbf7e073f277 100644 --- a/src/ToolBox/SOS/Strike/sosdocs.txt +++ b/src/ToolBox/SOS/Strike/sosdocs.txt @@ -25,15 +25,15 @@ Object Inspection Examining code and stacks ----------------------------- ----------------------------- DumpObj (do) Threads DumpArray (da) ThreadState -DumpStackObjects (dso) IP2MD -DumpHeap U -DumpVC DumpStack -GCRoot EEStack -ObjSize CLRStack -FinalizeQueue GCInfo -PrintException (pe) EHInfo -TraverseHeap BPMD - COMState +DumpAsync IP2MD +DumpStackObjects (dso) U +DumpHeap DumpStack +DumpVC EEStack +GCRoot CLRStack +ObjSize GCInfo +FinalizeQueue EHInfo +PrintException (pe) BPMD +TraverseHeap COMState Examining CLR data structures Diagnostic Utilities ----------------------------- ----------------------------- @@ -338,6 +338,62 @@ The arguments in detail: 5b9a628c 4000002 4 System.Int32 instance 8 y 5b9a628c 4000003 8 System.Int32 instance 12 z +\\ + +COMMAND: dumpasync. +!DumpAsync [-mt ] + [-type ]] + +!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", "async ValueTask", and "async ValueTask". + +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 + #0 + 000001989f413de0 00007ff88c506ba8 112 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__4, test]] + StateMachine: Program+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+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+
d__0, test]] + -> 000001989f413f30 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__1, test]] + -> 000001989f413ec0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__2, test]] + -> 000001989f413e50 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__3, test]] + -> 000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+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+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. \\ diff --git a/src/ToolBox/SOS/Strike/sosdocsunix.txt b/src/ToolBox/SOS/Strike/sosdocsunix.txt index d0f19e323571..574ff4853739 100644 --- a/src/ToolBox/SOS/Strike/sosdocsunix.txt +++ b/src/ToolBox/SOS/Strike/sosdocsunix.txt @@ -25,12 +25,12 @@ Object Inspection Examining code and stacks ----------------------------- ----------------------------- DumpObj (dumpobj) Threads (clrthreads) DumpArray ThreadState -DumpStackObjects (dso) IP2MD (ip2md) -DumpHeap (dumpheap) u (clru) -DumpVC DumpStack (dumpstack) -GCRoot (gcroot) EEStack (eestack) -PrintException (pe) ClrStack (clrstack) - GCInfo +DumpAsync (dumpasync) IP2MD (ip2md) +DumpStackObjects (dso) u (clru) +DumpHeap (dumpheap) DumpStack (dumpstack) +DumpVC EEStack (eestack) +GCRoot (gcroot) CLRStack (clrstack) +PrintException (pe) GCInfo EHInfo bpmd (bpmd) @@ -199,6 +199,62 @@ The arguments in detail: 5b9a628c 4000002 4 System.Int32 instance 8 y 5b9a628c 4000003 8 System.Int32 instance 12 z +\\ + +COMMAND: dumpasync. +!DumpAsync [-mt ] + [-type ]] + +!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", "async ValueTask", and "async ValueTask". + +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 + #0 + 000001989f413de0 00007ff88c506ba8 112 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__4, test]] + StateMachine: Program+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+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+
d__0, test]] + -> 000001989f413f30 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__1, test]] + -> 000001989f413ec0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__2, test]] + -> 000001989f413e50 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+d__3, test]] + -> 000001989f413de0 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Program+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+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. \\ diff --git a/src/ToolBox/SOS/Strike/strike.cpp b/src/ToolBox/SOS/Strike/strike.cpp index 5637432d6346..6f5c1618da71 100644 --- a/src/ToolBox/SOS/Strike/strike.cpp +++ b/src/ToolBox/SOS/Strike/strike.cpp @@ -4098,6 +4098,188 @@ class DumpHeapImpl #endif }; +/**********************************************************************\ +* Routine Description: * +* * +* This function dumps async state machines on GC heap, * +* displaying details about each async operation found. * +* (May not work if GC is in progress.) * +* * +\**********************************************************************/ +DECLARE_API(DumpAsync) +{ + INIT_API(); + MINIDUMP_NOT_SUPPORTED(); + if (!g_snapshot.Build()) + { + ExtOut("Unable to build snapshot of the garbage collector state\n"); + return E_FAIL; + } + + try + { + // Process command-line arguments. + size_t nArg = 0; + TADDR mt = NULL; + ArrayHolder ansiType = NULL; + ArrayHolder type = NULL; + BOOL dml = 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 +#ifndef FEATURE_PAL + { "/d", &dml, COBOOL, FALSE }, // Debugger Markup Language +#endif + }; + if (!GetCMDOption(args, option, _countof(option), NULL, 0, &nArg)) + { + sos::Throw("Usage: DumpAsync [-mt MethodTableAddr] [-type TypeName] [-waiting]"); + } + if (nArg != 0) + { + sos::Throw("Unexpected command-line arguments."); + } + if (ansiType != NULL) + { + if (mt != NULL) + { + sos::Throw("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); + + // Display a message if the heap isn't verified. + sos::GCHeap gcheap; + if (!gcheap.AreGCStructuresValid()) + { + 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. + int numStateMachines = 0; + 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()) + { + continue; + } + + // Match only type name substrings the user requested. + if (type != NULL && _wcsstr(itr->GetTypeName(), type) == NULL) + { + 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. + { + continue; + } + + // Get the async state machine object's StateMachine field. If we can't, it's not + // an async state machine we can handle. + DacpFieldDescData stateMachineField; + int stateMachineFieldOffset = GetObjFieldOffset(TO_CDADDR(itr->GetAddress()), itr->GetMT(), W("StateMachine"), TRUE, &stateMachineField); + if (stateMachineFieldOffset <= 0) + { + continue; + } + + // 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 accomodate both. + BOOL bStateMachineIsValueType = stateMachineField.Type == ELEMENT_TYPE_VALUETYPE; + CLRDATA_ADDRESS stateMachineAddr; + CLRDATA_ADDRESS stateMachineMT; + if (bStateMachineIsValueType) + { + stateMachineAddr = itr->GetAddress() + stateMachineFieldOffset; + stateMachineMT = stateMachineField.MTOfType; + } + else + { + MOVE(stateMachineAddr, itr->GetAddress() + stateMachineFieldOffset); + DacpObjectData objData; + if (objData.Request(g_sos, stateMachineAddr) != S_OK) + { + // Couldn't get the class-based object; just skip this state machine. + continue; + } + stateMachineMT = objData.MethodTable; // update from Canon to actual type + } + + // We now have a state machine that's passed all of our criteria. Print out its details. + + // 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()); + + // Output the state machine's name and fields. + DacpMethodTableData mtabledata; + DacpMethodTableFieldData vMethodTableFields; + if (mtabledata.Request(g_sos, stateMachineMT) == S_OK && + vMethodTableFields.Request(g_sos, 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); + } + + // 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) + { + DWORD_PTR ContObjPtr; + MOVE(ContObjPtr, itr->GetAddress() + iContOffset); + DMLOut("Continuation: %s", DMLObject(ContObjPtr)); + if (sos::IsObject(ContObjPtr, false)) + { + sos::Object contObj = ContObjPtr; + ExtOut(" (%S)", contObj.GetTypeName()); + } + 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. + ExtOut("GC roots:\n"); + IncrementIndent(); + GCRootImpl gcroot; + gcroot.PrintRootsForObject(*itr, FALSE, FALSE); + DecrementIndent(); + + ExtOut("\n"); + } + + ExtOut("\nFound %d state machines.\n", numStateMachines); + return S_OK; + } + catch (const sos::Exception &e) + { + ExtOut("%s\n", e.what()); + return E_FAIL; + } +} + /**********************************************************************\ * Routine Description: * * * diff --git a/src/ToolBox/SOS/Strike/util.cpp b/src/ToolBox/SOS/Strike/util.cpp index 937d9a34f063..c7d78d26b482 100644 --- a/src/ToolBox/SOS/Strike/util.cpp +++ b/src/ToolBox/SOS/Strike/util.cpp @@ -1772,7 +1772,7 @@ int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, __in_z LPCWSTR wszFieldName, BOOL // 0 = field not found, // > 0 = offset to field from objAddr int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, - BOOL bFirst/*=TRUE*/) + BOOL bFirst/*=TRUE*/, DacpFieldDescData* pDacpFieldDescData/*=NULL*/) { #define EXITPOINT(EXPR) do { if(!(EXPR)) { return -1; } } while (0) @@ -1795,7 +1795,7 @@ int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCW if (dmtd.ParentMethodTable) { DWORD retVal = GetObjFieldOffset (cdaObj, dmtd.ParentMethodTable, - wszFieldName, FALSE); + wszFieldName, FALSE, pDacpFieldDescData); if (retVal != 0) { // return in case of error or success. @@ -1820,6 +1820,10 @@ int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCW NameForToken_s (TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); if (_wcscmp (wszFieldName, g_mdName) == 0) { + if (pDacpFieldDescData != NULL) + { + *pDacpFieldDescData = vFieldDesc; + } return offset; } numInstanceFields ++; diff --git a/src/ToolBox/SOS/Strike/util.h b/src/ToolBox/SOS/Strike/util.h index 0b0b34480f22..fa26c3faa2d4 100644 --- a/src/ToolBox/SOS/Strike/util.h +++ b/src/ToolBox/SOS/Strike/util.h @@ -1387,7 +1387,7 @@ const char *ElementTypeName (unsigned type); void DisplayFields (CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethodTableFieldData *pMTFD, DWORD_PTR dwStartAddr = 0, BOOL bFirst=TRUE, BOOL bValueClass=FALSE); int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, __in_z LPCWSTR wszFieldName, BOOL bFirst=TRUE); -int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, BOOL bFirst=TRUE); +int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, BOOL bFirst=TRUE, DacpFieldDescData* pDacpFieldDescData=NULL); BOOL IsValidToken(DWORD_PTR ModuleAddr, mdTypeDef mb); void NameForToken_s(DacpModuleData *pModule, mdTypeDef mb, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName, diff --git a/src/ToolBox/SOS/lldbplugin/soscommand.cpp b/src/ToolBox/SOS/lldbplugin/soscommand.cpp index 64d198cb5f63..bf2912249f7f 100644 --- a/src/ToolBox/SOS/lldbplugin/soscommand.cpp +++ b/src/ToolBox/SOS/lldbplugin/soscommand.cpp @@ -127,6 +127,7 @@ sosCommandInitialize(lldb::SBDebugger debugger) interpreter.AddCommand("clrthreads", new sosCommand("Threads"), "List the managed threads running."); interpreter.AddCommand("createdump", new sosCommand("CreateDump"), "Create a xplat minidump."); 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("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.");