diff --git a/src/ToolBox/SOS/Strike/apollososdocs.txt b/src/ToolBox/SOS/Strike/apollososdocs.txt index 34e7e4d84644..6af5f3255ef7 100644 --- a/src/ToolBox/SOS/Strike/apollososdocs.txt +++ b/src/ToolBox/SOS/Strike/apollososdocs.txt @@ -344,7 +344,9 @@ The arguments in detail: COMMAND: dumpasync. !DumpAsync [-mt ] - [-type ]] + [-type ] + [-waiting] + [-roots]] !DumpAsync traverses the garbage collected heap, looking for objects representing async state machines as created when an async method's state is transferred to the @@ -362,7 +364,7 @@ These details include: For example: - 0:011> !DumpAsync + 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) @@ -396,6 +398,8 @@ 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. \\ diff --git a/src/ToolBox/SOS/Strike/sosdocs.txt b/src/ToolBox/SOS/Strike/sosdocs.txt index cbf7e073f277..93c50ffc64c0 100644 --- a/src/ToolBox/SOS/Strike/sosdocs.txt +++ b/src/ToolBox/SOS/Strike/sosdocs.txt @@ -342,7 +342,9 @@ The arguments in detail: COMMAND: dumpasync. !DumpAsync [-mt ] - [-type ]] + [-type ] + [-waiting] + [-roots]] !DumpAsync traverses the garbage collected heap, looking for objects representing async state machines as created when an async method's state is transferred to the @@ -360,7 +362,7 @@ These details include: For example: - 0:011> !DumpAsync + 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) @@ -394,6 +396,8 @@ 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. \\ diff --git a/src/ToolBox/SOS/Strike/sosdocsunix.txt b/src/ToolBox/SOS/Strike/sosdocsunix.txt index 574ff4853739..1cc92fdbf95a 100644 --- a/src/ToolBox/SOS/Strike/sosdocsunix.txt +++ b/src/ToolBox/SOS/Strike/sosdocsunix.txt @@ -203,7 +203,9 @@ The arguments in detail: COMMAND: dumpasync. !DumpAsync [-mt ] - [-type ]] + [-type ] + [-waiting] + [-roots]] !DumpAsync traverses the garbage collected heap, looking for objects representing async state machines as created when an async method's state is transferred to the @@ -221,7 +223,7 @@ These details include: For example: - (lldb) dumpasync + (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) @@ -255,6 +257,8 @@ 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. \\ diff --git a/src/ToolBox/SOS/Strike/strike.cpp b/src/ToolBox/SOS/Strike/strike.cpp index 6f5c1618da71..ef19ece472d4 100644 --- a/src/ToolBox/SOS/Strike/strike.cpp +++ b/src/ToolBox/SOS/Strike/strike.cpp @@ -4123,18 +4123,20 @@ DECLARE_API(DumpAsync) TADDR mt = NULL; ArrayHolder ansiType = NULL; ArrayHolder type = NULL; - BOOL dml = FALSE; + BOOL dml = FALSE, waiting = FALSE, roots = 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 #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]"); + sos::Throw("Usage: DumpAsync [-mt MethodTableAddr] [-type TypeName] [-waiting] [-roots]"); } if (nArg != 0) { @@ -4164,6 +4166,7 @@ DECLARE_API(DumpAsync) 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; for (sos::ObjectIterator itr = gcheap.WalkHeap(); !IsInterrupt() && itr != NULL; ++itr) { @@ -4224,6 +4227,36 @@ DECLARE_API(DumpAsync) 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)) + { + missingStateFieldWarning = TRUE; + if (waiting) + { + // waiting was specified and we couldn't find the field to satisfy the query, + // so skip this object. + continue; + } + } + else + { + MOVE(stateValue, stateMachineAddr + stateFieldOffset); + if (waiting && stateValue < 0) + { + // 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; + } + } + // 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. @@ -4261,16 +4294,28 @@ DECLARE_API(DumpAsync) // 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(); + if (roots) + { + ExtOut("GC roots:\n"); + IncrementIndent(); + GCRootImpl gcroot; + int numRoots = gcroot.PrintRootsForObject(*itr, FALSE, FALSE); + DecrementIndent(); + + if (stateValue >= 0 && numRoots == 0) + { + ExtOut("Incomplete state machine (<>1__state == %d) with 0 roots.\n", stateValue); + } + } 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) diff --git a/src/ToolBox/SOS/Strike/util.cpp b/src/ToolBox/SOS/Strike/util.cpp index c7d78d26b482..ba2626f8f6d6 100644 --- a/src/ToolBox/SOS/Strike/util.cpp +++ b/src/ToolBox/SOS/Strike/util.cpp @@ -1838,6 +1838,66 @@ int GetObjFieldOffset(CLRDATA_ADDRESS cdaObj, CLRDATA_ADDRESS cdaMT, __in_z LPCW #undef EXITPOINT } + +// Return value: -1 = error +// -2 = not found +// >= 0 = offset to field from cdaValue +int GetValueFieldOffset(CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, DacpFieldDescData* pDacpFieldDescData) +{ +#define EXITPOINT(EXPR) do { if(!(EXPR)) { return -1; } } while (0) + + const int NOT_FOUND = -2; + DacpMethodTableData dmtd; + DacpMethodTableFieldData vMethodTableFields; + DacpFieldDescData vFieldDesc; + DacpModuleData module; + static DWORD numInstanceFields = 0; // Static due to recursion visiting parents + numInstanceFields = 0; + + EXITPOINT(vMethodTableFields.Request(g_sos, cdaMT) == S_OK); + + EXITPOINT(dmtd.Request(g_sos, cdaMT) == S_OK); + EXITPOINT(module.Request(g_sos, dmtd.Module) == S_OK); + if (dmtd.ParentMethodTable) + { + DWORD retVal = GetValueFieldOffset(dmtd.ParentMethodTable, wszFieldName, pDacpFieldDescData); + if (retVal != NOT_FOUND) + { + // Return in case of error or success. Fall through for field-not-found. + return retVal; + } + } + + CLRDATA_ADDRESS dwAddr = vMethodTableFields.FirstField; + ToRelease pImport = MDImportForModule(&module); + + while (numInstanceFields < vMethodTableFields.wNumInstanceFields) + { + EXITPOINT(vFieldDesc.Request(g_sos, dwAddr) == S_OK); + + if (!vFieldDesc.bIsStatic) + { + NameForToken_s(TokenFromRid(vFieldDesc.mb, mdtFieldDef), pImport, g_mdName, mdNameLen, false); + if (_wcscmp(wszFieldName, g_mdName) == 0) + { + if (pDacpFieldDescData != NULL) + { + *pDacpFieldDescData = vFieldDesc; + } + return vFieldDesc.dwOffset; + } + numInstanceFields++; + } + + dwAddr = vFieldDesc.NextField; + } + + // Field name not found... + return NOT_FOUND; + +#undef EXITPOINT +} + // Returns an AppDomain address if AssemblyPtr is loaded into that domain only. Otherwise // returns NULL CLRDATA_ADDRESS IsInOneDomainOnly(CLRDATA_ADDRESS AssemblyPtr) diff --git a/src/ToolBox/SOS/Strike/util.h b/src/ToolBox/SOS/Strike/util.h index fa26c3faa2d4..78516546ac00 100644 --- a/src/ToolBox/SOS/Strike/util.h +++ b/src/ToolBox/SOS/Strike/util.h @@ -1388,6 +1388,7 @@ void DisplayFields (CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethod 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, DacpFieldDescData* pDacpFieldDescData=NULL); +int GetValueFieldOffset(CLRDATA_ADDRESS cdaMT, __in_z LPCWSTR wszFieldName, DacpFieldDescData* pDacpFieldDescData); BOOL IsValidToken(DWORD_PTR ModuleAddr, mdTypeDef mb); void NameForToken_s(DacpModuleData *pModule, mdTypeDef mb, __out_ecount (capacity_mdName) WCHAR *mdName, size_t capacity_mdName,