From e141af57ae1990147a22c4e6fd753d7fa85acf6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:36:50 +0000 Subject: [PATCH 1/3] Implement SetCompilerFlags on DacDbiImpl using ILoader contract Replace the legacy-delegation stub with a contract-based implementation that: - Gets the ILoader contract and resolves the assembly pointer to a ModuleHandle - Reads current debugger info bits, clears DACF_ALLOW_JIT_OPTS and DACF_ENC_ENABLED, masks with DACF_CONTROL_FLAGS_MASK - Conditionally ORs in the requested flags - Delegates to SetDebuggerInfoBits which handles EncCapable check, JIT optimization disabled state, and EditAndContinue flag logic - Returns CORDBG_S_NOT_ALL_BITS_SET if EnC was requested but could not be enabled - Includes #if DEBUG legacy validation block Add CORDBG_S_NOT_ALL_BITS_SET constant to CorDbgHResults. Add unit tests covering: both flags set (EnC capable), both flags unset, EnC requested but not capable, and JIT opts toggling. Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/0d4cb517-9044-4d94-902e-a91169e2d2f5 Co-authored-by: barosiak <76071368+barosiak@users.noreply.github.com> --- .../CorDbHResults.cs | 1 + .../Dbi/DacDbiImpl.cs | 51 +++++++- src/native/managed/cdac/tests/LoaderTests.cs | 120 ++++++++++++++++++ 3 files changed, 171 insertions(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs index 34846f9fdaf8ce..63971df217ae9a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs @@ -10,4 +10,5 @@ public static class CorDbgHResults public const int CORDBG_E_READVIRTUAL_FAILURE = unchecked((int)0x80131c49); public const int ERROR_BUFFER_OVERFLOW = unchecked((int)0x8007006F); // HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW) public const int CORDBG_E_CLASS_NOT_LOADED = unchecked((int)0x80131303); + public const int CORDBG_S_NOT_ALL_BITS_SET = unchecked((int)0x00131c13); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 6448703ec5be38..81633b1192ba0b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -232,7 +232,56 @@ public int GetCompilerFlags(ulong vmAssembly, Interop.BOOL* pfAllowJITOpts, Inte => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetCompilerFlags(vmAssembly, pfAllowJITOpts, pfEnableEnC) : HResults.E_NOTIMPL; public int SetCompilerFlags(ulong vmAssembly, Interop.BOOL fAllowJitOpts, Interop.BOOL fEnableEnC) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.SetCompilerFlags(vmAssembly, fAllowJitOpts, fEnableEnC) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + Contracts.ILoader loader = _target.Contracts.Loader; + Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly)); + + // Read the current bits, clear the flags we're about to set, and mask with CONTROL_FLAGS_MASK. + Contracts.DebuggerAssemblyControlFlags dwBits = loader.GetDebuggerInfoBits(handle); + dwBits &= ~(Contracts.DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS | Contracts.DebuggerAssemblyControlFlags.DACF_ENC_ENABLED); + dwBits &= Contracts.DebuggerAssemblyControlFlags.DACF_CONTROL_FLAGS_MASK; + + if (fAllowJitOpts != Interop.BOOL.FALSE) + { + dwBits |= Contracts.DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS; + } + + if (fEnableEnC != Interop.BOOL.FALSE) + { + dwBits |= Contracts.DebuggerAssemblyControlFlags.DACF_ENC_ENABLED; + } + + // SetDebuggerInfoBits handles the EncCapable check, JIT optimization disabled state update, + // and EditAndContinue flag logic internally. + loader.SetDebuggerInfoBits(handle, dwBits); + + // If EnC was requested, check whether the module actually has EditAndContinue set. + // If not, it means the module was not capable of EnC. + if (fEnableEnC != Interop.BOOL.FALSE) + { + Contracts.ModuleFlags flags = loader.GetFlags(handle); + if ((flags & Contracts.ModuleFlags.EditAndContinue) == 0) + { + hr = CorDbgHResults.CORDBG_S_NOT_ALL_BITS_SET; + } + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + int hrLocal = _legacy.SetCompilerFlags(vmAssembly, fAllowJitOpts, fEnableEnC); + Debug.ValidateHResult(hr, hrLocal); + } +#endif + return hr; + } public int EnumerateAssembliesInAppDomain(ulong vmAppDomain, nint fpCallback, nint pUserData) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.EnumerateAssembliesInAppDomain(vmAppDomain, fpCallback, pUserData) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index 88a18aaee2711a..323439f698a7f9 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -712,3 +712,123 @@ public void SetDebuggerInfoBits_EnablesEnC_ExplicitFlag(MockTarget.Architecture Assert.True((rawFlags & IsEditAndContinue) != 0, "IS_EDIT_AND_CONTINUE should be set when DACF_ENC_ENABLED is explicitly requested"); } } + +public unsafe class SetCompilerFlagsTests +{ + private const uint IsEditAndContinue = 0x00000008; + private const uint IsEncCapable = 0x00000200; + private const uint DebuggerAllowJitOptsPriv = 0x00000800; + + private static (DacDbiImpl DacDbi, TestPlaceholderTarget Target) CreateDacDbiWithLoader( + MockTarget.Architecture arch, + Action configure) + { + var targetBuilder = new TestPlaceholderTarget.Builder(arch); + MockLoaderBuilder loader = new(targetBuilder.MemoryBuilder); + + configure(loader, targetBuilder); + + targetBuilder.AddTypes(LoaderTests.CreateContractTypes(loader)); + targetBuilder.AddContract(version: "c1"); + var target = targetBuilder.Build(); + var dacDbi = new DacDbiImpl(target, legacyObj: null); + return (dacDbi, target); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetCompilerFlags_BothFlagsSet_EncCapable(MockTarget.Architecture arch) + { + ulong assemblyAddr = 0; + int flagsOffset = 0; + + var (dacDbi, target) = CreateDacDbiWithLoader(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + var module = loader.AddModule(flags: IsEncCapable); + assemblyAddr = module.Assembly; + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); + + int hr = dacDbi.SetCompilerFlags(assemblyAddr, Interop.BOOL.TRUE, Interop.BOOL.TRUE); + + Assert.Equal(System.HResults.S_OK, hr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetCompilerFlags_BothFlagsUnset(MockTarget.Architecture arch) + { + ulong assemblyAddr = 0; + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; + + var (dacDbi, target) = CreateDacDbiWithLoader(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + var module = loader.AddModule(); + assemblyAddr = module.Assembly; + moduleAddr = new TargetPointer(module.Address); + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); + + int hr = dacDbi.SetCompilerFlags(assemblyAddr, Interop.BOOL.FALSE, Interop.BOOL.FALSE); + + Assert.Equal(System.HResults.S_OK, hr); + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); + Assert.Equal(0u, rawFlags & DebuggerAllowJitOptsPriv); + Assert.Equal(0u, rawFlags & IsEditAndContinue); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetCompilerFlags_EnCRequested_NotCapable_ReturnsNotAllBitsSet(MockTarget.Architecture arch) + { + ulong assemblyAddr = 0; + + var (dacDbi, _) = CreateDacDbiWithLoader(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + var module = loader.AddModule(); // Not EnC-capable + assemblyAddr = module.Assembly; + }); + + int hr = dacDbi.SetCompilerFlags(assemblyAddr, Interop.BOOL.TRUE, Interop.BOOL.TRUE); + + Assert.Equal(CorDbgHResults.CORDBG_S_NOT_ALL_BITS_SET, hr); + } + + [Theory] + [ClassData(typeof(MockTarget.StdArch))] + public void SetCompilerFlags_JitOptsToggling(MockTarget.Architecture arch) + { + ulong assemblyAddr = 0; + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; + + var (dacDbi, target) = CreateDacDbiWithLoader(arch, (loader, builder) => + { + var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); + builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); + var module = loader.AddModule(); + assemblyAddr = module.Assembly; + moduleAddr = new TargetPointer(module.Address); + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; + }); + + // Enable JIT opts + int hr = dacDbi.SetCompilerFlags(assemblyAddr, Interop.BOOL.TRUE, Interop.BOOL.FALSE); + Assert.Equal(System.HResults.S_OK, hr); + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); + Assert.NotEqual(0u, rawFlags & DebuggerAllowJitOptsPriv); + + // Disable JIT opts + hr = dacDbi.SetCompilerFlags(assemblyAddr, Interop.BOOL.FALSE, Interop.BOOL.FALSE); + Assert.Equal(System.HResults.S_OK, hr); + rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); + Assert.Equal(0u, rawFlags & DebuggerAllowJitOptsPriv); + } +} From 4b4bb022d63da020e32d0e137f6ee64c5ee6f644 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:38:40 +0000 Subject: [PATCH 2/3] Remove unused variable in SetCompilerFlags_BothFlagsSet_EncCapable test Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/0d4cb517-9044-4d94-902e-a91169e2d2f5 Co-authored-by: barosiak <76071368+barosiak@users.noreply.github.com> --- src/native/managed/cdac/tests/LoaderTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index 323439f698a7f9..743862aa10d216 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -740,15 +740,13 @@ private static (DacDbiImpl DacDbi, TestPlaceholderTarget Target) CreateDacDbiWit public void SetCompilerFlags_BothFlagsSet_EncCapable(MockTarget.Architecture arch) { ulong assemblyAddr = 0; - int flagsOffset = 0; - var (dacDbi, target) = CreateDacDbiWithLoader(arch, (loader, builder) => + var (dacDbi, _) = CreateDacDbiWithLoader(arch, (loader, builder) => { var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); var module = loader.AddModule(flags: IsEncCapable); assemblyAddr = module.Assembly; - flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; }); int hr = dacDbi.SetCompilerFlags(assemblyAddr, Interop.BOOL.TRUE, Interop.BOOL.TRUE); From 187ffb1f21e39cbce87e24aec98e85981a4a27a6 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Tue, 21 Apr 2026 13:51:30 -0700 Subject: [PATCH 3/3] Code cleanup --- .../Dbi/DacDbiImpl.cs | 30 +++++++------------ src/native/managed/cdac/tests/LoaderTests.cs | 24 +++++++-------- 2 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs index 81633b1192ba0b..a047942b93bcd1 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs @@ -239,34 +239,26 @@ public int SetCompilerFlags(ulong vmAssembly, Interop.BOOL fAllowJitOpts, Intero Contracts.ILoader loader = _target.Contracts.Loader; Contracts.ModuleHandle handle = loader.GetModuleHandleFromAssemblyPtr(new TargetPointer(vmAssembly)); - // Read the current bits, clear the flags we're about to set, and mask with CONTROL_FLAGS_MASK. - Contracts.DebuggerAssemblyControlFlags dwBits = loader.GetDebuggerInfoBits(handle); - dwBits &= ~(Contracts.DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS | Contracts.DebuggerAssemblyControlFlags.DACF_ENC_ENABLED); - dwBits &= Contracts.DebuggerAssemblyControlFlags.DACF_CONTROL_FLAGS_MASK; + Contracts.DebuggerAssemblyControlFlags controlFlags = loader.GetDebuggerInfoBits(handle); + controlFlags &= ~(Contracts.DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS | Contracts.DebuggerAssemblyControlFlags.DACF_ENC_ENABLED); + controlFlags &= Contracts.DebuggerAssemblyControlFlags.DACF_CONTROL_FLAGS_MASK; - if (fAllowJitOpts != Interop.BOOL.FALSE) + if (fAllowJitOpts == Interop.BOOL.TRUE) { - dwBits |= Contracts.DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS; + controlFlags |= Contracts.DebuggerAssemblyControlFlags.DACF_ALLOW_JIT_OPTS; } - if (fEnableEnC != Interop.BOOL.FALSE) + if (fEnableEnC == Interop.BOOL.TRUE) { - dwBits |= Contracts.DebuggerAssemblyControlFlags.DACF_ENC_ENABLED; + controlFlags |= Contracts.DebuggerAssemblyControlFlags.DACF_ENC_ENABLED; } - // SetDebuggerInfoBits handles the EncCapable check, JIT optimization disabled state update, - // and EditAndContinue flag logic internally. - loader.SetDebuggerInfoBits(handle, dwBits); + loader.SetDebuggerInfoBits(handle, controlFlags); - // If EnC was requested, check whether the module actually has EditAndContinue set. - // If not, it means the module was not capable of EnC. - if (fEnableEnC != Interop.BOOL.FALSE) + // Check if EnC was requested but the module was not capable. + if (fEnableEnC == Interop.BOOL.TRUE && (loader.GetFlags(handle) & Contracts.ModuleFlags.EditAndContinue) == 0) { - Contracts.ModuleFlags flags = loader.GetFlags(handle); - if ((flags & Contracts.ModuleFlags.EditAndContinue) == 0) - { - hr = CorDbgHResults.CORDBG_S_NOT_ALL_BITS_SET; - } + hr = CorDbgHResults.CORDBG_S_NOT_ALL_BITS_SET; } } catch (System.Exception ex) diff --git a/src/native/managed/cdac/tests/LoaderTests.cs b/src/native/managed/cdac/tests/LoaderTests.cs index 743862aa10d216..e0149988d48068 100644 --- a/src/native/managed/cdac/tests/LoaderTests.cs +++ b/src/native/managed/cdac/tests/LoaderTests.cs @@ -43,7 +43,7 @@ private static ILoader CreateLoaderContract(MockTarget.Architecture arch, Action return target.Contracts.Loader; } - private static (ILoader Contract, TestPlaceholderTarget Target) CreateLoaderContractWithTarget( + internal static (ILoader Contract, TestPlaceholderTarget Target) CreateLoaderContractWithTarget( MockTarget.Architecture arch, Action configure) { @@ -723,14 +723,7 @@ private static (DacDbiImpl DacDbi, TestPlaceholderTarget Target) CreateDacDbiWit MockTarget.Architecture arch, Action configure) { - var targetBuilder = new TestPlaceholderTarget.Builder(arch); - MockLoaderBuilder loader = new(targetBuilder.MemoryBuilder); - - configure(loader, targetBuilder); - - targetBuilder.AddTypes(LoaderTests.CreateContractTypes(loader)); - targetBuilder.AddContract(version: "c1"); - var target = targetBuilder.Build(); + var (_, target) = LoaderTests.CreateLoaderContractWithTarget(arch, configure); var dacDbi = new DacDbiImpl(target, legacyObj: null); return (dacDbi, target); } @@ -740,18 +733,25 @@ private static (DacDbiImpl DacDbi, TestPlaceholderTarget Target) CreateDacDbiWit public void SetCompilerFlags_BothFlagsSet_EncCapable(MockTarget.Architecture arch) { ulong assemblyAddr = 0; + TargetPointer moduleAddr = TargetPointer.Null; + int flagsOffset = 0; - var (dacDbi, _) = CreateDacDbiWithLoader(arch, (loader, builder) => + var (dacDbi, target) = CreateDacDbiWithLoader(arch, (loader, builder) => { var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.Debug); builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); var module = loader.AddModule(flags: IsEncCapable); assemblyAddr = module.Assembly; + moduleAddr = new TargetPointer(module.Address); + flagsOffset = loader.ModuleLayout.GetField(nameof(Data.Module.Flags)).Offset; }); int hr = dacDbi.SetCompilerFlags(assemblyAddr, Interop.BOOL.TRUE, Interop.BOOL.TRUE); Assert.Equal(System.HResults.S_OK, hr); + uint rawFlags = target.Read(moduleAddr + (ulong)flagsOffset); + Assert.NotEqual(0u, rawFlags & DebuggerAllowJitOptsPriv); + Assert.NotEqual(0u, rawFlags & IsEditAndContinue); } [Theory] @@ -782,7 +782,7 @@ public void SetCompilerFlags_BothFlagsUnset(MockTarget.Architecture arch) [Theory] [ClassData(typeof(MockTarget.StdArch))] - public void SetCompilerFlags_EnCRequested_NotCapable_ReturnsNotAllBitsSet(MockTarget.Architecture arch) + public void SetCompilerFlags_EnCRequested_NotCapable(MockTarget.Architecture arch) { ulong assemblyAddr = 0; @@ -790,7 +790,7 @@ public void SetCompilerFlags_EnCRequested_NotCapable_ReturnsNotAllBitsSet(MockTa { var config = loader.AddEEConfig((uint)ClrModifiableAssemblies.None); builder.AddGlobals((Constants.Globals.EEConfig, config.Address)); - var module = loader.AddModule(); // Not EnC-capable + var module = loader.AddModule(); assemblyAddr = module.Assembly; });