Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Add additional features to SOS DumpAsync command #18213

Merged
merged 1 commit into from
May 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/ToolBox/SOS/Strike/apollososdocs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,9 @@ The arguments in detail:

COMMAND: dumpasync.
!DumpAsync [-mt <MethodTable address>]
[-type <partial type name>]]
[-type <partial type name>]
[-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
Expand All @@ -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+<MethodD>d__4, test]]
StateMachine: Program+<MethodD>d__4 (struct)
Expand Down Expand Up @@ -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.

\\

Expand Down
8 changes: 6 additions & 2 deletions src/ToolBox/SOS/Strike/sosdocs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,9 @@ The arguments in detail:

COMMAND: dumpasync.
!DumpAsync [-mt <MethodTable address>]
[-type <partial type name>]]
[-type <partial type name>]
[-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
Expand All @@ -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+<MethodD>d__4, test]]
StateMachine: Program+<MethodD>d__4 (struct)
Expand Down Expand Up @@ -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.

\\

Expand Down
8 changes: 6 additions & 2 deletions src/ToolBox/SOS/Strike/sosdocsunix.txt
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ The arguments in detail:

COMMAND: dumpasync.
!DumpAsync [-mt <MethodTable address>]
[-type <partial type name>]]
[-type <partial type name>]
[-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
Expand All @@ -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+<MethodD>d__4, test]]
StateMachine: Program+<MethodD>d__4 (struct)
Expand Down Expand Up @@ -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.

\\

Expand Down
61 changes: 53 additions & 8 deletions src/ToolBox/SOS/Strike/strike.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4123,18 +4123,20 @@ DECLARE_API(DumpAsync)
TADDR mt = NULL;
ArrayHolder<char> ansiType = NULL;
ArrayHolder<WCHAR> 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<sos::Exception>("Usage: DumpAsync [-mt MethodTableAddr] [-type TypeName] [-waiting]");
sos::Throw<sos::Exception>("Usage: DumpAsync [-mt MethodTableAddr] [-type TypeName] [-waiting] [-roots]");
}
if (nArg != 0)
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
60 changes: 60 additions & 0 deletions src/ToolBox/SOS/Strike/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<IMetaDataImport> 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)
Expand Down
1 change: 1 addition & 0 deletions src/ToolBox/SOS/Strike/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down