diff --git a/docs/design/datacontracts/BuiltInCOM.md b/docs/design/datacontracts/BuiltInCOM.md new file mode 100644 index 00000000000000..f77d633799a4ec --- /dev/null +++ b/docs/design/datacontracts/BuiltInCOM.md @@ -0,0 +1,53 @@ +# Contract BuiltInCOM + +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 2a83a8044ec5b6..18a19dc7641772 100644 --- a/docs/design/datacontracts/ComWrappers.md +++ b/docs/design/datacontracts/ComWrappers.md @@ -48,6 +48,7 @@ Contracts used: ``` csharp + 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 323e69c353534c..023cd2f4bbf825 100644 --- a/docs/design/datacontracts/GC.md +++ b/docs/design/datacontracts/GC.md @@ -108,6 +108,12 @@ public readonly struct GCOomData 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); // Gets the global allocation context pointer and limit void GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit); ``` @@ -160,6 +166,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) | | `GCAllocContext` | AllocBytes | VM | Number of bytes allocated on SOH by this context | | `GCAllocContext` | AllocBytesLoh | VM | Number of bytes allocated not on SOH by this context | | `EEAllocContext` | GCAllocationContext | VM | The `GCAllocContext` struct within an `EEAllocContext` | @@ -198,16 +213,28 @@ 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 | | `GlobalAllocContext` | TargetPointer | VM | Pointer to the global `EEAllocContext` | +| `TotalCpuCount` | uint | GC | Number of available processors | Contracts used: | Contract Name | | --- | -| _(none)_ | - +| BuiltInCOM | +| Object | Constants used: | Name | Type | Purpose | Value | @@ -556,6 +583,154 @@ private List ReadGCHeapDataArray(TargetPointer arrayStart, uint len } ``` +GetHandles +```csharp +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"); + 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) + { + TargetPointer bucketsPtr = target.ReadPointer(handleTableMap + /* HandleTableMap::BucketsPtr offset */); + foreach (/* read global variable "InitialHandleTableArraySize" bucketPtrs starting at bucketsPtr */) + { + if (bucketPtr == TargetPointer.Null) + continue; + + 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 */); + TargetPointer handleTablePtr = target.ReadPointer(table + (ulong)(j * target.PointerSize)); + if (handleTablePtr == TargetPointer.Null) + continue; + + foreach (HandleType 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; +} + +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. + // 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 + // RgUserData = 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]; + 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); + byte uHead = uBlock; + do + { + 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, HandleType type, List handles) +{ + for (uint k = 0; k < target.ReadGlobal("HandlesPerBlock"); k++) + { + uint offset = uBlock * target.ReadGlobal("HandlesPerBlock") + k; + TargetPointer handleAddress = 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) => // 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, HandleType type) +{ + HandleData handleData = default; + handleData.Handle = handleAddress; + handleData.Type = GetInternalHandleType(type); + handleData.JupiterRefCount = 0; + handleData.IsPegged = false; + handleData.StrongReference = IsStrongReference(type); + if (HasSecondary(type)) + { + byte blockIndex = target.Read(segmentPtr + /* TableSegment::RgUserData offset */ + uBlock); + 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 + { + 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) + { + IBuiltInCOM builtInCOM = target.Contracts.BuiltInCOM; + handleData.RefCount = (uint)builtInCOM.GetRefCount(ccw); + handleData.StrongReference = handleData.StrongReference || (handleData.RefCount > 0 && !builtInCOM.IsHandleWeak(ccw)); + } + } + + return handleData; +} +``` + GetGlobalAllocationContext ```csharp void IGC.GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) 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..3b9b487aa069d4 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, /*uintptr_t*/, (uintptr_t)DEBUG_DestroyedHandleValue) +#else +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) +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 @@ -151,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/coreclr/gc/gcinterface.h b/src/coreclr/gc/gcinterface.h index 7f56715805634b..43bd1752cc4168 100644 --- a/src/coreclr/gc/gcinterface.h +++ b/src/coreclr/gc/gcinterface.h @@ -411,6 +411,7 @@ 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/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 14d93705d8c2ed..76710985539e6b 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1032,6 +1032,19 @@ CDAC_TYPE_FIELD(DynamicILBlobTable, /*uint32*/, EntryMethodToken, cdac_data::EntryIL) CDAC_TYPE_END(DynamicILBlobTable) +#ifdef FEATURE_COMINTEROP +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, /*int64*/, RefCount, cdac_data::RefCount) +CDAC_TYPE_FIELD(SimpleComCallWrapper, /*uint32*/, Flags, cdac_data::Flags) +CDAC_TYPE_END(SimpleComCallWrapper) +#endif // FEATURE_COMINTEROP + #ifdef FEATURE_COMWRAPPERS CDAC_TYPE_BEGIN(ComWrappersVtablePtrs) CDAC_TYPE_SIZE(g_numKnownQueryInterfaceImplementations * sizeof(PCODE)) @@ -1102,6 +1115,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 @@ -1175,7 +1203,9 @@ CDAC_GLOBAL_POINTER(ProfilerControlBlock, &::g_profControlBlock) 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 @@ -1184,6 +1214,9 @@ CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address) // 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) 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 6f20777576e1cd..23050e3262147d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -94,6 +94,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/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.Abstractions/Contracts/IGC.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IGC.cs index 444424176f9405..761219b245b431 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,27 @@ 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, + uint Type, + bool StrongReference, + uint RefCount, + uint JupiterRefCount, + bool IsPegged); + public readonly struct GCHeapData { public TargetPointer MarkArray { get; init; } @@ -102,6 +123,9 @@ public interface IGC : IContract GCOomData GetOomData() => throw new NotImplementedException(); // server variant GCOomData GetOomData(TargetPointer heapAddress) => throw new NotImplementedException(); + List GetHandles(HandleType[] types) => throw new NotImplementedException(); + HandleType[] GetSupportedHandleTypes() => throw new NotImplementedException(); + HandleType[] GetHandleTypes(uint[] types) => throw new NotImplementedException(); void GetGlobalAllocationContext(out TargetPointer allocPtr, out TargetPointer allocLimit) => throw new NotImplementedException(); } 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 74e4f327f5e073..2e9663aedf96f7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -141,6 +141,8 @@ public enum DataType HijackFrame, TailCallFrame, StubDispatchFrame, + ComCallWrapper, + SimpleComCallWrapper, /* GC Data Types */ @@ -150,4 +152,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 cabf020e0cfacd..d8d6a5bbf8c886 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 FeaturePortableEntrypoints = nameof(FeaturePortableEntrypoints); @@ -73,6 +76,8 @@ public static class Globals public const string PlatformMetadata = nameof(PlatformMetadata); public const string ProfilerControlBlock = nameof(ProfilerControlBlock); + public const string ComRefcountMask = nameof(ComRefcountMask); + public const string HashMapSlotsPerBucket = nameof(HashMapSlotsPerBucket); public const string HashMapValueMask = nameof(HashMapValueMask); @@ -121,6 +126,14 @@ 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 const string TotalCpuCount = nameof(TotalCpuCount); } public static class FieldNames { 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/GC/GCFactory.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCFactory.cs index 9eb2570226ed47..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 @@ -9,9 +9,13 @@ 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); + uint handleMaxInternalTypes = target.ReadGlobal(Constants.Globals.HandleMaxInternalTypes); return version switch { - 1 => new GC_1(target), + 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 2183181cdf55f7..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 @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Microsoft.Diagnostics.DataContractReader.Contracts.GCHelpers; @@ -19,11 +20,31 @@ private enum GCType Server, } + private enum HandleType_1 + { + WeakShort = 0, + WeakLong = 1, + Strong = 2, + Pinned = 3, + RefCounted = 5, + Dependent = 6, + WeakInteriorPointer = 10, + CrossReference = 11 + } + private readonly Target _target; + private readonly uint _handlesPerBlock; + private readonly byte _blockInvalid; + private readonly TargetPointer _debugDestroyedHandleValue; + private readonly uint _handleMaxInternalTypes; - internal GC_1(Target target) + 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() @@ -285,4 +306,192 @@ private bool IsDatasEnabled() string[] identifiers = ((IGC)this).GetGCIdentifiers(); return identifiers.Contains(GCIdentifiers.DynamicHeapCount); } + + List IGC.GetHandles(HandleType[] types) + { + List typesList = types.ToList(); + 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); + foreach (TargetPointer bucketPtr in handleTableData.BucketsPtr) + { + if (bucketPtr == TargetPointer.Null) + continue; + + Data.HandleTableBucket bucket = _target.ProcessedData.GetOrAdd(bucketPtr); + for (uint j = 0; j < tableCount; j++) + { + TargetPointer handleTablePtr = _target.ReadPointer(bucket.Table + (ulong)(j * _target.PointerSize)); + if (handleTablePtr == TargetPointer.Null) + continue; + + Data.HandleTable handleTable = _target.ProcessedData.GetOrAdd(handleTablePtr); + if (handleTable.SegmentList == TargetPointer.Null) + continue; + foreach (HandleType type in typesList) + { + 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; + } + + HandleType[] IGC.GetSupportedHandleTypes() + { + List supportedTypes = + [ + 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(HandleType.RefCounted); + } + if (_target.ReadGlobal(Constants.Globals.FeatureJavaMarshal) != 0) + { + supportedTypes.Add(HandleType.CrossReference); + } + return supportedTypes.ToArray(); + } + + HandleType[] IGC.GetHandleTypes(uint[] types) + { + List handleTypes = new(); + foreach (uint type in types) + { + if (type >= _handleMaxInternalTypes) + continue; + + HandleType? mappedType = 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, + _ => null, + }; + + if (mappedType is HandleType concreteType) + { + handleTypes.Add(concreteType); + } + } + 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(GetInternalHandleType(type) < _handleMaxInternalTypes); + byte uBlock = tableSegment.RgTail[(int)GetInternalHandleType(type)]; + if (uBlock == _blockInvalid) + return; + uBlock = tableSegment.RgAllocation[uBlock]; + byte uHead = uBlock; + // for each block in the segment for the given handle type + do + { + GetHandlesForBlock(tableSegment, uBlock, type, handles); + uBlock = tableSegment.RgAllocation[uBlock]; + } while (uBlock != uHead); + } + + private void GetHandlesForBlock(Data.TableSegment tableSegment, byte uBlock, HandleType 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(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, HandleType type) + { + HandleData handleData = default; + handleData.Handle = handleAddress; + handleData.Type = GetInternalHandleType(type); + handleData.JupiterRefCount = 0; + handleData.IsPegged = false; + handleData.StrongReference = IsStrongReference(type); + if (HasSecondary(type)) + { + byte blockIndex = tableSegment.RgUserData[uBlock]; + if (blockIndex == _blockInvalid) + handleData.Secondary = 0; + else + { + 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) + { + IBuiltInCOM builtInCOM = _target.Contracts.BuiltInCOM; + handleData.RefCount = (uint)builtInCOM.GetRefCount(ccw); + handleData.StrongReference = handleData.StrongReference || (handleData.RefCount > 0 && !builtInCOM.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..dc36501ed11940 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ComCallWrapper.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. + + +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..072c9ef00dfe5d --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTable.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. + + +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..648705c88b31cb --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/HandleTableBucket.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. + + +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..b442c410be0bb0 --- /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; + + +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..cb0271254b8aea --- /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. + + + +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..6ff3113662bf16 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/TableSegment.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. + + +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 890dff70130e65..0a34475562859b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ISOSDacInterface.cs @@ -437,6 +437,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 @@ -598,9 +618,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 3528f3da470344..36e1bc17b9d034 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -1371,12 +1371,178 @@ 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, HandleType[] types, ISOSHandleEnum? legacyHandleEnum) + { + _target = target; + _legacyHandleEnum = legacyHandleEnum; + _handles = GetHandles(types); + } + + private SOSHandleData[] GetHandles(HandleType[] 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 = _index < _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; + HandleType[] 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 + IGC gc = _target.Contracts.GC; + HandleType[] handleTypes = gc.GetHandleTypes(types); + ppHandleEnum = new SOSHandleEnum(_target, handleTypes, 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) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 63e8ada1803f29..46468a7af170c7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -47,6 +47,7 @@ public CachingContractRegistry(Target target, TryGetContractVersionDelegate tryG [typeof(IGCInfo)] = new GCInfoFactory(), [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..2a00258b14af7f 100644 --- a/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/ServerGCDumpTests.cs @@ -116,4 +116,41 @@ 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.True(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 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)); + + 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 5e5d70e724e5ae..8045d9dd0e97d0 100644 --- a/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/WorkstationGCDumpTests.cs @@ -90,11 +90,46 @@ public void WorkstationGC_BoundsAreReasonable(TestConfiguration config) [ConditionalTheory] [MemberData(nameof(TestConfigurations))] - public void WorkstationGC_GlobalAllocationContextIsReadable(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, + $"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.True(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 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)); + + Assert.Contains( + dependentHandles, + handle => handle.Secondary != TargetPointer.Null); + } + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "GC contract is not available in .NET 10 dumps")] + public void WorkstationGC_GlobalAllocationContextIsReadable(TestConfiguration config) + { + InitializeDumpTest(config); + IGC gcContract = Target.Contracts.GC; gcContract.GetGlobalAllocationContext(out TargetPointer pointer, out TargetPointer limit); if (pointer != TargetPointer.Null)