From c2e0f915adba13103bf4285d0c265707fd6d4fd7 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Mon, 6 Apr 2026 17:51:24 -0700 Subject: [PATCH 1/7] Implement SetJITCompilerFlags for cDAC --- docs/design/datacontracts/Loader.md | 18 ++++ src/coreclr/inc/cordbpriv.h | 1 + src/coreclr/vm/ceeload.h | 2 +- .../Contracts/ILoader.cs | 3 + .../Contracts/Loader_1.cs | 21 ++++ .../ClrDataModule.cs | 44 ++++++++- .../IXCLRData.cs | 15 +++ src/native/managed/cdac/tests/LoaderTests.cs | 95 +++++++++++++++++++ .../MockDescriptors/MockDescriptors.Loader.cs | 9 +- .../cdac/tests/TestPlaceholderTarget.cs | 31 +++++- 10 files changed, 231 insertions(+), 8 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 7bbb2c31f1d98e..540ee56a63ec03 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -90,6 +90,9 @@ TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer); TargetPointer GetILHeader(ModuleHandle handle, uint token); TargetPointer GetDynamicIL(ModuleHandle handle, uint token); IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer); + +uint GetDebuggerInfoBits(ModuleHandle handle); +void SetDebuggerInfoBits(ModuleHandle handle, uint newBits); ``` ## Version 1 @@ -194,6 +197,8 @@ IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer | `ASSEMBLY_NOTIFYFLAGS_PROFILER_NOTIFIED` | uint | Flag in Assembly NotifyFlags indicating the Assembly will notify profilers. | `0x1` | | `DefaultDomainFriendlyName` | string | Friendly name returned when `AppDomain.FriendlyName` is null (matches native `DEFAULT_DOMAIN_FRIENDLY_NAME`) | `"DefaultDomain"` | | `MaxWebcilSections` | ushort | Maximum number of COFF sections supported in a Webcil image (must stay in sync with native `WEBCIL_MAX_SECTIONS`) | `16` | +| `DebuggerInfoMask` | uint | Mask for the debugger info bits within the Module's transient flags | `0x0000Fc00` | +| `DebuggerInfoShift` | int | Bit shift for the debugger info bits within the Module's transient flags | `10` | Contracts used: | Contract Name | @@ -821,6 +826,19 @@ TargetPointer GetDynamicIL(ModuleHandle handle, uint token) Data.DynamicILBlobEntry blobEntry = shashContract.LookupSHash(shash, token); return /* blob entry IL address */ } + +uint GetDebuggerInfoBits(ModuleHandle handle) +{ + uint flags = // read Module::Flags (uint32) at handle.Address + Flags offset + return (flags & DebuggerInfoMask) >> DebuggerInfoShift; +} + +void SetDebuggerInfoBits(ModuleHandle handle, uint newBits) +{ + uint currentFlags = // read Module::Flags (uint32) at handle.Address + Flags offset + uint updated = (currentFlags & ~DebuggerInfoMask) | (newBits << DebuggerInfoShift); + // write updated uint32 back to handle.Address + Flags offset +} ``` ### DacEnumerableHash (EETypeHashTable and InstMethodHashTable) diff --git a/src/coreclr/inc/cordbpriv.h b/src/coreclr/inc/cordbpriv.h index 32a85c28b07401..35130e7b12f83a 100644 --- a/src/coreclr/inc/cordbpriv.h +++ b/src/coreclr/inc/cordbpriv.h @@ -52,6 +52,7 @@ enum DebuggerControlFlag // enum DebuggerAssemblyControlFlags { + // [cDAC] [IXCLRData]: Contract depends on these values. DACF_NONE = 0x00, DACF_USER_OVERRIDE = 0x01, DACF_ALLOW_JIT_OPTS = 0x02, diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 1bcc3b2bda95e1..5de1c182a5a090 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -618,7 +618,7 @@ class Module : public ModuleBase enum { // These are the values set in m_dwTransientFlags. - // [cDAC] [Loader]: Contract depends on the values of MODULE_IS_TENURED, IS_EDIT_AND_CONTINUE, and IS_REFLECTION_EMIT. + // [cDAC] [Loader]: Contract depends on the values of MODULE_IS_TENURED, IS_EDIT_AND_CONTINUE, and IS_REFLECTION_EMIT, DEBUGGER_INFO_MASK_PRIV, DEBUGGER_INFO_SHIFT_PRIV. MODULE_IS_TENURED = 0x00000001, // Set once we know for sure the Module will not be freed until the appdomain itself exits // unused = 0x00000002, diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 1a3828bb3de62c..16f4580d827ec9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -98,6 +98,9 @@ public interface ILoader : IContract TargetPointer GetObjectHandle(TargetPointer loaderAllocatorPointer) => throw new NotImplementedException(); TargetPointer GetDynamicIL(ModuleHandle handle, uint token) => throw new NotImplementedException(); IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) => throw new NotImplementedException(); + + uint GetDebuggerInfoBits(ModuleHandle handle) => throw new NotImplementedException(); + void SetDebuggerInfoBits(ModuleHandle handle, uint newBits) => throw new NotImplementedException(); } public readonly struct Loader : ILoader diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 9f23be73d90167..9ecc9a28164873 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -23,6 +23,9 @@ private enum ModuleFlags_1 : uint ReflectionEmit = 0x40, // Reflection.Emit was used to create this module } + private const uint DebuggerInfoMask = 0x0000Fc00; + private const int DebuggerInfoShift = 10; + private enum PEImageFlags : uint { FLAG_MAPPED = 0x01, // the file is mapped/hydrated (vs. the raw disk layout) @@ -376,6 +379,24 @@ ModuleFlags ILoader.GetFlags(ModuleHandle handle) return GetFlags(module); } + uint ILoader.GetDebuggerInfoBits(ModuleHandle handle) + { + Target.TypeInfo type = _target.GetTypeInfo(DataType.Module); + ulong flagsAddr = handle.Address + (ulong)type.Fields[nameof(Data.Module.Flags)].Offset; + uint flags = _target.Read(flagsAddr); + + return (flags & DebuggerInfoMask) >> DebuggerInfoShift; + } + + void ILoader.SetDebuggerInfoBits(ModuleHandle handle, uint newBits) + { + Target.TypeInfo type = _target.GetTypeInfo(DataType.Module); + ulong flagsAddr = handle.Address + (ulong)type.Fields[nameof(Data.Module.Flags)].Offset; + uint currentFlags = _target.Read(flagsAddr); + uint updated = (currentFlags & ~DebuggerInfoMask) | (newBits << DebuggerInfoShift); + _target.Write(flagsAddr, updated); + } + bool ILoader.IsReadyToRun(ModuleHandle handle) { Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index c54f916f7a1b85..2bc22c8c118498 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -42,6 +42,8 @@ public ClrDataModule(TargetPointer address, Target target, IXCLRDataModule? lega } } + private const uint CORDEBUG_JIT_DEFAULT = 0x1; + private const uint CORDEBUG_JIT_DISABLE_OPTIMIZATION = 0x3; private static readonly Guid IID_IMetaDataImport = Guid.Parse("7DAC8207-D3AE-4c75-9B67-92801A497D44"); CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out nint ppv) @@ -476,5 +478,45 @@ int IXCLRDataModule.GetVersionId(Guid* vid) => _legacyModule is not null ? _legacyModule.GetVersionId(vid) : HResults.E_NOTIMPL; int IXCLRDataModule2.SetJITCompilerFlags(uint flags) - => _legacyModule2 is not null ? _legacyModule2.SetJITCompilerFlags(flags) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + if ((flags != CORDEBUG_JIT_DEFAULT) && (flags != CORDEBUG_JIT_DISABLE_OPTIMIZATION)) + throw new ArgumentException(); + + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(_address); + + bool allowJitOpts = (flags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION; + uint bits = loader.GetDebuggerInfoBits(handle) + & ~((uint)DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS + | (uint)DebuggerAssemblyControlFlags.DACF_ENC_ENABLED); + bits &= (uint)DebuggerAssemblyControlFlags.DACF_CONTROL_FLAGS_MASK; + + if (allowJitOpts) + { + bits |= (uint)DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS; + } + + // Settings from the debugger take precedence over all other settings. + bits |= (uint)DebuggerAssemblyControlFlags.DACF_USER_OVERRIDE; + + loader.SetDebuggerInfoBits(handle, bits); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } + +#if DEBUG + if (_legacyModule2 is not null) + { + int hrLocal = _legacyModule2.SetJITCompilerFlags(flags); + Debug.ValidateHResult(hr, hrLocal); + } +#endif + + return hr; + } } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs index 87cfe6ee2dacaf..b0c58622d0fa79 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs @@ -145,6 +145,21 @@ public unsafe partial interface IXCLRDataModule int GetVersionId(Guid* vid); } +[Flags] +public enum DebuggerAssemblyControlFlags : uint +{ + DACF_NONE = 0x00, + DACF_USER_OVERRIDE = 0x01, + DACF_ALLOW_JIT_OPTS = 0x02, + DACF_OBSOLETE_TRACK_JIT_INFO = 0x04, // obsolete in V2.0, we're always tracking. + DACF_ENC_ENABLED = 0x08, + DACF_IGNORE_PDBS = 0x20, + DACF_CONTROL_FLAGS_MASK = 0x2F, + + DACF_PDBS_COPIED = 0x10, + DACF_MISC_FLAGS_MASK = 0x10, +} + [GeneratedComInterface] [Guid("34625881-7EB3-4524-817B-8DB9D064C760")] public unsafe partial interface IXCLRDataModule2 diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index 82b3fe8afc4491..6fc50f04e0c359 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -479,4 +479,99 @@ public void GetILAddr_WebcilRvaNotInAnySectionThrows(MockTarget.Architecture arc Assert.Throws(() => contract.GetILAddr(peAssemblyAddr, 0x5000)); } + + public static IEnumerable GetDebuggerInfoBitsData() + { + foreach (var arch in new MockTarget.StdArch()) + { + yield return [0x00000000u, 0x00u, arch[0]]; // No debugger bits + yield return [0x00000800u, 0x02u, arch[0]]; // DACF_ALLOW_JIT_OPTS + yield return [0x00002000u, 0x08u, arch[0]]; // DACF_ENC_ENABLED + yield return [0x00000C00u, 0x03u, arch[0]]; // DACF_ALLOW_JIT_OPTS | DACF_USER_OVERRIDE + } + } + + [Theory] + [MemberData(nameof(GetDebuggerInfoBitsData))] + public void GetDebuggerInfoBits(uint rawFlags, uint expectedBits, MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockLoader loader = new(builder); + TargetPointer moduleAddr = loader.AddModule(flags: rawFlags); + + var memoryContext = builder.GetMemoryContext(); + var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, writer: memoryContext.WriteToTarget); + target.SetContracts(Mock.Of( + c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + + ILoader contract = target.Contracts.Loader; + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + + uint actual = contract.GetDebuggerInfoBits(handle); + Assert.Equal(expectedBits, actual); + } + + public static IEnumerable SetDebuggerInfoBitsData() + { + foreach (var arch in new MockTarget.StdArch()) + { + yield return [0x00u, 0x00000000u, arch[0]]; // Clear all debugger bits + yield return [0x02u, 0x00000800u, arch[0]]; // DACF_ALLOW_JIT_OPTS + yield return [0x08u, 0x00002000u, arch[0]]; // DACF_ENC_ENABLED + yield return [0x03u, 0x00000C00u, arch[0]]; // DACF_ALLOW_JIT_OPTS | DACF_USER_OVERRIDE + } + } + + [Theory] + [MemberData(nameof(SetDebuggerInfoBitsData))] + public void SetDebuggerInfoBits(uint newBits, uint expectedRawFlags, MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockLoader loader = new(builder); + TargetPointer moduleAddr = loader.AddModule(); + + var memoryContext = builder.GetMemoryContext(); + var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, writer: memoryContext.WriteToTarget); + target.SetContracts(Mock.Of( + c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + + ILoader contract = target.Contracts.Loader; + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + + contract.SetDebuggerInfoBits(handle, newBits); + + // Verify the raw flags in memory have the correct shifted value. + uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + Assert.Equal(expectedRawFlags, rawFlags); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetDebuggerInfoBits_PreservesOtherFlags(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockLoader loader = new(builder); + + uint initialFlags = 0x41u; // DACF_TENURED | DACF_REFLECTION_EMIT + TargetPointer moduleAddr = loader.AddModule(flags: initialFlags); + + var memoryContext = builder.GetMemoryContext(); + var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, writer: memoryContext.WriteToTarget); + target.SetContracts(Mock.Of( + c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + + ILoader contract = target.Contracts.Loader; + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + + uint debuggerBits = 0x03u; // DACF_ALLOW_JIT_OPTS | DACF_USER_OVERRIDE + int debuggerInfoShift = 10; + contract.SetDebuggerInfoBits(handle, debuggerBits); + + // Verify other flags (Tenured, ReflectionEmit) are preserved. + uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + Assert.Equal(initialFlags | (debuggerBits << debuggerInfoShift), rawFlags); + } } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs index bc7d2de6920f05..488d5282fd432a 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs @@ -41,7 +41,7 @@ public Loader(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocati ]); } - internal TargetPointer AddModule(string? path = null, string? fileName = null, string? simpleName = null, byte[]? simpleNameBytes = null) + internal TargetPointer AddModule(string? path = null, string? fileName = null, string? simpleName = null, byte[]? simpleNameBytes = null, uint flags = 0) { TargetTestHelpers helpers = _builder.TargetTestHelpers; Target.TypeInfo typeInfo = Types[DataType.Module]; @@ -49,6 +49,13 @@ internal TargetPointer AddModule(string? path = null, string? fileName = null, s MockMemorySpace.HeapFragment module = _allocator.Allocate(size, "Module"); _builder.AddHeapFragment(module); + if (flags != 0) + { + helpers.Write( + module.Data.AsSpan().Slice(typeInfo.Fields[nameof(Data.Module.Flags)].Offset, sizeof(uint)), + flags); + } + byte[]? rawSimpleName = simpleName is not null ? Encoding.UTF8.GetBytes(simpleName) : simpleNameBytes; if (rawSimpleName != null) { diff --git a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs index 2ba7c98187bffc..4d9fd939969251 100644 --- a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs @@ -23,12 +23,14 @@ internal class TestPlaceholderTarget : Target private readonly (string Name, string Value)[] _globalStrings; internal delegate int ReadFromTargetDelegate(ulong address, Span buffer); + internal delegate int WriteToTargetDelegate(ulong address, Span buffer); private readonly ReadFromTargetDelegate _dataReader; + private readonly WriteToTargetDelegate? _dataWriter; private static readonly UTF8Encoding strictUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private static readonly UTF8Encoding looseUTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false); - public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, Dictionary types = null, (string Name, ulong Value)[] globals = null, (string Name, string Value)[] globalStrings = null) + public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegate reader, Dictionary types = null, (string Name, ulong Value)[] globals = null, (string Name, string Value)[] globalStrings = null, WriteToTargetDelegate? writer = null) { IsLittleEndian = arch.IsLittleEndian; PointerSize = arch.Is64Bit ? 8 : 4; @@ -36,6 +38,7 @@ public TestPlaceholderTarget(MockTarget.Architecture arch, ReadFromTargetDelegat _dataCache = new DefaultDataCache(this); _typeInfoCache = types ?? []; _dataReader = reader; + _dataWriter = writer; _globals = globals ?? []; _globalStrings = globalStrings ?? []; } @@ -94,12 +97,14 @@ public Builder AddContract(Func factory) where TCo public TestPlaceholderTarget Build() { + var memoryContext = _memBuilder.GetMemoryContext(); var target = new TestPlaceholderTarget( _arch, - _memBuilder.GetMemoryContext().ReadFromTarget, + memoryContext.ReadFromTarget, _types, _globals.ToArray(), - _globalStrings.ToArray()); + _globalStrings.ToArray(), + memoryContext.WriteToTarget); var registry = new TestContractRegistry(); foreach (var (type, factory) in _contractFactories) @@ -159,7 +164,13 @@ public override void ReadBuffer(ulong address, Span buffer) if (_dataReader(address, buffer) < 0) throw new VirtualReadException($"Failed to read {buffer.Length} bytes at 0x{address:x8}."); } - public override void WriteBuffer(ulong address, Span buffer) => throw new NotImplementedException(); + public override void WriteBuffer(ulong address, Span buffer) + { + if (_dataWriter is null) + throw new NotImplementedException(); + if (_dataWriter(address, buffer) < 0) + throw new InvalidOperationException($"Failed to write {buffer.Length} bytes at 0x{address:x8}."); + } public override string ReadUtf8String(ulong address, bool strict = false) { @@ -278,7 +289,17 @@ public override bool TryRead(ulong address, out T value) return true; } - public override void Write(ulong address, T value) => throw new NotImplementedException(); + public override void Write(ulong address, T value) + { + if (_dataWriter is null) + throw new NotImplementedException(); + Span buffer = stackalloc byte[Unsafe.SizeOf()]; + if (IsLittleEndian) + value.TryWriteLittleEndian(buffer, out _); + else + value.TryWriteBigEndian(buffer, out _); + WriteBuffer(address, buffer); + } #region subclass reader helpers From 2dae6204a14d404eb6881b6e64c14bf3ff7b06c2 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak <76071368+barosiak@users.noreply.github.com> Date: Tue, 7 Apr 2026 10:53:15 -0700 Subject: [PATCH 2/7] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Contracts/Loader_1.cs | 3 ++- src/native/managed/cdac/tests/TestPlaceholderTarget.cs | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 9ecc9a28164873..20735de9f1bb7c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -393,7 +393,8 @@ void ILoader.SetDebuggerInfoBits(ModuleHandle handle, uint newBits) Target.TypeInfo type = _target.GetTypeInfo(DataType.Module); ulong flagsAddr = handle.Address + (ulong)type.Fields[nameof(Data.Module.Flags)].Offset; uint currentFlags = _target.Read(flagsAddr); - uint updated = (currentFlags & ~DebuggerInfoMask) | (newBits << DebuggerInfoShift); + uint debuggerInfoBitsMask = DebuggerInfoMask >> DebuggerInfoShift; + uint updated = (currentFlags & ~DebuggerInfoMask) | ((newBits & debuggerInfoBitsMask) << DebuggerInfoShift); _target.Write(flagsAddr, updated); } diff --git a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs index 4d9fd939969251..62da789860db83 100644 --- a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs @@ -294,10 +294,12 @@ public override void Write(ulong address, T value) if (_dataWriter is null) throw new NotImplementedException(); Span buffer = stackalloc byte[Unsafe.SizeOf()]; - if (IsLittleEndian) - value.TryWriteLittleEndian(buffer, out _); - else - value.TryWriteBigEndian(buffer, out _); + bool success = IsLittleEndian + ? value.TryWriteLittleEndian(buffer, out int bytesWritten) + : value.TryWriteBigEndian(buffer, out bytesWritten); + + if (!success || bytesWritten != buffer.Length) + throw new InvalidOperationException($"Failed to write {typeof(T)} to buffer."); WriteBuffer(address, buffer); } From 7b7b74e08b359e8f252135bb877b7878ec223908 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Thu, 9 Apr 2026 18:21:15 -0700 Subject: [PATCH 3/7] Address pr comments, add EnC logic, simplify flags --- docs/design/datacontracts/Loader.md | 63 ++++++- src/coreclr/debug/daccess/dacdbiimpl.cpp | 2 - src/coreclr/debug/daccess/task.cpp | 3 - src/coreclr/inc/cordbpriv.h | 6 +- src/coreclr/vm/ceeload.cpp | 4 + src/coreclr/vm/ceeload.h | 41 ++--- .../vm/datadescriptor/datadescriptor.inc | 6 + src/coreclr/vm/eeconfig.h | 7 + src/coreclr/vm/vars.hpp | 8 +- .../Contracts/ILoader.cs | 20 ++- .../DataType.cs | 1 + .../Constants.cs | 1 + .../Contracts/Loader_1.cs | 53 ++++-- .../Data/EEConfig.cs | 19 +++ .../Data/Module.cs | 2 +- .../ClrDataModule.cs | 12 +- .../IXCLRData.cs | 15 -- src/native/managed/cdac/tests/LoaderTests.cs | 158 ++++++++++++++++-- .../MockDescriptors/MockDescriptors.Loader.cs | 16 ++ .../tests/MockDescriptors/MockDescriptors.cs | 9 + 20 files changed, 343 insertions(+), 103 deletions(-) create mode 100644 src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.cs diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 540ee56a63ec03..cddc0e11a9972a 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -91,8 +91,29 @@ TargetPointer GetILHeader(ModuleHandle handle, uint token); TargetPointer GetDynamicIL(ModuleHandle handle, uint token); IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer); -uint GetDebuggerInfoBits(ModuleHandle handle); -void SetDebuggerInfoBits(ModuleHandle handle, uint newBits); +DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle); +void SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFlags newBits); +``` + +The `DebuggerAssemblyControlFlags` enum is defined as: +```csharp +[Flags] +enum DebuggerAssemblyControlFlags : uint +{ + DACF_NONE = 0x00, + DACF_ALLOW_JIT_OPTS = 0x02, + DACF_ENC_ENABLED = 0x08, +} +``` + +The `ClrModifiableAssemblies` enum (from `EEConfig::ModifiableAssemblies`) is defined as: +```csharp +enum ClrModifiableAssemblies : uint +{ + Unset = 0, + None = 1, + Debug = 2, +} ``` ## Version 1 @@ -181,6 +202,7 @@ void SetDebuggerInfoBits(ModuleHandle handle, uint newBits); | `WebcilSectionHeader` | `VirtualAddress` | RVA of the section | | `WebcilSectionHeader` | `SizeOfRawData` | Size of the section's raw data | | `WebcilSectionHeader` | `PointerToRawData` | File offset to the section's raw data | +| `EEConfig` | `ModifiableAssemblies` | Controls Edit and Continue support (ClrModifiableAssemblies enum) | @@ -189,6 +211,7 @@ void SetDebuggerInfoBits(ModuleHandle handle, uint newBits); | --- | --- | --- | | `AppDomain` | TargetPointer | Pointer to the global AppDomain | | `SystemDomain` | TargetPointer | Pointer to the global SystemDomain | +| `EEConfig` | TargetPointer | Pointer to the global EEConfig (runtime configuration) | ### Contract Constants: @@ -199,6 +222,11 @@ void SetDebuggerInfoBits(ModuleHandle handle, uint newBits); | `MaxWebcilSections` | ushort | Maximum number of COFF sections supported in a Webcil image (must stay in sync with native `WEBCIL_MAX_SECTIONS`) | `16` | | `DebuggerInfoMask` | uint | Mask for the debugger info bits within the Module's transient flags | `0x0000Fc00` | | `DebuggerInfoShift` | int | Bit shift for the debugger info bits within the Module's transient flags | `10` | +| `IS_JIT_OPTIMIZATION_DISABLED` | uint | Cached flag: JIT optimizations are disabled | `0x00000002` | +| `IS_EDIT_AND_CONTINUE` | uint | Flag: EnC is enabled for this module | `0x00000008` | +| `PROF_DISABLE_OPTIMIZATIONS` | uint | Profiler disabled JIT optimizations | `0x00000080` | +| `IS_ENC_CAPABLE` | uint | Cached flag: module is Edit and Continue capable | `0x00000200` | +| `DEBUGGER_ALLOW_JIT_OPTS_PRIV` | uint | Debugger allows JIT optimizations (shifted in transient flags) | `0x00000800` | Contracts used: | Contract Name | @@ -827,17 +855,34 @@ TargetPointer GetDynamicIL(ModuleHandle handle, uint token) return /* blob entry IL address */ } -uint GetDebuggerInfoBits(ModuleHandle handle) +DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle) { - uint flags = // read Module::Flags (uint32) at handle.Address + Flags offset - return (flags & DebuggerInfoMask) >> DebuggerInfoShift; + uint flags = // read Module::Flags at handle.Address + Flags offset + return (DebuggerAssemblyControlFlags)((flags & DebuggerInfoMask) >> DebuggerInfoShift); } -void SetDebuggerInfoBits(ModuleHandle handle, uint newBits) +void SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFlags newBits) { - uint currentFlags = // read Module::Flags (uint32) at handle.Address + Flags offset - uint updated = (currentFlags & ~DebuggerInfoMask) | (newBits << DebuggerInfoShift); - // write updated uint32 back to handle.Address + Flags offset + uint currentFlags = // read Module::Flags at handle.Address + Flags offset + uint debuggerInfoBitsMask = DebuggerInfoMask >> DebuggerInfoShift; + uint updated = (currentFlags & ~DebuggerInfoMask) | (((uint)newBits & debuggerInfoBitsMask) << DebuggerInfoShift); + + bool jitOptDisabled = (updated & DEBUGGER_ALLOW_JIT_OPTS_PRIV) == 0 || (updated & PROF_DISABLE_OPTIMIZATIONS) != 0; + // Set or clear IS_JIT_OPTIMIZATION_DISABLED accordingly. + + if ((updated & IS_ENC_CAPABLE) != 0) + { + ClrModifiableAssemblies modifiable = // read EEConfig::ModifiableAssemblies from g_pConfig + if (modifiable != None) + { + bool encRequested = (newBits & DACF_ENC_ENABLED) != 0; + bool setEnC = encRequested || (modifiable == Debug && jitOptDisabled); + if (setEnC) + updated |= IS_EDIT_AND_CONTINUE; + } + } + + // Write updated flags back to handle.Address + Flags offset } ``` diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index a7e1b1cd50cea2..2e5ab23cd03a31 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -879,8 +879,6 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::SetCompilerFlags(VMPTR_DomainAsse hr = CORDBG_S_NOT_ALL_BITS_SET; } } - // Settings from the debugger take precedence over all other settings. - dwBits |= DACF_USER_OVERRIDE; // set flags. This will write back to the target pModule->SetDebuggerInfoBits((DebuggerAssemblyControlFlags)dwBits); diff --git a/src/coreclr/debug/daccess/task.cpp b/src/coreclr/debug/daccess/task.cpp index 651285ad5e8f3f..c8ed4df807d135 100644 --- a/src/coreclr/debug/daccess/task.cpp +++ b/src/coreclr/debug/daccess/task.cpp @@ -2798,9 +2798,6 @@ ClrDataModule::SetJITCompilerFlags( dwBits |= DACF_ALLOW_JIT_OPTS; } - // Settings from the debugger take precedence over all other settings. - dwBits |= DACF_USER_OVERRIDE; - // set flags. This will write back to the target m_module->SetDebuggerInfoBits((DebuggerAssemblyControlFlags)dwBits); diff --git a/src/coreclr/inc/cordbpriv.h b/src/coreclr/inc/cordbpriv.h index 35130e7b12f83a..e323937df63ce2 100644 --- a/src/coreclr/inc/cordbpriv.h +++ b/src/coreclr/inc/cordbpriv.h @@ -36,7 +36,6 @@ enum DebuggerControlFlag DBCF_USER_MASK = 0x00FF, DBCF_GENERATE_DEBUG_CODE = 0x0001, - DBCF_ALLOW_JIT_OPT = 0x0008, DBCF_PROFILER_ENABLED = 0x0020, // DBCF_ACTIVATE_REMOTE_DEBUGGING = 0x0040, Deprecated. DO NOT USE @@ -50,16 +49,15 @@ enum DebuggerControlFlag // Flags used to control the debuggable state of modules and // assemblies. // +// [cDAC] [Loader]: Contract depends on DACF_NONE, DACF_ALLOW_JIT_OPTS, DACF_ENC_ENABLED, DACF_CONTROL_FLAGS_MASK. enum DebuggerAssemblyControlFlags { - // [cDAC] [IXCLRData]: Contract depends on these values. DACF_NONE = 0x00, - DACF_USER_OVERRIDE = 0x01, DACF_ALLOW_JIT_OPTS = 0x02, DACF_OBSOLETE_TRACK_JIT_INFO = 0x04, // obsolete in V2.0, we're always tracking. DACF_ENC_ENABLED = 0x08, DACF_IGNORE_PDBS = 0x20, - DACF_CONTROL_FLAGS_MASK = 0x2F, + DACF_CONTROL_FLAGS_MASK = 0x2E, DACF_PDBS_COPIED = 0x10, DACF_MISC_FLAGS_MASK = 0x10, diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index a33ffa588586ad..03c73525539025 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -516,6 +516,8 @@ void Module::Initialize(AllocMemTracker *pamTracker, LPCWSTR szName) m_dwTransientFlags = m_dwTransientFlags | PROF_DISABLE_OPTIMIZATIONS; } + UpdateJitOptimizationDisabledState(); + m_pJitInlinerTrackingMap = NULL; if (ReJitManager::IsReJITInlineTrackingEnabled()) { @@ -538,6 +540,7 @@ void Module::SetDebuggerInfoBits(DebuggerAssemblyControlFlags newBits) ~DEBUGGER_INFO_MASK_PRIV) == 0); SetTransientFlagInterlockedWithMask(newBits << DEBUGGER_INFO_SHIFT_PRIV, DEBUGGER_INFO_MASK_PRIV); + UpdateJitOptimizationDisabledState(); #ifdef DEBUGGING_SUPPORTED if (IsEditAndContinueCapable()) @@ -611,6 +614,7 @@ Module *Module::Create(Assembly *pAssembly, PEAssembly *pPEAssembly, AllocMemTra void* pMemory = pamTracker->Track(pAssembly->GetHighFrequencyHeap()->AllocMem(S_SIZE_T(sizeof(EditAndContinueModule)))); pModule = new (pMemory) EditAndContinueModule(pAssembly, pPEAssembly); + pModule->SetTransientFlagInterlocked(IS_ENC_CAPABLE); } else #endif // FEATURE_METADATA_UPDATER diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 5de1c182a5a090..ba67fc1db141e9 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -618,10 +618,10 @@ class Module : public ModuleBase enum { // These are the values set in m_dwTransientFlags. - // [cDAC] [Loader]: Contract depends on the values of MODULE_IS_TENURED, IS_EDIT_AND_CONTINUE, and IS_REFLECTION_EMIT, DEBUGGER_INFO_MASK_PRIV, DEBUGGER_INFO_SHIFT_PRIV. + // [cDAC] [Loader]: Contract depends on the values of MODULE_IS_TENURED, IS_EDIT_AND_CONTINUE, IS_REFLECTION_EMIT, IS_JIT_OPTIMIZATION_DISABLED, IS_ENC_CAPABLE, PROF_DISABLE_OPTIMIZATIONS, DEBUGGER_INFO_MASK_PRIV, DEBUGGER_INFO_SHIFT_PRIV. MODULE_IS_TENURED = 0x00000001, // Set once we know for sure the Module will not be freed until the appdomain itself exits - // unused = 0x00000002, + IS_JIT_OPTIMIZATION_DISABLED= 0x00000002, // Cached result: JIT optimizations are disabled for this module (by debugger or profiler) CLASSES_FREED = 0x00000004, IS_EDIT_AND_CONTINUE = 0x00000008, // is EnC Enabled for this module @@ -632,12 +632,14 @@ class Module : public ModuleBase PROF_DISABLE_OPTIMIZATIONS = 0x00000080, // indicates if Profiler disabled JIT optimization event mask was set when loaded PROF_DISABLE_INLINING = 0x00000100, // indicates if Profiler disabled JIT Inlining event mask was set when loaded + IS_ENC_CAPABLE = 0x00000200, // Cached result of IsEditAndContinueCapable() at Module creation + // // Note: The values below must match the ones defined in // cordbpriv.h for DebuggerAssemblyControlFlags when shifted // right DEBUGGER_INFO_SHIFT bits. // - DEBUGGER_USER_OVERRIDE_PRIV = 0x00000400, + // DEBUGGER_USER_OVERRIDE_PRIV was 0x00000400. Deprecated. DEBUGGER_ALLOW_JIT_OPTS_PRIV= 0x00000800, DEBUGGER_TRACK_JIT_INFO_PRIV= 0x00001000, DEBUGGER_ENC_ENABLED_PRIV = 0x00002000, // this is what was attempted to be set. IS_EDIT_AND_CONTINUE is actual result. @@ -651,7 +653,6 @@ class Module : public ModuleBase IS_BEING_UNLOADED = 0x00100000, }; - static_assert(DEBUGGER_USER_OVERRIDE_PRIV >> DEBUGGER_INFO_SHIFT_PRIV == DebuggerAssemblyControlFlags::DACF_USER_OVERRIDE); static_assert(DEBUGGER_ALLOW_JIT_OPTS_PRIV >> DEBUGGER_INFO_SHIFT_PRIV == DebuggerAssemblyControlFlags::DACF_ALLOW_JIT_OPTS); static_assert(DEBUGGER_TRACK_JIT_INFO_PRIV >> DEBUGGER_INFO_SHIFT_PRIV == DebuggerAssemblyControlFlags::DACF_OBSOLETE_TRACK_JIT_INFO); static_assert(DEBUGGER_ENC_ENABLED_PRIV >> DEBUGGER_INFO_SHIFT_PRIV == DebuggerAssemblyControlFlags::DACF_ENC_ENABLED); @@ -938,27 +939,10 @@ class Module : public ModuleBase BOOL AreJITOptimizationsDisabled() const { - WRAPPER_NO_CONTRACT; + LIMITED_METHOD_CONTRACT; SUPPORTS_DAC; -#ifdef DEBUGGING_SUPPORTED - // check if debugger has disallowed JIT optimizations - auto dwDebuggerBits = GetDebuggerInfoBits(); - if (!CORDebuggerAllowJITOpts(dwDebuggerBits)) - { - return TRUE; - } -#endif // DEBUGGING_SUPPORTED - -#if defined(PROFILING_SUPPORTED) || defined(PROFILING_SUPPORTED_DATA) - // check if profiler had disabled JIT optimizations when module was loaded - if (m_dwTransientFlags & PROF_DISABLE_OPTIMIZATIONS) - { - return TRUE; - } -#endif // defined(PROFILING_SUPPORTED) || defined(PROFILING_SUPPORTED_DATA) - - return FALSE; + return (m_dwTransientFlags & IS_JIT_OPTIMIZATION_DISABLED) != 0; } #ifdef FEATURE_METADATA_UPDATER @@ -976,6 +960,17 @@ class Module : public ModuleBase SetTransientFlagInterlocked(IS_EDIT_AND_CONTINUE); } + // Recompute and cache the IS_JIT_OPTIMIZATION_DISABLED bit from the debugger and profiler source bits. + // Must be called after any change to DEBUGGER_ALLOW_JIT_OPTS_PRIV or PROF_DISABLE_OPTIMIZATIONS. + void UpdateJitOptimizationDisabledState() + { + LIMITED_METHOD_CONTRACT; + + DWORD flags = m_dwTransientFlags; + bool disabled = !(flags & DEBUGGER_ALLOW_JIT_OPTS_PRIV) || (flags & PROF_DISABLE_OPTIMIZATIONS); + SetTransientFlagInterlockedWithMask(disabled ? IS_JIT_OPTIMIZATION_DISABLED : 0, IS_JIT_OPTIMIZATION_DISABLED); + } + public: BOOL IsTenured() { diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index b85488e42c2a88..44fe393efc1498 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -1294,6 +1294,11 @@ CDAC_TYPE_FIELD(AuxiliarySymbolInfo, T_POINTER, Address, offsetof(VMAUXILIARYSYM CDAC_TYPE_FIELD(AuxiliarySymbolInfo, T_POINTER, Name, offsetof(VMAUXILIARYSYMBOLDEF, name)) CDAC_TYPE_END(AuxiliarySymbolInfo) +CDAC_TYPE_BEGIN(EEConfig) +CDAC_TYPE_INDETERMINATE(EEConfig) +CDAC_TYPE_FIELD(EEConfig, T_UINT32, ModifiableAssemblies, cdac_data::ModifiableAssemblies) +CDAC_TYPE_END(EEConfig) + CDAC_TYPES_END() CDAC_GLOBALS_BEGIN() @@ -1344,6 +1349,7 @@ CDAC_GLOBAL_POINTER(SystemDomain, cdac_data::SystemDomainPtr) CDAC_GLOBAL_POINTER(ThreadStore, &ThreadStore::s_pThreadStore) CDAC_GLOBAL_POINTER(FinalizerThread, &::g_pFinalizerThread) CDAC_GLOBAL_POINTER(GCThread, &::g_pSuspensionThread) +CDAC_GLOBAL_POINTER(EEConfig, &::g_pConfig) #if defined(DEBUGGING_SUPPORTED) && !defined(TARGET_WASM) CDAC_GLOBAL_POINTER(Debugger, &::g_pDebugger) CDAC_GLOBAL_POINTER(CLRJitAttachState, &::CLRJitAttachState) diff --git a/src/coreclr/vm/eeconfig.h b/src/coreclr/vm/eeconfig.h index 7319b85991c1bd..b21a2018a050f2 100644 --- a/src/coreclr/vm/eeconfig.h +++ b/src/coreclr/vm/eeconfig.h @@ -43,6 +43,7 @@ enum { OPT_BLENDED, OPT_RANDOM, OPT_DEFAULT = OPT_BLENDED }; +// [cDAC] [Loader]: Contract depends on these values. enum ClrModifiableAssemblies { /* modifiable assemblies are implicitly disabled */ MODIFIABLE_ASSM_UNSET = 0, @@ -59,6 +60,7 @@ enum ParseCtl { class EEConfig { + friend struct ::cdac_data; public: static HRESULT Setup(); @@ -715,6 +717,11 @@ class EEConfig { return dwSleepOnExit; } }; +template<> +struct cdac_data +{ + static constexpr size_t ModifiableAssemblies = offsetof(EEConfig, modifiableAssemblies); +}; #ifdef _DEBUG_IMPL diff --git a/src/coreclr/vm/vars.hpp b/src/coreclr/vm/vars.hpp index 7558c0882857da..101ca5635afe86 100644 --- a/src/coreclr/vm/vars.hpp +++ b/src/coreclr/vm/vars.hpp @@ -544,14 +544,8 @@ inline bool CORDebuggerAttached() return (g_CORDebuggerControlFlags & DBCF_ATTACHED) && !IsAtProcessExit(); } -// This only check debugger bits. However JIT optimizations can be disabled by other ways on a module -// In most cases Module::AreJITOptimizationsDisabled() should be the prefered for checking if JIT optimizations -// are disabled for a module (it does check both debugger bits and profiler jit deoptimization flag) #define CORDebuggerAllowJITOpts(dwDebuggerBits) \ - (((dwDebuggerBits) & DACF_ALLOW_JIT_OPTS) \ - || \ - ((g_CORDebuggerControlFlags & DBCF_ALLOW_JIT_OPT) && \ - !((dwDebuggerBits) & DACF_USER_OVERRIDE))) + (((dwDebuggerBits) & DACF_ALLOW_JIT_OPTS) != 0) #define CORDebuggerEnCMode(dwDebuggerBits) \ ((dwDebuggerBits) & DACF_ENC_ENABLED) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index 16f4580d827ec9..ddb66fe41bbf37 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -24,6 +24,22 @@ public enum ModuleFlags ReflectionEmit = 0x40, // Reflection.Emit was used to create this module } +[Flags] +public enum DebuggerAssemblyControlFlags : uint +{ + DACF_NONE = 0x00, + DACF_ALLOW_JIT_OPTS = 0x02, + DACF_ENC_ENABLED = 0x08, + DACF_CONTROL_FLAGS_MASK = 0x2E, +} + +public enum ClrModifiableAssemblies : uint +{ + Unset = 0, + None = 1, + Debug = 2, +} + [Flags] public enum AssemblyIterationFlags { @@ -99,8 +115,8 @@ public interface ILoader : IContract TargetPointer GetDynamicIL(ModuleHandle handle, uint token) => throw new NotImplementedException(); IReadOnlyDictionary GetLoaderAllocatorHeaps(TargetPointer loaderAllocatorPointer) => throw new NotImplementedException(); - uint GetDebuggerInfoBits(ModuleHandle handle) => throw new NotImplementedException(); - void SetDebuggerInfoBits(ModuleHandle handle, uint newBits) => throw new NotImplementedException(); + DebuggerAssemblyControlFlags GetDebuggerInfoBits(ModuleHandle handle) => throw new NotImplementedException(); + void SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFlags newBits) => throw new NotImplementedException(); } public readonly struct Loader : ILoader 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 307956def85147..0b3cf9e833539c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.cs @@ -139,6 +139,7 @@ public enum DataType PatchpointInfo, PortableEntryPoint, VirtualCallStubManager, + EEConfig, TransitionBlock, DebuggerEval, 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 c2cc5f234141a1..41eccea4c65d96 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.cs @@ -16,6 +16,7 @@ public static class Globals public const string Debugger = nameof(Debugger); public const string CLRJitAttachState = nameof(CLRJitAttachState); public const string MetadataUpdatesApplied = nameof(MetadataUpdatesApplied); + public const string EEConfig = nameof(EEConfig); public const string FeatureCOMInterop = nameof(FeatureCOMInterop); public const string FeatureComWrappers = nameof(FeatureComWrappers); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index 20735de9f1bb7c..2002f1d1d6499a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -26,6 +26,13 @@ private enum ModuleFlags_1 : uint private const uint DebuggerInfoMask = 0x0000Fc00; private const int DebuggerInfoShift = 10; + // Transient flag bits from m_dwTransientFlags used for JIT optimization and EnC logic. + private const uint IS_JIT_OPTIMIZATION_DISABLED = 0x00000002; + private const uint IS_EDIT_AND_CONTINUE = 0x00000008; + private const uint PROF_DISABLE_OPTIMIZATIONS = 0x00000080; + private const uint IS_ENC_CAPABLE = 0x00000200; + private const uint DEBUGGER_ALLOW_JIT_OPTS_PRIV = 0x00000800; + private enum PEImageFlags : uint { FLAG_MAPPED = 0x01, // the file is mapped/hydrated (vs. the raw disk layout) @@ -379,23 +386,47 @@ ModuleFlags ILoader.GetFlags(ModuleHandle handle) return GetFlags(module); } - uint ILoader.GetDebuggerInfoBits(ModuleHandle handle) + DebuggerAssemblyControlFlags ILoader.GetDebuggerInfoBits(ModuleHandle handle) { - Target.TypeInfo type = _target.GetTypeInfo(DataType.Module); - ulong flagsAddr = handle.Address + (ulong)type.Fields[nameof(Data.Module.Flags)].Offset; - uint flags = _target.Read(flagsAddr); - - return (flags & DebuggerInfoMask) >> DebuggerInfoShift; + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + return (DebuggerAssemblyControlFlags)((module.Flags & DebuggerInfoMask) >> DebuggerInfoShift); } - void ILoader.SetDebuggerInfoBits(ModuleHandle handle, uint newBits) + void ILoader.SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFlags newBits) { + Data.Module module = _target.ProcessedData.GetOrAdd(handle.Address); + uint currentFlags = module.Flags; + uint debuggerInfoBitsMask = DebuggerInfoMask >> DebuggerInfoShift; + uint updatedFlags = (currentFlags & ~DebuggerInfoMask) | (((uint)newBits & debuggerInfoBitsMask) << DebuggerInfoShift); + + bool jitOptDisabled = (updatedFlags & DEBUGGER_ALLOW_JIT_OPTS_PRIV) == 0 || (updatedFlags & PROF_DISABLE_OPTIMIZATIONS) != 0; + if (jitOptDisabled) + updatedFlags |= IS_JIT_OPTIMIZATION_DISABLED; + else + updatedFlags &= ~IS_JIT_OPTIMIZATION_DISABLED; + + if ((updatedFlags & IS_ENC_CAPABLE) != 0) + { + TargetPointer configPtr = _target.ReadGlobalPointer(Constants.Globals.EEConfig); + Data.EEConfig config = _target.ProcessedData.GetOrAdd(configPtr); + ClrModifiableAssemblies modifiableAssemblies = (ClrModifiableAssemblies)config.ModifiableAssemblies; + + if (modifiableAssemblies != ClrModifiableAssemblies.None) + { + bool encRequested = (newBits & DebuggerAssemblyControlFlags.DACF_ENC_ENABLED) != 0; + bool jitOptsDisabledForEnc = (updatedFlags & IS_JIT_OPTIMIZATION_DISABLED) != 0; + bool setEnC = encRequested || (modifiableAssemblies == ClrModifiableAssemblies.Debug && jitOptsDisabledForEnc); + + if (setEnC) + updatedFlags |= IS_EDIT_AND_CONTINUE; + } + } + Target.TypeInfo type = _target.GetTypeInfo(DataType.Module); ulong flagsAddr = handle.Address + (ulong)type.Fields[nameof(Data.Module.Flags)].Offset; - uint currentFlags = _target.Read(flagsAddr); - uint debuggerInfoBitsMask = DebuggerInfoMask >> DebuggerInfoShift; - uint updated = (currentFlags & ~DebuggerInfoMask) | ((newBits & debuggerInfoBitsMask) << DebuggerInfoShift); - _target.Write(flagsAddr, updated); + _target.Write(flagsAddr, updatedFlags); + + module.Flags = updatedFlags; } bool ILoader.IsReadyToRun(ModuleHandle handle) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.cs new file mode 100644 index 00000000000000..8d5a253dc1d664 --- /dev/null +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.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 EEConfig : IData +{ + static EEConfig IData.Create(Target target, TargetPointer address) + => new EEConfig(target, address); + + public EEConfig(Target target, TargetPointer address) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.EEConfig); + + ModifiableAssemblies = target.Read(address + (ulong)type.Fields[nameof(ModifiableAssemblies)].Offset); + } + + public uint ModifiableAssemblies { get; init; } +} diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index 2e7b7b589eb412..6dfb62794def3c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -40,7 +40,7 @@ public Module(Target target, TargetPointer address) public TargetPointer Assembly { get; init; } public TargetPointer PEAssembly { get; init; } - public uint Flags { get; init; } + public uint Flags { get; set; } public TargetPointer Base { get; init; } public TargetPointer LoaderAllocator { get; init; } public TargetPointer DynamicMetadata { get; init; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs index 2bc22c8c118498..dc564882e16298 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/ClrDataModule.cs @@ -489,19 +489,15 @@ int IXCLRDataModule2.SetJITCompilerFlags(uint flags) Contracts.ModuleHandle handle = loader.GetModuleHandleFromModulePtr(_address); bool allowJitOpts = (flags & CORDEBUG_JIT_DISABLE_OPTIMIZATION) != CORDEBUG_JIT_DISABLE_OPTIMIZATION; - uint bits = loader.GetDebuggerInfoBits(handle) - & ~((uint)DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS - | (uint)DebuggerAssemblyControlFlags.DACF_ENC_ENABLED); - bits &= (uint)DebuggerAssemblyControlFlags.DACF_CONTROL_FLAGS_MASK; + DebuggerAssemblyControlFlags bits = loader.GetDebuggerInfoBits(handle) + & ~(DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS | DebuggerAssemblyControlFlags.DACF_ENC_ENABLED); + bits &= DebuggerAssemblyControlFlags.DACF_CONTROL_FLAGS_MASK; if (allowJitOpts) { - bits |= (uint)DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS; + bits |= DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS; } - // Settings from the debugger take precedence over all other settings. - bits |= (uint)DebuggerAssemblyControlFlags.DACF_USER_OVERRIDE; - loader.SetDebuggerInfoBits(handle, bits); } catch (System.Exception ex) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs index b0c58622d0fa79..87cfe6ee2dacaf 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/IXCLRData.cs @@ -145,21 +145,6 @@ public unsafe partial interface IXCLRDataModule int GetVersionId(Guid* vid); } -[Flags] -public enum DebuggerAssemblyControlFlags : uint -{ - DACF_NONE = 0x00, - DACF_USER_OVERRIDE = 0x01, - DACF_ALLOW_JIT_OPTS = 0x02, - DACF_OBSOLETE_TRACK_JIT_INFO = 0x04, // obsolete in V2.0, we're always tracking. - DACF_ENC_ENABLED = 0x08, - DACF_IGNORE_PDBS = 0x20, - DACF_CONTROL_FLAGS_MASK = 0x2F, - - DACF_PDBS_COPIED = 0x10, - DACF_MISC_FLAGS_MASK = 0x10, -} - [GeneratedComInterface] [Guid("34625881-7EB3-4524-817B-8DB9D064C760")] public unsafe partial interface IXCLRDataModule2 diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index 6fc50f04e0c359..2987d9f9942478 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -17,6 +17,12 @@ namespace Microsoft.Diagnostics.DataContractReader.Tests; public unsafe class LoaderTests { + private const uint IsJitOptimizationDisabled = 0x00000002; + private const uint IsEditAndContinue = 0x00000008; + private const uint IsEncCapable = 0x00000200; + private const uint DebuggerAllowJitOptsPriv = 0x00000800; + private const uint DebuggerEncEnabledPriv = 0x00002000; + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetPath(MockTarget.Architecture arch) @@ -484,16 +490,15 @@ public static IEnumerable GetDebuggerInfoBitsData() { foreach (var arch in new MockTarget.StdArch()) { - yield return [0x00000000u, 0x00u, arch[0]]; // No debugger bits - yield return [0x00000800u, 0x02u, arch[0]]; // DACF_ALLOW_JIT_OPTS - yield return [0x00002000u, 0x08u, arch[0]]; // DACF_ENC_ENABLED - yield return [0x00000C00u, 0x03u, arch[0]]; // DACF_ALLOW_JIT_OPTS | DACF_USER_OVERRIDE + yield return [0u, DebuggerAssemblyControlFlags.DACF_NONE, arch[0]]; + yield return [DebuggerAllowJitOptsPriv, DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS, arch[0]]; + yield return [DebuggerEncEnabledPriv, DebuggerAssemblyControlFlags.DACF_ENC_ENABLED, arch[0]]; } } [Theory] [MemberData(nameof(GetDebuggerInfoBitsData))] - public void GetDebuggerInfoBits(uint rawFlags, uint expectedBits, MockTarget.Architecture arch) + public void GetDebuggerInfoBits(uint rawFlags, DebuggerAssemblyControlFlags expectedBits, MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); @@ -508,7 +513,7 @@ public void GetDebuggerInfoBits(uint rawFlags, uint expectedBits, MockTarget.Arc ILoader contract = target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); - uint actual = contract.GetDebuggerInfoBits(handle); + DebuggerAssemblyControlFlags actual = contract.GetDebuggerInfoBits(handle); Assert.Equal(expectedBits, actual); } @@ -516,24 +521,27 @@ public static IEnumerable SetDebuggerInfoBitsData() { foreach (var arch in new MockTarget.StdArch()) { - yield return [0x00u, 0x00000000u, arch[0]]; // Clear all debugger bits - yield return [0x02u, 0x00000800u, arch[0]]; // DACF_ALLOW_JIT_OPTS - yield return [0x08u, 0x00002000u, arch[0]]; // DACF_ENC_ENABLED - yield return [0x03u, 0x00000C00u, arch[0]]; // DACF_ALLOW_JIT_OPTS | DACF_USER_OVERRIDE + // IS_JIT_OPTIMIZATION_DISABLED is set when DACF_ALLOW_JIT_OPTS is absent + yield return [DebuggerAssemblyControlFlags.DACF_NONE, IsJitOptimizationDisabled, arch[0]]; + yield return [DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS, DebuggerAllowJitOptsPriv, arch[0]]; + yield return [DebuggerAssemblyControlFlags.DACF_ENC_ENABLED, DebuggerEncEnabledPriv | IsJitOptimizationDisabled, arch[0]]; } } [Theory] [MemberData(nameof(SetDebuggerInfoBitsData))] - public void SetDebuggerInfoBits(uint newBits, uint expectedRawFlags, MockTarget.Architecture arch) + public void SetDebuggerInfoBits(DebuggerAssemblyControlFlags newBits, uint expectedRawFlags, MockTarget.Architecture arch) { TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); MockLoader loader = new(builder); + TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); TargetPointer moduleAddr = loader.AddModule(); var memoryContext = builder.GetMemoryContext(); - var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, writer: memoryContext.WriteToTarget); + var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, + globals: [(Constants.Globals.EEConfig, configAddr)], + writer: memoryContext.WriteToTarget); target.SetContracts(Mock.Of( c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); @@ -542,7 +550,6 @@ public void SetDebuggerInfoBits(uint newBits, uint expectedRawFlags, MockTarget. contract.SetDebuggerInfoBits(handle, newBits); - // Verify the raw flags in memory have the correct shifted value. uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); Assert.Equal(expectedRawFlags, rawFlags); } @@ -554,24 +561,139 @@ public void SetDebuggerInfoBits_PreservesOtherFlags(MockTarget.Architecture arch TargetTestHelpers helpers = new(arch); MockMemorySpace.Builder builder = new(helpers); MockLoader loader = new(builder); + TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); - uint initialFlags = 0x41u; // DACF_TENURED | DACF_REFLECTION_EMIT + uint initialFlags = (uint)(ModuleFlags.Tenured | ModuleFlags.ReflectionEmit); TargetPointer moduleAddr = loader.AddModule(flags: initialFlags); var memoryContext = builder.GetMemoryContext(); - var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, writer: memoryContext.WriteToTarget); + var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, + globals: [(Constants.Globals.EEConfig, configAddr)], + writer: memoryContext.WriteToTarget); target.SetContracts(Mock.Of( c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); ILoader contract = target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); - uint debuggerBits = 0x03u; // DACF_ALLOW_JIT_OPTS | DACF_USER_OVERRIDE + DebuggerAssemblyControlFlags debuggerBits = DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS; int debuggerInfoShift = 10; contract.SetDebuggerInfoBits(handle, debuggerBits); - // Verify other flags (Tenured, ReflectionEmit) are preserved. uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); - Assert.Equal(initialFlags | (debuggerBits << debuggerInfoShift), rawFlags); + Assert.Equal(initialFlags | ((uint)debuggerBits << debuggerInfoShift), rawFlags); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetDebuggerInfoBits_UpdatesJitOptimizationDisabledState(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockLoader loader = new(builder); + TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); + TargetPointer moduleAddr = loader.AddModule(); + + var memoryContext = builder.GetMemoryContext(); + var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, + globals: [(Constants.Globals.EEConfig, configAddr)], + writer: memoryContext.WriteToTarget); + target.SetContracts(Mock.Of( + c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + + ILoader contract = target.Contracts.Loader; + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + + // Setting debugger bits without DACF_ALLOW_JIT_OPTS should set IS_JIT_OPTIMIZATION_DISABLED + contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_NONE); + + uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + Assert.True((rawFlags & IsJitOptimizationDisabled) != 0, "IS_JIT_OPTIMIZATION_DISABLED should be set when DACF_ALLOW_JIT_OPTS is not set"); + + // Setting debugger bits WITH DACF_ALLOW_JIT_OPTS should clear IS_JIT_OPTIMIZATION_DISABLED + contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS); + + rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + Assert.True((rawFlags & IsJitOptimizationDisabled) == 0, "IS_JIT_OPTIMIZATION_DISABLED should be cleared when DACF_ALLOW_JIT_OPTS is set"); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetDebuggerInfoBits_DoesNotEnableEnC(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockLoader loader = new(builder); + TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); + TargetPointer moduleAddr = loader.AddModule(); + + var memoryContext = builder.GetMemoryContext(); + var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, + globals: [(Constants.Globals.EEConfig, configAddr)], + writer: memoryContext.WriteToTarget); + target.SetContracts(Mock.Of( + c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + + ILoader contract = target.Contracts.Loader; + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + + contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_NONE); + + uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + Assert.True((rawFlags & IsEditAndContinue) == 0, "IS_EDIT_AND_CONTINUE should NOT be set when module is not EnC-capable"); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetDebuggerInfoBits_EnablesEnC_DisabledJitOpts(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockLoader loader = new(builder); + TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); + TargetPointer moduleAddr = loader.AddModule(flags: IsEncCapable); + + var memoryContext = builder.GetMemoryContext(); + var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, + globals: [(Constants.Globals.EEConfig, configAddr)], + writer: memoryContext.WriteToTarget); + target.SetContracts(Mock.Of( + c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + + ILoader contract = target.Contracts.Loader; + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + + // Disable JIT opts (don't set DACF_ALLOW_JIT_OPTS) → JIT opts disabled + ModifiableAssemblies == Debug → EnC enabled + contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_NONE); + + uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + Assert.True((rawFlags & IsEditAndContinue) != 0, "IS_EDIT_AND_CONTINUE should be set when module is EnC-capable, config is Debug, and JIT opts are disabled"); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetDebuggerInfoBits_EnablesEnC_ExplicitFlag(MockTarget.Architecture arch) + { + TargetTestHelpers helpers = new(arch); + MockMemorySpace.Builder builder = new(helpers); + MockLoader loader = new(builder); + TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); + TargetPointer moduleAddr = loader.AddModule(flags: IsEncCapable); + + var memoryContext = builder.GetMemoryContext(); + var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, + globals: [(Constants.Globals.EEConfig, configAddr)], + writer: memoryContext.WriteToTarget); + target.SetContracts(Mock.Of( + c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + + ILoader contract = target.Contracts.Loader; + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + + // Explicitly request EnC via DACF_ENC_ENABLED, even with JIT opts enabled + contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS | DebuggerAssemblyControlFlags.DACF_ENC_ENABLED); + + uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + Assert.True((rawFlags & IsEditAndContinue) != 0, "IS_EDIT_AND_CONTINUE should be set when DACF_ENC_ENABLED is explicitly requested"); } } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs index 488d5282fd432a..fa3ad04eab2706 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs @@ -38,9 +38,25 @@ public Loader(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocati [ ModuleFields, AssemblyFields, + EEConfigFields, ]); } + internal TargetPointer AddEEConfig(uint modifiableAssemblies) + { + TargetTestHelpers helpers = _builder.TargetTestHelpers; + Target.TypeInfo typeInfo = Types[DataType.EEConfig]; + uint size = typeInfo.Size.Value; + MockMemorySpace.HeapFragment config = _allocator.Allocate(size, "EEConfig"); + _builder.AddHeapFragment(config); + + helpers.Write( + config.Data.AsSpan().Slice(typeInfo.Fields[nameof(Data.EEConfig.ModifiableAssemblies)].Offset, sizeof(uint)), + modifiableAssemblies); + + return config.Address; + } + internal TargetPointer AddModule(string? path = null, string? fileName = null, string? simpleName = null, byte[]? simpleNameBytes = null, uint flags = 0) { TargetTestHelpers helpers = _builder.TargetTestHelpers; diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs index f3fcde7cdf13d0..59e95008107bfc 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs @@ -183,6 +183,15 @@ internal record TypeFields ] }; + internal static readonly TypeFields EEConfigFields = new TypeFields() + { + DataType = DataType.EEConfig, + Fields = + [ + new(nameof(Data.EEConfig.ModifiableAssemblies), DataType.uint32), + ] + }; + private static readonly TypeFields ExceptionInfoFields = new TypeFields() { DataType = DataType.ExceptionInfo, From cb891cf297734656f6312e515ead3eeca9d9a53a Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Mon, 13 Apr 2026 15:56:08 -0700 Subject: [PATCH 4/7] Address pr comments --- docs/design/datacontracts/Loader.md | 16 +- .../Contracts/ILoader.cs | 9 +- .../Contracts/Loader_1.cs | 25 +-- .../Data/Module.cs | 11 + src/native/managed/cdac/tests/LoaderTests.cs | 188 ++++++++---------- .../MockDescriptors/MockDescriptors.Loader.cs | 39 +++- 6 files changed, 158 insertions(+), 130 deletions(-) diff --git a/docs/design/datacontracts/Loader.md b/docs/design/datacontracts/Loader.md index 0476de79d739cc..6070834e1b285d 100644 --- a/docs/design/datacontracts/Loader.md +++ b/docs/design/datacontracts/Loader.md @@ -15,9 +15,12 @@ readonly struct ModuleHandle [Flags] enum ModuleFlags { - Tenured = 0x00000001, // Set once we know for sure the Module will not be freed until the appdomain itself exits - EditAndContinue = 0x00000008, // Edit and Continue is enabled for this module - ReflectionEmit = 0x00000040, // Reflection.Emit was used to create this module + Tenured = 0x1, // Set once we know for sure the Module will not be freed until the appdomain itself exits + JitOptimizationDisabled = 0x2, // Cached flag: JIT optimizations are disabled + EditAndContinue = 0x8, // Edit and Continue is enabled for this module + ReflectionEmit = 0x40, // Reflection.Emit was used to create this module + ProfDisableOptimizations = 0x80, // Profiler disabled JIT optimizations + EncCapable = 0x200, // Cached flag: module is Edit and Continue capable } [Flags] @@ -103,6 +106,7 @@ enum DebuggerAssemblyControlFlags : uint DACF_NONE = 0x00, DACF_ALLOW_JIT_OPTS = 0x02, DACF_ENC_ENABLED = 0x08, + DACF_CONTROL_FLAGS_MASK = 0x2E, } ``` @@ -215,12 +219,8 @@ enum ClrModifiableAssemblies : uint | `ASSEMBLY_NOTIFYFLAGS_PROFILER_NOTIFIED` | uint | Flag in Assembly NotifyFlags indicating the Assembly will notify profilers. | `0x1` | | `DefaultDomainFriendlyName` | string | Friendly name returned when `AppDomain.FriendlyName` is null (matches native `DEFAULT_DOMAIN_FRIENDLY_NAME`) | `"DefaultDomain"` | | `MaxWebcilSections` | ushort | Maximum number of COFF sections supported in a Webcil image (must stay in sync with native `WEBCIL_MAX_SECTIONS`) | `16` | -| `DebuggerInfoMask` | uint | Mask for the debugger info bits within the Module's transient flags | `0x0000Fc00` | +| `DebuggerInfoMask` | uint | Mask for the debugger info bits within the Module's transient flags | `0x0000FC00` | | `DebuggerInfoShift` | int | Bit shift for the debugger info bits within the Module's transient flags | `10` | -| `IS_JIT_OPTIMIZATION_DISABLED` | uint | Cached flag: JIT optimizations are disabled | `0x00000002` | -| `IS_EDIT_AND_CONTINUE` | uint | Flag: EnC is enabled for this module | `0x00000008` | -| `PROF_DISABLE_OPTIMIZATIONS` | uint | Profiler disabled JIT optimizations | `0x00000080` | -| `IS_ENC_CAPABLE` | uint | Cached flag: module is Edit and Continue capable | `0x00000200` | | `DEBUGGER_ALLOW_JIT_OPTS_PRIV` | uint | Debugger allows JIT optimizations (shifted in transient flags) | `0x00000800` | Contracts used: diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs index ddb66fe41bbf37..0b1a63db75ea96 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ILoader.cs @@ -19,9 +19,12 @@ public ModuleHandle(TargetPointer address) [Flags] public enum ModuleFlags { - Tenured = 0x1, // Set once we know for sure the Module will not be freed until the appdomain itself exits - EditAndContinue = 0x8, // Edit and Continue is enabled for this module - ReflectionEmit = 0x40, // Reflection.Emit was used to create this module + Tenured = 0x1, // Set once we know for sure the Module will not be freed until the appdomain itself exits + JitOptimizationDisabled = 0x2, // Cached flag: JIT optimizations are disabled + EditAndContinue = 0x8, // Edit and Continue is enabled for this module + ReflectionEmit = 0x40, // Reflection.Emit was used to create this module + ProfDisableOptimizations = 0x80, // Profiler disabled JIT optimizations + EncCapable = 0x200, // Cached flag: module is Edit and Continue capable } [Flags] diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs index a9271ee13966b8..08584f9c097cb0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Loader_1.cs @@ -23,14 +23,9 @@ private enum ModuleFlags_1 : uint ReflectionEmit = 0x40, // Reflection.Emit was used to create this module } - private const uint DebuggerInfoMask = 0x0000Fc00; + private const uint DebuggerInfoMask = 0x0000FC00; private const int DebuggerInfoShift = 10; - // Transient flag bits from m_dwTransientFlags used for JIT optimization and EnC logic. - private const uint IS_JIT_OPTIMIZATION_DISABLED = 0x00000002; - private const uint IS_EDIT_AND_CONTINUE = 0x00000008; - private const uint PROF_DISABLE_OPTIMIZATIONS = 0x00000080; - private const uint IS_ENC_CAPABLE = 0x00000200; private const uint DEBUGGER_ALLOW_JIT_OPTS_PRIV = 0x00000800; private enum PEImageFlags : uint @@ -399,13 +394,13 @@ void ILoader.SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFla uint debuggerInfoBitsMask = DebuggerInfoMask >> DebuggerInfoShift; uint updatedFlags = (currentFlags & ~DebuggerInfoMask) | (((uint)newBits & debuggerInfoBitsMask) << DebuggerInfoShift); - bool jitOptDisabled = (updatedFlags & DEBUGGER_ALLOW_JIT_OPTS_PRIV) == 0 || (updatedFlags & PROF_DISABLE_OPTIMIZATIONS) != 0; + bool jitOptDisabled = (updatedFlags & DEBUGGER_ALLOW_JIT_OPTS_PRIV) == 0 || (updatedFlags & (uint)ModuleFlags.ProfDisableOptimizations) != 0; if (jitOptDisabled) - updatedFlags |= IS_JIT_OPTIMIZATION_DISABLED; + updatedFlags |= (uint)ModuleFlags.JitOptimizationDisabled; else - updatedFlags &= ~IS_JIT_OPTIMIZATION_DISABLED; + updatedFlags &= ~(uint)ModuleFlags.JitOptimizationDisabled; - if ((updatedFlags & IS_ENC_CAPABLE) != 0) + if ((updatedFlags & (uint)ModuleFlags.EncCapable) != 0) { TargetPointer configPtr = _target.ReadGlobalPointer(Constants.Globals.EEConfig); Data.EEConfig config = _target.ProcessedData.GetOrAdd(configPtr); @@ -414,19 +409,15 @@ void ILoader.SetDebuggerInfoBits(ModuleHandle handle, DebuggerAssemblyControlFla if (modifiableAssemblies != ClrModifiableAssemblies.None) { bool encRequested = (newBits & DebuggerAssemblyControlFlags.DACF_ENC_ENABLED) != 0; - bool jitOptsDisabledForEnc = (updatedFlags & IS_JIT_OPTIMIZATION_DISABLED) != 0; + bool jitOptsDisabledForEnc = (updatedFlags & (uint)ModuleFlags.JitOptimizationDisabled) != 0; bool setEnC = encRequested || (modifiableAssemblies == ClrModifiableAssemblies.Debug && jitOptsDisabledForEnc); if (setEnC) - updatedFlags |= IS_EDIT_AND_CONTINUE; + updatedFlags |= (uint)ModuleFlags.EditAndContinue; } } - Target.TypeInfo type = _target.GetTypeInfo(DataType.Module); - ulong flagsAddr = handle.Address + (ulong)type.Fields[nameof(Data.Module.Flags)].Offset; - _target.Write(flagsAddr, updatedFlags); - - module.Flags = updatedFlags; + module.WriteFlags(_target, updatedFlags); } bool ILoader.IsReadyToRun(ModuleHandle handle) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index 8f680b4b9c4866..5e3aa418ac6b7a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -14,6 +14,7 @@ public Module(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.Module); + _address = address; Flags = target.ReadField(address, type, nameof(Flags)); Assembly = target.ReadPointerField(address, type, nameof(Assembly)); PEAssembly = target.ReadPointerField(address, type, nameof(PEAssembly)); @@ -38,6 +39,16 @@ public Module(Target target, TargetPointer address) DynamicILBlobTable = target.ReadPointerField(address, type, nameof(DynamicILBlobTable)); } + private readonly TargetPointer _address; + + public void WriteFlags(Target target, uint flags) + { + Target.TypeInfo type = target.GetTypeInfo(DataType.Module); + ulong flagsAddr = _address + (ulong)type.Fields[nameof(Flags)].Offset; + target.Write(flagsAddr, flags); + Flags = flags; + } + public TargetPointer Assembly { get; init; } public TargetPointer PEAssembly { get; init; } public uint Flags { get; set; } diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index e7b87fc4372ca7..751902cdf3884d 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -26,6 +26,7 @@ public unsafe class LoaderTests { [DataType.Module] = TargetTestHelpers.CreateTypeInfo(loader.ModuleLayout), [DataType.Assembly] = TargetTestHelpers.CreateTypeInfo(loader.AssemblyLayout), + [DataType.EEConfig] = TargetTestHelpers.CreateTypeInfo(loader.EEConfigLayout), }; private static ILoader CreateLoaderContract(MockTarget.Architecture arch, Action configure) @@ -42,6 +43,21 @@ private static ILoader CreateLoaderContract(MockTarget.Architecture arch, Action return target.Contracts.Loader; } + private static (ILoader Contract, TestPlaceholderTarget Target) CreateLoaderContractWithTarget( + MockTarget.Architecture arch, + Action configure) + { + var targetBuilder = new TestPlaceholderTarget.Builder(arch); + MockLoaderBuilder loader = new(targetBuilder.MemoryBuilder); + + configure(loader, targetBuilder); + + targetBuilder.AddTypes(CreateContractTypes(loader)); + targetBuilder.AddContract(version: 1); + var target = targetBuilder.Build(); + return (target.Contracts.Loader, target); + } + [Theory] [ClassData(typeof(MockTarget.StdArch))] public void GetPath(MockTarget.Architecture arch) @@ -534,19 +550,14 @@ public static IEnumerable GetDebuggerInfoBitsData() [MemberData(nameof(GetDebuggerInfoBitsData))] public void GetDebuggerInfoBits(uint rawFlags, DebuggerAssemblyControlFlags expectedBits, MockTarget.Architecture arch) { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockLoader loader = new(builder); - TargetPointer moduleAddr = loader.AddModule(flags: rawFlags); + TargetPointer moduleAddr = TargetPointer.Null; - var memoryContext = builder.GetMemoryContext(); - var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, writer: memoryContext.WriteToTarget); - target.SetContracts(Mock.Of( - c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + ILoader contract = CreateLoaderContract(arch, loader => + { + moduleAddr = loader.AddModule(flags: rawFlags).Address; + }); - ILoader contract = target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); - DebuggerAssemblyControlFlags actual = contract.GetDebuggerInfoBits(handle); Assert.Equal(expectedBits, actual); } @@ -566,25 +577,21 @@ public static IEnumerable SetDebuggerInfoBitsData() [MemberData(nameof(SetDebuggerInfoBitsData))] public void SetDebuggerInfoBits(DebuggerAssemblyControlFlags newBits, uint expectedRawFlags, MockTarget.Architecture arch) { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockLoader loader = new(builder); - TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); - TargetPointer moduleAddr = loader.AddModule(); - - var memoryContext = builder.GetMemoryContext(); - var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, - globals: [(Constants.Globals.EEConfig, configAddr)], - writer: memoryContext.WriteToTarget); - target.SetContracts(Mock.Of( - c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; - ILoader contract = target.Contracts.Loader; - Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule().Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); contract.SetDebuggerInfoBits(handle, newBits); - uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); Assert.Equal(expectedRawFlags, rawFlags); } @@ -592,29 +599,25 @@ public void SetDebuggerInfoBits(DebuggerAssemblyControlFlags newBits, uint expec [ClassData(typeof(MockTarget.StdArch))] public void SetDebuggerInfoBits_PreservesOtherFlags(MockTarget.Architecture arch) { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockLoader loader = new(builder); - TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); - uint initialFlags = (uint)(ModuleFlags.Tenured | ModuleFlags.ReflectionEmit); - TargetPointer moduleAddr = loader.AddModule(flags: initialFlags); + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; - var memoryContext = builder.GetMemoryContext(); - var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, - globals: [(Constants.Globals.EEConfig, configAddr)], - writer: memoryContext.WriteToTarget); - target.SetContracts(Mock.Of( - c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule(flags: initialFlags).Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); - ILoader contract = target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); DebuggerAssemblyControlFlags debuggerBits = DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS; int debuggerInfoShift = 10; contract.SetDebuggerInfoBits(handle, debuggerBits); - uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); Assert.Equal(initialFlags | ((uint)debuggerBits << debuggerInfoShift), rawFlags); } @@ -622,32 +625,29 @@ public void SetDebuggerInfoBits_PreservesOtherFlags(MockTarget.Architecture arch [ClassData(typeof(MockTarget.StdArch))] public void SetDebuggerInfoBits_UpdatesJitOptimizationDisabledState(MockTarget.Architecture arch) { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockLoader loader = new(builder); - TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); - TargetPointer moduleAddr = loader.AddModule(); - - var memoryContext = builder.GetMemoryContext(); - var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, - globals: [(Constants.Globals.EEConfig, configAddr)], - writer: memoryContext.WriteToTarget); - target.SetContracts(Mock.Of( - c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; + + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule().Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); - ILoader contract = target.Contracts.Loader; Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); // Setting debugger bits without DACF_ALLOW_JIT_OPTS should set IS_JIT_OPTIMIZATION_DISABLED contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_NONE); - uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); Assert.True((rawFlags & IsJitOptimizationDisabled) != 0, "IS_JIT_OPTIMIZATION_DISABLED should be set when DACF_ALLOW_JIT_OPTS is not set"); // Setting debugger bits WITH DACF_ALLOW_JIT_OPTS should clear IS_JIT_OPTIMIZATION_DISABLED contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS); - rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); Assert.True((rawFlags & IsJitOptimizationDisabled) == 0, "IS_JIT_OPTIMIZATION_DISABLED should be cleared when DACF_ALLOW_JIT_OPTS is set"); } @@ -655,25 +655,21 @@ public void SetDebuggerInfoBits_UpdatesJitOptimizationDisabledState(MockTarget.A [ClassData(typeof(MockTarget.StdArch))] public void SetDebuggerInfoBits_DoesNotEnableEnC(MockTarget.Architecture arch) { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockLoader loader = new(builder); - TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); - TargetPointer moduleAddr = loader.AddModule(); - - var memoryContext = builder.GetMemoryContext(); - var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, - globals: [(Constants.Globals.EEConfig, configAddr)], - writer: memoryContext.WriteToTarget); - target.SetContracts(Mock.Of( - c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; - ILoader contract = target.Contracts.Loader; - Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule().Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_NONE); - uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); Assert.True((rawFlags & IsEditAndContinue) == 0, "IS_EDIT_AND_CONTINUE should NOT be set when module is not EnC-capable"); } @@ -681,26 +677,21 @@ public void SetDebuggerInfoBits_DoesNotEnableEnC(MockTarget.Architecture arch) [ClassData(typeof(MockTarget.StdArch))] public void SetDebuggerInfoBits_EnablesEnC_DisabledJitOpts(MockTarget.Architecture arch) { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockLoader loader = new(builder); - TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); - TargetPointer moduleAddr = loader.AddModule(flags: IsEncCapable); - - var memoryContext = builder.GetMemoryContext(); - var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, - globals: [(Constants.Globals.EEConfig, configAddr)], - writer: memoryContext.WriteToTarget); - target.SetContracts(Mock.Of( - c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; - ILoader contract = target.Contracts.Loader; - Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule(flags: IsEncCapable).Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); - // Disable JIT opts (don't set DACF_ALLOW_JIT_OPTS) → JIT opts disabled + ModifiableAssemblies == Debug → EnC enabled + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_NONE); - uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); Assert.True((rawFlags & IsEditAndContinue) != 0, "IS_EDIT_AND_CONTINUE should be set when module is EnC-capable, config is Debug, and JIT opts are disabled"); } @@ -708,26 +699,21 @@ public void SetDebuggerInfoBits_EnablesEnC_DisabledJitOpts(MockTarget.Architectu [ClassData(typeof(MockTarget.StdArch))] public void SetDebuggerInfoBits_EnablesEnC_ExplicitFlag(MockTarget.Architecture arch) { - TargetTestHelpers helpers = new(arch); - MockMemorySpace.Builder builder = new(helpers); - MockLoader loader = new(builder); - TargetPointer configAddr = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); - TargetPointer moduleAddr = loader.AddModule(flags: IsEncCapable); - - var memoryContext = builder.GetMemoryContext(); - var target = new TestPlaceholderTarget(arch, memoryContext.ReadFromTarget, loader.Types, - globals: [(Constants.Globals.EEConfig, configAddr)], - writer: memoryContext.WriteToTarget); - target.SetContracts(Mock.Of( - c => c.Loader == ((IContractFactory)new LoaderFactory()).CreateContract(target, 1))); + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; - ILoader contract = target.Contracts.Loader; - Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); + var (contract, target) = CreateLoaderContractWithTarget(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + moduleAddr = loader.AddModule(flags: IsEncCapable).Address; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); - // Explicitly request EnC via DACF_ENC_ENABLED, even with JIT opts enabled + Contracts.ModuleHandle handle = contract.GetModuleHandleFromModulePtr(moduleAddr); contract.SetDebuggerInfoBits(handle, DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS | DebuggerAssemblyControlFlags.DACF_ENC_ENABLED); - uint rawFlags = target.Read(moduleAddr + (ulong)loader.Types[DataType.Module].Fields[nameof(Data.Module.Flags)].Offset); + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); Assert.True((rawFlags & IsEditAndContinue) != 0, "IS_EDIT_AND_CONTINUE should be set when DACF_ENC_ENABLED is explicitly requested"); } } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs index 7e922885ffc0e8..8b75df5e9942f5 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs @@ -80,6 +80,12 @@ public ulong FileName set => WritePointerField(FileNameFieldName, value); } + public uint Flags + { + get => ReadUInt32Field(FlagsFieldName); + set => WriteUInt32Field(FlagsFieldName, value); + } + public ulong ReadyToRunInfo { get => ReadPointerField(ReadyToRunInfoFieldName); @@ -113,6 +119,22 @@ public ulong Module } } +internal sealed class MockEEConfig : TypedView +{ + private const string ModifiableAssembliesFieldName = "ModifiableAssemblies"; + + public static Layout CreateLayout(MockTarget.Architecture architecture) + => new SequentialLayoutBuilder("EEConfig", architecture) + .AddUInt32Field(ModifiableAssembliesFieldName) + .Build(); + + public uint ModifiableAssemblies + { + get => ReadUInt32Field(ModifiableAssembliesFieldName); + set => WriteUInt32Field(ModifiableAssembliesFieldName, value); + } +} + internal sealed class MockLoaderBuilder { private const ulong DefaultAllocationRangeStart = 0x0001_0000; @@ -121,6 +143,7 @@ internal sealed class MockLoaderBuilder internal MockMemorySpace.Builder Builder { get; } internal Layout ModuleLayout { get; } internal Layout AssemblyLayout { get; } + internal Layout EEConfigLayout { get; } private readonly MockMemorySpace.BumpAllocator _allocator; @@ -138,16 +161,23 @@ public MockLoaderBuilder(MockMemorySpace.Builder builder, (ulong Start, ulong En ModuleLayout = MockLoaderModule.CreateLayout(builder.TargetTestHelpers.Arch); AssemblyLayout = MockLoaderAssembly.CreateLayout(builder.TargetTestHelpers.Arch); + EEConfigLayout = MockEEConfig.CreateLayout(builder.TargetTestHelpers.Arch); } internal MockLoaderModule AddModule( string? path = null, string? fileName = null, string? simpleName = null, - byte[]? simpleNameBytes = null) + byte[]? simpleNameBytes = null, + uint flags = 0) { MockLoaderModule module = ModuleLayout.Create(AllocateAndAdd((ulong)ModuleLayout.Size, "Module")); + if (flags != 0) + { + module.Flags = flags; + } + byte[]? rawSimpleName = simpleName is not null ? Encoding.UTF8.GetBytes(simpleName) : simpleNameBytes; if (rawSimpleName is not null) { @@ -170,6 +200,13 @@ internal MockLoaderModule AddModule( return module; } + internal MockEEConfig AddEEConfig(uint modifiableAssemblies) + { + MockEEConfig config = EEConfigLayout.Create(AllocateAndAdd((ulong)EEConfigLayout.Size, "EEConfig")); + config.ModifiableAssemblies = modifiableAssemblies; + return config; + } + private ulong AddNullTerminatedUtf8(ReadOnlySpan bytes, string name) { MockMemorySpace.HeapFragment fragment = AllocateAndAdd((ulong)bytes.Length + 1, name); From 87812cc90dede4d65b4e0850584c33ec2caa814d Mon Sep 17 00:00:00 2001 From: Barbara Rosiak <76071368+barosiak@users.noreply.github.com> Date: Mon, 13 Apr 2026 16:23:36 -0700 Subject: [PATCH 5/7] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../Data/EEConfig.cs | 2 +- src/native/managed/cdac/tests/TestPlaceholderTarget.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.cs index 8d5a253dc1d664..dd318f16da0e44 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/EEConfig.cs @@ -12,7 +12,7 @@ public EEConfig(Target target, TargetPointer address) { Target.TypeInfo type = target.GetTypeInfo(DataType.EEConfig); - ModifiableAssemblies = target.Read(address + (ulong)type.Fields[nameof(ModifiableAssemblies)].Offset); + ModifiableAssemblies = target.ReadField(address, type, nameof(ModifiableAssemblies)); } public uint ModifiableAssemblies { get; init; } diff --git a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs index 234e60f89fff43..a63379f384f9e1 100644 --- a/src/native/managed/cdac/tests/TestPlaceholderTarget.cs +++ b/src/native/managed/cdac/tests/TestPlaceholderTarget.cs @@ -143,7 +143,7 @@ public TestPlaceholderTarget Build() var memoryContext = _memBuilder.GetMemoryContext(); var target = new TestPlaceholderTarget( _arch, - _readerOverride ?? _memBuilder.GetMemoryContext().ReadFromTarget, + _readerOverride ?? memoryContext.ReadFromTarget, _types, _globals.ToArray(), _globalStrings.ToArray(), From f48e8df787ad1a178288ac9ce94b7fd9a3cdf575 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Wed, 15 Apr 2026 13:44:18 -0700 Subject: [PATCH 6/7] Make Flags setter private --- .../Data/Module.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs index 5e3aa418ac6b7a..8e16e53b190183 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Module.cs @@ -51,7 +51,7 @@ public void WriteFlags(Target target, uint flags) public TargetPointer Assembly { get; init; } public TargetPointer PEAssembly { get; init; } - public uint Flags { get; set; } + public uint Flags { get; private set; } public TargetPointer Base { get; init; } public TargetPointer LoaderAllocator { get; init; } public TargetPointer DynamicMetadata { get; init; } From 18ca3ab936c8a26d3aeafe75dba49a07fe26cbac Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Wed, 15 Apr 2026 16:18:50 -0700 Subject: [PATCH 7/7] Fix build failure --- .../cdac/tests/MockDescriptors/MockDescriptors.Loader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs index ed389e445d5e9a..b1ad52a8cd197a 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Loader.cs @@ -202,7 +202,7 @@ internal MockLoaderModule AddModule( internal MockEEConfig AddEEConfig(uint modifiableAssemblies) { - MockEEConfig config = EEConfigLayout.Create(AllocateAndAdd((ulong)EEConfigLayout.Size, "EEConfig")); + MockEEConfig config = EEConfigLayout.Create(_allocator.Allocate((ulong)EEConfigLayout.Size, "EEConfig")); config.ModifiableAssemblies = modifiableAssemblies; return config; }