From ed042717ace9966a1308d8d428b6761cd7afdc66 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 17 Feb 2026 17:04:44 -0800 Subject: [PATCH 01/22] adding GetHandleForEnum cDAC API --- docs/design/datacontracts/ComWrappers.md | 31 ++++ docs/design/datacontracts/GC.md | 156 +++++++++++++++- .../gc/datadescriptor/datadescriptor.h | 1 + .../gc/datadescriptor/datadescriptor.inc | 37 ++++ src/coreclr/vm/comcallablewrapper.h | 17 +- .../vm/datadescriptor/datadescriptor.h | 1 + .../vm/datadescriptor/datadescriptor.inc | 28 ++- .../Contracts/IComWrappers.cs | 2 + .../Contracts/IGC.cs | 11 ++ .../DataType.cs | 6 + .../Constants.cs | 11 ++ .../Contracts/ComWrappers_1.cs | 18 ++ .../Contracts/GC/GCFactory.cs | 5 +- .../Contracts/GC/GC_1.cs | 162 ++++++++++++++++- .../Data/ComCallWrapper.cs | 19 ++ .../Data/HandleTable.cs | 20 ++ .../Data/HandleTableBucket.cs | 20 ++ .../Data/HandleTableMap.cs | 29 +++ .../Data/SimpleComCallWrapper.cs | 21 +++ .../Data/TableSegment.cs | 39 ++++ .../ISOSDacInterface.cs | 24 ++- .../SOSDacImpl.cs | 172 +++++++++++++++++- 22 files changed, 817 insertions(+), 13 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ComCallWrapper.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTable.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableBucket.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableMap.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SimpleComCallWrapper.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TableSegment.cs diff --git a/docs/design/datacontracts/ComWrappers.md b/docs/design/datacontracts/ComWrappers.md index 73c3bcd9930728..777d863eac09cd 100644 --- a/docs/design/datacontracts/ComWrappers.md +++ b/docs/design/datacontracts/ComWrappers.md @@ -7,6 +7,12 @@ This contract is for getting information related to COM wrappers. ``` csharp // Get the address of the external COM object TargetPointer GetComWrappersIdentity(TargetPointer rcw); + +// Get the refcount for a COM call wrapper. +ulong GetRefCount(TargetPointer ccw); + +// Check whether the COM wrappers handle is weak. +bool IsHandleWeak(TargetPointer ccw); ``` ## Version 1 @@ -15,10 +21,14 @@ Data descriptors used: | Data Descriptor Name | Field | Meaning | | --- | --- | --- | | `NativeObjectWrapperObject` | `ExternalComObject` | Address of the external COM object | +| `ComCallWrapper` | `SimpleWrapper` | Address of the associated `SimpleComCallWrapper` | +| `SimpleComCallWrapper` | `RefCount` | The wrapper refcount value | +| `SimpleComCallWrapper` | `Flags` | Bit flags for wrapper properties | Global variables used: | Global Name | Type | Purpose | | --- | --- | --- | +| `ComRefcountMask` | `long` | Mask applied to `SimpleComCallWrapper.RefCount` to produce the visible refcount | Contracts used: | Contract Name | @@ -26,8 +36,29 @@ Contracts used: ``` csharp + +private enum Flags +{ + IsHandleWeak = 0x4, +} + public TargetPointer GetComWrappersIdentity(TargetPointer address) { return _target.ReadPointer(address + /* NativeObjectWrapperObject::ExternalComObject offset */); } + +public ulong GetRefCount(TargetPointer address) +{ + var ccw = _target.ReadPointer(address + /* ComCallWrapper::SimpleWrapper offset */); + ulong refCount = _target.Read(ccw + /* SimpleComCallWrapper::RefCount offset */); + long refCountMask = _target.ReadGlobal("ComRefcountMask"); + return refCount & (ulong)refCountMask; +} + +public bool IsHandleWeak(TargetPointer address) +{ + var ccw = _target.ReadPointer(address + /* ComCallWrapper::SimpleWrapper offset */); + uint flags = _target.Read(ccw + /* SimpleComCallWrapper::Flags offset */); + return (flags & Flags.IsHandleWeak) != 0; +} ``` diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index bbb6d3ebafb121..ae1ca440ec7150 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -157,6 +157,15 @@ Data descriptors used: | `OomHistory` | LohP | GC | Large object heap flag indicating if OOM was related to LOH | | `GCAllocContext` | Pointer | VM | Current GCAllocContext pointer | | `GCAllocContext` | Limit | VM | Pointer to the GCAllocContext limit | +| `HandleTableMap` | BucketsPtr | GC | Pointer to the bucket pointer array | +| `HandleTableMap` | Next | GC | Pointer to the next handle table map in the linked list | +| `HandleTableBucket` | Table | GC | Pointer to per-heap `HandleTable*` array | +| `HandleTable` | SegmentList | GC | Head of linked list of handle table segments | +| `TableSegment` | NextSegment | GC | Pointer to the next segment | +| `TableSegment` | RgTail | GC | Tail block index per handle type | +| `TableSegment` | RgAllocation | GC | Circular block-list links per block | +| `TableSegment` | RgValue | GC | Start of handle value storage | +| `TableSegment` | RgUserData | GC | Auxiliary per-block metadata (e.g. secondary handle blocks) | Global variables used: | Global Name | Type | Source | Purpose | @@ -192,15 +201,26 @@ Global variables used: | `GCHeapExpandMechanisms` | TargetPointer | GC | Data array stored per heap (in workstation builds) | | `GCHeapInterestingMechanismBits` | TargetPointer | GC | Data array stored per heap (in workstation builds) | | `CurrentGCState` | uint | GC | `c_gc_state` enum value. Only available when `GCIdentifiers` contains `background`. | -| `DynamicAdaptationMode | int | GC | GC heap dynamic adaptation mode. Only available when `GCIdentifiers` contains `dynamic_heap`. | +| `DynamicAdaptationMode` | int | GC | GC heap dynamic adaptation mode. Only available when `GCIdentifiers` contains `dynamic_heap`. | | `GCLowestAddress` | TargetPointer | VM | Lowest GC address as recorded by the VM/GC interface | | `GCHighestAddress` | TargetPointer | VM | Highest GC address as recorded by the VM/GC interface | +| `HandleTableMap` | TargetPointer | GC | Pointer to the head of the handle table map linked list | +| `InitialHandleTableArraySize` | uint | GC | Number of bucket entries in each `HandleTableMap` | +| `HandleBlocksPerSegment` | uint | GC | Number of blocks in each `TableSegment` | +| `HandleMaxInternalTypes` | uint | GC | Number of handle types (length of `TableSegment.RgTail`) | +| `HandlesPerBlock` | uint | GC | Number of handles in each handle block | +| `BlockInvalid` | byte | GC | Sentinel value indicating an invalid handle block index | +| `DebugDestroyedHandleValue` | TargetPointer | GC | Sentinel handle value used for destroyed handles | +| `FeatureCOMInterop` | byte | VM | Non-zero when COM interop support is enabled | +| `FeatureComWrappers` | byte | VM | Non-zero when `ComWrappers` support is enabled | +| `FeatureObjCMarshal` | byte | VM | Non-zero when Objective-C marshal support is enabled | +| `FeatureJavaMarshal` | byte | VM | Non-zero when Java marshal support is enabled | Contracts used: | Contract Name | | --- | -| _(none)_ | - +| ComWrappers | +| Object | Constants used: | Name | Type | Purpose | Value | @@ -548,3 +568,133 @@ private List ReadGCHeapDataArray(TargetPointer arrayStart, uint len return arr; } ``` + +GetHandles +```csharp +List IGC.GetHandles(uint[] types) +{ + List handles = new(); + TargetPointer handleTableMap = _target.ReadGlobalPointer("HandleTableMap"); + // for each handleTableMap in the linked list + while (handleTableMap != TargetPointer.Null) + { + TargetPointer bucketsPtr = target.ReadPointer(handleTableMap + /* HandleTableMap::BucketsPtr offset */); + foreach (/* read global variable "InitialHandleTableArraySize" bucketPtrs starting at bucketsPtr */) + { + if (bucketPtr == TargetPointer.Null) + continue; + + int heapCount = (int)((IGC)this).GetGCHeapCount(); + for (int j = 0; j < heapCount; j++) + { + // double dereference to iterate handle tables per array element per GC heap - native equivalent = map->pBuckets[i]->pTable[j] + TargetPointer table = target.ReadPointer(bucketPtr + /* HandleTableBucket::Table offset */); + TargetPointer handleTablePtr = _target.ReadPointer(table + (ulong)(j * _target.PointerSize)); + if (handleTablePtr == TargetPointer.Null) + continue; + + foreach (uint type in types) + { + // initialize segmentPtr and iterate through the linked list of segments. + TargetPointer segmentPtr = target.ReadPointer(handleTablePtr + /* HandleTable::SegmentList offset */); + if (segmentPtr == TargetPointer.Null) + continue; + do + { + GetHandlesForSegment(segmentPtr, type, handles); + segmentPtr = target.ReadPointer(segmentPtr + /* TableSegment::NextSegment offset */); + } while (segmentPtr != TargetPointer.Null); + } + } + } + handleTableMap = target.ReadPointer(handleTableMap + /* HandleTableMap::Next offset */); + } + return handles; +} + +uint[] IGC.GetSupportedHandleTypes() +{ + // currently supported types: HNDTYPE_WEAK_SHORT, HNDTYPE_WEAK_LONG, HNDTYPE_STRONG, HNDTYPE_PINNED, HNDTYPE_DEPENDENT, HNDTYPE_WEAK_INTERIOR_POINTER, HNDTYPE_REFCOUNTED (conditional on at least one of global variables "FeatureCOMInterop", "FeatureComWrappers", and "FeatureObjCMarshal"), and HNDTYPE_CROSSREFERENCE (conditional on global variable "FeatureJavaMarshal") +} + +private void GetHandlesForSegment(TargetPointer segmentPtr, uint type, List handles) +{ + // GC handles are stored in circular linked lists per segment and handle type. + // RgTail = array of bytes that is global variable "HandleMaxInternalTypes" long. + // Contains tail block indices for each GC handle type. + // RgAllocation = byte array of block indices that are linked together to find all blocks for a given type. It is global variable "HandleBlocksPerSegment" long + // RgUser = byte array of block indices for extra handle info such as dependent handles. It is also "HandleBlocksPerSegment" long. + // For example, target.Read(segmentPtr + TableSegment::RgTail offset + x); => RgTail[x]; + byte uBlock = target.Read(segmentPtr + /* TableSegment::RgTail offset */ + type); + if (uBlock == target.ReadGlobal("BlockInvalid")) + return; + uBlock = target.Read(segmentPtr + /* TableSegment::RgAllocation offset */ + uBlock); + byte uHead = uBlock; + do + { + if (HasSecondary(type)) + { + byte blockIndex = target.Read(segmentPtr + /* TableSegment::RgUserData offset */ + uBlock); + if (blockIndex == target.ReadGlobal("BlockInvalid")) + continue; + } + GetHandlesForBlock(segmentPtr, uBlock, type, handles); + // update uBlock + uBlock = target.Read(segmentPtr + /* TableSegment::RgAllocation offset */ + uBlock); + } while (uBlock != uHead); +} + +private void GetHandlesForBlock(TargetPointer segmentPtr, byte uBlock, uint type, List handles) +{ + for (uint k = 0; k < target.ReadGlobal("HandlesPerBlock"); k++) + { + uint offset = uBlock * target.ReadGlobal("HandlesPerBlock") + k; + TargetPointer handleAddress = target.ReadPointer(segmentPtr + /* TableSegment::RgValue offset */ + offset * (uint)_target.PointerSize); + TargetPointer handle = _target.ReadPointer(handleAddress); + if (handle == TargetPointer.Null || handle == target.ReadGlobalPointer("DebugDestroyedHandleValue")) + continue; + handles.Add(CreateHandleData(handleAddress, uBlock, k, segmentPtr, type)); + } +} + +private static bool IsStrongReference(uint type) => // HNDTYPE_STRONG || HNDTYPE_PINNED; +private static bool HasSecondary(uint type) => // HNDTYPE_DEPENDENT || HNDTYPE_WEAK_INTERIOR_POINTER || HNDTYPE_CROSSREFERENCE; +private static bool IsRefCounted(uint type) => // HNDTYPE_REFCOUNTED; + +private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, uint intraBlockIndex, TargetPointer segmentPtr, uint type) +{ + HandleData handleData = default; + handleData.Handle = handleAddress; + handleData.Type = type; + handleData.JupiterRefCount = 0; + handleData.IsPegged = false; + handleData.StrongReference = IsStrongReference(type); + if (HasSecondary(type)) + { + byte blockIndex = target.Read(segmentPtr + /* TableSegment::RgUserData offset */ + uBlock); + uint offset = blockIndex * target.ReadGlobal("HandlesPerBlock") + intraBlockIndex; + TargetPointer secondaryPtr = target.ReadPointer(segmentPtr + /* TableSegment::RgValue offset */ + offset * _target.PointerSize); + handleData.Secondary = _target.ReadPointer(secondaryPtr); + } + else + { + handleData.Secondary = 0; + } + + if (_target.ReadGlobal("FeatureCOMInterop") != 0 && IsRefCounted(type)) + { + IObject obj = _target.Contracts.Object; + TargetPointer handle = _target.ReadPointer(handleAddress); + obj.GetBuiltInComData(handle, out _, out TargetPointer ccw); + if (ccw != TargetPointer.Null) + { + IComWrappers comWrappers = _target.Contracts.ComWrappers; + handleData.RefCount = (uint)comWrappers.GetRefCount(ccw); + handleData.StrongReference = handleData.StrongReference || handleData.RefCount > 0 && !comWrappers.IsHandleWeak(ccw); + } + } + + return handleData; +} + +``` diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.h b/src/coreclr/gc/datadescriptor/datadescriptor.h index d77cf6e3e2a8ad..e337cc0ca650e3 100644 --- a/src/coreclr/gc/datadescriptor/datadescriptor.h +++ b/src/coreclr/gc/datadescriptor/datadescriptor.h @@ -9,6 +9,7 @@ #include "gc.h" #include "gcscan.h" #include "gchandletableimpl.h" +#include "handletablepriv.h" #include "gceventstatus.h" #ifdef SERVER_GC diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.inc b/src/coreclr/gc/datadescriptor/datadescriptor.inc index 3b97e4d8f2a280..29a9d1bd14a5df 100644 --- a/src/coreclr/gc/datadescriptor/datadescriptor.inc +++ b/src/coreclr/gc/datadescriptor/datadescriptor.inc @@ -79,6 +79,31 @@ CDAC_TYPE_FIELD(OomHistory, /*nuint*/, AvailablePagefileMb, offsetof(oom_history CDAC_TYPE_FIELD(OomHistory, /*uint32*/, LohP, offsetof(oom_history, loh_p)) CDAC_TYPE_END(OomHistory) +CDAC_TYPE_BEGIN(HandleTableMap) +CDAC_TYPE_INDETERMINATE(HandleTableMap) +CDAC_TYPE_FIELD(HandleTableMap, /*pointer*/, BucketsPtr, offsetof(HandleTableMap, pBuckets)) +CDAC_TYPE_FIELD(HandleTableMap, /*pointer*/, Next, offsetof(HandleTableMap, pNext)) +CDAC_TYPE_END(HandleTableMap) + +CDAC_TYPE_BEGIN(HandleTableBucket) +CDAC_TYPE_INDETERMINATE(HandleTableBucket) +CDAC_TYPE_FIELD(HandleTableBucket, /*pointer*/, Table, offsetof(HandleTableBucket, pTable)) +CDAC_TYPE_END(HandleTableBucket) + +CDAC_TYPE_BEGIN(HandleTable) +CDAC_TYPE_INDETERMINATE(HandleTable) +CDAC_TYPE_FIELD(HandleTable, /*pointer*/, SegmentList, offsetof(HandleTable, pSegmentList)) +CDAC_TYPE_END(HandleTable) + +CDAC_TYPE_BEGIN(TableSegment) +CDAC_TYPE_INDETERMINATE(TableSegment) +CDAC_TYPE_FIELD(TableSegment, /*pointer*/, NextSegment, offsetof(TableSegment, pNextSegment)) +CDAC_TYPE_FIELD(TableSegment, /*uint8_t[]*/, RgAllocation, offsetof(TableSegment, rgAllocation)) +CDAC_TYPE_FIELD(TableSegment, /*uint8_t[]*/, RgTail, offsetof(TableSegment, rgTail)) +CDAC_TYPE_FIELD(TableSegment, /*uint8_t[]*/, RgValue, offsetof(TableSegment, rgValue)) +CDAC_TYPE_FIELD(TableSegment, /*uint8_t[]*/, RgUserData, offsetof(TableSegment, rgUserData)) +CDAC_TYPE_END(TableSegment) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -90,6 +115,16 @@ CDAC_GLOBAL(CompactReasonsLength, /*uint32*/, MAX_COMPACT_REASONS_COUNT) CDAC_GLOBAL(ExpandMechanismsLength, /*uint32*/, MAX_EXPAND_MECHANISMS_COUNT) CDAC_GLOBAL(InterestingMechanismBitsLength, /*uint32*/, MAX_GC_MECHANISM_BITS_COUNT) CDAC_GLOBAL(GlobalMechanismsLength, /*uint32*/, MAX_GLOBAL_GC_MECHANISMS_COUNT) +CDAC_GLOBAL(InitialHandleTableArraySize, /*uint32*/, INITIAL_HANDLE_TABLE_ARRAY_SIZE) +#ifdef DEBUG_DestroyedHandleValue +CDAC_GLOBAL(DebugDestroyedHandleValue, /*uint32*/, (uintptr_t)DEBUG_DestroyedHandleValue) +#else +CDAC_GLOBAL(DebugDestroyedHandleValue, /*uint32*/, (uintptr_t)(0)) +#endif +CDAC_GLOBAL(HandleBlocksPerSegment, /*uint32*/, HANDLE_BLOCKS_PER_SEGMENT) +CDAC_GLOBAL(HandleMaxInternalTypes, /*uint32*/, HANDLE_MAX_INTERNAL_TYPES) +CDAC_GLOBAL(HandlesPerBlock, /*uint32*/, HANDLE_HANDLES_PER_BLOCK) +CDAC_GLOBAL(BlockInvalid, /*uint8*/, BLOCK_INVALID) #ifndef SERVER_GC @@ -116,6 +151,8 @@ CDAC_GLOBAL_POINTER(GCHeapExpandMechanisms, cdac_data::Ex CDAC_GLOBAL_POINTER(GCHeapInterestingMechanismBits, cdac_data::InterestingMechanismBits) #endif // !SERVER_GC +CDAC_GLOBAL_POINTER(HandleTableMap, &g_HandleTableMap) + #ifdef SERVER_GC #define GC_TYPE server #else // SERVER_GC diff --git a/src/coreclr/vm/comcallablewrapper.h b/src/coreclr/vm/comcallablewrapper.h index 6b52861b66f030..d79359ae4a3727 100644 --- a/src/coreclr/vm/comcallablewrapper.h +++ b/src/coreclr/vm/comcallablewrapper.h @@ -1032,6 +1032,13 @@ class ComCallWrapper // Pointer to the next wrapper. PTR_ComCallWrapper m_pNext; + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t SimpleWrapper = offsetof(ComCallWrapper, m_pSimpleWrapper); }; FORCEINLINE void CCWRelease(ComCallWrapper* p) @@ -1598,7 +1605,15 @@ public : // This maintains the 32-bit COM refcount in 64-bits // to enable also tracking the Cleanup sentinel. See code:CLEANUP_SENTINEL LONGLONG m_llRefCount; - }; + friend struct ::cdac_data; +}; + +template<> +struct cdac_data +{ + static constexpr size_t RefCount = offsetof(SimpleComCallWrapper, m_llRefCount); + static constexpr size_t Flags = offsetof(SimpleComCallWrapper, m_flags); +}; //-------------------------------------------------------------------------------- // ComCallWrapper* ComCallWrapper::InlineGetWrapper(OBJECTREF* ppObj) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.h b/src/coreclr/vm/datadescriptor/datadescriptor.h index 651975ba0cc4c7..47737beadeec3a 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.h +++ b/src/coreclr/vm/datadescriptor/datadescriptor.h @@ -13,6 +13,7 @@ #include #include "cdacplatformmetadata.hpp" #include "interoplibinterface_comwrappers.h" +#include "comcallablewrapper.h" #include "methodtable.h" #include "threads.h" #include "vars.hpp" diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 660f79cf77edcb..3b2a1bad1ef73a 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1006,6 +1006,17 @@ CDAC_TYPE_FIELD(DynamicILBlobTable, /*uint32*/, EntryMethodToken, cdac_data::EntryIL) CDAC_TYPE_END(DynamicILBlobTable) +CDAC_TYPE_BEGIN(ComCallWrapper) +CDAC_TYPE_INDETERMINATE(ComCallWrapper) +CDAC_TYPE_FIELD(ComCallWrapper, /*pointer*/, SimpleWrapper, cdac_data::SimpleWrapper) +CDAC_TYPE_END(ComCallWrapper) + +CDAC_TYPE_BEGIN(SimpleComCallWrapper) +CDAC_TYPE_INDETERMINATE(SimpleComCallWrapper) +CDAC_TYPE_FIELD(SimpleComCallWrapper, /*pointer*/, RefCount, cdac_data::RefCount) +CDAC_TYPE_FIELD(SimpleComCallWrapper, /*uint32*/, Flags, cdac_data::Flags) +CDAC_TYPE_END(SimpleComCallWrapper) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -1070,6 +1081,21 @@ CDAC_GLOBAL(FeatureCOMInterop, uint8, 1) #else CDAC_GLOBAL(FeatureCOMInterop, uint8, 0) #endif +#if FEATURE_COMWRAPPERS +CDAC_GLOBAL(FeatureComWrappers, uint8, 1) +#else +CDAC_GLOBAL(FeatureComWrappers, uint8, 0) +#endif +#if FEATURE_OBJCMARSHAL +CDAC_GLOBAL(FeatureObjCMarshal, uint8, 1) +#else +CDAC_GLOBAL(FeatureObjCMarshal, uint8, 0) +#endif +#if FEATURE_JAVAMARSHAL +CDAC_GLOBAL(FeatureJavaMarshal, uint8, 1) +#else +CDAC_GLOBAL(FeatureJavaMarshal, uint8, 0) +#endif #ifdef FEATURE_ON_STACK_REPLACEMENT CDAC_GLOBAL(FeatureOnStackReplacement, uint8, 1) #else @@ -1134,7 +1160,7 @@ CDAC_GLOBAL_POINTER(MethodDescSizeTable, &MethodDesc::s_ClassificationSizeTable) CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address) CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address) - +CDAC_GLOBAL(ComRefcountMask, int64, SimpleComCallWrapper::COM_REFCOUNT_MASK) // It is important for the subdescriptor pointers to be the last pointers in the global structure. // EnumMemDataDescriptors in enummem.cpp hardcodes the number of subdescriptors and utilizes diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IComWrappers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IComWrappers.cs index 257012cb5528e0..59a5745c82570f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IComWrappers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IComWrappers.cs @@ -9,6 +9,8 @@ public interface IComWrappers : IContract { static string IContract.Name { get; } = nameof(ComWrappers); TargetPointer GetComWrappersIdentity(TargetPointer address) => throw new NotImplementedException(); + ulong GetRefCount(TargetPointer address) => throw new NotImplementedException(); + bool IsHandleWeak(TargetPointer address) => throw new NotImplementedException(); } public readonly struct ComWrappers : IComWrappers diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs index e0213bc48a086c..ca8ae214d80b6e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs @@ -19,6 +19,15 @@ public static class GCIdentifiers public const string DynamicHeapCount = "dynamic_heap"; } +public record struct HandleData( + TargetPointer Handle, + TargetPointer Secondary, + uint Type, + bool StrongReference, + uint RefCount, + uint JupiterRefCount, + bool IsPegged); + public readonly struct GCHeapData { public TargetPointer MarkArray { get; init; } @@ -102,6 +111,8 @@ public interface IGC : IContract GCOomData GetOomData() => throw new NotImplementedException(); // server variant GCOomData GetOomData(TargetPointer heapAddress) => throw new NotImplementedException(); + List GetHandles(uint[] types) => throw new NotImplementedException(); + uint[] GetSupportedHandleTypes() => throw new NotImplementedException(); } public readonly struct GC : IGC diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs index 35f243961ee539..6720ac9ff3d81d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -135,6 +135,8 @@ public enum DataType HijackFrame, TailCallFrame, StubDispatchFrame, + ComCallWrapper, + SimpleComCallWrapper, /* GC Data Types */ @@ -144,4 +146,8 @@ public enum DataType CFinalize, HeapSegment, OomHistory, + HandleTableMap, + HandleTableBucket, + HandleTable, + TableSegment, } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 1903486453ce0d..7b473cf20e6ab9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -15,6 +15,9 @@ public static class Globals public const string GCThread = nameof(GCThread); public const string FeatureCOMInterop = nameof(FeatureCOMInterop); + public const string FeatureComWrappers = nameof(FeatureComWrappers); + public const string FeatureObjCMarshal = nameof(FeatureObjCMarshal); + public const string FeatureJavaMarshal = nameof(FeatureJavaMarshal); public const string FeatureOnStackReplacement = nameof(FeatureOnStackReplacement); public const string ObjectToMethodTableUnmask = nameof(ObjectToMethodTableUnmask); @@ -70,6 +73,7 @@ public static class Globals public const string ProfilerControlBlock = nameof(ProfilerControlBlock); public const string MethodDescSizeTable = nameof(MethodDescSizeTable); + public const string ComRefcountMask = nameof(ComRefcountMask); public const string HashMapSlotsPerBucket = nameof(HashMapSlotsPerBucket); public const string HashMapValueMask = nameof(HashMapValueMask); @@ -119,6 +123,13 @@ public static class Globals public const string GCHeapCompactReasons = nameof(GCHeapCompactReasons); public const string GCHeapExpandMechanisms = nameof(GCHeapExpandMechanisms); public const string GCHeapInterestingMechanismBits = nameof(GCHeapInterestingMechanismBits); + public const string HandleTableMap = nameof(HandleTableMap); + public const string InitialHandleTableArraySize = nameof(InitialHandleTableArraySize); + public const string DebugDestroyedHandleValue = nameof(DebugDestroyedHandleValue); + public const string HandleBlocksPerSegment = nameof(HandleBlocksPerSegment); + public const string HandleMaxInternalTypes = nameof(HandleMaxInternalTypes); + public const string HandlesPerBlock = nameof(HandlesPerBlock); + public const string BlockInvalid = nameof(BlockInvalid); } public static class FieldNames { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs index 98b0a54dc0c416..0099b5e0ec1306 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs @@ -11,6 +11,10 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; internal readonly struct ComWrappers_1 : IComWrappers { private readonly Target _target; + private enum Flags + { + IsHandleWeak = 0x4, + } public ComWrappers_1(Target target) { @@ -22,4 +26,18 @@ public TargetPointer GetComWrappersIdentity(TargetPointer address) Data.NativeObjectWrapperObject wrapper = _target.ProcessedData.GetOrAdd(address); return wrapper.ExternalComObject; } + + public ulong GetRefCount(TargetPointer address) + { + Data.ComCallWrapper wrapper = _target.ProcessedData.GetOrAdd(address); + Data.SimpleComCallWrapper simpleWrapper = _target.ProcessedData.GetOrAdd(wrapper.SimpleWrapper); + return simpleWrapper.RefCount & (ulong)_target.ReadGlobal(Constants.Globals.ComRefcountMask); + } + + public bool IsHandleWeak(TargetPointer address) + { + Data.ComCallWrapper wrapper = _target.ProcessedData.GetOrAdd(address); + Data.SimpleComCallWrapper simpleWrapper = _target.ProcessedData.GetOrAdd(wrapper.SimpleWrapper); + return (simpleWrapper.Flags & (uint)Flags.IsHandleWeak) != 0; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCFactory.cs index 9eb2570226ed47..b822b84ddeb3f9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCFactory.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCFactory.cs @@ -9,9 +9,12 @@ public sealed class GCFactory : IContractFactory { IGC IContractFactory.CreateContract(Target target, int version) { + uint handlesPerBlock = target.ReadGlobal(Constants.Globals.HandlesPerBlock); + byte blockInvalid = target.ReadGlobal(Constants.Globals.BlockInvalid); + TargetPointer debugDestroyedHandleValue = target.ReadGlobalPointer(Constants.Globals.DebugDestroyedHandleValue); return version switch { - 1 => new GC_1(target), + 1 => new GC_1(target, handlesPerBlock, blockInvalid, debugDestroyedHandleValue), _ => default(GC), }; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index cf101d271b3767..ff3c0dc69e1d12 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -19,11 +19,29 @@ private enum GCType Server, } + private enum HandleType_1 + { + HNDTYPE_WEAK_SHORT = 0, + HNDTYPE_WEAK_LONG = 1, + HNDTYPE_STRONG = 2, + HNDTYPE_PINNED = 3, + HNDTYPE_REFCOUNTED = 5, + HNDTYPE_DEPENDENT = 6, + HNDTYPE_WEAK_INTERIOR_POINTER = 10, + HNDTYPE_CROSSREFERENCE = 11 + } + private readonly Target _target; + private readonly uint _handlesPerBlock; + private readonly byte _blockInvalid; + private readonly TargetPointer _debugDestroyedHandleValue; - internal GC_1(Target target) + internal GC_1(Target target, uint handlesPerBlock, byte blockInvalid, TargetPointer debugDestroyedHandleValue) { _target = target; + _handlesPerBlock = handlesPerBlock; + _blockInvalid = blockInvalid; + _debugDestroyedHandleValue = debugDestroyedHandleValue; } string[] IGC.GetGCIdentifiers() @@ -277,4 +295,146 @@ private bool IsDatasEnabled() string[] identifiers = ((IGC)this).GetGCIdentifiers(); return identifiers.Contains(GCIdentifiers.DynamicHeapCount); } + + List IGC.GetHandles(uint[] types) + { + List typesList = types.ToList(); + typesList.Sort(); + List handles = new(); + TargetPointer handleTableMap = _target.ReadGlobalPointer(Constants.Globals.HandleTableMap); + while (handleTableMap != TargetPointer.Null) + { + Data.HandleTableMap handleTableData = _target.ProcessedData.GetOrAdd(handleTableMap); + foreach (TargetPointer bucketPtr in handleTableData.BucketsPtr) + { + if (bucketPtr == TargetPointer.Null) + continue; + + Data.HandleTableBucket bucket = _target.ProcessedData.GetOrAdd(bucketPtr); + int heapCount = (int)((IGC)this).GetGCHeapCount(); + for (int j = 0; j < heapCount; j++) + { + TargetPointer handleTablePtr = _target.ReadPointer(bucket.Table + (ulong)(j * _target.PointerSize)); + if (handleTablePtr == TargetPointer.Null) + continue; + + Data.HandleTable handleTable = _target.ProcessedData.GetOrAdd(handleTablePtr); + foreach (uint type in typesList) + { + if (handleTable.SegmentList == TargetPointer.Null) + continue; + TargetPointer segmentPtr = handleTable.SegmentList; + do + { + Data.TableSegment tableSegment = _target.ProcessedData.GetOrAdd(segmentPtr); + segmentPtr = tableSegment.NextSegment; + GetHandlesForSegment(tableSegment, type, handles); + } while (segmentPtr != TargetPointer.Null); + } + } + } + handleTableMap = handleTableData.Next; + } + return handles; + } + + uint[] IGC.GetSupportedHandleTypes() + { + List supportedTypes = + [ + .. new uint[] + { + (uint)HandleType_1.HNDTYPE_WEAK_SHORT, + (uint)HandleType_1.HNDTYPE_WEAK_LONG, + (uint)HandleType_1.HNDTYPE_STRONG, + (uint)HandleType_1.HNDTYPE_PINNED, + (uint)HandleType_1.HNDTYPE_DEPENDENT, + (uint)HandleType_1.HNDTYPE_WEAK_INTERIOR_POINTER + }, + ]; + if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) != 0 || _target.ReadGlobal(Constants.Globals.FeatureComWrappers) != 0 || _target.ReadGlobal(Constants.Globals.FeatureObjCMarshal) != 0) + { + supportedTypes.Add((uint)HandleType_1.HNDTYPE_REFCOUNTED); + } + if (_target.ReadGlobal(Constants.Globals.FeatureJavaMarshal) != 0) + { + supportedTypes.Add((uint)HandleType_1.HNDTYPE_CROSSREFERENCE); + } + return supportedTypes.ToArray(); + } + + private void GetHandlesForSegment(Data.TableSegment tableSegment, uint type, List handles) + { + byte uBlock = tableSegment.RgTail[type]; + if (uBlock == _blockInvalid) + return; + uBlock = tableSegment.RgAllocation[uBlock]; + byte uHead = uBlock; + byte uBlockOld; + // for each block in the segment for the given handle type + do + { + uBlockOld = uBlock; + uBlock = tableSegment.RgAllocation[uBlock]; + if (HasSecondary(type)) + { + byte blockIndex = tableSegment.RgUserData[uBlockOld]; + if (blockIndex == _blockInvalid) + continue; + } + GetHandlesForBlock(tableSegment, uBlockOld, type, handles); + } while (uBlock != uHead); + } + + private void GetHandlesForBlock(Data.TableSegment tableSegment, byte uBlock, uint type, List handles) + { + for (uint k = 0; k < _handlesPerBlock; k++) + { + uint offset = uBlock * _handlesPerBlock + k; + TargetPointer handleAddress = tableSegment.RgValue + offset * (uint)_target.PointerSize; + TargetPointer handle = _target.ReadPointer(handleAddress); + if (handle == TargetPointer.Null || handle == _debugDestroyedHandleValue) + continue; + handles.Add(CreateHandleData(handleAddress, uBlock, k, tableSegment, type)); + } + } + + private static bool IsStrongReference(uint type) => type == (uint)HandleType_1.HNDTYPE_STRONG || type == (uint)HandleType_1.HNDTYPE_PINNED; + private static bool HasSecondary(uint type) => type == (uint)HandleType_1.HNDTYPE_DEPENDENT || type == (uint)HandleType_1.HNDTYPE_WEAK_INTERIOR_POINTER || type == (uint)HandleType_1.HNDTYPE_CROSSREFERENCE; + private static bool IsRefCounted(uint type) => type == (uint)HandleType_1.HNDTYPE_REFCOUNTED; + + private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, uint intraBlockIndex, Data.TableSegment tableSegment, uint type) + { + HandleData handleData = default; + handleData.Handle = handleAddress; + handleData.Type = type; + handleData.JupiterRefCount = 0; + handleData.IsPegged = false; + handleData.StrongReference = IsStrongReference(type); + if (HasSecondary(type)) + { + byte blockIndex = tableSegment.RgUserData[uBlock]; + uint offset = blockIndex * _handlesPerBlock + intraBlockIndex; + handleData.Secondary = _target.ReadPointer(tableSegment.RgValue + offset * (uint)_target.PointerSize); + } + else + { + handleData.Secondary = 0; + } + + if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) != 0 && IsRefCounted(type)) + { + IObject obj = _target.Contracts.Object; + TargetPointer handle = _target.ReadPointer(handleAddress); + obj.GetBuiltInComData(handle, out _, out TargetPointer ccw); + if (ccw != TargetPointer.Null) + { + IComWrappers comWrappers = _target.Contracts.ComWrappers; + handleData.RefCount = (uint)comWrappers.GetRefCount(ccw); + handleData.StrongReference = handleData.StrongReference || handleData.RefCount > 0 && !comWrappers.IsHandleWeak(ccw); + } + } + + return handleData; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ComCallWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ComCallWrapper.cs new file mode 100644 index 00000000000000..e755ee1fa54ae3 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ComCallWrapper.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class ComCallWrapper : IData +{ + static ComCallWrapper IData.Create(Target target, TargetPointer address) => new ComCallWrapper(target, address); + public ComCallWrapper(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.ComCallWrapper); + + SimpleWrapper = target.ReadPointer(address + (ulong)type.Fields[nameof(SimpleWrapper)].Offset); + } + + public TargetPointer SimpleWrapper { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTable.cs new file mode 100644 index 00000000000000..b81dcc060130b0 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTable.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection.Metadata; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class HandleTable : IData +{ + static HandleTable IData.Create(Target target, TargetPointer address) + => new HandleTable(target, address); + + public HandleTable(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.HandleTable); + SegmentList = target.ReadPointer(address + (ulong)type.Fields[nameof(SegmentList)].Offset); + } + + public TargetPointer SegmentList { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableBucket.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableBucket.cs new file mode 100644 index 00000000000000..b472d9c3ac1b38 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableBucket.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Reflection.Metadata; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class HandleTableBucket : IData +{ + static HandleTableBucket IData.Create(Target target, TargetPointer address) + => new HandleTableBucket(target, address); + + public HandleTableBucket(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.HandleTableBucket); + Table = target.ReadPointer(address + (ulong)type.Fields[nameof(Table)].Offset); + } + + public TargetPointer Table { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableMap.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableMap.cs new file mode 100644 index 00000000000000..4331f966ed5b30 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableMap.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Reflection.Metadata; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class HandleTableMap : IData +{ + static HandleTableMap IData.Create(Target target, TargetPointer address) + => new HandleTableMap(target, address); + + public HandleTableMap(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.HandleTableMap); + TargetPointer bucketsPtr = target.ReadPointer(address + (ulong)type.Fields[nameof(BucketsPtr)].Offset); + uint arrayLength = target.ReadGlobal(Constants.Globals.InitialHandleTableArraySize); + for (int i = 0; i < arrayLength; i++) + { + TargetPointer bucketPtr = target.ReadPointer(bucketsPtr + (ulong)(i * target.PointerSize)); + BucketsPtr.Add(bucketPtr); + } + Next = target.ReadPointer(address + (ulong)type.Fields[nameof(Next)].Offset); + } + + public List BucketsPtr { get; init; } = new List(); + public TargetPointer Next { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SimpleComCallWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SimpleComCallWrapper.cs new file mode 100644 index 00000000000000..33280185efe0b6 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SimpleComCallWrapper.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class SimpleComCallWrapper : IData +{ + static SimpleComCallWrapper IData.Create(Target target, TargetPointer address) => new SimpleComCallWrapper(target, address); + public SimpleComCallWrapper(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.SimpleComCallWrapper); + + RefCount = target.Read(address + (ulong)type.Fields[nameof(RefCount)].Offset); + Flags = target.Read(address + (ulong)type.Fields[nameof(Flags)].Offset); + } + + public ulong RefCount { get; init; } + public uint Flags { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TableSegment.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TableSegment.cs new file mode 100644 index 00000000000000..435ba646511c34 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TableSegment.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Data; + +internal sealed class TableSegment : IData +{ + static TableSegment IData.Create(Target target, TargetPointer address) => new TableSegment(target, address); + public TableSegment(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.TableSegment); + NextSegment = target.ReadPointer(address + (ulong)type.Fields[nameof(NextSegment)].Offset); + uint handleBlocksPerSegment = target.ReadGlobal(Constants.Globals.HandleBlocksPerSegment); + uint handleMaxInternalTypes = target.ReadGlobal(Constants.Globals.HandleMaxInternalTypes); + + TargetPointer rgTailPtr = address + (ulong)type.Fields[nameof(RgTail)].Offset; + RgTail = new byte[handleMaxInternalTypes]; + target.ReadBuffer(rgTailPtr, RgTail); + + TargetPointer rgAllocationPtr = address + (ulong)type.Fields[nameof(RgAllocation)].Offset; + RgAllocation = new byte[handleBlocksPerSegment]; + target.ReadBuffer(rgAllocationPtr, RgAllocation); + + // let's not read the entire array because it is large and not always fully mapped. + RgValue = address + (ulong)type.Fields[nameof(RgValue)].Offset; + + TargetPointer rgUserDataPtr = address + (ulong)type.Fields[nameof(RgUserData)].Offset; + RgUserData = new byte[handleBlocksPerSegment]; + target.ReadBuffer(rgUserDataPtr, RgUserData); + } + + public TargetPointer NextSegment { get; init; } + public byte[] RgTail { get; init; } + public byte[] RgAllocation { get; init; } + public TargetPointer RgValue { get; init; } + public byte[] RgUserData { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs index a43959d81f63dc..53c7518ddae85d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs @@ -406,6 +406,26 @@ public struct DacpFieldDescData public ClrDataAddress NextField; }; +public struct SOSHandleData +{ + public ClrDataAddress AppDomain; + public ClrDataAddress Handle; + public ClrDataAddress Secondary; + public uint Type; + public int StrongReference; // BOOL + public uint RefCount; + public uint JupiterRefCount; + public int IsPegged; // BOOL +} + +[GeneratedComInterface] +[Guid("3E269830-4A2B-4301-8EE2-D6805B29B2FA")] +public unsafe partial interface ISOSHandleEnum : ISOSEnum +{ + [PreserveSig] + int Next(uint count, [In, Out, MarshalUsing(CountElementName = nameof(count))] SOSHandleData[] handles, uint* pNeeded); +} + [GeneratedComInterface] [Guid("436f00f2-b42a-4b9f-870c-e73db66ae930")] public unsafe partial interface ISOSDacInterface @@ -567,9 +587,9 @@ public unsafe partial interface ISOSDacInterface // Handles [PreserveSig] - int GetHandleEnum(/*ISOSHandleEnum*/ void** ppHandleEnum); + int GetHandleEnum(out ISOSHandleEnum? ppHandleEnum); [PreserveSig] - int GetHandleEnumForTypes([In, MarshalUsing(CountElementName = nameof(count))] uint[] types, uint count, /*ISOSHandleEnum*/ void** ppHandleEnum); + int GetHandleEnumForTypes([In, MarshalUsing(CountElementName = nameof(count))] uint[] types, uint count, out ISOSHandleEnum? ppHandleEnum); [PreserveSig] int GetHandleEnumForGC(uint gen, /*ISOSHandleEnum*/ void** ppHandleEnum); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 4ac39286bb3dad..78df3196304275 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -1315,12 +1315,176 @@ int ISOSDacInterface.GetGCHeapStaticData(DacpGcHeapDetails* details) return hr; } - int ISOSDacInterface.GetHandleEnum(void** ppHandleEnum) - => _legacyImpl is not null ? _legacyImpl.GetHandleEnum(ppHandleEnum) : HResults.E_NOTIMPL; + [GeneratedComClass] + internal sealed unsafe partial class SOSHandleEnum : ISOSHandleEnum + { + private readonly Target _target; + private readonly SOSHandleData[] _handles; + private readonly ISOSHandleEnum? _legacyHandleEnum; + private uint _index; + + public SOSHandleEnum(Target target, uint[] types, ISOSHandleEnum? legacyHandleEnum) + { + _target = target; + _legacyHandleEnum = legacyHandleEnum; + _handles = GetHandles(types); + } + + private SOSHandleData[] GetHandles(uint[] types) + { + IGC gc = _target.Contracts.GC; + List handles = gc.GetHandles(types); + + TargetPointer appDomainPointer = _target.ReadGlobalPointer(Constants.Globals.AppDomain); + TargetPointer appDomain = _target.ReadPointer(appDomainPointer); + ClrDataAddress appDomainClrAddress = appDomain.ToClrDataAddress(_target); + + SOSHandleData[] sosHandles = new SOSHandleData[handles.Count]; + for (int i = 0; i < handles.Count; i++) + { + HandleData h = handles[i]; + sosHandles[i] = new SOSHandleData + { + AppDomain = appDomainClrAddress, + Handle = h.Handle.ToClrDataAddress(_target), + Secondary = h.Secondary.ToClrDataAddress(_target), + Type = h.Type, + StrongReference = h.StrongReference ? 1 : 0, + RefCount = h.RefCount, + JupiterRefCount = h.JupiterRefCount, + IsPegged = h.IsPegged ? 1 : 0, + }; + } + + return sosHandles; + } + + int ISOSHandleEnum.Next(uint count, SOSHandleData[] handles, uint* pNeeded) + { + int hr = HResults.S_OK; + try + { + if (pNeeded is null || handles is null) + throw new NullReferenceException(); + + uint written = 0; + while (written < count && _index < _handles.Length) + handles[written++] = _handles[(int)_index++]; + + *pNeeded = written; + hr = written < _handles.Length ? HResults.S_FALSE : HResults.S_OK; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacyHandleEnum is not null) + { + SOSHandleData[] handlesLocal = new SOSHandleData[count]; + uint neededLocal; + int hrLocal = _legacyHandleEnum.Next(count, handlesLocal, &neededLocal); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + if (hr == HResults.S_OK || hr == HResults.S_FALSE) + { + Debug.Assert(*pNeeded == neededLocal, $"cDAC: {*pNeeded}, DAC: {neededLocal}"); + for (int i = 0; i < neededLocal; i++) + { + Debug.Assert(handles[i].AppDomain == handlesLocal[i].AppDomain, $"cDAC: {handles[i].AppDomain:x}, DAC: {handlesLocal[i].AppDomain:x}"); + Debug.Assert(handles[i].Handle == handlesLocal[i].Handle, $"cDAC: {handles[i].Handle:x}, DAC: {handlesLocal[i].Handle:x}"); + Debug.Assert(handles[i].Secondary == handlesLocal[i].Secondary, $"cDAC: {handles[i].Secondary:x}, DAC: {handlesLocal[i].Secondary:x}"); + Debug.Assert(handles[i].Type == handlesLocal[i].Type, $"cDAC: {handles[i].Type}, DAC: {handlesLocal[i].Type}"); + Debug.Assert(handles[i].StrongReference == handlesLocal[i].StrongReference, $"cDAC: {handles[i].StrongReference}, DAC: {handlesLocal[i].StrongReference}"); + Debug.Assert(handles[i].RefCount == handlesLocal[i].RefCount, $"cDAC: {handles[i].RefCount}, DAC: {handlesLocal[i].RefCount}"); + Debug.Assert(handles[i].JupiterRefCount == handlesLocal[i].JupiterRefCount, $"cDAC: {handles[i].JupiterRefCount}, DAC: {handlesLocal[i].JupiterRefCount}"); + Debug.Assert(handles[i].IsPegged == handlesLocal[i].IsPegged, $"cDAC: {handles[i].IsPegged}, DAC: {handlesLocal[i].IsPegged}"); + } + } + } +#endif + return hr; + } + + int ISOSEnum.Skip(uint count) + { + _index += count; +#if DEBUG + _legacyHandleEnum?.Skip(count); +#endif + return HResults.S_OK; + } + int ISOSEnum.Reset() + { + _index = 0; +#if DEBUG + _legacyHandleEnum?.Reset(); +#endif + return HResults.S_OK; + } + int ISOSEnum.GetCount(uint* pCount) + { + if (pCount is null) return HResults.E_POINTER; + *pCount = (uint)_handles.Length; +#if DEBUG + if (_legacyHandleEnum is not null) + { + uint countLocal; + _legacyHandleEnum.GetCount(&countLocal); + Debug.Assert(countLocal == (uint)_handles.Length); + } +#endif + return HResults.S_OK; + } + } + + int ISOSDacInterface.GetHandleEnum(out ISOSHandleEnum? ppHandleEnum) + { + int hr = HResults.S_OK; + ppHandleEnum = default; + try + { + IGC gc = _target.Contracts.GC; + uint[] supportedHandleTypes = gc.GetSupportedHandleTypes(); + ISOSHandleEnum? legacyHandleEnum = null; +#if DEBUG + if (_legacyImpl is not null) + { + int hrLocal = _legacyImpl.GetHandleEnum(out legacyHandleEnum); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + } +#endif + ppHandleEnum = new SOSHandleEnum(_target, supportedHandleTypes, legacyHandleEnum); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + return hr; + } int ISOSDacInterface.GetHandleEnumForGC(uint gen, void** ppHandleEnum) => _legacyImpl is not null ? _legacyImpl.GetHandleEnumForGC(gen, ppHandleEnum) : HResults.E_NOTIMPL; - int ISOSDacInterface.GetHandleEnumForTypes([In, MarshalUsing(CountElementName = "count")] uint[] types, uint count, void** ppHandleEnum) - => _legacyImpl is not null ? _legacyImpl.GetHandleEnumForTypes(types, count, ppHandleEnum) : HResults.E_NOTIMPL; + int ISOSDacInterface.GetHandleEnumForTypes([In, MarshalUsing(CountElementName = "count")] uint[] types, uint count, out ISOSHandleEnum? ppHandleEnum) + { + int hr = HResults.S_OK; + ppHandleEnum = null; + try + { + ISOSHandleEnum? legacyHandleEnum = null; +#if DEBUG + if (_legacyImpl is not null) + { + int hrLocal = _legacyImpl.GetHandleEnumForTypes(types, count, out legacyHandleEnum); + Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); + } +#endif + ppHandleEnum = new SOSHandleEnum(_target, types, legacyHandleEnum); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + return hr; + } int ISOSDacInterface.GetHeapAllocData(uint count, void* data, uint* pNeeded) => _legacyImpl is not null ? _legacyImpl.GetHeapAllocData(count, data, pNeeded) : HResults.E_NOTIMPL; int ISOSDacInterface.GetHeapAnalyzeData(ClrDataAddress addr, DacpGcHeapAnalyzeData* data) From edcfa8a0f0c8639ed4691f188ee5f95cd0e243cf Mon Sep 17 00:00:00 2001 From: Rachel Date: Mon, 23 Feb 2026 11:14:56 -0800 Subject: [PATCH 02/22] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 2 +- .../Contracts/GC/GC_1.cs | 2 +- .../Data/HandleTable.cs | 1 - .../Data/HandleTableBucket.cs | 1 - .../Data/HandleTableMap.cs | 2 +- .../SOSDacImpl.cs | 2 +- 6 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index ae1ca440ec7150..295c492f6d794f 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -623,7 +623,7 @@ private void GetHandlesForSegment(TargetPointer segmentPtr, uint type, List(segmentPtr + TableSegment::RgTail offset + x); => RgTail[x]; byte uBlock = target.Read(segmentPtr + /* TableSegment::RgTail offset */ + type); if (uBlock == target.ReadGlobal("BlockInvalid")) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index ff3c0dc69e1d12..8dca7b0ea1471c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -431,7 +431,7 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui { IComWrappers comWrappers = _target.Contracts.ComWrappers; handleData.RefCount = (uint)comWrappers.GetRefCount(ccw); - handleData.StrongReference = handleData.StrongReference || handleData.RefCount > 0 && !comWrappers.IsHandleWeak(ccw); + handleData.StrongReference = handleData.StrongReference || (handleData.RefCount > 0 && !comWrappers.IsHandleWeak(ccw)); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTable.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTable.cs index b81dcc060130b0..072c9ef00dfe5d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTable.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTable.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Reflection.Metadata; namespace Microsoft.Diagnostics.DataContractReader.Data; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableBucket.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableBucket.cs index b472d9c3ac1b38..648705c88b31cb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableBucket.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableBucket.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Reflection.Metadata; namespace Microsoft.Diagnostics.DataContractReader.Data; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableMap.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableMap.cs index 4331f966ed5b30..b442c410be0bb0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableMap.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableMap.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Reflection.Metadata; + namespace Microsoft.Diagnostics.DataContractReader.Data; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 5046cf2e2dcf96..3655061d02c41a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -1373,7 +1373,7 @@ int ISOSHandleEnum.Next(uint count, SOSHandleData[] handles, uint* pNeeded) handles[written++] = _handles[(int)_index++]; *pNeeded = written; - hr = written < _handles.Length ? HResults.S_FALSE : HResults.S_OK; + hr = _index < _handles.Length ? HResults.S_FALSE : HResults.S_OK; } catch (System.Exception ex) { From 25e4f10e7bda0e8818601ac06496902f7c70d10d Mon Sep 17 00:00:00 2001 From: Rachel Date: Mon, 23 Feb 2026 11:15:49 -0800 Subject: [PATCH 03/22] Change DebugDestroyedHandleValue type to uintptr_t --- src/coreclr/gc/datadescriptor/datadescriptor.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.inc b/src/coreclr/gc/datadescriptor/datadescriptor.inc index 29a9d1bd14a5df..0115718d33064d 100644 --- a/src/coreclr/gc/datadescriptor/datadescriptor.inc +++ b/src/coreclr/gc/datadescriptor/datadescriptor.inc @@ -117,9 +117,9 @@ CDAC_GLOBAL(InterestingMechanismBitsLength, /*uint32*/, MAX_GC_MECHANISM_BITS_CO CDAC_GLOBAL(GlobalMechanismsLength, /*uint32*/, MAX_GLOBAL_GC_MECHANISMS_COUNT) CDAC_GLOBAL(InitialHandleTableArraySize, /*uint32*/, INITIAL_HANDLE_TABLE_ARRAY_SIZE) #ifdef DEBUG_DestroyedHandleValue -CDAC_GLOBAL(DebugDestroyedHandleValue, /*uint32*/, (uintptr_t)DEBUG_DestroyedHandleValue) +CDAC_GLOBAL(DebugDestroyedHandleValue, /*uintptr_t*/, (uintptr_t)DEBUG_DestroyedHandleValue) #else -CDAC_GLOBAL(DebugDestroyedHandleValue, /*uint32*/, (uintptr_t)(0)) +CDAC_GLOBAL(DebugDestroyedHandleValue, /*uintptr_t*/, (uintptr_t)(0)) #endif CDAC_GLOBAL(HandleBlocksPerSegment, /*uint32*/, HANDLE_BLOCKS_PER_SEGMENT) CDAC_GLOBAL(HandleMaxInternalTypes, /*uint32*/, HANDLE_MAX_INTERNAL_TYPES) From c02b4aaa5887b06ce397b07c6cad1d3652de4d9d Mon Sep 17 00:00:00 2001 From: Rachel Date: Mon, 23 Feb 2026 11:27:21 -0800 Subject: [PATCH 04/22] Update docs/design/datacontracts/GC.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 295c492f6d794f..b8e1cfff826710 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -673,8 +673,7 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui { byte blockIndex = target.Read(segmentPtr + /* TableSegment::RgUserData offset */ + uBlock); uint offset = blockIndex * target.ReadGlobal("HandlesPerBlock") + intraBlockIndex; - TargetPointer secondaryPtr = target.ReadPointer(segmentPtr + /* TableSegment::RgValue offset */ + offset * _target.PointerSize); - handleData.Secondary = _target.ReadPointer(secondaryPtr); + handleData.Secondary = target.ReadPointer(segmentPtr + /* TableSegment::RgValue offset */ + offset * target.PointerSize); } else { From 8a2cccef7184cd02460e70b34c400056fe52065c Mon Sep 17 00:00:00 2001 From: Rachel Date: Mon, 23 Feb 2026 11:32:14 -0800 Subject: [PATCH 05/22] Update docs/design/datacontracts/GC.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index b8e1cfff826710..816f4f8e8012bc 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -649,7 +649,7 @@ private void GetHandlesForBlock(TargetPointer segmentPtr, byte uBlock, uint type for (uint k = 0; k < target.ReadGlobal("HandlesPerBlock"); k++) { uint offset = uBlock * target.ReadGlobal("HandlesPerBlock") + k; - TargetPointer handleAddress = target.ReadPointer(segmentPtr + /* TableSegment::RgValue offset */ + offset * (uint)_target.PointerSize); + TargetPointer handleAddress = segmentPtr + /* TableSegment::RgValue offset */ + offset * (uint)_target.PointerSize; TargetPointer handle = _target.ReadPointer(handleAddress); if (handle == TargetPointer.Null || handle == target.ReadGlobalPointer("DebugDestroyedHandleValue")) continue; From 5b40d424dbc3bb440f97e754ebadae147cbea2b6 Mon Sep 17 00:00:00 2001 From: Rachel Date: Mon, 23 Feb 2026 11:35:28 -0800 Subject: [PATCH 06/22] Update docs/design/datacontracts/ComWrappers.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/ComWrappers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/datacontracts/ComWrappers.md b/docs/design/datacontracts/ComWrappers.md index c2bae5752f966d..00b913ed124f1d 100644 --- a/docs/design/datacontracts/ComWrappers.md +++ b/docs/design/datacontracts/ComWrappers.md @@ -79,7 +79,7 @@ public bool IsHandleWeak(TargetPointer address) { var ccw = _target.ReadPointer(address + /* ComCallWrapper::SimpleWrapper offset */); uint flags = _target.Read(ccw + /* SimpleComCallWrapper::Flags offset */); - return (flags & Flags.IsHandleWeak) != 0; + return (flags & (uint)Flags.IsHandleWeak) != 0; } private bool GetComWrappersCCWVTableQIAddress(TargetPointer ccw, out TargetPointer vtable, out TargetPointer qiAddress) From d74a5159d3e5e1c17e838e028fc428ed10720374 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Mon, 23 Feb 2026 11:52:02 -0800 Subject: [PATCH 07/22] copilot etc --- docs/design/datacontracts/GC.md | 13 +++++++------ src/coreclr/vm/datadescriptor/datadescriptor.inc | 2 ++ .../Contracts/GC/GCFactory.cs | 3 ++- .../Contracts/GC/GC_1.cs | 10 +++++++--- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 816f4f8e8012bc..55c23351b46334 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -574,7 +574,7 @@ GetHandles List IGC.GetHandles(uint[] types) { List handles = new(); - TargetPointer handleTableMap = _target.ReadGlobalPointer("HandleTableMap"); + TargetPointer handleTableMap = target.ReadGlobalPointer("HandleTableMap"); // for each handleTableMap in the linked list while (handleTableMap != TargetPointer.Null) { @@ -589,7 +589,7 @@ List IGC.GetHandles(uint[] types) { // double dereference to iterate handle tables per array element per GC heap - native equivalent = map->pBuckets[i]->pTable[j] TargetPointer table = target.ReadPointer(bucketPtr + /* HandleTableBucket::Table offset */); - TargetPointer handleTablePtr = _target.ReadPointer(table + (ulong)(j * _target.PointerSize)); + TargetPointer handleTablePtr = target.ReadPointer(table + (ulong)(j * target.PointerSize)); if (handleTablePtr == TargetPointer.Null) continue; @@ -625,6 +625,7 @@ private void GetHandlesForSegment(TargetPointer segmentPtr, uint type, List(segmentPtr + TableSegment::RgTail offset + x); => RgTail[x]; + Debug.Assert(type < target.ReadGlobal("HandleMaxInternalTypes")); byte uBlock = target.Read(segmentPtr + /* TableSegment::RgTail offset */ + type); if (uBlock == target.ReadGlobal("BlockInvalid")) return; @@ -680,14 +681,14 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui handleData.Secondary = 0; } - if (_target.ReadGlobal("FeatureCOMInterop") != 0 && IsRefCounted(type)) + if (target.ReadGlobal("FeatureCOMInterop") != 0 && IsRefCounted(type)) { - IObject obj = _target.Contracts.Object; - TargetPointer handle = _target.ReadPointer(handleAddress); + IObject obj = target.Contracts.Object; + TargetPointer handle = target.ReadPointer(handleAddress); obj.GetBuiltInComData(handle, out _, out TargetPointer ccw); if (ccw != TargetPointer.Null) { - IComWrappers comWrappers = _target.Contracts.ComWrappers; + IComWrappers comWrappers = target.Contracts.ComWrappers; handleData.RefCount = (uint)comWrappers.GetRefCount(ccw); handleData.StrongReference = handleData.StrongReference || handleData.RefCount > 0 && !comWrappers.IsHandleWeak(ccw); } diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 7090485c785ba2..783840dd6a2a81 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1183,7 +1183,9 @@ CDAC_GLOBAL_POINTER(MethodDescSizeTable, &MethodDesc::s_ClassificationSizeTable) CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address) CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address) +#if FEATURE_COMINTEROP CDAC_GLOBAL(ComRefcountMask, int64, SimpleComCallWrapper::COM_REFCOUNT_MASK) +#endif // FEATURE_COMINTEROP // It is important for the subdescriptor pointers to be the last pointers in the global structure. // EnumMemDataDescriptors in enummem.cpp hardcodes the number of subdescriptors and utilizes diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCFactory.cs index b822b84ddeb3f9..7b3e48c490dd14 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCFactory.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCFactory.cs @@ -12,9 +12,10 @@ IGC IContractFactory.CreateContract(Target target, int version) uint handlesPerBlock = target.ReadGlobal(Constants.Globals.HandlesPerBlock); byte blockInvalid = target.ReadGlobal(Constants.Globals.BlockInvalid); TargetPointer debugDestroyedHandleValue = target.ReadGlobalPointer(Constants.Globals.DebugDestroyedHandleValue); + uint handleMaxInternalTypes = target.ReadGlobal(Constants.Globals.HandleMaxInternalTypes); return version switch { - 1 => new GC_1(target, handlesPerBlock, blockInvalid, debugDestroyedHandleValue), + 1 => new GC_1(target, handlesPerBlock, blockInvalid, debugDestroyedHandleValue, handleMaxInternalTypes), _ => default(GC), }; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index 8dca7b0ea1471c..227b719436330c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Microsoft.Diagnostics.DataContractReader.Contracts.GCHelpers; @@ -35,13 +36,15 @@ private enum HandleType_1 private readonly uint _handlesPerBlock; private readonly byte _blockInvalid; private readonly TargetPointer _debugDestroyedHandleValue; + private readonly uint _handleMaxInternalTypes; - internal GC_1(Target target, uint handlesPerBlock, byte blockInvalid, TargetPointer debugDestroyedHandleValue) + internal GC_1(Target target, uint handlesPerBlock, byte blockInvalid, TargetPointer debugDestroyedHandleValue, uint handleMaxInternalTypes) { _target = target; _handlesPerBlock = handlesPerBlock; _blockInvalid = blockInvalid; _debugDestroyedHandleValue = debugDestroyedHandleValue; + _handleMaxInternalTypes = handleMaxInternalTypes; } string[] IGC.GetGCIdentifiers() @@ -319,10 +322,10 @@ List IGC.GetHandles(uint[] types) continue; Data.HandleTable handleTable = _target.ProcessedData.GetOrAdd(handleTablePtr); + if (handleTable.SegmentList == TargetPointer.Null) + continue; foreach (uint type in typesList) { - if (handleTable.SegmentList == TargetPointer.Null) - continue; TargetPointer segmentPtr = handleTable.SegmentList; do { @@ -365,6 +368,7 @@ uint[] IGC.GetSupportedHandleTypes() private void GetHandlesForSegment(Data.TableSegment tableSegment, uint type, List handles) { + Debug.Assert(type < _handleMaxInternalTypes); byte uBlock = tableSegment.RgTail[type]; if (uBlock == _blockInvalid) return; From c30a102dc461a4adb794a669f38a4e183c8ca143 Mon Sep 17 00:00:00 2001 From: Rachel Date: Mon, 23 Feb 2026 13:12:34 -0800 Subject: [PATCH 08/22] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/vm/datadescriptor/datadescriptor.inc | 2 +- .../Data/ComCallWrapper.cs | 1 - .../Data/SimpleComCallWrapper.cs | 2 +- .../Data/TableSegment.cs | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 783840dd6a2a81..c16c526bb6927b 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1025,7 +1025,7 @@ CDAC_TYPE_END(ComCallWrapper) CDAC_TYPE_BEGIN(SimpleComCallWrapper) CDAC_TYPE_INDETERMINATE(SimpleComCallWrapper) -CDAC_TYPE_FIELD(SimpleComCallWrapper, /*pointer*/, RefCount, cdac_data::RefCount) +CDAC_TYPE_FIELD(SimpleComCallWrapper, /*int64*/, RefCount, cdac_data::RefCount) CDAC_TYPE_FIELD(SimpleComCallWrapper, /*uint32*/, Flags, cdac_data::Flags) CDAC_TYPE_END(SimpleComCallWrapper) #endif // FEATURE_COMINTEROP diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ComCallWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ComCallWrapper.cs index e755ee1fa54ae3..dc36501ed11940 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ComCallWrapper.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ComCallWrapper.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; namespace Microsoft.Diagnostics.DataContractReader.Data; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SimpleComCallWrapper.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SimpleComCallWrapper.cs index 33280185efe0b6..cb0271254b8aea 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SimpleComCallWrapper.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/SimpleComCallWrapper.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; + namespace Microsoft.Diagnostics.DataContractReader.Data; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TableSegment.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TableSegment.cs index 435ba646511c34..6ff3113662bf16 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TableSegment.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TableSegment.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; namespace Microsoft.Diagnostics.DataContractReader.Data; From 2744100560c5d4cabd47503a5c2c486c644a29ea Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 24 Feb 2026 10:39:21 -0800 Subject: [PATCH 09/22] e --- docs/design/datacontracts/GC.md | 8 +- .../Contracts/IGC.cs | 17 ++- .../Contracts/GC/GC_1.cs | 102 ++++++++++++------ .../SOSDacImpl.cs | 10 +- 4 files changed, 94 insertions(+), 43 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 55c23351b46334..2a02de6fefa078 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -614,7 +614,7 @@ List IGC.GetHandles(uint[] types) uint[] IGC.GetSupportedHandleTypes() { - // currently supported types: HNDTYPE_WEAK_SHORT, HNDTYPE_WEAK_LONG, HNDTYPE_STRONG, HNDTYPE_PINNED, HNDTYPE_DEPENDENT, HNDTYPE_WEAK_INTERIOR_POINTER, HNDTYPE_REFCOUNTED (conditional on at least one of global variables "FeatureCOMInterop", "FeatureComWrappers", and "FeatureObjCMarshal"), and HNDTYPE_CROSSREFERENCE (conditional on global variable "FeatureJavaMarshal") + // currently supported types: WeakShort, WeakLong, Strong, Pinned, Dependent, WeakInteriorPointer, RefCounted (conditional on at least one of global variables "FeatureCOMInterop", "FeatureComWrappers", and "FeatureObjCMarshal"), and CrossReference (conditional on global variable "FeatureJavaMarshal") } private void GetHandlesForSegment(TargetPointer segmentPtr, uint type, List handles) @@ -658,9 +658,9 @@ private void GetHandlesForBlock(TargetPointer segmentPtr, byte uBlock, uint type } } -private static bool IsStrongReference(uint type) => // HNDTYPE_STRONG || HNDTYPE_PINNED; -private static bool HasSecondary(uint type) => // HNDTYPE_DEPENDENT || HNDTYPE_WEAK_INTERIOR_POINTER || HNDTYPE_CROSSREFERENCE; -private static bool IsRefCounted(uint type) => // HNDTYPE_REFCOUNTED; +private static bool IsStrongReference(uint type) => // Strong || Pinned; +private static bool HasSecondary(uint type) => // Dependent || WeakInteriorPointer || CrossReference; +private static bool IsRefCounted(uint type) => // RefCounted; private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, uint intraBlockIndex, TargetPointer segmentPtr, uint type) { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs index ca8ae214d80b6e..499565f342db38 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs @@ -19,6 +19,18 @@ public static class GCIdentifiers public const string DynamicHeapCount = "dynamic_heap"; } +public enum HandleType +{ + WeakShort = 0, + WeakLong = 1, + Strong = 2, + Pinned = 3, + RefCounted = 5, + Dependent = 6, + WeakInteriorPointer = 10, + CrossReference = 11, +} + public record struct HandleData( TargetPointer Handle, TargetPointer Secondary, @@ -111,8 +123,9 @@ public interface IGC : IContract GCOomData GetOomData() => throw new NotImplementedException(); // server variant GCOomData GetOomData(TargetPointer heapAddress) => throw new NotImplementedException(); - List GetHandles(uint[] types) => throw new NotImplementedException(); - uint[] GetSupportedHandleTypes() => throw new NotImplementedException(); + List GetHandles(HandleType[] types) => throw new NotImplementedException(); + HandleType[] GetSupportedHandleTypes() => throw new NotImplementedException(); + HandleType[] GetHandleTypes(uint[] types) => throw new NotImplementedException(); } public readonly struct GC : IGC diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index 227b719436330c..79c77aeb056df8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -22,14 +22,14 @@ private enum GCType private enum HandleType_1 { - HNDTYPE_WEAK_SHORT = 0, - HNDTYPE_WEAK_LONG = 1, - HNDTYPE_STRONG = 2, - HNDTYPE_PINNED = 3, - HNDTYPE_REFCOUNTED = 5, - HNDTYPE_DEPENDENT = 6, - HNDTYPE_WEAK_INTERIOR_POINTER = 10, - HNDTYPE_CROSSREFERENCE = 11 + WeakShort = 0, + WeakLong = 1, + Strong = 2, + Pinned = 3, + RefCounted = 5, + Dependent = 6, + WeakInteriorPointer = 10, + CrossReference = 11 } private readonly Target _target; @@ -299,9 +299,9 @@ private bool IsDatasEnabled() return identifiers.Contains(GCIdentifiers.DynamicHeapCount); } - List IGC.GetHandles(uint[] types) + List IGC.GetHandles(HandleType[] types) { - List typesList = types.ToList(); + List typesList = types.ToList(); typesList.Sort(); List handles = new(); TargetPointer handleTableMap = _target.ReadGlobalPointer(Constants.Globals.HandleTableMap); @@ -324,7 +324,7 @@ List IGC.GetHandles(uint[] types) Data.HandleTable handleTable = _target.ProcessedData.GetOrAdd(handleTablePtr); if (handleTable.SegmentList == TargetPointer.Null) continue; - foreach (uint type in typesList) + foreach (HandleType type in typesList) { TargetPointer segmentPtr = handleTable.SegmentList; do @@ -341,35 +341,71 @@ List IGC.GetHandles(uint[] types) return handles; } - uint[] IGC.GetSupportedHandleTypes() + HandleType[] IGC.GetSupportedHandleTypes() { - List supportedTypes = + List supportedTypes = [ - .. new uint[] - { - (uint)HandleType_1.HNDTYPE_WEAK_SHORT, - (uint)HandleType_1.HNDTYPE_WEAK_LONG, - (uint)HandleType_1.HNDTYPE_STRONG, - (uint)HandleType_1.HNDTYPE_PINNED, - (uint)HandleType_1.HNDTYPE_DEPENDENT, - (uint)HandleType_1.HNDTYPE_WEAK_INTERIOR_POINTER - }, + HandleType.WeakShort, + HandleType.WeakLong, + HandleType.Strong, + HandleType.Pinned, + HandleType.Dependent, + HandleType.WeakInteriorPointer ]; if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) != 0 || _target.ReadGlobal(Constants.Globals.FeatureComWrappers) != 0 || _target.ReadGlobal(Constants.Globals.FeatureObjCMarshal) != 0) { - supportedTypes.Add((uint)HandleType_1.HNDTYPE_REFCOUNTED); + supportedTypes.Add(HandleType.RefCounted); } if (_target.ReadGlobal(Constants.Globals.FeatureJavaMarshal) != 0) { - supportedTypes.Add((uint)HandleType_1.HNDTYPE_CROSSREFERENCE); + supportedTypes.Add(HandleType.CrossReference); } return supportedTypes.ToArray(); } - private void GetHandlesForSegment(Data.TableSegment tableSegment, uint type, List handles) + HandleType[] IGC.GetHandleTypes(uint[] types) + { + List handleTypes = new(); + foreach (uint type in types) + { + if (type >= _handleMaxInternalTypes) + continue; + handleTypes.Add(type switch + { + (uint)HandleType_1.WeakShort => HandleType.WeakShort, + (uint)HandleType_1.WeakLong => HandleType.WeakLong, + (uint)HandleType_1.Strong => HandleType.Strong, + (uint)HandleType_1.Pinned => HandleType.Pinned, + (uint)HandleType_1.RefCounted => HandleType.RefCounted, + (uint)HandleType_1.Dependent => HandleType.Dependent, + (uint)HandleType_1.WeakInteriorPointer => HandleType.WeakInteriorPointer, + (uint)HandleType_1.CrossReference => HandleType.CrossReference, + _ => throw new InvalidOperationException($"Unknown handle type {type}"), + }); + } + return handleTypes.ToArray(); + } + + private static uint GetInternalHandleType(HandleType type) + { + return type switch + { + HandleType.WeakShort => (uint)HandleType_1.WeakShort, + HandleType.WeakLong => (uint)HandleType_1.WeakLong, + HandleType.Strong => (uint)HandleType_1.Strong, + HandleType.Pinned => (uint)HandleType_1.Pinned, + HandleType.Dependent => (uint)HandleType_1.Dependent, + HandleType.WeakInteriorPointer => (uint)HandleType_1.WeakInteriorPointer, + HandleType.RefCounted => (uint)HandleType_1.RefCounted, + HandleType.CrossReference => (uint)HandleType_1.CrossReference, + _ => throw new ArgumentOutOfRangeException(nameof(type), type, null) + }; + } + + private void GetHandlesForSegment(Data.TableSegment tableSegment, HandleType type, List handles) { - Debug.Assert(type < _handleMaxInternalTypes); - byte uBlock = tableSegment.RgTail[type]; + Debug.Assert(GetInternalHandleType(type) < _handleMaxInternalTypes); + byte uBlock = tableSegment.RgTail[GetInternalHandleType(type)]; if (uBlock == _blockInvalid) return; uBlock = tableSegment.RgAllocation[uBlock]; @@ -390,7 +426,7 @@ private void GetHandlesForSegment(Data.TableSegment tableSegment, uint type, Lis } while (uBlock != uHead); } - private void GetHandlesForBlock(Data.TableSegment tableSegment, byte uBlock, uint type, List handles) + private void GetHandlesForBlock(Data.TableSegment tableSegment, byte uBlock, HandleType type, List handles) { for (uint k = 0; k < _handlesPerBlock; k++) { @@ -403,15 +439,15 @@ private void GetHandlesForBlock(Data.TableSegment tableSegment, byte uBlock, uin } } - private static bool IsStrongReference(uint type) => type == (uint)HandleType_1.HNDTYPE_STRONG || type == (uint)HandleType_1.HNDTYPE_PINNED; - private static bool HasSecondary(uint type) => type == (uint)HandleType_1.HNDTYPE_DEPENDENT || type == (uint)HandleType_1.HNDTYPE_WEAK_INTERIOR_POINTER || type == (uint)HandleType_1.HNDTYPE_CROSSREFERENCE; - private static bool IsRefCounted(uint type) => type == (uint)HandleType_1.HNDTYPE_REFCOUNTED; + private static bool IsStrongReference(HandleType type) => type == HandleType.Strong || type == HandleType.Pinned; + private static bool HasSecondary(HandleType type) => type == HandleType.Dependent || type == HandleType.WeakInteriorPointer || type == HandleType.CrossReference; + private static bool IsRefCounted(HandleType type) => type == HandleType.RefCounted; - private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, uint intraBlockIndex, Data.TableSegment tableSegment, uint type) + private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, uint intraBlockIndex, Data.TableSegment tableSegment, HandleType type) { HandleData handleData = default; handleData.Handle = handleAddress; - handleData.Type = type; + handleData.Type = GetInternalHandleType(type); handleData.JupiterRefCount = 0; handleData.IsPegged = false; handleData.StrongReference = IsStrongReference(type); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs index 3655061d02c41a..c5cd5ccd4e4b1f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -1324,14 +1324,14 @@ internal sealed unsafe partial class SOSHandleEnum : ISOSHandleEnum private readonly ISOSHandleEnum? _legacyHandleEnum; private uint _index; - public SOSHandleEnum(Target target, uint[] types, ISOSHandleEnum? legacyHandleEnum) + public SOSHandleEnum(Target target, HandleType[] types, ISOSHandleEnum? legacyHandleEnum) { _target = target; _legacyHandleEnum = legacyHandleEnum; _handles = GetHandles(types); } - private SOSHandleData[] GetHandles(uint[] types) + private SOSHandleData[] GetHandles(HandleType[] types) { IGC gc = _target.Contracts.GC; List handles = gc.GetHandles(types); @@ -1445,7 +1445,7 @@ int ISOSDacInterface.GetHandleEnum(out ISOSHandleEnum? ppHandleEnum) try { IGC gc = _target.Contracts.GC; - uint[] supportedHandleTypes = gc.GetSupportedHandleTypes(); + HandleType[] supportedHandleTypes = gc.GetSupportedHandleTypes(); ISOSHandleEnum? legacyHandleEnum = null; #if DEBUG if (_legacyImpl is not null) @@ -1478,7 +1478,9 @@ int ISOSDacInterface.GetHandleEnumForTypes([In, MarshalUsing(CountElementName = Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}"); } #endif - ppHandleEnum = new SOSHandleEnum(_target, types, legacyHandleEnum); + IGC gc = _target.Contracts.GC; + HandleType[] handleTypes = gc.GetHandleTypes(types); + ppHandleEnum = new SOSHandleEnum(_target, handleTypes, legacyHandleEnum); } catch (System.Exception ex) { From 7b4785be8f6bc5a7ec3bf074bffe24d4c976b6db Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 24 Feb 2026 14:15:42 -0800 Subject: [PATCH 10/22] code review --- docs/design/datacontracts/ComWrappers.md | 25 +------------- .../vm/datadescriptor/datadescriptor.inc | 1 + .../ContractRegistry.cs | 4 +++ .../Contracts/IComWrappers.cs | 2 -- .../Contracts/ComWrappers_1.cs | 20 ----------- .../Contracts/GC/GC_1.cs | 6 ++-- .../CachingContractRegistry.cs | 1 + .../DumpTests/Debuggees/GCRoots/Program.cs | 12 ++++++- .../DumpTests/Debuggees/ServerGC/Program.cs | 18 ++++++++++ .../cdac/tests/DumpTests/ServerGCDumpTests.cs | 33 +++++++++++++++++++ .../tests/DumpTests/WorkstationGCDumpTests.cs | 32 ++++++++++++++++++ 11 files changed, 104 insertions(+), 50 deletions(-) diff --git a/docs/design/datacontracts/ComWrappers.md b/docs/design/datacontracts/ComWrappers.md index 00b913ed124f1d..0598c70b059a89 100644 --- a/docs/design/datacontracts/ComWrappers.md +++ b/docs/design/datacontracts/ComWrappers.md @@ -7,10 +7,6 @@ This contract is for getting information related to COM wrappers. ``` csharp // Get the address of the external COM object TargetPointer GetComWrappersIdentity(TargetPointer rcw); -// Get the refcount for a COM call wrapper. -public ulong GetRefCount(TargetPointer ccw); -// Check whether the COM wrappers handle is weak. -public bool IsHandleWeak(TargetPointer ccw); // Given a ccw pointer, return the managed object wrapper public TargetPointer GetManagedObjectWrapperFromCCW(TargetPointer ccw); // Given a managed object wrapper, return the comwrappers pointer @@ -26,10 +22,7 @@ public bool IsComWrappersRCW(TargetPointer rcw); Data descriptors used: | Data Descriptor Name | Field | Meaning | | --- | --- | --- | -| `NativeObjectWrapperObject` | `ExternalComObject` | Address of the external COM object | -| `ComCallWrapper` | `SimpleWrapper` | Address of the associated `SimpleComCallWrapper` | -| `SimpleComCallWrapper` | `RefCount` | The wrapper refcount value | -| `SimpleComCallWrapper` | `Flags` | Bit flags for wrapper properties | +| `NativeObjectWrapperObject` | `ExternalComObject` | Address of the external COM object || | `ManagedObjectWrapperHolderObject` | `WrappedObject` | Address of the wrapped object | | `ManagedObjectWrapperLayout` | `RefCount` | Reference count of the managed object wrapper | | `ComWrappersVtablePtrs` | `Size` | Size of vtable pointers array | @@ -37,7 +30,6 @@ Data descriptors used: Global variables used: | Global Name | Type | Purpose | | --- | --- | --- | -| `ComRefcountMask` | `long` | Mask applied to `SimpleComCallWrapper.RefCount` to produce the visible refcount | | `ComWrappersVtablePtrs` | TargetPointer | Pointer to struct containing ComWrappers-related function pointers | | `DispatchThisPtrMask` | TargetPointer | Used to mask low bits of CCW pointer to the nearest valid address from which to read a managed object wrapper | @@ -67,21 +59,6 @@ public TargetPointer GetComWrappersIdentity(TargetPointer address) return _target.ReadPointer(address + /* NativeObjectWrapperObject::ExternalComObject offset */); } -public ulong GetRefCount(TargetPointer address) -{ - var ccw = _target.ReadPointer(address + /* ComCallWrapper::SimpleWrapper offset */); - ulong refCount = _target.Read(ccw + /* SimpleComCallWrapper::RefCount offset */); - long refCountMask = _target.ReadGlobal("ComRefcountMask"); - return refCount & (ulong)refCountMask; -} - -public bool IsHandleWeak(TargetPointer address) -{ - var ccw = _target.ReadPointer(address + /* ComCallWrapper::SimpleWrapper offset */); - uint flags = _target.Read(ccw + /* SimpleComCallWrapper::Flags offset */); - return (flags & (uint)Flags.IsHandleWeak) != 0; -} - private bool GetComWrappersCCWVTableQIAddress(TargetPointer ccw, out TargetPointer vtable, out TargetPointer qiAddress) { vtable = TargetPointer.Null; diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index ab039c79971cb3..4573d15bc15a3d 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1197,6 +1197,7 @@ CDAC_GLOBAL(ComRefcountMask, int64, SimpleComCallWrapper::COM_REFCOUNT_MASK) // When adding a new subdescriptor, EnumMemDescriptors must be updated appropriately. CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor)) +CDAC_GLOBAL_CONTRACT(BuiltInCOM, 1) CDAC_GLOBAL_CONTRACT(CodeVersions, 1) #ifdef FEATURE_COMWRAPPERS CDAC_GLOBAL_CONTRACT(ComWrappers, 1) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs index 80723c030051b4..2fac48faab7c2c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -90,6 +90,10 @@ public abstract class ContractRegistry /// Gets an instance of the SignatureDecoder contract for the target. /// public virtual ISignatureDecoder SignatureDecoder => GetContract(); + /// + /// Gets an instance of the BuiltInCOM contract for the target. + /// + public virtual IBuiltInCOM BuiltInCOM => GetContract(); public abstract TContract GetContract() where TContract : IContract; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IComWrappers.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IComWrappers.cs index 137ad1182a7a7a..2904e98ccda794 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IComWrappers.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IComWrappers.cs @@ -9,8 +9,6 @@ public interface IComWrappers : IContract { static string IContract.Name { get; } = nameof(ComWrappers); TargetPointer GetComWrappersIdentity(TargetPointer address) => throw new NotImplementedException(); - ulong GetRefCount(TargetPointer address) => throw new NotImplementedException(); - bool IsHandleWeak(TargetPointer address) => throw new NotImplementedException(); TargetPointer GetManagedObjectWrapperFromCCW(TargetPointer ccw) => throw new NotImplementedException(); TargetPointer GetComWrappersObjectFromMOW(TargetPointer mow) => throw new NotImplementedException(); long GetMOWReferenceCount(TargetPointer mow) => throw new NotImplementedException(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs index 3530d5cd671fb9..d963a38177ccf4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs @@ -13,11 +13,6 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; private const string NativeObjectWrapperNamespace = "System.Runtime.InteropServices"; private const string NativeObjectWrapperName = "ComWrappers+NativeObjectWrapper"; private readonly Target _target; - private enum Flags - { - IsHandleWeak = 0x4, - } - public ComWrappers_1(Target target) { _target = target; @@ -28,21 +23,6 @@ public TargetPointer GetComWrappersIdentity(TargetPointer address) Data.NativeObjectWrapperObject wrapper = _target.ProcessedData.GetOrAdd(address); return wrapper.ExternalComObject; } - - public ulong GetRefCount(TargetPointer address) - { - Data.ComCallWrapper wrapper = _target.ProcessedData.GetOrAdd(address); - Data.SimpleComCallWrapper simpleWrapper = _target.ProcessedData.GetOrAdd(wrapper.SimpleWrapper); - return simpleWrapper.RefCount & (ulong)_target.ReadGlobal(Constants.Globals.ComRefcountMask); - } - - public bool IsHandleWeak(TargetPointer address) - { - Data.ComCallWrapper wrapper = _target.ProcessedData.GetOrAdd(address); - Data.SimpleComCallWrapper simpleWrapper = _target.ProcessedData.GetOrAdd(wrapper.SimpleWrapper); - return (simpleWrapper.Flags & (uint)Flags.IsHandleWeak) != 0; - } - private bool GetComWrappersCCWVTableQIAddress(TargetPointer ccw, out TargetPointer vtable, out TargetPointer qiAddress) { qiAddress = TargetPointer.Null; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index 79c77aeb056df8..9bfdbd0f88d413 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -469,9 +469,9 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui obj.GetBuiltInComData(handle, out _, out TargetPointer ccw); if (ccw != TargetPointer.Null) { - IComWrappers comWrappers = _target.Contracts.ComWrappers; - handleData.RefCount = (uint)comWrappers.GetRefCount(ccw); - handleData.StrongReference = handleData.StrongReference || (handleData.RefCount > 0 && !comWrappers.IsHandleWeak(ccw)); + IBuiltInCOM builtInCOM = _target.Contracts.BuiltInCOM; + handleData.RefCount = (uint)builtInCOM.GetRefCount(ccw); + handleData.StrongReference = handleData.StrongReference || (handleData.RefCount > 0 && !builtInCOM.IsHandleWeak(ccw)); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 629a2d66e58cd4..39032fe25f10bf 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -46,6 +46,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IGC)] = new GCFactory(), [typeof(INotifications)] = new NotificationsFactory(), [typeof(ISignatureDecoder)] = new SignatureDecoderFactory(), + [typeof(IBuiltInCOM)] = new BuiltInCOMFactory(), }; foreach (IContractFactory factory in additionalFactories) diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/Program.cs index fabc70ce665962..16c7febf9115f3 100644 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/Program.cs +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/Program.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Runtime; using System.Runtime.InteropServices; /// @@ -33,6 +34,12 @@ private static void Main() // Create weak and strong handles var weakRef = new WeakReference(new object()); var strongHandle = GCHandle.Alloc(testString, GCHandleType.Normal); + var weakLongHandle = GCHandle.Alloc(new object(), GCHandleType.WeakTrackResurrection); + + // Create dependent handle + object dependentTarget = new object(); + object dependentValue = new byte[16]; + DependentHandle dependentHandle = new(dependentTarget, dependentValue); // Keep references alive GC.KeepAlive(testString); @@ -42,7 +49,10 @@ private static void Main() GC.KeepAlive(pinnedArrays); GC.KeepAlive(weakRef); GC.KeepAlive(strongHandle); - + GC.KeepAlive(weakLongHandle); + GC.KeepAlive(dependentTarget); + GC.KeepAlive(dependentValue); + GC.KeepAlive(dependentHandle); Environment.FailFast("cDAC dump test: GCRoots debuggee intentional crash"); } } diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/Program.cs b/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/Program.cs index ea8cd960dc8d88..9da2ff4c9fab04 100644 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/Program.cs +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/Program.cs @@ -11,6 +11,7 @@ /// internal static class Program { + public const string TestStringValue = "cDAC-ServerGC-test-string"; private static void Main() { // Verify server GC is enabled @@ -34,8 +35,25 @@ private static void Main() pinnedHandles[i] = GCHandle.Alloc(new byte[256], GCHandleType.Pinned); } + // Create weak and strong handles + var weakRef = new WeakReference(new object()); + var strongHandle = GCHandle.Alloc(TestStringValue, GCHandleType.Normal); + var weakLongHandle = GCHandle.Alloc(new object(), GCHandleType.WeakTrackResurrection); + + // Create dependent handle + object dependentTarget = new object(); + object dependentValue = new byte[16]; + DependentHandle dependentHandle = new(dependentTarget, dependentValue); + + GC.KeepAlive(roots); GC.KeepAlive(pinnedHandles); + GC.KeepAlive(weakRef); + GC.KeepAlive(strongHandle); + GC.KeepAlive(weakLongHandle); + GC.KeepAlive(dependentTarget); + GC.KeepAlive(dependentValue); + GC.KeepAlive(dependentHandle); Environment.FailFast("cDAC dump test: ServerGC debuggee intentional crash"); } diff --git a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs index 7d68338f89d9ae..1973df8c905a99 100644 --- a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs @@ -116,4 +116,37 @@ public void ServerGC_EachHeapHasGenerationData(TestConfiguration config) $"Expected generation table for heap 0x{heap:X} to be non-empty"); } } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "GC contract is not available in .NET 10 dumps")] + public void ServerGC_CanEnumerateExpectedHandles(TestConfiguration config) + { + InitializeDumpTest(config); + IGC gcContract = Target.Contracts.GC; + + var pinnedHandles = gcContract.GetHandles([HandleType.Pinned]); + Assert.True( + pinnedHandles.Count >= 10, + $"Expected at least 10 pinned handles, found {pinnedHandles.Count}"); + Assert.All(pinnedHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + + var strongHandles = gcContract.GetHandles([HandleType.Strong]); + Assert.True(strongHandles.Count >= 1, "Expected at least 1 strong handle"); + Assert.All(strongHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + Assert.All(strongHandles, handle => Assert.NotEqual(0u, handle.StrongReference)); + + var weakShortHandles = gcContract.GetHandles([HandleType.WeakShort]); + Assert.True(weakShortHandles.Count >= 1, "Expected at least 1 weak-short handle"); + Assert.All(weakShortHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + + var dependentHandles = gcContract.GetHandles([HandleType.Dependent]); + Assert.True(dependentHandles.Count >= 1, "Expected at least 1 dependent handle"); + Assert.All(dependentHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + + Assert.Contains( + dependentHandles, + handle => handle.Secondary != TargetPointer.Null); + } + } diff --git a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs index 4bf67825f5cc8c..054126a7e2d8bb 100644 --- a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs @@ -87,4 +87,36 @@ public void WorkstationGC_BoundsAreReasonable(TestConfiguration config) Assert.True(minAddr < maxAddr, $"Expected GC min address (0x{minAddr:X}) < max address (0x{maxAddr:X})"); } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "GC contract is not available in .NET 10 dumps")] + public void WorkstationGC_CanEnumerateExpectedHandles(TestConfiguration config) + { + InitializeDumpTest(config); + IGC gcContract = Target.Contracts.GC; + + var pinnedHandles = gcContract.GetHandles([HandleType.Pinned]); + Assert.True( + pinnedHandles.Count >= 5, + $"Expected at least 5 pinned handles, found {pinnedHandles.Count}"); + Assert.All(pinnedHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + + var strongHandles = gcContract.GetHandles([HandleType.Strong]); + Assert.True(strongHandles.Count >= 1, "Expected at least 1 strong handle"); + Assert.All(strongHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + Assert.All(strongHandles, handle => Assert.NotEqual(0u, handle.StrongReference)); + + var weakShortHandles = gcContract.GetHandles([HandleType.WeakShort]); + Assert.True(weakShortHandles.Count >= 1, "Expected at least 1 weak-short handle"); + Assert.All(weakShortHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + + var dependentHandles = gcContract.GetHandles([HandleType.Dependent]); + Assert.True(dependentHandles.Count >= 1, "Expected at least 1 dependent handle"); + Assert.All(dependentHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + + Assert.Contains( + dependentHandles, + handle => handle.Secondary != TargetPointer.Null); + } } From ab92284757765e988720ca7b41a985b6005183cc Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 24 Feb 2026 14:39:31 -0800 Subject: [PATCH 11/22] fixing docs and missing files --- docs/design/datacontracts/BuiltInCOM.md | 53 +++++++++++++++++++ docs/design/datacontracts/ComWrappers.md | 2 +- docs/design/datacontracts/GC.md | 46 +++++++++++----- src/coreclr/gc/gcinterface.h | 13 +++-- .../Contracts/IBuiltInCOM.cs | 18 +++++++ .../Contracts/BuiltInCOM.cs | 38 +++++++++++++ .../Contracts/BuiltInCOMFactory.cs | 19 +++++++ .../Contracts/ComWrappers_1.cs | 2 + .../Contracts/GC/GC_1.cs | 2 +- .../tests/DumpTests/WorkstationGCDumpTests.cs | 2 +- 10 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 docs/design/datacontracts/BuiltInCOM.md create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IBuiltInCOM.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOM.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOMFactory.cs diff --git a/docs/design/datacontracts/BuiltInCOM.md b/docs/design/datacontracts/BuiltInCOM.md new file mode 100644 index 00000000000000..1f99b6b60ea4ae --- /dev/null +++ b/docs/design/datacontracts/BuiltInCOM.md @@ -0,0 +1,53 @@ +# Contract ComWrappers + +This contract is for getting information related to built-in COM. + +## APIs of contract + +``` csharp +public ulong GetRefCount(TargetPointer ccw); +// Check whether the COM wrappers handle is weak. +public bool IsHandleWeak(TargetPointer ccw); +``` + +## Version 1 + +Data descriptors used: +| Data Descriptor Name | Field | Meaning | +| --- | --- | --- | +| `ComCallWrapper` | `SimpleWrapper` | Address of the associated `SimpleComCallWrapper` | +| `SimpleComCallWrapper` | `RefCount` | The wrapper refcount value | +| `SimpleComCallWrapper` | `Flags` | Bit flags for wrapper properties | + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| `ComRefcountMask` | `long` | Mask applied to `SimpleComCallWrapper.RefCount` to produce the visible refcount | + +Contracts used: +| Contract Name | +| --- | +`None` + +``` csharp + +private enum Flags +{ + IsHandleWeak = 0x4, +} + +public ulong GetRefCount(TargetPointer address) +{ + var ccw = _target.ReadPointer(address + /* ComCallWrapper::SimpleWrapper offset */); + ulong refCount = _target.Read(ccw + /* SimpleComCallWrapper::RefCount offset */); + long refCountMask = _target.ReadGlobal("ComRefcountMask"); + return refCount & (ulong)refCountMask; +} + +public bool IsHandleWeak(TargetPointer address) +{ + var ccw = _target.ReadPointer(address + /* ComCallWrapper::SimpleWrapper offset */); + uint flags = _target.Read(ccw + /* SimpleComCallWrapper::Flags offset */); + return (flags & (uint)Flags.IsHandleWeak) != 0; +} +``` diff --git a/docs/design/datacontracts/ComWrappers.md b/docs/design/datacontracts/ComWrappers.md index 0598c70b059a89..10d02225e34cd2 100644 --- a/docs/design/datacontracts/ComWrappers.md +++ b/docs/design/datacontracts/ComWrappers.md @@ -22,7 +22,7 @@ public bool IsComWrappersRCW(TargetPointer rcw); Data descriptors used: | Data Descriptor Name | Field | Meaning | | --- | --- | --- | -| `NativeObjectWrapperObject` | `ExternalComObject` | Address of the external COM object || +| `NativeObjectWrapperObject` | `ExternalComObject` | Address of the external COM object | | `ManagedObjectWrapperHolderObject` | `WrappedObject` | Address of the wrapped object | | `ManagedObjectWrapperLayout` | `RefCount` | Reference count of the managed object wrapper | | `ComWrappersVtablePtrs` | `Size` | Size of vtable pointers array | diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 2a02de6fefa078..9e762ef5ecdc56 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -107,6 +107,13 @@ public readonly struct GCOomData // Gets data about a managed OOM occurance GCOomData GetOomData(); GCOomData GetOomData(TargetPointer heapAddress); + + // Gets all GC handles of specified types + List GetHandles(HandleType[] types); + // Gets the supported handle types + HandleType[] GetSupportedHandleTypes(); + // Converts integer types into HandleType enum + HandleType[] GetHandleTypes(uint[] types); ``` ## Version 1 @@ -219,7 +226,7 @@ Global variables used: Contracts used: | Contract Name | | --- | -| ComWrappers | +| BuiltInCOM | | Object | Constants used: @@ -571,7 +578,19 @@ private List ReadGCHeapDataArray(TargetPointer arrayStart, uint len GetHandles ```csharp -List IGC.GetHandles(uint[] types) +public enum HandleType +{ + WeakShort = 0, + WeakLong = 1, + Strong = 2, + Pinned = 3, + RefCounted = 5, + Dependent = 6, + WeakInteriorPointer = 10, + CrossReference = 11, +} + +List IGC.GetHandles(HandleType[] types) { List handles = new(); TargetPointer handleTableMap = target.ReadGlobalPointer("HandleTableMap"); @@ -593,7 +612,7 @@ List IGC.GetHandles(uint[] types) if (handleTablePtr == TargetPointer.Null) continue; - foreach (uint type in types) + foreach (HandleType type in types) { // initialize segmentPtr and iterate through the linked list of segments. TargetPointer segmentPtr = target.ReadPointer(handleTablePtr + /* HandleTable::SegmentList offset */); @@ -612,12 +631,12 @@ List IGC.GetHandles(uint[] types) return handles; } -uint[] IGC.GetSupportedHandleTypes() +HandleType[] IGC.GetSupportedHandleTypes() { // currently supported types: WeakShort, WeakLong, Strong, Pinned, Dependent, WeakInteriorPointer, RefCounted (conditional on at least one of global variables "FeatureCOMInterop", "FeatureComWrappers", and "FeatureObjCMarshal"), and CrossReference (conditional on global variable "FeatureJavaMarshal") } -private void GetHandlesForSegment(TargetPointer segmentPtr, uint type, List handles) +private void GetHandlesForSegment(TargetPointer segmentPtr, HandleType type, List handles) { // GC handles are stored in circular linked lists per segment and handle type. // RgTail = array of bytes that is global variable "HandleMaxInternalTypes" long. @@ -625,8 +644,8 @@ private void GetHandlesForSegment(TargetPointer segmentPtr, uint type, List(segmentPtr + TableSegment::RgTail offset + x); => RgTail[x]; - Debug.Assert(type < target.ReadGlobal("HandleMaxInternalTypes")); - byte uBlock = target.Read(segmentPtr + /* TableSegment::RgTail offset */ + type); + Debug.Assert(GetInternalHandleType(type) < target.ReadGlobal("HandleMaxInternalTypes")); + byte uBlock = target.Read(segmentPtr + /* TableSegment::RgTail offset */ + GetInternalHandleType(type)); if (uBlock == target.ReadGlobal("BlockInvalid")) return; uBlock = target.Read(segmentPtr + /* TableSegment::RgAllocation offset */ + uBlock); @@ -645,7 +664,7 @@ private void GetHandlesForSegment(TargetPointer segmentPtr, uint type, List handles) +private void GetHandlesForBlock(TargetPointer segmentPtr, byte uBlock, HandleType type, List handles) { for (uint k = 0; k < target.ReadGlobal("HandlesPerBlock"); k++) { @@ -661,12 +680,13 @@ private void GetHandlesForBlock(TargetPointer segmentPtr, byte uBlock, uint type private static bool IsStrongReference(uint type) => // Strong || Pinned; private static bool HasSecondary(uint type) => // Dependent || WeakInteriorPointer || CrossReference; private static bool IsRefCounted(uint type) => // RefCounted; +private static uint GetInternalHandleType(HandleType type) => // convert the HandleType enum to the corresponding runtime-dependent constant uint. -private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, uint intraBlockIndex, TargetPointer segmentPtr, uint type) +private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, uint intraBlockIndex, TargetPointer segmentPtr, HandleType type) { HandleData handleData = default; handleData.Handle = handleAddress; - handleData.Type = type; + handleData.Type = GetInternalHandleType(type); handleData.JupiterRefCount = 0; handleData.IsPegged = false; handleData.StrongReference = IsStrongReference(type); @@ -688,9 +708,9 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui obj.GetBuiltInComData(handle, out _, out TargetPointer ccw); if (ccw != TargetPointer.Null) { - IComWrappers comWrappers = target.Contracts.ComWrappers; - handleData.RefCount = (uint)comWrappers.GetRefCount(ccw); - handleData.StrongReference = handleData.StrongReference || handleData.RefCount > 0 && !comWrappers.IsHandleWeak(ccw); + IBuiltInCOM builtInCOM = target.Contracts.BuiltInCOM; + handleData.RefCount = (uint)builtInCOM.GetRefCount(ccw); + handleData.StrongReference = handleData.StrongReference || handleData.RefCount > 0 && !builtInCOM.IsHandleWeak(ccw); } } diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h index dc319277c413f8..43a20f7fac63e1 100644 --- a/src/coreclr/gc/gcinterface.h +++ b/src/coreclr/gc/gcinterface.h @@ -411,7 +411,8 @@ typedef enum * severed, even if the object will be visible from a pending finalization * graph. This further implies that short weak handles do not track * across object resurrections. - * + * [cDAC] [GC]: Contract depends on this value + * */ HNDTYPE_WEAK_SHORT = 0, @@ -422,6 +423,7 @@ typedef enum * object is actually reclaimed. Unlike short weak handles, long weak handles * continue to track their referents through finalization and across any * resurrections that may occur. + * [cDAC] [GC]: Contract depends on this value * */ HNDTYPE_WEAK_LONG = 1, @@ -433,6 +435,7 @@ typedef enum * Strong handles are handles which function like a normal object reference. * The existence of a strong handle for an object will cause the object to * be promoted (remain alive) through a garbage collection cycle. + * [cDAC] [GC]: Contract depends on this value * */ HNDTYPE_STRONG = 2, @@ -445,6 +448,7 @@ typedef enum * prevent an object from moving during a garbage collection cycle. This is * useful when passing a pointer to object innards out of the runtime while GC * may be enabled. + * [cDAC] [GC]: Contract depends on this value * * NOTE: PINNING AN OBJECT IS EXPENSIVE AS IT PREVENTS THE GC FROM ACHIEVING * OPTIMAL PACKING OF OBJECTS DURING EPHEMERAL COLLECTIONS. THIS TYPE @@ -469,6 +473,7 @@ typedef enum * * Refcounted handles are handles that behave as strong handles while the * refcount on them is greater than 0 and behave as weak handles otherwise. + * [cDAC] [GC]: Contract depends on this value * */ HNDTYPE_REFCOUNTED = 5, @@ -485,7 +490,7 @@ typedef enum * * They are also used to implement the managed ConditionalWeakTable class. If you want to use * these from managed code, they are exposed to BCL through the managed DependentHandle class. - * + * [cDAC] [GC]: Contract depends on this value * */ HNDTYPE_DEPENDENT = 6, @@ -538,7 +543,8 @@ typedef enum * Interior pointer handles allow the vm to request that the GC keep an interior pointer to * a given object updated to keep pointing at the same location within an object. These handles * have an extra pointer which points at an interior pointer into the first object. - * + * [cDAC] [GC]: Contract depends on this value + * */ HNDTYPE_WEAK_INTERIOR_POINTER = 10, @@ -546,6 +552,7 @@ typedef enum * CROSSREFERENCE HANDLES * * Crossreference handles are used to track the lifetime of an object in another VM heap. + * [cDAC] [GC]: Contract depends on this value */ HNDTYPE_CROSSREFERENCE = 11 } HandleType; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IBuiltInCOM.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IBuiltInCOM.cs new file mode 100644 index 00000000000000..3cb6653830b981 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IBuiltInCOM.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public interface IBuiltInCOM : IContract +{ + static string IContract.Name { get; } = nameof(BuiltInCOM); + ulong GetRefCount(TargetPointer address) => throw new NotImplementedException(); + bool IsHandleWeak(TargetPointer address) => throw new NotImplementedException(); +} + +public readonly struct BuiltInCOM : IBuiltInCOM +{ + // Everything throws NotImplementedException +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOM.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOM.cs new file mode 100644 index 00000000000000..4c618c526b0080 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOM.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +internal readonly struct BuiltInCOM_1 : IBuiltInCOM +{ + private readonly Target _target; + + private enum Flags + { + IsHandleWeak = 0x4, + } + + internal BuiltInCOM_1(Target target) + { + _target = target; + } + + public ulong GetRefCount(TargetPointer address) + { + Data.ComCallWrapper wrapper = _target.ProcessedData.GetOrAdd(address); + Data.SimpleComCallWrapper simpleWrapper = _target.ProcessedData.GetOrAdd(wrapper.SimpleWrapper); + return simpleWrapper.RefCount & (ulong)_target.ReadGlobal(Constants.Globals.ComRefcountMask); + } + + public bool IsHandleWeak(TargetPointer address) + { + Data.ComCallWrapper wrapper = _target.ProcessedData.GetOrAdd(address); + Data.SimpleComCallWrapper simpleWrapper = _target.ProcessedData.GetOrAdd(wrapper.SimpleWrapper); + return (simpleWrapper.Flags & (uint)Flags.IsHandleWeak) != 0; + } + +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOMFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOMFactory.cs new file mode 100644 index 00000000000000..f65056f6205b85 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/BuiltInCOMFactory.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Microsoft.Diagnostics.DataContractReader.Contracts; + +public sealed class BuiltInCOMFactory : IContractFactory +{ + IBuiltInCOM IContractFactory.CreateContract(Target target, int version) + { + return version switch + { + 1 => new BuiltInCOM_1(target), + _ => default(BuiltInCOM), + }; + } + +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs index d963a38177ccf4..0b9ce1d310cdff 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ComWrappers_1.cs @@ -13,6 +13,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; private const string NativeObjectWrapperNamespace = "System.Runtime.InteropServices"; private const string NativeObjectWrapperName = "ComWrappers+NativeObjectWrapper"; private readonly Target _target; + public ComWrappers_1(Target target) { _target = target; @@ -23,6 +24,7 @@ public TargetPointer GetComWrappersIdentity(TargetPointer address) Data.NativeObjectWrapperObject wrapper = _target.ProcessedData.GetOrAdd(address); return wrapper.ExternalComObject; } + private bool GetComWrappersCCWVTableQIAddress(TargetPointer ccw, out TargetPointer vtable, out TargetPointer qiAddress) { qiAddress = TargetPointer.Null; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index 9bfdbd0f88d413..adce70a014cb19 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -405,7 +405,7 @@ private static uint GetInternalHandleType(HandleType type) private void GetHandlesForSegment(Data.TableSegment tableSegment, HandleType type, List handles) { Debug.Assert(GetInternalHandleType(type) < _handleMaxInternalTypes); - byte uBlock = tableSegment.RgTail[GetInternalHandleType(type)]; + byte uBlock = tableSegment.RgTail[(int)GetInternalHandleType(type)]; if (uBlock == _blockInvalid) return; uBlock = tableSegment.RgAllocation[uBlock]; diff --git a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs index 054126a7e2d8bb..74141c5ea37ace 100644 --- a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs @@ -105,7 +105,7 @@ public void WorkstationGC_CanEnumerateExpectedHandles(TestConfiguration config) var strongHandles = gcContract.GetHandles([HandleType.Strong]); Assert.True(strongHandles.Count >= 1, "Expected at least 1 strong handle"); Assert.All(strongHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); - Assert.All(strongHandles, handle => Assert.NotEqual(0u, handle.StrongReference)); + Assert.All(strongHandles, handle => Assert.True(handle.StrongReference)); var weakShortHandles = gcContract.GetHandles([HandleType.WeakShort]); Assert.True(weakShortHandles.Count >= 1, "Expected at least 1 weak-short handle"); From fe5eb4d693d83ef6259fdaa10e15ff76ddc4792d Mon Sep 17 00:00:00 2001 From: Rachel Date: Tue, 24 Feb 2026 14:41:26 -0800 Subject: [PATCH 12/22] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs index 1973df8c905a99..7c6e4f4560e991 100644 --- a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs @@ -134,7 +134,7 @@ public void ServerGC_CanEnumerateExpectedHandles(TestConfiguration config) var strongHandles = gcContract.GetHandles([HandleType.Strong]); Assert.True(strongHandles.Count >= 1, "Expected at least 1 strong handle"); Assert.All(strongHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); - Assert.All(strongHandles, handle => Assert.NotEqual(0u, handle.StrongReference)); + Assert.All(strongHandles, handle => Assert.True(handle.StrongReference)); var weakShortHandles = gcContract.GetHandles([HandleType.WeakShort]); Assert.True(weakShortHandles.Count >= 1, "Expected at least 1 weak-short handle"); From edc8c2cd6327f794087fd8270c62a202b7f3f765 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 24 Feb 2026 18:45:04 -0800 Subject: [PATCH 13/22] copilot and trying a full dump for gc dump tests and other fixes --- docs/design/datacontracts/BuiltInCOM.md | 2 +- docs/design/datacontracts/GC.md | 17 ++++++++--------- .../Contracts/GC/GC_1.cs | 19 ++++++++----------- .../Debuggees/GCRoots/GCRoots.csproj | 3 +++ .../Debuggees/ServerGC/ServerGC.csproj | 1 + 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/design/datacontracts/BuiltInCOM.md b/docs/design/datacontracts/BuiltInCOM.md index 1f99b6b60ea4ae..f77d633799a4ec 100644 --- a/docs/design/datacontracts/BuiltInCOM.md +++ b/docs/design/datacontracts/BuiltInCOM.md @@ -1,4 +1,4 @@ -# Contract ComWrappers +# Contract BuiltInCOM This contract is for getting information related to built-in COM. diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 9e762ef5ecdc56..eed48c849c623f 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -652,12 +652,6 @@ private void GetHandlesForSegment(TargetPointer segmentPtr, HandleType type, Lis byte uHead = uBlock; do { - if (HasSecondary(type)) - { - byte blockIndex = target.Read(segmentPtr + /* TableSegment::RgUserData offset */ + uBlock); - if (blockIndex == target.ReadGlobal("BlockInvalid")) - continue; - } GetHandlesForBlock(segmentPtr, uBlock, type, handles); // update uBlock uBlock = target.Read(segmentPtr + /* TableSegment::RgAllocation offset */ + uBlock); @@ -693,8 +687,13 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui if (HasSecondary(type)) { byte blockIndex = target.Read(segmentPtr + /* TableSegment::RgUserData offset */ + uBlock); - uint offset = blockIndex * target.ReadGlobal("HandlesPerBlock") + intraBlockIndex; - handleData.Secondary = target.ReadPointer(segmentPtr + /* TableSegment::RgValue offset */ + offset * target.PointerSize); + if (blockIndex == target.ReadGlobal("BlockInvalid")) + handleData.Secondary = 0; + else + { + uint offset = blockIndex * target.ReadGlobal("HandlesPerBlock") + intraBlockIndex; + handleData.Secondary = target.ReadPointer(segmentPtr + /* TableSegment::RgValue offset */ + offset * target.PointerSize); + } } else { @@ -710,7 +709,7 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui { IBuiltInCOM builtInCOM = target.Contracts.BuiltInCOM; handleData.RefCount = (uint)builtInCOM.GetRefCount(ccw); - handleData.StrongReference = handleData.StrongReference || handleData.RefCount > 0 && !builtInCOM.IsHandleWeak(ccw); + handleData.StrongReference = handleData.StrongReference || (handleData.RefCount > 0 && !builtInCOM.IsHandleWeak(ccw)); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index adce70a014cb19..7111d9a76a8dc6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -410,19 +410,11 @@ private void GetHandlesForSegment(Data.TableSegment tableSegment, HandleType typ return; uBlock = tableSegment.RgAllocation[uBlock]; byte uHead = uBlock; - byte uBlockOld; // for each block in the segment for the given handle type do { - uBlockOld = uBlock; + GetHandlesForBlock(tableSegment, uBlock, type, handles); uBlock = tableSegment.RgAllocation[uBlock]; - if (HasSecondary(type)) - { - byte blockIndex = tableSegment.RgUserData[uBlockOld]; - if (blockIndex == _blockInvalid) - continue; - } - GetHandlesForBlock(tableSegment, uBlockOld, type, handles); } while (uBlock != uHead); } @@ -454,8 +446,13 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui if (HasSecondary(type)) { byte blockIndex = tableSegment.RgUserData[uBlock]; - uint offset = blockIndex * _handlesPerBlock + intraBlockIndex; - handleData.Secondary = _target.ReadPointer(tableSegment.RgValue + offset * (uint)_target.PointerSize); + if (blockIndex == _blockInvalid) + handleData.Secondary = 0; + else + { + uint offset = blockIndex * _handlesPerBlock + intraBlockIndex; + handleData.Secondary = _target.ReadPointer(tableSegment.RgValue + offset * (uint)_target.PointerSize); + } } else { diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj index 35e3d8428b7cfc..bb776824769fe6 100644 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj @@ -1,2 +1,5 @@ + + Full + diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj index e634aa8ad21491..48306a8779683f 100644 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj @@ -1,5 +1,6 @@ true + Full From 165ad43d404cd7aec96d3ec39f46803d8e584c0b Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 24 Feb 2026 19:48:35 -0800 Subject: [PATCH 14/22] fix --- src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs index 7c6e4f4560e991..6a0f06b76601b4 100644 --- a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs @@ -16,6 +16,7 @@ namespace Microsoft.Diagnostics.DataContractReader.DumpTests; public class ServerGCDumpTests : DumpTestBase { protected override string DebuggeeName => "ServerGC"; + protected override string DumpType => "full"; [ConditionalTheory] [MemberData(nameof(TestConfigurations))] From ce9789520bde61836ad6cb84b016860705223daa Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 24 Feb 2026 19:59:31 -0800 Subject: [PATCH 15/22] fix --- .../managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs index 74141c5ea37ace..3fcd31dbd1a63b 100644 --- a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs @@ -14,6 +14,7 @@ namespace Microsoft.Diagnostics.DataContractReader.DumpTests; public class WorkstationGCDumpTests : DumpTestBase { protected override string DebuggeeName => "GCRoots"; + protected override string DumpType => "full"; [ConditionalTheory] [MemberData(nameof(TestConfigurations))] From e01c15a953314e2746386ce01a95898734b57bc6 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 24 Feb 2026 20:19:08 -0800 Subject: [PATCH 16/22] md --- src/coreclr/gc/gcinterface.h | 2 +- .../Contracts/GC/GC_1.cs | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h index 43a20f7fac63e1..2acc70015db514 100644 --- a/src/coreclr/gc/gcinterface.h +++ b/src/coreclr/gc/gcinterface.h @@ -412,7 +412,7 @@ typedef enum * graph. This further implies that short weak handles do not track * across object resurrections. * [cDAC] [GC]: Contract depends on this value - * + * */ HNDTYPE_WEAK_SHORT = 0, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index 7111d9a76a8dc6..b3f34e3f2f58d0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -370,7 +370,8 @@ HandleType[] IGC.GetHandleTypes(uint[] types) { if (type >= _handleMaxInternalTypes) continue; - handleTypes.Add(type switch + + HandleType? mappedType = type switch { (uint)HandleType_1.WeakShort => HandleType.WeakShort, (uint)HandleType_1.WeakLong => HandleType.WeakLong, @@ -380,8 +381,13 @@ HandleType[] IGC.GetHandleTypes(uint[] types) (uint)HandleType_1.Dependent => HandleType.Dependent, (uint)HandleType_1.WeakInteriorPointer => HandleType.WeakInteriorPointer, (uint)HandleType_1.CrossReference => HandleType.CrossReference, - _ => throw new InvalidOperationException($"Unknown handle type {type}"), - }); + _ => null, + }; + + if (mappedType is HandleType concreteType) + { + handleTypes.Add(concreteType); + } } return handleTypes.ToArray(); } From a49a6fadc217f74340c5486ef31dd01e2ee2d9b9 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 24 Feb 2026 20:20:07 -0800 Subject: [PATCH 17/22] md --- docs/design/datacontracts/ComWrappers.md | 5 ----- docs/design/datacontracts/GC.md | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/design/datacontracts/ComWrappers.md b/docs/design/datacontracts/ComWrappers.md index 10d02225e34cd2..18a19dc7641772 100644 --- a/docs/design/datacontracts/ComWrappers.md +++ b/docs/design/datacontracts/ComWrappers.md @@ -49,11 +49,6 @@ Contracts used: ``` csharp -private enum Flags -{ - IsHandleWeak = 0x4, -} - public TargetPointer GetComWrappersIdentity(TargetPointer address) { return _target.ReadPointer(address + /* NativeObjectWrapperObject::ExternalComObject offset */); diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index eed48c849c623f..3e95475d91d0bc 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -636,6 +636,8 @@ HandleType[] IGC.GetSupportedHandleTypes() // currently supported types: WeakShort, WeakLong, Strong, Pinned, Dependent, WeakInteriorPointer, RefCounted (conditional on at least one of global variables "FeatureCOMInterop", "FeatureComWrappers", and "FeatureObjCMarshal"), and CrossReference (conditional on global variable "FeatureJavaMarshal") } +HandleType[] GetHandleTypes(uint[] types) => // map raw uint into HandleType enum + private void GetHandlesForSegment(TargetPointer segmentPtr, HandleType type, List handles) { // GC handles are stored in circular linked lists per segment and handle type. From c9e855b9454e98fce9e8abaad8d81ee4ab2e675b Mon Sep 17 00:00:00 2001 From: Rachel Date: Tue, 24 Feb 2026 20:40:38 -0800 Subject: [PATCH 18/22] Update src/coreclr/vm/datadescriptor/datadescriptor.inc Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/vm/datadescriptor/datadescriptor.inc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index 4573d15bc15a3d..6afdcd43d4115c 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1197,7 +1197,9 @@ CDAC_GLOBAL(ComRefcountMask, int64, SimpleComCallWrapper::COM_REFCOUNT_MASK) // When adding a new subdescriptor, EnumMemDescriptors must be updated appropriately. CDAC_GLOBAL_SUB_DESCRIPTOR(GC, &(g_gc_dac_vars.gc_descriptor)) +#if FEATURE_COMINTEROP CDAC_GLOBAL_CONTRACT(BuiltInCOM, 1) +#endif // FEATURE_COMINTEROP CDAC_GLOBAL_CONTRACT(CodeVersions, 1) #ifdef FEATURE_COMWRAPPERS CDAC_GLOBAL_CONTRACT(ComWrappers, 1) From f062e5499a58d15e9f1830c88d8725c7f6eda334 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Wed, 25 Feb 2026 15:57:03 -0800 Subject: [PATCH 19/22] fix the enum bug --- docs/design/datacontracts/GC.md | 10 ++++++++-- src/coreclr/gc/datadescriptor/datadescriptor.inc | 1 + src/coreclr/gc/gc.cpp | 2 -- src/coreclr/gc/gc.h | 2 ++ .../Constants.cs | 1 + .../Contracts/GC/GC_1.cs | 10 ++++++++-- .../tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj | 3 --- .../tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj | 1 - .../managed/cdac/tests/DumpTests/ServerGCDumpTests.cs | 1 - .../cdac/tests/DumpTests/WorkstationGCDumpTests.cs | 1 - 10 files changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index 11acdcf2c8d5c0..d99864615486ff 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -228,6 +228,7 @@ Global variables used: | `FeatureObjCMarshal` | byte | VM | Non-zero when Objective-C marshal support is enabled | | `FeatureJavaMarshal` | byte | VM | Non-zero when Java marshal support is enabled | | `GlobalAllocContext` | TargetPointer | VM | Pointer to the global `EEAllocContext` | +| `TotalCpuCount` | uint | GC | Number of available processors | Contracts used: | Contract Name | @@ -600,6 +601,12 @@ List IGC.GetHandles(HandleType[] types) { List handles = new(); TargetPointer handleTableMap = target.ReadGlobalPointer("HandleTableMap"); + string[] gcIdentifiers = GetGCIdentifiers(); + uint tableCount = 0; + if (!gcType.Contains("workstation")) + tableCount = 1; + else + tableCount = _target.Read(_target.ReadGlobalPointer("TotalCpuCount")), // for each handleTableMap in the linked list while (handleTableMap != TargetPointer.Null) { @@ -609,8 +616,7 @@ List IGC.GetHandles(HandleType[] types) if (bucketPtr == TargetPointer.Null) continue; - int heapCount = (int)((IGC)this).GetGCHeapCount(); - for (int j = 0; j < heapCount; j++) + for (int j = 0; j < tableCount; j++) { // double dereference to iterate handle tables per array element per GC heap - native equivalent = map->pBuckets[i]->pTable[j] TargetPointer table = target.ReadPointer(bucketPtr + /* HandleTableBucket::Table offset */); diff --git a/src/coreclr/gc/datadescriptor/datadescriptor.inc b/src/coreclr/gc/datadescriptor/datadescriptor.inc index 0115718d33064d..3b9b487aa069d4 100644 --- a/src/coreclr/gc/datadescriptor/datadescriptor.inc +++ b/src/coreclr/gc/datadescriptor/datadescriptor.inc @@ -188,6 +188,7 @@ CDAC_GLOBAL_POINTER(StructureInvalidCount, &GCScan::m_GcStructuresInvalidCnt) #ifdef SERVER_GC CDAC_GLOBAL_POINTER(NumHeaps, &GC_NAMESPACE::gc_heap::n_heaps) CDAC_GLOBAL_POINTER(Heaps, cdac_data::Heaps) +CDAC_GLOBAL_POINTER(TotalCpuCount, &g_totalCpuCount) #endif // SERVER_GC #ifdef BACKGROUND_GC diff --git a/src/coreclr/gc/gc.cpp b/src/coreclr/gc/gc.cpp index 5554b19b8552c6..87712176209629 100644 --- a/src/coreclr/gc/gc.cpp +++ b/src/coreclr/gc/gc.cpp @@ -58,8 +58,6 @@ #endif // TARGET_AMD64 || TARGET_ARM64 #include "introsort.h" -extern uint32_t g_totalCpuCount; - #ifdef SERVER_GC namespace SVR { #else // SERVER_GC diff --git a/src/coreclr/gc/gc.h b/src/coreclr/gc/gc.h index 348215878a581a..af6097e1a3ebca 100644 --- a/src/coreclr/gc/gc.h +++ b/src/coreclr/gc/gc.h @@ -161,6 +161,8 @@ extern "C" uint32_t g_num_processors; extern VOLATILE(int32_t) g_fSuspensionPending; +extern uint32_t g_totalCpuCount; + ::IGCHandleManager* CreateGCHandleManager(); namespace WKS { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs index 5e4ef1b1c305a5..d8d6a5bbf8c886 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -133,6 +133,7 @@ public static class Globals public const string HandleMaxInternalTypes = nameof(HandleMaxInternalTypes); public const string HandlesPerBlock = nameof(HandlesPerBlock); public const string BlockInvalid = nameof(BlockInvalid); + public const string TotalCpuCount = nameof(TotalCpuCount); } public static class FieldNames { diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs index d26a6a89c254e2..df3afa820a350c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GC_1.cs @@ -313,6 +313,13 @@ List IGC.GetHandles(HandleType[] types) typesList.Sort(); List handles = new(); TargetPointer handleTableMap = _target.ReadGlobalPointer(Constants.Globals.HandleTableMap); + GCType gcType = GetGCType(); + uint tableCount = gcType switch + { + GCType.Workstation => 1, + GCType.Server => _target.Read(_target.ReadGlobalPointer(Constants.Globals.TotalCpuCount)), + _ => 0 // unknown + }; while (handleTableMap != TargetPointer.Null) { Data.HandleTableMap handleTableData = _target.ProcessedData.GetOrAdd(handleTableMap); @@ -322,8 +329,7 @@ List IGC.GetHandles(HandleType[] types) continue; Data.HandleTableBucket bucket = _target.ProcessedData.GetOrAdd(bucketPtr); - int heapCount = (int)((IGC)this).GetGCHeapCount(); - for (int j = 0; j < heapCount; j++) + for (uint j = 0; j < tableCount; j++) { TargetPointer handleTablePtr = _target.ReadPointer(bucket.Table + (ulong)(j * _target.PointerSize)); if (handleTablePtr == TargetPointer.Null) diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj index bb776824769fe6..35e3d8428b7cfc 100644 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/GCRoots/GCRoots.csproj @@ -1,5 +1,2 @@ - - Full - diff --git a/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj b/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj index 48306a8779683f..e634aa8ad21491 100644 --- a/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj +++ b/src/native/managed/cdac/tests/DumpTests/Debuggees/ServerGC/ServerGC.csproj @@ -1,6 +1,5 @@ true - Full diff --git a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs index 6a0f06b76601b4..7c6e4f4560e991 100644 --- a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs @@ -16,7 +16,6 @@ namespace Microsoft.Diagnostics.DataContractReader.DumpTests; public class ServerGCDumpTests : DumpTestBase { protected override string DebuggeeName => "ServerGC"; - protected override string DumpType => "full"; [ConditionalTheory] [MemberData(nameof(TestConfigurations))] diff --git a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs index 5472e93da699f7..dbf4097f2f388c 100644 --- a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs @@ -14,7 +14,6 @@ namespace Microsoft.Diagnostics.DataContractReader.DumpTests; public class WorkstationGCDumpTests : DumpTestBase { protected override string DebuggeeName => "GCRoots"; - protected override string DumpType => "full"; [ConditionalTheory] [MemberData(nameof(TestConfigurations))] From 3d3465179507e0ffd6c969783b142fc0fea8f31d Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 26 Feb 2026 10:47:07 -0800 Subject: [PATCH 20/22] add assert --- src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs | 4 ++++ .../managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs index 7c6e4f4560e991..2a00258b14af7f 100644 --- a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs @@ -140,6 +140,10 @@ public void ServerGC_CanEnumerateExpectedHandles(TestConfiguration config) Assert.True(weakShortHandles.Count >= 1, "Expected at least 1 weak-short handle"); Assert.All(weakShortHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + var weakLongHandles = gcContract.GetHandles([HandleType.WeakLong]); + Assert.True(weakLongHandles.Count >= 1, "Expected at least 1 weak-long handle"); + Assert.All(weakLongHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + var dependentHandles = gcContract.GetHandles([HandleType.Dependent]); Assert.True(dependentHandles.Count >= 1, "Expected at least 1 dependent handle"); Assert.All(dependentHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); diff --git a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs index dbf4097f2f388c..38427e925b8b4f 100644 --- a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs @@ -109,6 +109,10 @@ public void WorkstationGC_CanEnumerateExpectedHandles(TestConfiguration config) Assert.True(weakShortHandles.Count >= 1, "Expected at least 1 weak-short handle"); Assert.All(weakShortHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + var weakLongHandles = gcContract.GetHandles([HandleType.WeakLong]); + Assert.True(weakLongHandles.Count >= 1, "Expected at least 1 weak-long handle"); + Assert.All(weakLongHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); + var dependentHandles = gcContract.GetHandles([HandleType.Dependent]); Assert.True(dependentHandles.Count >= 1, "Expected at least 1 dependent handle"); Assert.All(dependentHandles, handle => Assert.NotEqual(TargetPointer.Null, handle.Handle)); From 59a168dc3f2db0e4a1e482e762eee6aebc135989 Mon Sep 17 00:00:00 2001 From: Rachel Date: Thu, 26 Feb 2026 10:53:46 -0800 Subject: [PATCH 21/22] Update docs/design/datacontracts/GC.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/GC.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/datacontracts/GC.md b/docs/design/datacontracts/GC.md index d99864615486ff..023cd2f4bbf825 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -603,10 +603,10 @@ List IGC.GetHandles(HandleType[] types) TargetPointer handleTableMap = target.ReadGlobalPointer("HandleTableMap"); string[] gcIdentifiers = GetGCIdentifiers(); uint tableCount = 0; - if (!gcType.Contains("workstation")) + if (gcType.Contains("workstation")) tableCount = 1; else - tableCount = _target.Read(_target.ReadGlobalPointer("TotalCpuCount")), + tableCount = target.Read(target.ReadGlobalPointer("TotalCpuCount")); // for each handleTableMap in the linked list while (handleTableMap != TargetPointer.Null) { From 910a8ce4e689c0e0b0bce89bf32feee5d21c93f8 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Thu, 26 Feb 2026 10:55:19 -0800 Subject: [PATCH 22/22] fix bad merge --- .../managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs index 38427e925b8b4f..8045d9dd0e97d0 100644 --- a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs @@ -93,7 +93,8 @@ public void WorkstationGC_BoundsAreReasonable(TestConfiguration config) [SkipOnVersion("net10.0", "GC contract is not available in .NET 10 dumps")] public void WorkstationGC_CanEnumerateExpectedHandles(TestConfiguration config) { - + InitializeDumpTest(config); + IGC gcContract = Target.Contracts.GC; var pinnedHandles = gcContract.GetHandles([HandleType.Pinned]); Assert.True( pinnedHandles.Count >= 5,