From fbb5a67dc2a1a1fa1d920f819b4d8650b6981675 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 5 Nov 2018 12:42:47 -0500 Subject: [PATCH 1/6] Help DumpDelegate to dump more cases --- src/ToolBox/SOS/Strike/util.cpp | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/ToolBox/SOS/Strike/util.cpp b/src/ToolBox/SOS/Strike/util.cpp index 27e866507916..33ce3a7fdc28 100644 --- a/src/ToolBox/SOS/Strike/util.cpp +++ b/src/ToolBox/SOS/Strike/util.cpp @@ -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; + } + } } } From 5859ba952bef7adff8b36765c44f103fc8e9cecf Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 5 Nov 2018 12:47:22 -0500 Subject: [PATCH 2/6] 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 --- .../src/System/Threading/Tasks/Task.cs | 5 +- src/ToolBox/SOS/Strike/apollososdocs.txt | 62 +- src/ToolBox/SOS/Strike/sosdocs.txt | 62 +- src/ToolBox/SOS/Strike/sosdocsunix.txt | 62 +- src/ToolBox/SOS/Strike/strike.cpp | 605 ++++++++++++++---- 5 files changed, 525 insertions(+), 271 deletions(-) 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 2adef9c92964..f78dcd0b1e64 100644 --- a/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs +++ b/src/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @@ -153,7 +153,7 @@ public class Task : IAsyncResult, IDisposable 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 @@ public class Task : IAsyncResult, IDisposable 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. diff --git a/src/ToolBox/SOS/Strike/apollososdocs.txt b/src/ToolBox/SOS/Strike/apollososdocs.txt index 5b3c35253819..f38bb16ffe9c 100644 --- a/src/ToolBox/SOS/Strike/apollososdocs.txt +++ b/src/ToolBox/SOS/Strike/apollososdocs.txt @@ -344,64 +344,20 @@ The arguments in detail: \\ COMMAND: dumpasync. -!DumpAsync [-mt ] +!DumpAsync [-mt ] [-type ] - [-waiting] - [-roots]] + [-tasks] + [-completed] + [-fields] + [-stacks] + [-roots] + [-dgmlPath ] !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 -roots - #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. --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", "async ValueTask", and "async ValueTask". It also optionally supports +any other tasks. \\ COMMAND: dumpstackobjects. diff --git a/src/ToolBox/SOS/Strike/sosdocs.txt b/src/ToolBox/SOS/Strike/sosdocs.txt index 8b778657b30b..9f8f8723ab80 100644 --- a/src/ToolBox/SOS/Strike/sosdocs.txt +++ b/src/ToolBox/SOS/Strike/sosdocs.txt @@ -342,64 +342,20 @@ The arguments in detail: \\ COMMAND: dumpasync. -!DumpAsync [-mt ] +!DumpAsync [-mt ] [-type ] - [-waiting] - [-roots]] + [-tasks] + [-completed] + [-fields] + [-stacks] + [-roots] + [-dgmlPath ] !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 -roots - #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. --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", "async ValueTask", and "async ValueTask". It also optionally supports +any other tasks. \\ COMMAND: dumpstackobjects. diff --git a/src/ToolBox/SOS/Strike/sosdocsunix.txt b/src/ToolBox/SOS/Strike/sosdocsunix.txt index 5cd3a87a7868..fe3d7d09302b 100644 --- a/src/ToolBox/SOS/Strike/sosdocsunix.txt +++ b/src/ToolBox/SOS/Strike/sosdocsunix.txt @@ -203,64 +203,20 @@ The arguments in detail: \\ COMMAND: dumpasync. -!DumpAsync [-mt ] +!DumpAsync [-mt ] [-type ] - [-waiting] - [-roots]] + [-tasks] + [-completed] + [-fields] + [-stacks] + [-roots] + [-dgmlPath ] !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 -roots - #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. --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", "async ValueTask", and "async ValueTask". It also optionally supports +any other tasks. \\ COMMAND: dumpstackobjects. diff --git a/src/ToolBox/SOS/Strike/strike.cpp b/src/ToolBox/SOS/Strike/strike.cpp index d7794f45dd3c..f93bc303b047 100644 --- a/src/ToolBox/SOS/Strike/strike.cpp +++ b/src/ToolBox/SOS/Strike/strike.cpp @@ -147,6 +147,7 @@ const PROCESSINFOCLASS ProcessVmCounters = static_cast(3); #endif // !FEATURE_PAL #include +#include BOOL CallStatus; BOOL ControlC = FALSE; @@ -156,7 +157,6 @@ WCHAR g_mdName[mdNameLen]; #ifndef FEATURE_PAL HMODULE g_hInstance = NULL; -#include #include #endif // !FEATURE_PAL @@ -4234,11 +4234,116 @@ class DumpHeapImpl /**********************************************************************\ * 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 = *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 = *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 = *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 = *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 Continuations; +}; + +bool AsyncRecordIsCompleted(AsyncRecord& ar) +{ + const int TASK_STATE_COMPLETED_MASK = 0x1600000; + return (ar.TaskStateFlags & TASK_STATE_COMPLETED_MASK) != 0; +} + +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(actionMD, g_mdName, mdNameLen); + ExtOut("(%S) ", g_mdName); + } + } +} + DECLARE_API(DumpAsync) { INIT_API(); @@ -4249,31 +4354,43 @@ DECLARE_API(DumpAsync) return E_FAIL; } + FILE* pDgmlFile = NULL; try { // Process command-line arguments. size_t nArg = 0; TADDR mt = NULL; ArrayHolder ansiType = NULL; + ArrayHolder dgmlPath = NULL; ArrayHolder 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 + { "-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 + { "-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 fields of found async state machines + { "-stacks", &includeStacks, COBOOL, FALSE }, // gather and output continuation/stack information + { "-roots", &includeRoots, COBOOL, FALSE }, // gather and output GC root information + { "-dgmlPath", &dgmlPath, COSTRING, TRUE }, // output state machine graph to specified dgml file #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("Usage: DumpAsync [-mt MethodTableAddr] [-type TypeName] [-waiting] [-roots]"); - } - if (nArg != 0) - { - sos::Throw("Unexpected command-line arguments."); + if (!GetCMDOption(args, option, _countof(option), NULL, 0, &nArg) || nArg != 0) + { + sos::Throw( + "Usage: DumpAsync [-mt MethodTableAddr] [-type TypeName] [-tasks] [-completed] [-fields] [-stacks] [-roots] [-dgmlPath outputPath]\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" + "[-dgmlPath outputPath] => Output to the specified path a DGML representation of the discovered async objects.\n" + ); } if (ansiType != NULL) { @@ -4286,7 +4403,9 @@ DECLARE_API(DumpAsync) type = new WCHAR[ansiTypeLen]; MultiByteToWideChar(CP_ACP, 0, ansiType, -1, type, (int)ansiTypeLen); } + EnableDMLHolder dmlHolder(dml); + BOOL hasTypeFilter = mt != NULL || ansiType != NULL; // Display a message if the heap isn't verified. sos::GCHeap gcheap; @@ -4295,164 +4414,430 @@ 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 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) + // Skip objects too small to be state machines or tasks, avoiding some compiler-generated caching data structures. + if (itr->GetSize() <= 24) { continue; } - // Match only MTs the user requested. - if (mt != NULL && mt != itr->GetMT()) + // 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; + } + } + else + { + // 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; + } } - // Match only type name substrings the user requested. - if (type != NULL && _wcsstr(itr->GetTypeName(), type) == NULL) + // 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. + + // 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 = 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) { - 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); + } } - // 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. + // If we only want to include incomplete async objects, skip this one if it's completed. + if (!includeCompleted && AsyncRecordIsCompleted(ar)) { 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) + // 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 || dgmlPath != NULL) && TryGetContinuation(itr->GetAddress(), itr->GetMT(), &nextAddr)) { - continue; + sos::Object contObj = nextAddr; + if (_wcsncmp(contObj.GetTypeName(), W("System.Collections.Generic.List`1"), 33) == 0) + { + // The continuation is a List. 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()); + } } - // 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) + // We've gathered all of the needed information for this heap object. Add it to our list of async records. + asyncRecords.insert(std::pair(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. + HeapStat stats; + for (std::map::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt) + { + if (!hasTypeFilter || arIt->second.FilteredByOptions) { - stateMachineAddr = itr->GetAddress() + stateMachineFieldOffset; - stateMachineMT = stateMachineField.MTOfType; + stats.Add(arIt->second.MT, 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 || dgmlPath != NULL) && !hasTypeFilter) + { + size_t uniqueChains = asyncRecords.size(); + for (std::map::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt) { - MOVE(stateMachineAddr, itr->GetAddress() + stateMachineFieldOffset); - DacpObjectData objData; - if (objData.Request(g_sos, stateMachineAddr) != S_OK) + for (std::vector::iterator contIt = arIt->second.Continuations.begin(); contIt != arIt->second.Continuations.end(); ++contIt) { - // Couldn't get the class-based object; just skip this state machine. - continue; + std::map::iterator found = asyncRecords.find(*contIt); + if (found != asyncRecords.end()) + { + if (found->second.IsTopLevel) + { + found->second.IsTopLevel = false; + uniqueChains--; + } + } } - 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)) + ExtOut("In %d chains.\n", uniqueChains); + } + + // If the user has asked for a DGML rendering of the async records, create it. + if (dgmlPath != NULL) + { + // Count the occurrences of each MT. + std::map> mtCounts; + for (std::map::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt) { - missingStateFieldWarning = TRUE; - if (waiting) + std::map>::iterator found = mtCounts.find(arIt->second.MT); + if (found == mtCounts.end()) { - // waiting was specified and we couldn't find the field to satisfy the query, - // so skip this object. - continue; + mtCounts.insert(std::pair>(arIt->second.MT, std::pair(arIt->second.StateMachineMT, 1))); + } + else + { + found->second.second++; + } + + for (std::vector::iterator contIt = arIt->second.Continuations.begin(); contIt != arIt->second.Continuations.end(); ++contIt) + { + if (asyncRecords.find(*contIt) == asyncRecords.end()) + { + sos::Object contObj = *contIt; + std::map>::iterator found = mtCounts.find(contObj.GetMT()); + if (found == mtCounts.end()) + { + mtCounts.insert(std::pair>(contObj.GetMT(), std::pair(contObj.GetMT(), 1))); + } + else + { + found->second.second++; + } + } } } - else + + // Open the output file. + if (fopen_s(&pDgmlFile, dgmlPath, "w") != 0) { - MOVE(stateValue, stateMachineAddr + stateFieldOffset); - if (waiting && stateValue < 0) + ExtOut("Unable to open output DGML file.\n"); + return Status; + } + + // Render the header. + fprintf_s(pDgmlFile, + "" + "" + ""); + + // Render each node. + for (std::map>::iterator mtIt = mtCounts.begin(); mtIt != mtCounts.end(); ++mtIt) + { + fprintf_s(pDgmlFile, " first); + sos::MethodTable curMT = mtIt->second.first; + for (const WCHAR* c = curMT.GetName(); *c != 0; c++) { - // 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; + switch (*c) + { + case '&': fprintf_s(pDgmlFile, "&"); break; + case '\"': fprintf_s(pDgmlFile, """); break; + case '\'': fprintf_s(pDgmlFile, "'"); break; + case '<': fprintf_s(pDgmlFile, "<"); break; + case '>': fprintf_s(pDgmlFile, ">"); break; + default: fprintf_s(pDgmlFile, "%c", *c); break; + } + } + fprintf_s(pDgmlFile, " (%d)\" />", mtIt->second.second); + } + + fprintf_s(pDgmlFile, + "" + ""); + + // Render each link between nodes. + for (std::map::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt) + { + for (std::vector::iterator contIt = arIt->second.Continuations.begin(); contIt != arIt->second.Continuations.end(); ++contIt) + { + sos::Object contObj = *contIt; + fprintf_s(pDgmlFile, " ", arIt->second.MT, contObj.GetMT()); } } - // We now have a state machine that's passed all of our criteria. Print out its details. + // Render the footer. + fprintf_s(pDgmlFile, + "" + "" + " " + "" + "" + " " + "" + ""); + + // We're done; close the file. + fclose(pDgmlFile); + pDgmlFile = NULL; + ExtOut("Graph saved to %s.\n", dgmlPath); + } + + // Print out header for the main line of each result. + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %8s %10s %s\n", "Address", "MT", "Size", "State", "Description"); - // 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 each top-level async record. + int counter = 0; + for (std::map::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt) + { + if (!arIt->second.IsTopLevel || + (hasTypeFilter && !arIt->second.FilteredByOptions)) + { + continue; + } - // Output the state machine's name and fields. + // Output the state machine's details as a single line. + sos::Object obj = 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 = (TADDR)arIt->second.StateMachineMT; + DMLOut("%s %s %8d %10d", DMLObject(obj.GetAddress()), DMLDumpHeapMT(obj.GetMT()), obj.GetSize(), arIt->second.StateValue); + ExtOut(" %S\n", 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 [%08x]", DMLObject(obj.GetAddress()), DMLDumpHeapMT(obj.GetMT()), obj.GetSize(), arIt->second.TaskStateFlags); + ExtOut(" %S ", obj.GetTypeName()); + ExtOutTaskDelegateMethod(obj); + ExtOut("\n"); } - // 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> continuationChainToExplore; + continuationChainToExplore.push_back(std::pair(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 seen; + while (continuationChainToExplore.size() > 0) { - sos::Object contObj = ContObjPtr; - ExtOut(" (%S)", contObj.GetTypeName()); + // Pop the next continuation from the stack. + std::pair cur = continuationChainToExplore.back(); + continuationChainToExplore.pop_back(); + + // Get the async record for this continuation. It should be one we already know about. + std::map::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::iterator contIt = curAsyncRecord->second.Continuations.begin(); contIt != curAsyncRecord->second.Continuations.end(); ++contIt) + { + sos::Object cont = *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::iterator contAsyncRecord = asyncRecords.find(cont.GetAddress()); + if (contAsyncRecord != asyncRecords.end()) + { + sos::MethodTable contMT = 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(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) { + if (pDgmlFile != NULL) + { + fclose(pDgmlFile); + } + ExtOut("%s\n", e.what()); return E_FAIL; } From e0a36ca13fac5aeed8464850312cb8c133b8b9af Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 6 Nov 2018 13:36:17 -0500 Subject: [PATCH 3/6] Decode task state flags --- src/ToolBox/SOS/Strike/strike.cpp | 81 +++++++++++++++++++++++++------ 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/src/ToolBox/SOS/Strike/strike.cpp b/src/ToolBox/SOS/Strike/strike.cpp index f93bc303b047..57bd86383677 100644 --- a/src/ToolBox/SOS/Strike/strike.cpp +++ b/src/ToolBox/SOS/Strike/strike.cpp @@ -4327,6 +4327,18 @@ bool AsyncRecordIsCompleted(AsyncRecord& ar) 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; @@ -4344,6 +4356,47 @@ void ExtOutTaskDelegateMethod(sos::Object& obj) } } +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(); @@ -4703,14 +4756,15 @@ DECLARE_API(DumpAsync) } // Print out header for the main line of each result. - ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %8s %10s %s\n", "Address", "MT", "Size", "State", "Description"); + ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %8s ", "Address", "MT", "Size"); + if (includeCompleted) ExtOut("%8s ", "Status"); + ExtOut("%10s %s\n", "State", "Description"); // Output each top-level async record. int counter = 0; for (std::map::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt) { - if (!arIt->second.IsTopLevel || - (hasTypeFilter && !arIt->second.FilteredByOptions)) + if (!arIt->second.IsTopLevel || (hasTypeFilter && !arIt->second.FilteredByOptions)) { continue; } @@ -4726,20 +4780,20 @@ DECLARE_API(DumpAsync) { // This has a StateMachine. Output its details. sos::MethodTable mt = (TADDR)arIt->second.StateMachineMT; - DMLOut("%s %s %8d %10d", DMLObject(obj.GetAddress()), DMLDumpHeapMT(obj.GetMT()), obj.GetSize(), arIt->second.StateValue); - ExtOut(" %S\n", mt.GetName()); - if (dumpFields) - { - DisplayFields(arIt->second.StateMachineMT, &mtabledata, &vMethodTableFields, (DWORD_PTR)arIt->second.StateMachineAddr, TRUE, arIt->second.IsValueType); - } + DMLOut("%s %s %8d ", DMLObject(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 [%08x]", DMLObject(obj.GetAddress()), DMLDumpHeapMT(obj.GetMT()), obj.GetSize(), arIt->second.TaskStateFlags); - ExtOut(" %S ", obj.GetTypeName()); + DMLOut("%s %s %8d ", DMLObject(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 we gathered any continuations for this record, output the chains now. @@ -4790,10 +4844,7 @@ DECLARE_API(DumpAsync) if (contAsyncRecord != asyncRecords.end()) { sos::MethodTable contMT = contAsyncRecord->second.StateMachineMT; - if (contAsyncRecord->second.IsStateMachine) - { - ExtOut("(%d) ", contAsyncRecord->second.StateValue); - } + if (contAsyncRecord->second.IsStateMachine) ExtOut("(%d) ", contAsyncRecord->second.StateValue); ExtOut("%S\n", contMT.GetName()); } else From 8d9de54d8d4cae349804d3c21a54dbea95277bba Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 6 Nov 2018 14:52:52 -0500 Subject: [PATCH 4/6] Add addr option and associated DumpAsync DML --- src/ToolBox/SOS/Strike/apollososdocs.txt | 3 +- src/ToolBox/SOS/Strike/sosdocs.txt | 3 +- src/ToolBox/SOS/Strike/sosdocsunix.txt | 3 +- src/ToolBox/SOS/Strike/strike.cpp | 43 ++++++++++++------------ src/ToolBox/SOS/Strike/util.cpp | 1 + src/ToolBox/SOS/Strike/util.h | 2 ++ 6 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/ToolBox/SOS/Strike/apollososdocs.txt b/src/ToolBox/SOS/Strike/apollososdocs.txt index f38bb16ffe9c..76ebdc07dd3b 100644 --- a/src/ToolBox/SOS/Strike/apollososdocs.txt +++ b/src/ToolBox/SOS/Strike/apollososdocs.txt @@ -344,7 +344,8 @@ The arguments in detail: \\ COMMAND: dumpasync. -!DumpAsync [-mt ] +!DumpAsync [-addr ] + [-mt ] [-type ] [-tasks] [-completed] diff --git a/src/ToolBox/SOS/Strike/sosdocs.txt b/src/ToolBox/SOS/Strike/sosdocs.txt index 9f8f8723ab80..cde4a7d2f563 100644 --- a/src/ToolBox/SOS/Strike/sosdocs.txt +++ b/src/ToolBox/SOS/Strike/sosdocs.txt @@ -342,7 +342,8 @@ The arguments in detail: \\ COMMAND: dumpasync. -!DumpAsync [-mt ] +!DumpAsync [-addr ] + [-mt ] [-type ] [-tasks] [-completed] diff --git a/src/ToolBox/SOS/Strike/sosdocsunix.txt b/src/ToolBox/SOS/Strike/sosdocsunix.txt index fe3d7d09302b..de5cae624d8b 100644 --- a/src/ToolBox/SOS/Strike/sosdocsunix.txt +++ b/src/ToolBox/SOS/Strike/sosdocsunix.txt @@ -203,7 +203,8 @@ The arguments in detail: \\ COMMAND: dumpasync. -!DumpAsync [-mt ] +!DumpAsync [-addr ] + [-mt ] [-type ] [-tasks] [-completed] diff --git a/src/ToolBox/SOS/Strike/strike.cpp b/src/ToolBox/SOS/Strike/strike.cpp index 57bd86383677..055987e1b728 100644 --- a/src/ToolBox/SOS/Strike/strike.cpp +++ b/src/ToolBox/SOS/Strike/strike.cpp @@ -4412,18 +4412,19 @@ DECLARE_API(DumpAsync) { // Process command-line arguments. size_t nArg = 0; - TADDR mt = NULL; + TADDR mt = NULL, addr = NULL; ArrayHolder ansiType = NULL; ArrayHolder dgmlPath = NULL; ArrayHolder type = NULL; 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 + { "-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 fields of found async state machines + { "-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 { "-dgmlPath", &dgmlPath, COSTRING, TRUE }, // output state machine graph to specified dgml file @@ -4434,7 +4435,8 @@ DECLARE_API(DumpAsync) if (!GetCMDOption(args, option, _countof(option), NULL, 0, &nArg) || nArg != 0) { sos::Throw( - "Usage: DumpAsync [-mt MethodTableAddr] [-type TypeName] [-tasks] [-completed] [-fields] [-stacks] [-roots] [-dgmlPath outputPath]\n" + "Usage: DumpAsync [-addr ObjectAddr] [-mt MethodTableAddr] [-type TypeName] [-tasks] [-completed] [-fields] [-stacks] [-roots] [-dgmlPath outputPath]\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" @@ -4447,18 +4449,13 @@ DECLARE_API(DumpAsync) } 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); - BOOL hasTypeFilter = mt != NULL || ansiType != NULL; + BOOL hasTypeFilter = mt != NULL || ansiType != NULL || addr != NULL; // Display a message if the heap isn't verified. sos::GCHeap gcheap; @@ -4515,7 +4512,8 @@ DECLARE_API(DumpAsync) 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. + (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 @@ -4616,16 +4614,19 @@ DECLARE_API(DumpAsync) // 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. - HeapStat stats; - for (std::map::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt) + if (addr == NULL) // no point in stats if we're only targeting a single object { - if (!hasTypeFilter || arIt->second.FilteredByOptions) + HeapStat stats; + for (std::map::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt) { - stats.Add(arIt->second.MT, arIt->second.Size); + if (!hasTypeFilter || arIt->second.FilteredByOptions) + { + stats.Add(arIt->second.MT, arIt->second.Size); + } } + stats.Sort(); + stats.Print(); } - 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 @@ -4780,7 +4781,7 @@ DECLARE_API(DumpAsync) { // This has a StateMachine. Output its details. sos::MethodTable mt = (TADDR)arIt->second.StateMachineMT; - DMLOut("%s %s %8d ", DMLObject(obj.GetAddress()), DMLDumpHeapMT(obj.GetMT()), obj.GetSize()); + 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); @@ -4788,7 +4789,7 @@ DECLARE_API(DumpAsync) else { // This does not have a StateMachine. Output the details of the Task itself. - DMLOut("%s %s %8d ", DMLObject(obj.GetAddress()), DMLDumpHeapMT(obj.GetMT()), obj.GetSize()); + 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); @@ -4867,7 +4868,7 @@ DECLARE_API(DumpAsync) GCRootImpl gcroot; int numRoots = gcroot.PrintRootsForObject(obj.GetAddress(), FALSE, FALSE); DecrementIndent(); - if (numRoots == 0 && AsyncRecordIsCompleted(arIt->second)) + if (numRoots == 0 && !AsyncRecordIsCompleted(arIt->second)) { ExtOut("Incomplete state machine or task with 0 roots.\n"); } diff --git a/src/ToolBox/SOS/Strike/util.cpp b/src/ToolBox/SOS/Strike/util.cpp index 33ce3a7fdc28..27f9c58ca2d2 100644 --- a/src/ToolBox/SOS/Strike/util.cpp +++ b/src/ToolBox/SOS/Strike/util.cpp @@ -5513,6 +5513,7 @@ const char * const DMLFormats[] = "%s", // DML_RCWrapper "%s", // DML_CCWrapper "%S", // DML_ManagedVar + "%s", // DML_Async }; void ConvertToLower(__out_ecount(len) char *buffer, size_t len) diff --git a/src/ToolBox/SOS/Strike/util.h b/src/ToolBox/SOS/Strike/util.h index a519559072d9..88a5cc73ce21 100644 --- a/src/ToolBox/SOS/Strike/util.h +++ b/src/ToolBox/SOS/Strike/util.h @@ -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(); From 53e5f904e942c97a8fb37b188449d81f794a7c34 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 6 Nov 2018 18:23:35 -0500 Subject: [PATCH 5/6] Fix cast warnings in checked / fprintf_s on Unix --- src/ToolBox/SOS/Strike/strike.cpp | 44 +++++++++++++++++++------------ 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/ToolBox/SOS/Strike/strike.cpp b/src/ToolBox/SOS/Strike/strike.cpp index 055987e1b728..8e11241ae84f 100644 --- a/src/ToolBox/SOS/Strike/strike.cpp +++ b/src/ToolBox/SOS/Strike/strike.cpp @@ -122,7 +122,6 @@ #include "ExpressionNode.h" #include "WatchCmd.h" -#include #include #include "tls.h" @@ -146,6 +145,7 @@ const PROCESSINFOCLASS ProcessVmCounters = static_cast(3); #endif // !FEATURE_PAL +#include #include #include @@ -4243,7 +4243,7 @@ class DumpHeapImpl void ResolveContinuation(CLRDATA_ADDRESS* contAddr) { // Ideally this continuation is itself an async method box. - sos::Object contObj = *contAddr; + sos::Object contObj = TO_TADDR(*contAddr); if (GetObjFieldOffset(contObj.GetAddress(), contObj.GetMT(), W("StateMachine")) == 0) { // It was something else. @@ -4255,7 +4255,7 @@ void ResolveContinuation(CLRDATA_ADDRESS* contAddr) MOVE(*contAddr, contObj.GetAddress() + offset); if (sos::IsObject(*contAddr, false)) { - contObj = *contAddr; + contObj = TO_TADDR(*contAddr); } } else @@ -4266,7 +4266,7 @@ void ResolveContinuation(CLRDATA_ADDRESS* contAddr) MOVE(*contAddr, contObj.GetAddress() + offset); if (sos::IsObject(*contAddr, false)) { - contObj = *contAddr; + contObj = TO_TADDR(*contAddr); } } @@ -4276,7 +4276,7 @@ void ResolveContinuation(CLRDATA_ADDRESS* contAddr) MOVE(*contAddr, contObj.GetAddress() + offset); if (sos::IsObject(*contAddr, false)) { - contObj = *contAddr; + contObj = TO_TADDR(*contAddr); } } } @@ -4350,7 +4350,7 @@ void ExtOutTaskDelegateMethod(sos::Object& obj) CLRDATA_ADDRESS actionMD; if (actionAddr != NULL && TryGetMethodDescriptorForDelegate(actionAddr, &actionMD)) { - NameForMD_s(actionMD, g_mdName, mdNameLen); + NameForMD_s((DWORD_PTR)actionMD, g_mdName, mdNameLen); ExtOut("(%S) ", g_mdName); } } @@ -4407,14 +4407,18 @@ DECLARE_API(DumpAsync) return E_FAIL; } +#ifndef FEATURE_PAL FILE* pDgmlFile = NULL; +#endif try { // Process command-line arguments. size_t nArg = 0; TADDR mt = NULL, addr = NULL; ArrayHolder ansiType = NULL; +#ifndef FEATURE_PAL ArrayHolder dgmlPath = NULL; +#endif ArrayHolder type = NULL; BOOL dml = FALSE, includeCompleted = FALSE, includeStacks = FALSE, includeRoots = FALSE, includeAllTasks = FALSE, dumpFields = FALSE; CMDOption option[] = @@ -4427,8 +4431,8 @@ DECLARE_API(DumpAsync) { "-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 - { "-dgmlPath", &dgmlPath, COSTRING, TRUE }, // output state machine graph to specified dgml file #ifndef FEATURE_PAL + { "-dgmlPath", &dgmlPath, COSTRING, TRUE }, // output state machine graph to specified dgml file { "/d", &dml, COBOOL, FALSE }, // Debugger Markup Language #endif }; @@ -4444,7 +4448,9 @@ DECLARE_API(DumpAsync) "[-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" +#ifndef FEATURE_PAL "[-dgmlPath outputPath] => Output to the specified path a DGML representation of the discovered async objects.\n" +#endif ); } if (ansiType != NULL) @@ -4522,7 +4528,7 @@ DECLARE_API(DumpAsync) int offset = GetObjFieldOffset(ar.Address, ar.MT, W("m_stateFlags"), TRUE, &stateFlagsField); if (offset != 0) { - sos::Object obj = ar.Address; + sos::Object obj = TO_TADDR(ar.Address); MOVE(ar.TaskStateFlags, obj.GetAddress() + offset); } @@ -4573,7 +4579,7 @@ DECLARE_API(DumpAsync) CLRDATA_ADDRESS nextAddr; if ((includeStacks || dgmlPath != NULL) && TryGetContinuation(itr->GetAddress(), itr->GetMT(), &nextAddr)) { - sos::Object contObj = nextAddr; + sos::Object contObj = TO_TADDR(nextAddr); if (_wcsncmp(contObj.GetTypeName(), W("System.Collections.Generic.List`1"), 33) == 0) { // The continuation is a List. Iterate through its internal object[] @@ -4621,7 +4627,7 @@ DECLARE_API(DumpAsync) { if (!hasTypeFilter || arIt->second.FilteredByOptions) { - stats.Add(arIt->second.MT, arIt->second.Size); + stats.Add((DWORD_PTR)arIt->second.MT, (DWORD)arIt->second.Size); } } stats.Sort(); @@ -4654,6 +4660,7 @@ DECLARE_API(DumpAsync) ExtOut("In %d chains.\n", uniqueChains); } +#ifndef FEATURE_PAL // If the user has asked for a DGML rendering of the async records, create it. if (dgmlPath != NULL) { @@ -4675,7 +4682,7 @@ DECLARE_API(DumpAsync) { if (asyncRecords.find(*contIt) == asyncRecords.end()) { - sos::Object contObj = *contIt; + sos::Object contObj = TO_TADDR(*contIt); std::map>::iterator found = mtCounts.find(contObj.GetMT()); if (found == mtCounts.end()) { @@ -4706,7 +4713,7 @@ DECLARE_API(DumpAsync) for (std::map>::iterator mtIt = mtCounts.begin(); mtIt != mtCounts.end(); ++mtIt) { fprintf_s(pDgmlFile, " first); - sos::MethodTable curMT = mtIt->second.first; + sos::MethodTable curMT = TO_TADDR(mtIt->second.first); for (const WCHAR* c = curMT.GetName(); *c != 0; c++) { switch (*c) @@ -4731,7 +4738,7 @@ DECLARE_API(DumpAsync) { for (std::vector::iterator contIt = arIt->second.Continuations.begin(); contIt != arIt->second.Continuations.end(); ++contIt) { - sos::Object contObj = *contIt; + sos::Object contObj = TO_TADDR(*contIt); fprintf_s(pDgmlFile, " ", arIt->second.MT, contObj.GetMT()); } } @@ -4755,6 +4762,7 @@ DECLARE_API(DumpAsync) pDgmlFile = NULL; ExtOut("Graph saved to %s.\n", dgmlPath); } +#endif // Print out header for the main line of each result. ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %8s ", "Address", "MT", "Size"); @@ -4771,7 +4779,7 @@ DECLARE_API(DumpAsync) } // Output the state machine's details as a single line. - sos::Object obj = arIt->second.Address; + sos::Object obj = TO_TADDR(arIt->second.Address); DacpMethodTableData mtabledata; DacpMethodTableFieldData vMethodTableFields; if (arIt->second.IsStateMachine && @@ -4780,7 +4788,7 @@ DECLARE_API(DumpAsync) vMethodTableFields.wNumInstanceFields + vMethodTableFields.wNumStaticFields > 0) { // This has a StateMachine. Output its details. - sos::MethodTable mt = (TADDR)arIt->second.StateMachineMT; + 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()); @@ -4830,7 +4838,7 @@ DECLARE_API(DumpAsync) // Iterate through all continuations from this object. for (std::vector::iterator contIt = curAsyncRecord->second.Continuations.begin(); contIt != curAsyncRecord->second.Continuations.end(); ++contIt) { - sos::Object cont = *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("."); @@ -4844,7 +4852,7 @@ DECLARE_API(DumpAsync) std::map::iterator contAsyncRecord = asyncRecords.find(cont.GetAddress()); if (contAsyncRecord != asyncRecords.end()) { - sos::MethodTable contMT = contAsyncRecord->second.StateMachineMT; + sos::MethodTable contMT = TO_TADDR(contAsyncRecord->second.StateMachineMT); if (contAsyncRecord->second.IsStateMachine) ExtOut("(%d) ", contAsyncRecord->second.StateValue); ExtOut("%S\n", contMT.GetName()); } @@ -4885,10 +4893,12 @@ DECLARE_API(DumpAsync) } catch (const sos::Exception &e) { +#ifndef FEATURE_PAL if (pDgmlFile != NULL) { fclose(pDgmlFile); } +#endif ExtOut("%s\n", e.what()); return E_FAIL; From 583fd19ef22f1c4857b23488c7515e032e2d5b94 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 6 Nov 2018 21:10:16 -0500 Subject: [PATCH 6/6] 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/ToolBox/SOS/Strike/apollososdocs.txt | 1 - src/ToolBox/SOS/Strike/sosdocs.txt | 1 - src/ToolBox/SOS/Strike/sosdocsunix.txt | 1 - src/ToolBox/SOS/Strike/strike.cpp | 127 +---------------------- 4 files changed, 3 insertions(+), 127 deletions(-) diff --git a/src/ToolBox/SOS/Strike/apollososdocs.txt b/src/ToolBox/SOS/Strike/apollososdocs.txt index 76ebdc07dd3b..6ed1dcc9e23e 100644 --- a/src/ToolBox/SOS/Strike/apollososdocs.txt +++ b/src/ToolBox/SOS/Strike/apollososdocs.txt @@ -352,7 +352,6 @@ COMMAND: dumpasync. [-fields] [-stacks] [-roots] - [-dgmlPath ] !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 diff --git a/src/ToolBox/SOS/Strike/sosdocs.txt b/src/ToolBox/SOS/Strike/sosdocs.txt index cde4a7d2f563..cf3f079a76f1 100644 --- a/src/ToolBox/SOS/Strike/sosdocs.txt +++ b/src/ToolBox/SOS/Strike/sosdocs.txt @@ -350,7 +350,6 @@ COMMAND: dumpasync. [-fields] [-stacks] [-roots] - [-dgmlPath ] !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 diff --git a/src/ToolBox/SOS/Strike/sosdocsunix.txt b/src/ToolBox/SOS/Strike/sosdocsunix.txt index de5cae624d8b..d014df7c4df1 100644 --- a/src/ToolBox/SOS/Strike/sosdocsunix.txt +++ b/src/ToolBox/SOS/Strike/sosdocsunix.txt @@ -211,7 +211,6 @@ COMMAND: dumpasync. [-fields] [-stacks] [-roots] - [-dgmlPath ] !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 diff --git a/src/ToolBox/SOS/Strike/strike.cpp b/src/ToolBox/SOS/Strike/strike.cpp index 8e11241ae84f..8ff0c0d9ae82 100644 --- a/src/ToolBox/SOS/Strike/strike.cpp +++ b/src/ToolBox/SOS/Strike/strike.cpp @@ -4407,18 +4407,12 @@ DECLARE_API(DumpAsync) return E_FAIL; } -#ifndef FEATURE_PAL - FILE* pDgmlFile = NULL; -#endif try { // Process command-line arguments. size_t nArg = 0; TADDR mt = NULL, addr = NULL; ArrayHolder ansiType = NULL; -#ifndef FEATURE_PAL - ArrayHolder dgmlPath = NULL; -#endif ArrayHolder type = NULL; BOOL dml = FALSE, includeCompleted = FALSE, includeStacks = FALSE, includeRoots = FALSE, includeAllTasks = FALSE, dumpFields = FALSE; CMDOption option[] = @@ -4432,14 +4426,13 @@ DECLARE_API(DumpAsync) { "-stacks", &includeStacks, COBOOL, FALSE }, // gather and output continuation/stack information { "-roots", &includeRoots, COBOOL, FALSE }, // gather and output GC root information #ifndef FEATURE_PAL - { "-dgmlPath", &dgmlPath, COSTRING, TRUE }, // output state machine graph to specified dgml file { "/d", &dml, COBOOL, FALSE }, // Debugger Markup Language #endif }; if (!GetCMDOption(args, option, _countof(option), NULL, 0, &nArg) || nArg != 0) { sos::Throw( - "Usage: DumpAsync [-addr ObjectAddr] [-mt MethodTableAddr] [-type TypeName] [-tasks] [-completed] [-fields] [-stacks] [-roots] [-dgmlPath outputPath]\n" + "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" @@ -4448,9 +4441,6 @@ DECLARE_API(DumpAsync) "[-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" -#ifndef FEATURE_PAL - "[-dgmlPath outputPath] => Output to the specified path a DGML representation of the discovered async objects.\n" -#endif ); } if (ansiType != NULL) @@ -4577,7 +4567,7 @@ DECLARE_API(DumpAsync) // 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 || dgmlPath != NULL) && TryGetContinuation(itr->GetAddress(), itr->GetMT(), &nextAddr)) + if (includeStacks && TryGetContinuation(itr->GetAddress(), itr->GetMT(), &nextAddr)) { sos::Object contObj = TO_TADDR(nextAddr); if (_wcsncmp(contObj.GetTypeName(), W("System.Collections.Generic.List`1"), 33) == 0) @@ -4638,7 +4628,7 @@ DECLARE_API(DumpAsync) // 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 || dgmlPath != NULL) && !hasTypeFilter) + if (includeStacks && !hasTypeFilter) { size_t uniqueChains = asyncRecords.size(); for (std::map::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt) @@ -4660,110 +4650,6 @@ DECLARE_API(DumpAsync) ExtOut("In %d chains.\n", uniqueChains); } -#ifndef FEATURE_PAL - // If the user has asked for a DGML rendering of the async records, create it. - if (dgmlPath != NULL) - { - // Count the occurrences of each MT. - std::map> mtCounts; - for (std::map::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt) - { - std::map>::iterator found = mtCounts.find(arIt->second.MT); - if (found == mtCounts.end()) - { - mtCounts.insert(std::pair>(arIt->second.MT, std::pair(arIt->second.StateMachineMT, 1))); - } - else - { - found->second.second++; - } - - for (std::vector::iterator contIt = arIt->second.Continuations.begin(); contIt != arIt->second.Continuations.end(); ++contIt) - { - if (asyncRecords.find(*contIt) == asyncRecords.end()) - { - sos::Object contObj = TO_TADDR(*contIt); - std::map>::iterator found = mtCounts.find(contObj.GetMT()); - if (found == mtCounts.end()) - { - mtCounts.insert(std::pair>(contObj.GetMT(), std::pair(contObj.GetMT(), 1))); - } - else - { - found->second.second++; - } - } - } - } - - // Open the output file. - if (fopen_s(&pDgmlFile, dgmlPath, "w") != 0) - { - ExtOut("Unable to open output DGML file.\n"); - return Status; - } - - // Render the header. - fprintf_s(pDgmlFile, - "" - "" - ""); - - // Render each node. - for (std::map>::iterator mtIt = mtCounts.begin(); mtIt != mtCounts.end(); ++mtIt) - { - fprintf_s(pDgmlFile, " first); - sos::MethodTable curMT = TO_TADDR(mtIt->second.first); - for (const WCHAR* c = curMT.GetName(); *c != 0; c++) - { - switch (*c) - { - case '&': fprintf_s(pDgmlFile, "&"); break; - case '\"': fprintf_s(pDgmlFile, """); break; - case '\'': fprintf_s(pDgmlFile, "'"); break; - case '<': fprintf_s(pDgmlFile, "<"); break; - case '>': fprintf_s(pDgmlFile, ">"); break; - default: fprintf_s(pDgmlFile, "%c", *c); break; - } - } - fprintf_s(pDgmlFile, " (%d)\" />", mtIt->second.second); - } - - fprintf_s(pDgmlFile, - "" - ""); - - // Render each link between nodes. - for (std::map::iterator arIt = asyncRecords.begin(); arIt != asyncRecords.end(); ++arIt) - { - for (std::vector::iterator contIt = arIt->second.Continuations.begin(); contIt != arIt->second.Continuations.end(); ++contIt) - { - sos::Object contObj = TO_TADDR(*contIt); - fprintf_s(pDgmlFile, " ", arIt->second.MT, contObj.GetMT()); - } - } - - // Render the footer. - fprintf_s(pDgmlFile, - "" - "" - " " - "" - "" - " " - "" - ""); - - // We're done; close the file. - fclose(pDgmlFile); - pDgmlFile = NULL; - ExtOut("Graph saved to %s.\n", dgmlPath); - } -#endif - // Print out header for the main line of each result. ExtOut("%" POINTERSIZE "s %" POINTERSIZE "s %8s ", "Address", "MT", "Size"); if (includeCompleted) ExtOut("%8s ", "Status"); @@ -4893,13 +4779,6 @@ DECLARE_API(DumpAsync) } catch (const sos::Exception &e) { -#ifndef FEATURE_PAL - if (pDgmlFile != NULL) - { - fclose(pDgmlFile); - } -#endif - ExtOut("%s\n", e.what()); return E_FAIL; }