From c79db346a2ae9962b92b6089666cf4d0bdc4295f Mon Sep 17 00:00:00 2001 From: Max Charlamb <44248479+max-charlamb@users.noreply.github.com> Date: Fri, 19 Jun 2026 17:05:21 -0400 Subject: [PATCH] [cDAC] Add FeatureFlags contract, reduce direct global reads in legacy impl, expand NativeAOT descriptor parity - Add IFeatureFlags contract and FeatureFlags_1 implementation that treats absent globals as disabled (feature flag globals are now only emitted when enabled, removing the #else zero-value branches from CoreCLR datadescriptor) - Migrate direct ReadGlobal(FeatureX) calls in ExecutionManager, GC_1, and SOSDacImpl to use IFeatureFlags.IsEnabled() - Make GC heap-analyze globals optional (InternalRootArray, InternalRootArrayIndex, HeapAnalyzeSuccess now nullable) to handle builds where they are absent - Add RuntimeInstance/TypeManager cdac_data specializations and type descriptors in NativeAOT datadescriptor for module enumeration - Declare additional NativeAOT contract parity entries (Loader, ExecutionManager, SyncBlock, CodeVersions, ReJIT, PrecodeStubs, PlatformMetadata, FeatureFlags) - Add FeatureFlags.md data contract documentation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/design/datacontracts/FeatureFlags.md | 58 +++++++++++++++++++ .../nativeaot/Runtime/RuntimeInstance.h | 8 +++ src/coreclr/nativeaot/Runtime/TypeManager.h | 8 +++ .../Runtime/datadescriptor/datadescriptor.h | 2 + .../Runtime/datadescriptor/datadescriptor.inc | 46 +++++++++++++-- .../vm/datadescriptor/datadescriptor.inc | 15 +---- .../ContractRegistry.cs | 4 ++ .../Contracts/IFeatureFlags.cs | 32 ++++++++++ .../Contracts/IGC.cs | 6 +- .../ExecutionManagerCore.EEJitManager.cs | 2 +- .../ExecutionManager/ExecutionManagerCore.cs | 2 +- .../Contracts/FeatureFlags_1.cs | 32 ++++++++++ .../Contracts/GC/GCHeapWKS.cs | 15 +++-- .../Contracts/GC/GC_1.cs | 7 ++- .../Contracts/GC/IGCHeap.cs | 6 +- .../CoreCLRContracts.cs | 2 + .../Data/GC/GCHeapSVR.cs | 6 +- .../SOSDacImpl.cs | 19 +++--- 18 files changed, 221 insertions(+), 49 deletions(-) create mode 100644 docs/design/datacontracts/FeatureFlags.md create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IFeatureFlags.cs create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/FeatureFlags_1.cs diff --git a/docs/design/datacontracts/FeatureFlags.md b/docs/design/datacontracts/FeatureFlags.md new file mode 100644 index 00000000000000..cdfef8ff260f7c --- /dev/null +++ b/docs/design/datacontracts/FeatureFlags.md @@ -0,0 +1,58 @@ +# Contract FeatureFlags + +This contract exposes whether optional runtime features are enabled in the target. + +## APIs of contract + +```csharp +public enum RuntimeFeature +{ + COMInterop, + ComWrappers, + ObjCMarshal, + JavaMarshal, + OnStackReplacement, + PortableEntrypoints, + Webcil, +} + +// Returns true if the target runtime has the given feature enabled. +// A feature is considered disabled when its global is absent from the +// target descriptor or present with a zero value. +bool IsEnabled(RuntimeFeature feature); +``` + +## Version 1 + +Global variables used: +| Global Name | Type | Purpose | +| --- | --- | --- | +| FeatureCOMInterop | uint8 | Present (nonzero) when COM interop is enabled | +| FeatureComWrappers | uint8 | Present (nonzero) when ComWrappers is enabled | +| FeatureObjCMarshal | uint8 | Present (nonzero) when Objective-C marshalling is enabled | +| FeatureJavaMarshal | uint8 | Present (nonzero) when Java marshalling is enabled | +| FeatureOnStackReplacement | uint8 | Present (nonzero) when on-stack replacement is enabled | +| FeaturePortableEntrypoints | uint8 | Present (nonzero) when portable entrypoints are enabled | +| FeatureWebcil | uint8 | Present (nonzero) when Webcil is enabled | + +Feature flag globals are **only present in the descriptor when the feature is enabled**. A missing global and a zero-valued global are both treated as "disabled". + +```csharp +bool IsEnabled(RuntimeFeature feature) +{ + string? globalName = feature switch + { + RuntimeFeature.COMInterop => "FeatureCOMInterop", + RuntimeFeature.ComWrappers => "FeatureComWrappers", + RuntimeFeature.ObjCMarshal => "FeatureObjCMarshal", + RuntimeFeature.JavaMarshal => "FeatureJavaMarshal", + RuntimeFeature.OnStackReplacement => "FeatureOnStackReplacement", + RuntimeFeature.PortableEntrypoints => "FeaturePortableEntrypoints", + RuntimeFeature.Webcil => "FeatureWebcil", + _ => null, + }; + return globalName is not null + && TryReadGlobal(globalName, out byte? value) + && value != 0; +} +``` diff --git a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h index e50ddac18dfd40..079eb766adc262 100644 --- a/src/coreclr/nativeaot/Runtime/RuntimeInstance.h +++ b/src/coreclr/nativeaot/Runtime/RuntimeInstance.h @@ -20,6 +20,7 @@ class RuntimeInstance friend class AsmOffsets; friend class Thread; friend void PopulateDebugHeaders(); + friend struct ::cdac_data; HANDLE m_hPalInstance; // this is the HANDLE passed into DllMain @@ -109,6 +110,13 @@ class RuntimeInstance }; typedef DPTR(RuntimeInstance) PTR_RuntimeInstance; +template<> struct cdac_data +{ + static constexpr size_t OsModuleList = offsetof(RuntimeInstance, m_OsModuleList); + static constexpr size_t TypeManagerList = offsetof(RuntimeInstance, m_TypeManagerList); + static constexpr size_t ManagedCodeStartRange = offsetof(RuntimeInstance, m_pvManagedCodeStartRange); + static constexpr size_t ManagedCodeRangeSize = offsetof(RuntimeInstance, m_cbManagedCodeRange); +}; PTR_RuntimeInstance GetRuntimeInstance(); diff --git a/src/coreclr/nativeaot/Runtime/TypeManager.h b/src/coreclr/nativeaot/Runtime/TypeManager.h index 4429ee4e28cb48..4bfce3f0acecae 100644 --- a/src/coreclr/nativeaot/Runtime/TypeManager.h +++ b/src/coreclr/nativeaot/Runtime/TypeManager.h @@ -6,6 +6,8 @@ class TypeManager { + friend struct ::cdac_data; + // NOTE: Part of this layout is a contract with the managed side in TypeManagerHandle.cs HANDLE m_osModule; ReadyToRunHeader * m_pHeader; @@ -32,6 +34,12 @@ class TypeManager }; }; +template<> struct cdac_data +{ + static constexpr size_t OsModule = offsetof(TypeManager, m_osModule); + static constexpr size_t Header = offsetof(TypeManager, m_pHeader); +}; + // TypeManagerHandle represents an AOT module in MRT based runtimes. // These handles are a pointer to a TypeManager. struct TypeManagerHandle diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h index 948fded3e79f32..f7183721433e7e 100644 --- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h +++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.h @@ -11,6 +11,7 @@ #include "Pal.h" #include "holder.h" #include "RuntimeInstance.h" +#include "TypeManager.h" #include "regdisplay.h" #include "StackFrameIterator.h" #include "thread.h" @@ -23,6 +24,7 @@ GPTR_DECL(MethodTable, g_pFreeObjectEEType); GPTR_DECL(StressLog, g_pStressLog); +GPTR_DECL(RuntimeInstance, g_pTheRuntimeInstance); // ILC emits a ContractDescriptor named "DotNetManagedContractDescriptor" with // managed type layouts. We take its address so datadescriptor.inc can reference diff --git a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc index f344d2ba7ba78a..c3a4c6b42ec288 100644 --- a/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc +++ b/src/coreclr/nativeaot/Runtime/datadescriptor/datadescriptor.inc @@ -106,6 +106,7 @@ CDAC_TYPE_FIELD(StressLog, T_POINTER, Logs, offsetof(StressLog, logs)) CDAC_TYPE_FIELD(StressLog, T_UINT64, TickFrequency, offsetof(StressLog, tickFrequency)) CDAC_TYPE_FIELD(StressLog, T_UINT64, StartTimestamp, offsetof(StressLog, startTimeStamp)) CDAC_TYPE_FIELD(StressLog, T_UINT64, StartTime, offsetof(StressLog, startTime)) +CDAC_TYPE_FIELD(StressLog, T_NUINT, ModuleOffset, offsetof(StressLog, moduleOffset)) CDAC_TYPE_END(StressLog) CDAC_TYPE_BEGIN(ThreadStressLog) @@ -138,6 +139,30 @@ CDAC_TYPE_FIELD(StressMsg, TYPE(StressMsgHeader), Header, 0) CDAC_TYPE_FIELD(StressMsg, T_POINTER, Args, offsetof(StressMsg, args)) CDAC_TYPE_END(StressMsg) +// ======================== +// Loader (RuntimeInstance / TypeManager) +// ======================== + +CDAC_TYPE_BEGIN(RuntimeInstance) +CDAC_TYPE_INDETERMINATE(RuntimeInstance) +CDAC_TYPE_FIELD(RuntimeInstance, T_POINTER, OsModuleList, cdac_data::OsModuleList) +CDAC_TYPE_FIELD(RuntimeInstance, T_POINTER, TypeManagerList, cdac_data::TypeManagerList) +CDAC_TYPE_FIELD(RuntimeInstance, T_POINTER, ManagedCodeStartRange, cdac_data::ManagedCodeStartRange) +CDAC_TYPE_FIELD(RuntimeInstance, T_UINT32, ManagedCodeRangeSize, cdac_data::ManagedCodeRangeSize) +CDAC_TYPE_END(RuntimeInstance) + +CDAC_TYPE_BEGIN(TypeManagerEntry) +CDAC_TYPE_INDETERMINATE(TypeManagerEntry) +CDAC_TYPE_FIELD(TypeManagerEntry, T_POINTER, Next, offsetof(RuntimeInstance::TypeManagerEntry, m_pNext)) +CDAC_TYPE_FIELD(TypeManagerEntry, T_POINTER, TypeManager, offsetof(RuntimeInstance::TypeManagerEntry, m_pTypeManager)) +CDAC_TYPE_END(TypeManagerEntry) + +CDAC_TYPE_BEGIN(TypeManager) +CDAC_TYPE_INDETERMINATE(TypeManager) +CDAC_TYPE_FIELD(TypeManager, T_POINTER, OsModule, cdac_data::OsModule) +CDAC_TYPE_FIELD(TypeManager, T_POINTER, Header, cdac_data::Header) +CDAC_TYPE_END(TypeManager) + CDAC_TYPES_END() // ======================== @@ -148,15 +173,13 @@ CDAC_GLOBALS_BEGIN() CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) +CDAC_GLOBAL_POINTER(RuntimeInstance, &g_pTheRuntimeInstance) + CDAC_GLOBAL_POINTER(FreeObjectMethodTable, &g_pFreeObjectEEType) CDAC_GLOBAL_POINTER(GCLowestAddress, &g_lowest_address) CDAC_GLOBAL_POINTER(GCHighestAddress, &g_highest_address) -// Thread state flag constants -CDAC_GLOBAL(ThreadStateFlagAttached, T_UINT32, 0x00000001) -CDAC_GLOBAL(ThreadStateFlagDetached, T_UINT32, 0x00000002) - // Object contract globals #ifdef TARGET_64BIT CDAC_GLOBAL(ObjectToMethodTableUnmask, T_UINT8, 7) @@ -164,7 +187,6 @@ CDAC_GLOBAL(ObjectToMethodTableUnmask, T_UINT8, 7) CDAC_GLOBAL(ObjectToMethodTableUnmask, T_UINT8, 3) #endif -// StressLog globals (no module table in NativeAOT) CDAC_GLOBAL(StressLogEnabled, T_UINT8, 1) CDAC_GLOBAL_POINTER(StressLog, &StressLog::theLog) CDAC_GLOBAL(StressLogHasModuleTable, T_UINT8, 0) @@ -172,6 +194,8 @@ CDAC_GLOBAL(StressLogChunkSize, T_UINT32, STRESSLOG_CHUNK_SIZE) CDAC_GLOBAL(StressLogValidChunkSig, T_UINT32, 0xCFCFCFCF) CDAC_GLOBAL(StressLogMaxMessageSize, T_UINT64, (uint64_t)StressMsg::maxMsgSize) +CDAC_GLOBAL(SOSBreakingChangeVersion, T_UINT8, 5) + #if defined(TARGET_BROWSER) #ifdef Browser #error Handle 'Browser' define @@ -222,10 +246,20 @@ CDAC_GLOBAL(RecommendedReaderVersion, T_UINT32, CDAC_RECOMMENDED_READER_VERSION) // Contracts: declare which contracts this runtime supports CDAC_GLOBAL_CONTRACT(Thread, n1) -CDAC_GLOBAL_CONTRACT(Exception, c1) +CDAC_GLOBAL_CONTRACT(Exception, n1) +CDAC_GLOBAL_CONTRACT(Object, n1) CDAC_GLOBAL_CONTRACT(RuntimeTypeSystem, n1) CDAC_GLOBAL_CONTRACT(RuntimeInfo, c1) CDAC_GLOBAL_CONTRACT(StressLog, c2) +CDAC_GLOBAL_CONTRACT(Loader, n1) +CDAC_GLOBAL_CONTRACT(ExecutionManager, n1) +CDAC_GLOBAL_CONTRACT(SyncBlock, n1) +CDAC_GLOBAL_CONTRACT(SymbolName, n1) +CDAC_GLOBAL_CONTRACT(CodeVersions, n1) +CDAC_GLOBAL_CONTRACT(ReJIT, n1) +CDAC_GLOBAL_CONTRACT(PrecodeStubs, n1) +CDAC_GLOBAL_CONTRACT(PlatformMetadata, n1) +CDAC_GLOBAL_CONTRACT(FeatureFlags, c1) // Managed type sub-descriptor: ILC emits a ContractDescriptor with managed type layouts // that the cDAC reader merges as a sub-descriptor. This provides field offsets for managed diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index d8ad288c1e1af2..33727a1458bf0f 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1590,33 +1590,21 @@ CDAC_GLOBAL(MethodDescTokenRemainderBitCount, T_UINT8, METHOD_TOKEN_REMAINDER_BI #if FEATURE_COMINTEROP CDAC_GLOBAL(FeatureCOMInterop, T_UINT8, 1) -#else -CDAC_GLOBAL(FeatureCOMInterop, T_UINT8, 0) #endif #if FEATURE_COMWRAPPERS CDAC_GLOBAL(FeatureComWrappers, T_UINT8, 1) -#else -CDAC_GLOBAL(FeatureComWrappers, T_UINT8, 0) #endif #if FEATURE_OBJCMARSHAL CDAC_GLOBAL(FeatureObjCMarshal, T_UINT8, 1) -#else -CDAC_GLOBAL(FeatureObjCMarshal, T_UINT8, 0) #endif #if FEATURE_JAVAMARSHAL CDAC_GLOBAL(FeatureJavaMarshal, T_UINT8, 1) -#else -CDAC_GLOBAL(FeatureJavaMarshal, T_UINT8, 0) #endif #ifdef FEATURE_ON_STACK_REPLACEMENT CDAC_GLOBAL(FeatureOnStackReplacement, T_UINT8, 1) -#else -CDAC_GLOBAL(FeatureOnStackReplacement, T_UINT8, 0) #endif // FEATURE_ON_STACK_REPLACEMENT #ifdef FEATURE_WEBCIL CDAC_GLOBAL(FeatureWebcil, T_UINT8, 1) -#else -CDAC_GLOBAL(FeatureWebcil, T_UINT8, 0) #endif // See Object::GetGCSafeMethodTable #ifdef TARGET_64BIT @@ -1626,8 +1614,6 @@ CDAC_GLOBAL(ObjectToMethodTableUnmask, T_UINT8, 1 | 1 << 1) #endif //TARGET_64BIT #ifdef FEATURE_PORTABLE_ENTRYPOINTS CDAC_GLOBAL(FeaturePortableEntrypoints, T_UINT8, 1) -#else -CDAC_GLOBAL(FeaturePortableEntrypoints, T_UINT8, 0) #endif // FEATURE_PORTABLE_ENTRYPOINTS CDAC_GLOBAL(SOSBreakingChangeVersion, T_UINT8, SOS_BREAKING_CHANGE_VERSION) CDAC_GLOBAL(RecommendedReaderVersion, T_UINT32, CDAC_RECOMMENDED_READER_VERSION) @@ -1754,6 +1740,7 @@ CDAC_GLOBAL_CONTRACT(Notifications, c1) CDAC_GLOBAL_CONTRACT(ObjectiveCMarshal, c1) #endif // FEATURE_OBJCMARSHAL CDAC_GLOBAL_CONTRACT(Object, c1) +CDAC_GLOBAL_CONTRACT(FeatureFlags, c1) CDAC_GLOBAL_CONTRACT(PlatformMetadata, c1) CDAC_GLOBAL_CONTRACT(PrecodeStubs, c3) #ifdef PROFILING_SUPPORTED 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 76b7ca71eb0537..d2b7888fdcc1d8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -54,6 +54,10 @@ public abstract class ContractRegistry /// public virtual IPlatformMetadata PlatformMetadata => GetContract(); /// + /// Gets an instance of the FeatureFlags contract for the target. + /// + public virtual IFeatureFlags FeatureFlags => GetContract(); + /// /// Gets an instance of the PrecodeStubs contract for the target. /// public virtual IPrecodeStubs PrecodeStubs => GetContract(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IFeatureFlags.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IFeatureFlags.cs new file mode 100644 index 00000000000000..24932c178aa839 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IFeatureFlags.cs @@ -0,0 +1,32 @@ +// 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; + +/// +/// Feature flags that a runtime may or may not support. +/// +public enum RuntimeFeature +{ + COMInterop, + ComWrappers, + ObjCMarshal, + JavaMarshal, + OnStackReplacement, + PortableEntrypoints, + Webcil, +} + +public interface IFeatureFlags : IContract +{ + static string IContract.Name { get; } = nameof(FeatureFlags); + + /// + /// Returns if the target runtime has the given feature enabled. + /// + bool IsEnabled(RuntimeFeature feature) => throw new NotImplementedException(); +} + +public readonly struct FeatureFlags : IFeatureFlags { } 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 3055fd58486de5..5d1f2d24022fa7 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 @@ -54,9 +54,9 @@ public readonly struct GCHeapData public TargetPointer SavedSweepEphemeralSegment { get; init; } /* Only valid in segment GC builds */ public TargetPointer SavedSweepEphemeralStart { get; init; } /* Only valid in segment GC builds */ - public TargetPointer InternalRootArray { get; init; } - public TargetNUInt InternalRootArrayIndex { get; init; } - public bool HeapAnalyzeSuccess { get; init; } + public TargetPointer? InternalRootArray { get; init; } + public TargetNUInt? InternalRootArrayIndex { get; init; } + public bool? HeapAnalyzeSuccess { get; init; } public IReadOnlyList InterestingData { get; init; } public IReadOnlyList CompactReasons { get; init; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs index 7373311067412a..c35521b4475eb5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.cs @@ -106,7 +106,7 @@ public override TargetPointer GetDebugInfo(RangeSection rangeSection, TargetCode if (!GetRealCodeHeader(rangeSection, codeStart, out Data.RealCodeHeader? realCodeHeader)) return TargetPointer.Null; - bool featureOnStackReplacement = Target.ReadGlobal(Constants.Globals.FeatureOnStackReplacement) != 0; + bool featureOnStackReplacement = Target.Contracts.FeatureFlags.IsEnabled(RuntimeFeature.OnStackReplacement); Data.EEJitManager eeJitManager = Target.ProcessedData.GetOrAdd(rangeSection.Data.JitManager); if (featureOnStackReplacement || eeJitManager.StoreRichDebugInfo) hasFlagByte = true; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs index 1e9aff1540ba9a..0cba73ce8bbcf7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.cs @@ -284,7 +284,7 @@ void IExecutionManager.GetMethodRegionInfo(CodeBlockHandle codeInfoHandle, out u TargetPointer IExecutionManager.NonVirtualEntry2MethodDesc(TargetCodePointer entrypoint) { - if (_target.ReadGlobal(Constants.Globals.FeaturePortableEntrypoints) != 0) + if (_target.Contracts.FeatureFlags.IsEnabled(RuntimeFeature.PortableEntrypoints)) { Data.PortableEntryPoint portableEntryPoint = _target.ProcessedData.GetOrAdd(entrypoint.AsTargetPointer); return portableEntryPoint.MethodDesc; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/FeatureFlags_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/FeatureFlags_1.cs new file mode 100644 index 00000000000000..5d3c6b678ea0a1 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/FeatureFlags_1.cs @@ -0,0 +1,32 @@ +// 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.Contracts; + +internal readonly struct FeatureFlags_1 : IFeatureFlags +{ + private readonly Target _target; + + public FeatureFlags_1(Target target) + { + _target = target; + } + + bool IFeatureFlags.IsEnabled(RuntimeFeature feature) + { + string? globalName = feature switch + { + RuntimeFeature.COMInterop => Constants.Globals.FeatureCOMInterop, + RuntimeFeature.ComWrappers => Constants.Globals.FeatureComWrappers, + RuntimeFeature.ObjCMarshal => Constants.Globals.FeatureObjCMarshal, + RuntimeFeature.JavaMarshal => Constants.Globals.FeatureJavaMarshal, + RuntimeFeature.OnStackReplacement => Constants.Globals.FeatureOnStackReplacement, + RuntimeFeature.PortableEntrypoints => Constants.Globals.FeaturePortableEntrypoints, + RuntimeFeature.Webcil => Constants.Globals.FeatureWebcil, + _ => null, + }; + return globalName is not null + && _target.TryReadGlobal(globalName, out byte? value) + && value != 0; + } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCHeapWKS.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCHeapWKS.cs index 355331f61cea28..20ab0a9fd28290 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCHeapWKS.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/GCHeapWKS.cs @@ -28,9 +28,12 @@ public GCHeapWKS(Target target) OomData = target.ProcessedData.GetOrAdd(target.ReadGlobalPointer(Constants.Globals.GCHeapOomData)); - InternalRootArray = target.ReadPointer(target.ReadGlobalPointer(Constants.Globals.GCHeapInternalRootArray)); - InternalRootArrayIndex = target.ReadNUInt(target.ReadGlobalPointer(Constants.Globals.GCHeapInternalRootArrayIndex)); - HeapAnalyzeSuccess = target.Read(target.ReadGlobalPointer(Constants.Globals.GCHeapHeapAnalyzeSuccess)) != 0; + if (target.TryReadGlobalPointer(Constants.Globals.GCHeapInternalRootArray, out TargetPointer? internalRootArrayPtr)) + InternalRootArray = target.ReadPointer(internalRootArrayPtr.Value); + if (target.TryReadGlobalPointer(Constants.Globals.GCHeapInternalRootArrayIndex, out TargetPointer? internalRootArrayIndexPtr)) + InternalRootArrayIndex = target.ReadNUInt(internalRootArrayIndexPtr.Value); + if (target.TryReadGlobalPointer(Constants.Globals.GCHeapHeapAnalyzeSuccess, out TargetPointer? heapAnalyzeSuccessPtr)) + HeapAnalyzeSuccess = target.Read(heapAnalyzeSuccessPtr.Value) != 0; InterestingData = target.ReadGlobalPointer(Constants.Globals.GCHeapInterestingData); CompactReasons = target.ReadGlobalPointer(Constants.Globals.GCHeapCompactReasons); @@ -60,9 +63,9 @@ public GCHeapWKS(Target target) public Data.OomHistory OomData { get; } - public TargetPointer InternalRootArray { get; } - public TargetNUInt InternalRootArrayIndex { get; } - public bool HeapAnalyzeSuccess { get; } + public TargetPointer? InternalRootArray { get; } + public TargetNUInt? InternalRootArrayIndex { get; } + public bool? HeapAnalyzeSuccess { get; } public TargetPointer InterestingData { get; } public TargetPointer CompactReasons { get; } 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 4d3d9d11a57303..fff1306ee7cf4a 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 @@ -549,11 +549,12 @@ HandleType[] IGC.GetSupportedHandleTypes() HandleType.Dependent, HandleType.WeakInteriorPointer ]; - if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) != 0 || _target.ReadGlobal(Constants.Globals.FeatureComWrappers) != 0 || _target.ReadGlobal(Constants.Globals.FeatureObjCMarshal) != 0) + IFeatureFlags featureFlags = _target.Contracts.FeatureFlags; + if (featureFlags.IsEnabled(RuntimeFeature.COMInterop) || featureFlags.IsEnabled(RuntimeFeature.ComWrappers) || featureFlags.IsEnabled(RuntimeFeature.ObjCMarshal)) { supportedTypes.Add(HandleType.RefCounted); } - if (_target.ReadGlobal(Constants.Globals.FeatureJavaMarshal) != 0) + if (featureFlags.IsEnabled(RuntimeFeature.JavaMarshal)) { supportedTypes.Add(HandleType.CrossReference); } @@ -662,7 +663,7 @@ private HandleData CreateHandleData(TargetPointer handleAddress, byte uBlock, ui handleData.Secondary = 0; } - if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) != 0 && IsRefCounted(type)) + if (_target.Contracts.FeatureFlags.IsEnabled(RuntimeFeature.COMInterop) && IsRefCounted(type)) { IObject obj = _target.Contracts.Object; TargetPointer handle = _target.ReadPointer(handleAddress); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/IGCHeap.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/IGCHeap.cs index 4264e9611db325..71ae0feeaa52c2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/IGCHeap.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GC/IGCHeap.cs @@ -20,9 +20,9 @@ internal interface IGCHeap Data.OomHistory OomData { get; } - TargetPointer InternalRootArray { get; } - TargetNUInt InternalRootArrayIndex { get; } - bool HeapAnalyzeSuccess { get; } + TargetPointer? InternalRootArray { get; } + TargetNUInt? InternalRootArrayIndex { get; } + bool? HeapAnalyzeSuccess { get; } TargetPointer InterestingData { get; } TargetPointer CompactReasons { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs index 1343a6028056bb..8447404f05e556 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs @@ -46,6 +46,8 @@ public static void Register(ContractRegistry registry) registry.Register("c1", static t => new PlatformMetadata_1(t)); + registry.Register("c1", static t => new FeatureFlags_1(t)); + registry.Register("c1", static t => new PrecodeStubs_1(t)); registry.Register("c2", static t => new PrecodeStubs_2(t)); registry.Register("c3", static t => new PrecodeStubs_3(t)); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeapSVR.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeapSVR.cs index 7f050fc642816e..50df7273e3a36a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeapSVR.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/GC/GCHeapSVR.cs @@ -27,9 +27,9 @@ internal sealed partial class GCHeapSVR : IData, IGCHeap [Field] public OomHistory OomData { get; } - [Field] public TargetPointer InternalRootArray { get; } - [Field] public TargetNUInt InternalRootArrayIndex { get; } - [Field(UnderlyingBoolType = typeof(int))] public bool HeapAnalyzeSuccess { get; } + [Field] public TargetPointer? InternalRootArray { get; } + [Field] public TargetNUInt? InternalRootArrayIndex { get; } + [Field(UnderlyingBoolType = typeof(int))] public bool? HeapAnalyzeSuccess { get; } [FieldAddress] public TargetPointer InterestingData { get; } [FieldAddress] public TargetPointer CompactReasons { get; } 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 5608f9a3ede3ca..062919b63ab2c5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -1894,9 +1894,9 @@ int ISOSDacInterface.GetHeapAnalyzeData(ClrDataAddress addr, DacpGcHeapAnalyzeDa GCHeapData heapData = gc.GetHeapData(addr.ToTargetPointer(_target)); data->heapAddr = addr; - data->internal_root_array = heapData.InternalRootArray.ToClrDataAddress(_target); - data->internal_root_array_index = heapData.InternalRootArrayIndex.Value; - data->heap_analyze_success = heapData.HeapAnalyzeSuccess ? (int)Interop.BOOL.TRUE : (int)Interop.BOOL.FALSE; + data->internal_root_array = (heapData.InternalRootArray ?? TargetPointer.Null).ToClrDataAddress(_target); + data->internal_root_array_index = (heapData.InternalRootArrayIndex ?? default).Value; + data->heap_analyze_success = (heapData.HeapAnalyzeSuccess ?? false) ? (int)Interop.BOOL.TRUE : (int)Interop.BOOL.FALSE; } catch (System.Exception ex) { @@ -1940,9 +1940,9 @@ int ISOSDacInterface.GetHeapAnalyzeStaticData(DacpGcHeapAnalyzeData* data) GCHeapData heapData = gc.GetHeapData(); data->heapAddr = 0; // Not applicable for static data - data->internal_root_array = heapData.InternalRootArray.ToClrDataAddress(_target); - data->internal_root_array_index = heapData.InternalRootArrayIndex.Value; - data->heap_analyze_success = heapData.HeapAnalyzeSuccess ? (int)Interop.BOOL.TRUE : (int)Interop.BOOL.FALSE; + data->internal_root_array = (heapData.InternalRootArray ?? TargetPointer.Null).ToClrDataAddress(_target); + data->internal_root_array_index = (heapData.InternalRootArrayIndex ?? default).Value; + data->heap_analyze_success = (heapData.HeapAnalyzeSuccess ?? false) ? (int)Interop.BOOL.TRUE : (int)Interop.BOOL.FALSE; } catch (System.Exception ex) { @@ -3376,7 +3376,7 @@ int ISOSDacInterface.GetObjectData(ClrDataAddress objAddr, DacpObjectData* data) } // Populate COM data if this is a COM object - if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) != 0 + if (_target.Contracts.FeatureFlags.IsEnabled(Contracts.RuntimeFeature.COMInterop) && objectContract.GetBuiltInComData(objPtr, out TargetPointer rcw, out TargetPointer ccw, out _)) { data->RCW = rcw & ~(_rcwMask); @@ -4995,7 +4995,7 @@ int ISOSDacInterface2.IsRCWDCOMProxy(ClrDataAddress rcwAddress, int* inDCOMProxy *inDCOMProxy = (int)Interop.BOOL.FALSE; - if (_target.ReadGlobal(Constants.Globals.FeatureCOMInterop) == 0) + if (!_target.Contracts.FeatureFlags.IsEnabled(Contracts.RuntimeFeature.COMInterop)) { hr = HResults.E_NOTIMPL; } @@ -5228,7 +5228,8 @@ int ISOSDacInterface3.GetGCGlobalMechanisms(nuint* globalMechanisms) int ISOSDacInterface4.GetClrNotification(ClrDataAddress[] arguments, int count, int* pNeeded) { int hr = HResults.S_OK; - uint MaxClrNotificationArgs = _target.ReadGlobal(Constants.Globals.MaxClrNotificationArgs); + _target.TryReadGlobal(Constants.Globals.MaxClrNotificationArgs, out uint? maxArgs); + uint MaxClrNotificationArgs = maxArgs ?? 0; try { *pNeeded = (int)MaxClrNotificationArgs;