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 a539964b0fe7d6..3d754b5284ebbb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs @@ -166,8 +166,9 @@ public abstract void Register(string version, Func where TContract : IContract; /// - /// Flush all cached data held by contracts in this registry. - /// Called when the target process state may have changed (e.g. on resume). + /// Flush all cached data held by contracts in this registry for the given + /// . Called when the target process state may have changed + /// (e.g. on resume) or as part of a stress-harness re-read of live target state. /// - public abstract void Flush(); + public abstract void Flush(FlushScope scope); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs index 27f72d3bccddfc..4c94ee13534b3d 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IContract.cs @@ -10,8 +10,14 @@ public interface IContract static virtual string Name => throw new NotImplementedException(); /// - /// Clear any cached data held by this contract. - /// Called when the target process state may have changed (e.g. on resume). + /// Clear cached data held by this contract for the given . + /// Called when the target process state may have changed (e.g. on resume) or as + /// part of a stress-harness re-read of live target state. + /// + /// Default implementation is a no-op. Contracts that maintain caches or capture + /// target-memory snapshots at construction must override this method and handle + /// each value appropriately. + /// /// - void Flush() { } + void Flush(FlushScope scope) { } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/FlushScope.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/FlushScope.cs new file mode 100644 index 00000000000000..d09c1aa6cbcdc7 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/FlushScope.cs @@ -0,0 +1,40 @@ +// 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; + +/// +/// Scope of a operation. +/// +public enum FlushScope +{ + /// + /// Clear every cache held by the target, including immutable metadata caches + /// (type layouts, contract instances, ECMA metadata, execution-manager ranges). + /// This is the safe default and matches the historical no-argument Flush behavior. + /// + All, + + /// + /// Flush only caches that may have become stale because the target process + /// has executed forward in time since the last flush (e.g. a debugger + /// resume/continue/step). Caches of immutable state that the runtime loads + /// once and never unloads (modules, types, code metadata, ECMA metadata, + /// execution-manager ranges, etc.) may be retained. + /// + /// This corresponds to ICorDebug's CorDebugStateChange.PROCESS_RUNNING: + /// see . + /// Use instead for arbitrary state snapshots where no + /// continuity with the previous target state can be assumed. + /// + /// + /// Each contract is responsible for its own correctness under this scope: a + /// contract that captures a target-memory snapshot at construction MUST + /// re-read that snapshot in its + /// implementation when + /// called with , otherwise the snapshot + /// becomes stale across the flush. + /// + /// + ForwardExecution, +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs index 2c4c504d1c14d3..733a43d9e7f2e3 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Target.cs @@ -324,12 +324,12 @@ public readonly record struct FieldInfo public abstract ContractRegistry Contracts { get; } /// - /// Clear all cached data held by this target, including processed data and contract caches. + /// Clear cached data held by this target for the given . /// Called when the target process state may have changed (e.g. on resume). /// - public virtual void Flush() + public virtual void Flush(FlushScope scope) { ProcessedData.Clear(); - Contracts.Flush(); + Contracts.Flush(scope); } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs index 432005cfaae081..00bcd71d07fe72 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/EcmaMetadata_1.cs @@ -16,7 +16,7 @@ internal sealed class EcmaMetadata_1(Target target) : IEcmaMetadata private readonly Dictionary _metadata = []; private readonly Dictionary _readOnlyMetadataAddress = []; - public void Flush() + public void Flush(FlushScope scope) { _metadata.Clear(); _readOnlyMetadataAddress.Clear(); 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 bdc5ed3fb07f87..7c8465b2baf26e 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 @@ -17,16 +17,19 @@ internal sealed partial class ExecutionManagerCore : IExecutionManager // maps CodeBlockHandle.Address (which is the CodeHeaderAddress) to the CodeBlock private readonly Dictionary _codeInfos = new(); - private readonly Data.RangeSectionMap _topRangeSectionMap; + private readonly TargetPointer _topRangeSectionMapAddress; private readonly ExecutionManagerHelpers.RangeSectionMap _rangeSectionMapLookup; private readonly EEJitManager _eeJitManager; private readonly ReadyToRunJitManager _r2rJitManager; private readonly InterpreterJitManager _interpreterJitManager; - public ExecutionManagerCore(Target target, Data.RangeSectionMap topRangeSectionMap) + private Data.RangeSectionMap _topRangeSectionMap + => _target.ProcessedData.GetOrAdd(_topRangeSectionMapAddress); + + public ExecutionManagerCore(Target target, TargetPointer topRangeSectionMapAddress) { _target = target; - _topRangeSectionMap = topRangeSectionMap; + _topRangeSectionMapAddress = topRangeSectionMapAddress; _rangeSectionMapLookup = ExecutionManagerHelpers.RangeSectionMap.Create(_target); INibbleMap nibbleMap = T.Create(_target); _eeJitManager = new EEJitManager(_target, nibbleMap); @@ -34,7 +37,7 @@ public ExecutionManagerCore(Target target, Data.RangeSectionMap topRangeSectionM _interpreterJitManager = new InterpreterJitManager(_target, nibbleMap); } - public void Flush() + public void Flush(FlushScope scope) { _codeInfos.Clear(); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs index e76ff65f697a27..a94a7c0d4b983c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_1.cs @@ -13,8 +13,7 @@ public sealed class ExecutionManager_1 : IExecutionManager internal ExecutionManager_1(Target target) { TargetPointer addr = target.ReadGlobalPointer(Constants.Globals.ExecutionManagerCodeRangeMapAddress); - Data.RangeSectionMap map = target.ProcessedData.GetOrAdd(addr); - _executionManagerCore = new ExecutionManagerCore(target, map); + _executionManagerCore = new ExecutionManagerCore(target, addr); } public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip); @@ -36,5 +35,5 @@ internal ExecutionManager_1(Target target) public IEnumerable GetCodeHeapInfos() => _executionManagerCore.GetCodeHeapInfos(); public CodeKind GetCodeKind(TargetCodePointer codeAddress) => _executionManagerCore.GetCodeKind(codeAddress); public TargetPointer FindReadyToRunModule(TargetPointer address) => _executionManagerCore.FindReadyToRunModule(address); - public void Flush() => _executionManagerCore.Flush(); + public void Flush(FlushScope scope) => _executionManagerCore.Flush(scope); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs index 61513725ef0823..6c447f2e563ab9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManager_2.cs @@ -13,8 +13,7 @@ public sealed class ExecutionManager_2 : IExecutionManager internal ExecutionManager_2(Target target) { TargetPointer addr = target.ReadGlobalPointer(Constants.Globals.ExecutionManagerCodeRangeMapAddress); - Data.RangeSectionMap map = target.ProcessedData.GetOrAdd(addr); - _executionManagerCore = new ExecutionManagerCore(target, map); + _executionManagerCore = new ExecutionManagerCore(target, addr); } public CodeBlockHandle? GetCodeBlockHandle(TargetCodePointer ip) => _executionManagerCore.GetCodeBlockHandle(ip); @@ -36,5 +35,5 @@ internal ExecutionManager_2(Target target) public IEnumerable GetCodeHeapInfos() => _executionManagerCore.GetCodeHeapInfos(); public CodeKind GetCodeKind(TargetCodePointer codeAddress) => _executionManagerCore.GetCodeKind(codeAddress); public TargetPointer FindReadyToRunModule(TargetPointer address) => _executionManagerCore.FindReadyToRunModule(address); - public void Flush() => _executionManagerCore.Flush(); + public void Flush(FlushScope scope) => _executionManagerCore.Flush(scope); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs index 74c1282a5e4a5c..db468f425f95b4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ManagedTypeSource_1.cs @@ -23,8 +23,15 @@ public ManagedTypeSource_1(Target target) _target = target; } - public void Flush() + public void Flush(FlushScope scope) { + // They are safe to retain across FlushScope.ForwardExecution because + // ManagedTypeSource_1 only resolves names in System.Private.CoreLib, which + // is loaded into the non-collectible default AssemblyLoadContext at runtime + // startup and whose ECMA metadata never changes for the process lifetime. + if (scope != FlushScope.All) + return; + _typeInfoCache.Clear(); _typeHandleCache.Clear(); _fieldDescCache.Clear(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadata_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadata_1.cs index 5cb85410897c06..4b1d06990fcf8e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadata_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/PlatformMetadata_1.cs @@ -5,16 +5,17 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -internal readonly partial struct PlatformMetadata_1 : IPlatformMetadata +internal readonly struct PlatformMetadata_1 : IPlatformMetadata { internal readonly Target _target; - private readonly Data.PlatformMetadata _cdacMetadata; + private readonly TargetPointer _cdacMetadataAddress; + private Data.PlatformMetadata _cdacMetadata + => _target.ProcessedData.GetOrAdd(_cdacMetadataAddress); public PlatformMetadata_1(Target target) { _target = target; - TargetPointer cdacMetadataAddress = target.ReadGlobalPointer(Constants.Globals.PlatformMetadata); - _cdacMetadata = target.ProcessedData.GetOrAdd(cdacMetadataAddress); + _cdacMetadataAddress = target.ReadGlobalPointer(Constants.Globals.PlatformMetadata); } TargetPointer IPlatformMetadata.GetPrecodeMachineDescriptor() diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs index 71b29a5eb8c844..e8e9fff8325ef9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ReJIT_1.cs @@ -7,10 +7,12 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; -internal readonly partial struct ReJIT_1 : IReJIT +internal readonly struct ReJIT_1 : IReJIT { internal readonly Target _target; - private readonly Data.ProfControlBlock _profControlBlock; + private readonly TargetPointer _profControlBlockAddress; + private Data.ProfControlBlock _profControlBlock + => _target.ProcessedData.GetOrAdd(_profControlBlockAddress); // see src/coreclr/inc/corprof.idl [Flags] @@ -33,8 +35,7 @@ public enum RejitFlags : uint public ReJIT_1(Target target) { _target = target; - TargetPointer profControlBlockAddress = target.ReadGlobalPointer(Constants.Globals.ProfilerControlBlock); - _profControlBlock = target.ProcessedData.GetOrAdd(profControlBlockAddress); + _profControlBlockAddress = target.ReadGlobalPointer(Constants.Globals.ProfilerControlBlock); } bool IReJIT.IsEnabled() diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs index fd06a25dd80840..f082df5c2d5802 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs @@ -32,7 +32,7 @@ internal partial struct RuntimeTypeSystem_1 : IRuntimeTypeSystem private readonly Dictionary _methodDescs = new(); private readonly Dictionary _typeHandles = new(); - public void Flush() + public void Flush(FlushScope scope) { _methodTables.Clear(); _methodDescs.Clear(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs index 1c07cf9f8a47f8..ee7cf0485ee0e8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Signature/Signature_1.cs @@ -25,7 +25,7 @@ internal Signature_1(Target target) _target = target; } - public void Flush() + public void Flush(FlushScope scope) { _thProviders.Clear(); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 5c8b54d69861ea..a6c30fe3d9ab5f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -67,7 +67,7 @@ public int CheckDbiVersion(DbiVersion* pVersion) public int FlushCache() { - _target.Flush(); + _target.Flush(FlushScope.All); return _legacy is not null ? _legacy.FlushCache() : HResults.S_OK; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs index 5eddb84087556b..a0840f0362f2ff 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs @@ -21,7 +21,7 @@ public sealed unsafe partial class SOSDacImpl : IXCLRDataProcess, IXCLRDataProce { int IXCLRDataProcess.Flush() { - _target.Flush(); + _target.Flush(FlushScope.All); // Flush is always propagated — it's cache management, not data retrieval. if (_legacyProcess is not null) 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 f0ec3ef67ed4f8..2007bb81728a1e 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.cs @@ -6712,7 +6712,7 @@ int ISOSDacInterface13.GetGCFreeRegions(DacComNullableByRef ppEn } int ISOSDacInterface13.LockedFlush() { - _target.Flush(); + _target.Flush(FlushScope.All); // As long as any part of cDAC falls back to the legacy DAC, we need to propagate the Flush call if (_legacyImpl13 is not null) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs index 8a6f499f13a685..376b680f35966c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/CachingContractRegistry.cs @@ -70,11 +70,11 @@ public override bool TryGetContract([NotNullWhen(true)] out TContract return true; } - public override void Flush() + public override void Flush(FlushScope scope) { foreach (IContract contract in _contracts.Values) { - contract.Flush(); + contract.Flush(scope); } } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs index 6bc5ee12bed173..273ca29a2ae7f5 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs @@ -143,9 +143,9 @@ private ContractDescriptorTarget(Descriptor mainDescriptor, DataTargetDelegates BuildDescriptors(forceBuild: true); } - public override void Flush() + public override void Flush(FlushScope scope) { - base.Flush(); + base.Flush(scope); BuildDescriptors(); } diff --git a/src/native/managed/cdac/tests/DataGenerator/TestTarget.cs b/src/native/managed/cdac/tests/DataGenerator/TestTarget.cs index e2435cae5f7f30..e1e9a7e8bd6392 100644 --- a/src/native/managed/cdac/tests/DataGenerator/TestTarget.cs +++ b/src/native/managed/cdac/tests/DataGenerator/TestTarget.cs @@ -283,7 +283,7 @@ public override bool TryGetContract([NotNullWhen(true)] out TContract public override void Register(string version, Func creator) => throw new NotImplementedException(); - public override void Flush() { } + public override void Flush(FlushScope scope) { } } // --- Trivial IDataCache for ReadDataField support ---------------- diff --git a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs index a6bfe639013663..af59d741181944 100644 --- a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs @@ -625,7 +625,7 @@ public override bool TryGetContract([NotNullWhen(true)] out TContract return true; } - public override void Flush() { } + public override void Flush(FlushScope scope) { } } }