diff --git a/src/SOS/Strike/sosdocs.txt b/src/SOS/Strike/sosdocs.txt index 7196fb6804..660dbd4dc0 100644 --- a/src/SOS/Strike/sosdocs.txt +++ b/src/SOS/Strike/sosdocs.txt @@ -1065,11 +1065,13 @@ You can use "!DumpHeap -thinlock" to list objects locked in this way. \\ COMMAND: dumpmt. -!DumpMT [-MD] +!DumpMT [-MD] [-all] Examine a MethodTable. Each managed object has a MethodTable pointer at the start. If you pass the "-MD" flag, you'll also see a list of all the methods -defined on the object. +defined on the object. If you pass the "-all" flag, you'll see class details +(attributes and fields) as well as the method list. The "-all" flag implies +"-MD". \\ COMMAND: dumpclass. diff --git a/src/SOS/Strike/sosdocsunix.txt b/src/SOS/Strike/sosdocsunix.txt index 443a134a4e..77d1906fb4 100644 --- a/src/SOS/Strike/sosdocsunix.txt +++ b/src/SOS/Strike/sosdocsunix.txt @@ -980,11 +980,13 @@ You can use "dumpheap -thinlock" to list objects locked in this way. \\ COMMAND: dumpmt. -DumpMT [-MD] +DumpMT [-MD] [-all] Examine a MethodTable. Each managed object has a MethodTable pointer at the start. If you pass the "-MD" flag, you'll also see a list of all the methods -defined on the object. +defined on the object. If you pass the "-all" flag, you'll see class details +(attributes and fields) as well as the method list. The "-all" flag implies +"-MD". \\ COMMAND: dumpclass. diff --git a/src/SOS/Strike/strike.cpp b/src/SOS/Strike/strike.cpp index 5ea9d956d2..5f4d6364cd 100644 --- a/src/SOS/Strike/strike.cpp +++ b/src/SOS/Strike/strike.cpp @@ -1017,6 +1017,59 @@ DECLARE_API(DumpSigElem) return Status; } +/**********************************************************************\ +* Routine Description: * +* * +* Helper function to display class details (attributes, vtable * +* slots, and fields). * +* * +\**********************************************************************/ +static void DisplayClassDetails(CLRDATA_ADDRESS methodTable, DacpMethodTableData *pMTData) +{ + ExtOut("Class Attributes: %08x ", pMTData->dwAttrClass); + if (IsTdInterface(pMTData->dwAttrClass)) + ExtOut("Interface "); + if (IsTdAbstract(pMTData->dwAttrClass)) + ExtOut("Abstract "); + if (IsTdImport(pMTData->dwAttrClass)) + ExtOut("ComImport "); + if (IsTdSealed(pMTData->dwAttrClass)) + ExtOut("Sealed "); + ExtOut("\n"); + + if (pMTData->wNumVirtuals != 0) + { + ExtOut("Vtable Slots: %d\n", pMTData->wNumVirtuals); + } + if (pMTData->wNumVtableSlots != 0) + { + ExtOut("Total Method Slots: %d\n", pMTData->wNumVtableSlots); + } + + DacpMethodTableFieldData vMethodTableFields; + if (SUCCEEDED(vMethodTableFields.Request(g_sos, methodTable))) + { + ExtOut("NumInstanceFields: %d\n", vMethodTableFields.wNumInstanceFields); + ExtOut("NumStaticFields: %d\n", vMethodTableFields.wNumStaticFields); + + if (vMethodTableFields.wNumThreadStaticFields != 0) + { + ExtOut("NumThreadStaticFields: %d\n", vMethodTableFields.wNumThreadStaticFields); + } + + if (vMethodTableFields.wContextStaticsSize) + { + ExtOut("ContextStaticOffset: 0x%x\n", vMethodTableFields.wContextStaticOffset); + ExtOut("ContextStaticsSize: %d\n", vMethodTableFields.wContextStaticsSize); + } + + if (vMethodTableFields.wNumInstanceFields + vMethodTableFields.wNumStaticFields > 0) + { + DisplayFields(methodTable, pMTData, &vMethodTableFields, (TADDR)0, TRUE, FALSE); + } + } +} + /**********************************************************************\ * Routine Description: * * * @@ -1096,57 +1149,16 @@ DECLARE_API(DumpClass) } else { - DMLOut("Parent MethodTable: %s\n", DMLMethodTable(mtdata.ParentMethodTable)); + DMLOut("Parent: %s\n", DMLMethodTable(mtdata.ParentMethodTable)); } DMLOut("Module: %s\n", DMLModule(mtdata.Module)); DMLOut("Method Table: %s\n", DMLMethodTable(methodTable)); - if (preferMT) - { - DMLOut("Canonical MethodTable: %s\n", DMLClass(mtdata.Class)); - } - if (mtdata.wNumVirtuals != 0) + if (preferMT && methodTable != mtdata.Class) { - ExtOut("Vtable Slots: %x\n", mtdata.wNumVirtuals); + DMLOut("Canonical: %s\n", DMLMethodTable(mtdata.Class)); } - if (mtdata.wNumVtableSlots != 0) - { - ExtOut("Total Method Slots: %x\n", mtdata.wNumVtableSlots); - } - ExtOut("Class Attributes: %x ", mtdata.dwAttrClass); - - if (IsTdInterface(mtdata.dwAttrClass)) - ExtOut("Interface, "); - if (IsTdAbstract(mtdata.dwAttrClass)) - ExtOut("Abstract, "); - if (IsTdImport(mtdata.dwAttrClass)) - ExtOut("ComImport, "); - - ExtOut("\n"); - DacpMethodTableFieldData vMethodTableFields; - if (SUCCEEDED(vMethodTableFields.Request(g_sos, methodTable))) - { - ExtOut("NumInstanceFields: %x\n", vMethodTableFields.wNumInstanceFields); - ExtOut("NumStaticFields: %x\n", vMethodTableFields.wNumStaticFields); - - if (vMethodTableFields.wNumThreadStaticFields != 0) - { - ExtOut("NumThreadStaticFields: %x\n", vMethodTableFields.wNumThreadStaticFields); - } - - - if (vMethodTableFields.wContextStaticsSize) - { - ExtOut("ContextStaticOffset: %x\n", vMethodTableFields.wContextStaticOffset); - ExtOut("ContextStaticsSize: %x\n", vMethodTableFields.wContextStaticsSize); - } - - - if (vMethodTableFields.wNumInstanceFields + vMethodTableFields.wNumStaticFields > 0) - { - DisplayFields(methodTable, &mtdata, &vMethodTableFields, (TADDR)0, TRUE, FALSE); - } - } + DisplayClassDetails(methodTable, &mtdata); return Status; } @@ -1168,11 +1180,13 @@ DECLARE_API(DumpMT) MINIDUMP_NOT_SUPPORTED(); BOOL bDumpMDTable = FALSE; + BOOL bDumpAll = FALSE; BOOL dml = FALSE; CMDOption option[] = { // name, vptr, type, hasValue {"-MD", &bDumpMDTable, COBOOL, FALSE}, + {"-all", &bDumpAll, COBOOL, FALSE}, {"/d", &dml, COBOOL, FALSE} }; CMDValue arg[] = @@ -1188,6 +1202,9 @@ DECLARE_API(DumpMT) EnableDMLHolder dmlHolder(dml); TableOutput table(2, 20, AlignLeft, false); + if (bDumpAll) + bDumpMDTable = TRUE; + if (nArg == 0) { Print("Missing MethodTable address\n"); @@ -1215,13 +1232,25 @@ DECLARE_API(DumpMT) DacpMethodTableCollectibleData vMethTableCollectible; vMethTableCollectible.Request(g_sos, TO_CDADDR(dwStartAddr)); - BOOL preferCanonMT = FALSE; - if (SUCCEEDED(PreferCanonMTOverEEClass(vMethTable.Class, &preferCanonMT)) && preferCanonMT) + // Check if runtime returns canonical MT instead of EEClass (.NET 9+) + BOOL runtimePrefersCanonMT = FALSE; + CLRDATA_ADDRESS canonicalMT = 0; + Status = PreferCanonMTOverEEClass(vMethTable.Class, &runtimePrefersCanonMT, &canonicalMT); + + table.WriteRow("Parent:", DMLMethodTable(vMethTable.ParentMethodTable)); + + if (SUCCEEDED(Status) && runtimePrefersCanonMT) { - table.WriteRow("Canonical MethodTable:", EEClassPtr(vMethTable.Class)); + // .NET 9+: vMethTable.Class contains canonical MT, not EEClass + // Only show "Canonical" if it differs from the current MT + if (canonicalMT != 0 && canonicalMT != TO_CDADDR(dwStartAddr)) + { + table.WriteRow("Canonical:", DMLMethodTable(canonicalMT)); + } } else { + // Legacy: vMethTable.Class contains EEClass table.WriteRow("EEClass:", EEClassPtr(vMethTable.Class)); } @@ -1263,9 +1292,9 @@ DECLARE_API(DumpMT) } table.WriteRow("BaseSize:", PrefixHex(vMethTable.BaseSize)); - table.WriteRow("ComponentSize:", PrefixHex(vMethTable.ComponentSize)); - table.WriteRow("DynamicStatics:", vMethTable.bIsDynamic ? "true" : "false"); - table.WriteRow("ContainsPointers:", vMethTable.bContainsPointers ? "true" : "false"); + if (vMethTable.ComponentSize != 0) + table.WriteRow("ComponentSize:", PrefixHex(vMethTable.ComponentSize)); + table.WriteRow("Has GC Pointers:", vMethTable.bContainsPointers ? "true" : "false"); table.WriteRow("Number of Methods:", Decimal(vMethTable.wNumMethods)); table.SetColWidth(0, 29); @@ -1363,6 +1392,14 @@ DECLARE_API(DumpMT) } } } + // When -all is specified, include class details (similar to DumpClass output) + if (bDumpAll) + { + Print("--------------------------------------\n"); + Print("Additional Details\n"); + + DisplayClassDetails(TO_CDADDR(dwStartAddr), &vMethTable); + } return Status; } @@ -1381,13 +1418,24 @@ HRESULT PrintVC(TADDR taMT, TADDR taObject, BOOL bPrintFields = TRUE) ExtOut("Name: %S\n", g_mdName); DMLOut("MethodTable: %s\n", DMLMethodTable(taMT)); - BOOL preferCanonMT = FALSE; - if (SUCCEEDED(PreferCanonMTOverEEClass(TO_CDADDR(taMT), &preferCanonMT)) && preferCanonMT) + + // Check if runtime returns canonical MT instead of EEClass (.NET 9+) + BOOL runtimePrefersCanonMT = FALSE; + CLRDATA_ADDRESS canonicalMT = 0; + Status = PreferCanonMTOverEEClass(mtabledata.Class, &runtimePrefersCanonMT, &canonicalMT); + + if (SUCCEEDED(Status) && runtimePrefersCanonMT) { - DMLOut("Canonical MethodTable: %s\n", DMLClass(mtabledata.Class)); + // .NET 9+: mtabledata.Class contains canonical MT, not EEClass + // Only show "Canonical" if it differs from the current MT + if (canonicalMT != 0 && canonicalMT != TO_CDADDR(taMT)) + { + DMLOut("Canonical: %s\n", DMLMethodTable(canonicalMT)); + } } else { + // Legacy: mtabledata.Class contains EEClass DMLOut("EEClass: %s\n", DMLClass(mtabledata.Class)); } ExtOut("Size: %d(0x%x) bytes\n", size, size); @@ -1478,23 +1526,10 @@ HRESULT PrintObj(TADDR taObj, BOOL bPrintFields = TRUE) ExtOut("Name: %S\n", obj.GetTypeName()); DMLOut("MethodTable: %s\n", DMLMethodTable(objData.MethodTable)); - DacpMethodTableData mtabledata; - if ((Status=mtabledata.Request(g_sos,objData.MethodTable)) == S_OK) - { - BOOL preferCanonMT = FALSE; - if (SUCCEEDED(PreferCanonMTOverEEClass(mtabledata.Class, &preferCanonMT)) && preferCanonMT) - { - DMLOut("Canonical MethodTable: %s\n", DMLClass(mtabledata.Class)); - } - else - { - DMLOut("EEClass: %s\n", DMLClass(mtabledata.Class)); - } - } - else + if ((Status=mtabledata.Request(g_sos,objData.MethodTable)) != S_OK) { - ExtOut("Invalid EEClass address\n"); + ExtOut("Invalid MethodTable address\n"); return Status; } @@ -1557,9 +1592,12 @@ HRESULT PrintObj(TADDR taObj, BOOL bPrintFields = TRUE) CLRDATA_ADDRESS objAddr = TO_CDADDR(taObj); BOOL isTrackedType; BOOL hasTaggedMemory; - if (SUCCEEDED(sos11->IsTrackedType(objAddr, &isTrackedType, &hasTaggedMemory))) + if (SUCCEEDED(sos11->IsTrackedType(objAddr, &isTrackedType, &hasTaggedMemory)) + && (isTrackedType || hasTaggedMemory)) { - ExtOut("Tracked Type: %s\n", isTrackedType ? "true" : "false"); + if (isTrackedType) + ExtOut("Tracked Type: true\n"); + if (hasTaggedMemory) { CLRDATA_ADDRESS taggedMemory = (TADDR)0; @@ -1639,10 +1677,6 @@ HRESULT PrintObj(TADDR taObj, BOOL bPrintFields = TRUE) StringObjectContent(taObj); ExtOut("\n"); } - else if (objData.ObjectType == OBJ_OBJECT) - { - ExtOut("Object\n"); - } if (bPrintFields) { diff --git a/src/SOS/Strike/util.cpp b/src/SOS/Strike/util.cpp index 69a238a291..7af2ee2d61 100644 --- a/src/SOS/Strike/util.cpp +++ b/src/SOS/Strike/util.cpp @@ -606,10 +606,14 @@ HRESULT GetStaticFieldPTR(DWORD_PTR* pOutPtr, DacpDomainLocalModuleData* pDLMD, } } - dwTmp = (DWORD_PTR)pBaseAddress + pFDD->dwOffset; - *pOutPtr = 0; + // Statics for this type may not be allocated yet, this is okay. + // See dynamic statics for more information. + if (pBaseAddress == 0) + return S_OK; + + dwTmp = (DWORD_PTR)pBaseAddress + pFDD->dwOffset; if (pSOS14) { MethodTableInitializationFlags initFlags; @@ -1032,7 +1036,7 @@ void DisplayFields(CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethodT if (bFirst) { ExtOutIndent(); - ExtOut("%" POINTERSIZE "s %8s %8s %20s %2s %8s %" POINTERSIZE "s %s\n", + ExtOut("%" POINTERSIZE "s %8s %8s %20s %4s %8s %" POINTERSIZE "s %s\n", "MT", "Field", "Offset", "Type", "VT", "Attr", "Value", "Name"); numInstanceFields = 0; } @@ -1094,7 +1098,7 @@ void DisplayFields(CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethodT } } - DMLOut("%s %8x %8x ", DMLMethodTable(vFieldDesc.MTOfType), + DMLOut("%s %08x %8x ", DMLMethodTable(vFieldDesc.MTOfType), TokenFromRid(vFieldDesc.mb, mdtFieldDef), offset); @@ -1123,7 +1127,7 @@ void DisplayFields(CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethodT } } - ExtOut("%2s ", (IsElementValueType(vFieldDesc.Type)) ? "1" : "0"); + ExtOut("%4s ", (IsElementValueType(vFieldDesc.Type)) ? "Yes" : "No"); if (vFieldDesc.bIsStatic && (vFieldDesc.bIsThreadLocal || vFieldDesc.bIsContextLocal)) { @@ -1229,7 +1233,7 @@ void DisplayFields(CLRDATA_ADDRESS cdaMT, DacpMethodTableData *pMTD, DacpMethodT } else { - ExtOut(" %8s", " "); + ExtOut("%" POINTERSIZE "s", " "); } @@ -5524,7 +5528,7 @@ WString DmlEscape(const WString &input) const WCHAR *str = input.c_str(); size_t len = input.length(); WString result; - + for (size_t i = 0; i < len; i++) { // Ampersand must be escaped FIRST to avoid double-escaping @@ -5548,7 +5552,7 @@ WString DmlEscape(const WString &input) result += temp; } } - + return result; } diff --git a/src/tests/SOS.UnitTests/Scripts/GCTests.script b/src/tests/SOS.UnitTests/Scripts/GCTests.script index acd1cdefc8..98261ec89a 100644 --- a/src/tests/SOS.UnitTests/Scripts/GCTests.script +++ b/src/tests/SOS.UnitTests/Scripts/GCTests.script @@ -32,7 +32,7 @@ VERIFY:\s+\s+\s+\s+System\.IO.TextWriter\s+\s+sh VERIFY:\s+>>\s+Domain:Value\s+:(|NotInit)\s+<<\s+ ENDIF:MAJOR_RUNTIME_VERSION_2 IFDEF:MAJOR_RUNTIME_VERSION_GE_3 -VERIFY:\s+\s+\s+\s+System\.IO.TextWriter\s+\s+static\s+\s+Null\s+ +VERIFY:\s+\s+\s+\s+System\.IO.TextWriter\s+No\s+static\s+\s+Null\s+ ENDIF:MAJOR_RUNTIME_VERSION_GE_3 SOSCOMMAND:DumpStackObjects @@ -53,7 +53,6 @@ VERIFY:\s+\s+\s+GCWhere\.Main\(\)\s+ SOSCOMMAND:DumpObj VERIFY:\s*Name:\s+GCWhere\s+ VERIFY:\s+MethodTable:\s+\s+ -VERIFY:\s+(EEClass|Canonical MethodTable):\s+\s+ VERIFY:\s+Fields:\s+ VERIFY:\s+\s+\s+\s+System\.String.*_string\s+ VERIFY:\s+\s+\s+\s+System\.UInt64.*52704621242434 _static\s+ @@ -61,12 +60,10 @@ VERIFY:\s+\s+\s+\s+System\.UInt64.*52704621242434 _stati SOSCOMMAND:DumpObj -nofields VERIFY:\s*Name:\s+GCWhere\s+ VERIFY:\s+MethodTable:\s+\s+ -VERIFY:\s+(EEClass|Canonical MethodTable):\s+\s+ SOSCOMMAND:DumpObj -refs VERIFY:\s*Name:\s+GCWhere\s+ VERIFY:\s+MethodTable:\s+\s+ -VERIFY:\s+(EEClass|Canonical MethodTable):\s+\s+ VERIFY:\s+Fields:\s+ VERIFY:\s+\s+\s+\s+System\.String.*_string\s+ VERIFY:\s+\s+\s+\s+System\.UInt64.*52704621242434 _static\s+ @@ -133,7 +130,6 @@ VERIFY:\s+\s+\s+GCWhere\.Main\(\)\s+ SOSCOMMAND:DumpObj VERIFY:\s*Name:\s+GCWhere\s+ VERIFY:\s+MethodTable:\s+\s+ -VERIFY:\s+(EEClass|Canonical MethodTable):\s+\s+ VERIFY:\s+Fields:\s+ VERIFY:\s+\s+\s+\s+System\.String.*_string\s+ VERIFY:\s+\s+\s+\s+System\.UInt64.*52704621242434 _static\s+ @@ -141,12 +137,10 @@ VERIFY:\s+\s+\s+\s+System\.UInt64.*52704621242434 _stati SOSCOMMAND:DumpObj -nofields VERIFY:\s*Name:\s+GCWhere\s+ VERIFY:\s+MethodTable:\s+\s+ -VERIFY:\s+(EEClass|Canonical MethodTable):\s+\s+ SOSCOMMAND:DumpObj -refs VERIFY:\s*Name:\s+GCWhere\s+ VERIFY:\s+MethodTable:\s+\s+ -VERIFY:\s+(EEClass|Canonical MethodTable):\s+\s+ VERIFY:\s+Fields:\s+ VERIFY:\s+\s+\s+\s+System\.String.*_string\s+ VERIFY:\s+\s+\s+\s+System\.UInt64.*52704621242434 _static\s+