From 998b48cd921e0ed217d3e7bd70894f3888f0a593 Mon Sep 17 00:00:00 2001 From: Barbara Rosiak Date: Fri, 22 May 2026 14:12:50 -0700 Subject: [PATCH 1/2] Implement CheckContext for cDAC --- .../debug/daccess/dacdbiimplstackwalk.cpp | 28 ++---- .../CorDbHResults.cs | 1 + .../StackWalk/Context/AMD64Context.cs | 3 + .../StackWalk/Context/ARM64Context.cs | 2 + .../Contracts/StackWalk/Context/ARMContext.cs | 3 + .../StackWalk/Context/ContextHolder.cs | 1 + .../Context/IPlatformAgnosticContext.cs | 1 + .../StackWalk/Context/IPlatformContext.cs | 1 + .../StackWalk/Context/LoongArch64Context.cs | 2 + .../StackWalk/Context/RISCV64Context.cs | 2 + .../Contracts/StackWalk/Context/X86Context.cs | 3 + .../Dbi/DacDbiImpl.cs | 31 +++++- .../managed/cdac/tests/DacDbiImplTests.cs | 95 +++++++++++++++++++ 13 files changed, 150 insertions(+), 23 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp b/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp index 3d4ae42557111e..cf294274ac514a 100644 --- a/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp +++ b/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp @@ -339,9 +339,6 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::UnwindStackWalkFrame(StackWalkHan return hr; } -bool g_fSkipStackCheck = false; -bool g_fSkipStackCheckInit = false; - // Check whether the specified CONTEXT is valid. The only check we perform right now is whether the // SP in the specified CONTEXT is in the stack range of the thread. HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::CheckContext(VMPTR_Thread vmThread, @@ -355,27 +352,14 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::CheckContext(VMPTR_Thread v return S_OK; } - if (!g_fSkipStackCheckInit) - { - g_fSkipStackCheck = (CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_DbgSkipStackCheck) != 0); - g_fSkipStackCheckInit = true; - } + // We don't have the backing store boundaries stored on the thread, but this is just + // a sanity check anyway. + Thread * pThread = vmThread.GetDacPtr(); + PTR_VOID sp = GetSP(reinterpret_cast(pContext)); - // Skip this check if the customer has set the reg key/env var. This is necessary for AutoCad. They - // enable fiber mode by calling the Win32 API ConvertThreadToFiber(), but when a managed debugger is - // attached, they don't actually call into our hosting APIs such as SwitchInLogicalThreadState(). This - // leads to the cached stack range on the Thread object being stale. - if (!g_fSkipStackCheck) + if ((sp < pThread->GetCachedStackLimit()) || (pThread->GetCachedStackBase() <= sp)) { - // We don't have the backing store boundaries stored on the thread, but this is just - // a sanity check anyway. - Thread * pThread = vmThread.GetDacPtr(); - PTR_VOID sp = GetSP(reinterpret_cast(pContext)); - - if ((sp < pThread->GetCachedStackLimit()) || (pThread->GetCachedStackBase() <= sp)) - { - return CORDBG_E_NON_MATCHING_CONTEXT; - } + return CORDBG_E_NON_MATCHING_CONTEXT; } return S_OK; 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 1ebab693d898ed..dd67b07f0e25ba 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/CorDbHResults.cs @@ -12,4 +12,5 @@ public static class CorDbgHResults public const int CORDBG_E_CLASS_NOT_LOADED = unchecked((int)0x80131303); public const int CORDBG_E_TARGET_INCONSISTENT = unchecked((int)0x80131c36); public const int CORDBG_S_NOT_ALL_BITS_SET = unchecked((int)0x00131c13); + public const int CORDBG_E_NON_MATCHING_CONTEXT = unchecked((int)0x80131327); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs index 85b1c98db7d171..5ce1ed5ec6467c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/AMD64Context.cs @@ -31,6 +31,9 @@ public enum ContextFlagsValues : uint } public readonly uint Size => 0x4d0; + + public readonly uint ContextControlFlags => (uint)ContextFlagsValues.CONTEXT_CONTROL; + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs index c74e7ac90042c1..fecff344c0aa10 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64Context.cs @@ -38,6 +38,8 @@ public enum ContextFlagsValues : uint public readonly uint Size => 0x390; + public readonly uint ContextControlFlags => (uint)ContextFlagsValues.CONTEXT_CONTROL; + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs index 1adcd7ab1c860d..1bec70d46057c6 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARMContext.cs @@ -29,6 +29,9 @@ public enum ContextFlagsValues : uint } public readonly uint Size => 0x1a0; + + public readonly uint ContextControlFlags => (uint)ContextFlagsValues.CONTEXT_CONTROL; + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs index 246a4bfd3c733e..41e777840d5253 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ContextHolder.cs @@ -12,6 +12,7 @@ public sealed class ContextHolder : IPlatformAgnosticContext, IEquatable Context.Size; + public uint ContextControlFlags => Context.ContextControlFlags; public uint FullContextFlags => Context.FullContextFlags; public uint AllContextFlags => Context.AllContextFlags; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs index 44dbb33a60b510..f7d210a6de2df2 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformAgnosticContext.cs @@ -8,6 +8,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; public interface IPlatformAgnosticContext { public abstract uint Size { get; } + public abstract uint ContextControlFlags { get; } public abstract uint FullContextFlags { get; } public abstract uint AllContextFlags { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs index 81a42dc45f6dd7..a4debfc3b641d0 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/IPlatformContext.cs @@ -6,6 +6,7 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; public interface IPlatformContext { uint Size { get; } + uint ContextControlFlags { get; } uint FullContextFlags { get; } uint AllContextFlags { get; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs index 48dedde40ef55c..e76d7ba37411ef 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/LoongArch64Context.cs @@ -36,6 +36,8 @@ public enum ContextFlagsValues : uint public readonly uint Size => 0x320; + public readonly uint ContextControlFlags => (uint)ContextFlagsValues.CONTEXT_CONTROL; + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64Context.cs index 0e85faf1459e4b..b8f84d959983a9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/RISCV64Context.cs @@ -36,6 +36,8 @@ public enum ContextFlagsValues : uint public readonly uint Size => 0x220; + public readonly uint ContextControlFlags => (uint)ContextFlagsValues.CONTEXT_CONTROL; + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs index 0a43d535be4616..735e889c798032 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86Context.cs @@ -38,6 +38,9 @@ public enum ContextFlagsValues : uint } public readonly uint Size => 0x2cc; + + public readonly uint ContextControlFlags => (uint)ContextFlagsValues.CONTEXT_CONTROL; + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; 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 fccf75ac7680f3..a79ec324c223c9 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 @@ -1146,7 +1146,36 @@ public int UnwindStackWalkFrame(nuint pSFIHandle, Interop.BOOL* pResult) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.UnwindStackWalkFrame(pSFIHandle, pResult) : HResults.E_NOTIMPL; public int CheckContext(ulong vmThread, nint pContext) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.CheckContext(vmThread, pContext) : HResults.E_NOTIMPL; + { + int hr = HResults.S_OK; + try + { + IPlatformAgnosticContext ctx = IPlatformAgnosticContext.GetContextForPlatform(_target); + ctx.FillFromBuffer(new Span((void*)pContext, (int)ctx.Size)); + + if ((ctx.RawContextFlags & ctx.ContextControlFlags) != 0) + { + _target.Contracts.Thread.GetStackLimitData(new TargetPointer(vmThread), out TargetPointer stackBase, out TargetPointer stackLimit, out _); + TargetPointer sp = ctx.StackPointer; + if (sp < stackLimit || stackBase <= sp) + { + hr = CorDbgHResults.CORDBG_E_NON_MATCHING_CONTEXT; + } + } + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + int hrLocal = _legacy.CheckContext(vmThread, pContext); + Debug.ValidateHResult(hr, hrLocal); + } +#endif + return hr; + } public int GetStackWalkCurrentFrameInfo(nuint pSFIHandle, nint pFrameData, int* pRetVal) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetStackWalkCurrentFrameInfo(pSFIHandle, pFrameData, pRetVal) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/tests/DacDbiImplTests.cs b/src/native/managed/cdac/tests/DacDbiImplTests.cs index 192a485d54f6f7..02f916928a0b03 100644 --- a/src/native/managed/cdac/tests/DacDbiImplTests.cs +++ b/src/native/managed/cdac/tests/DacDbiImplTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; using Microsoft.Diagnostics.DataContractReader.Legacy; using Moq; using Xunit; @@ -462,4 +463,98 @@ public void GetSymbolsBuffer_EmptyStream(MockTarget.Architecture arch) Assert.Equal(0u, targetBuffer.cbSize); Assert.Equal(SymbolFormat.None, symbolFormat); } + + public static IEnumerable TargetArchitectures() + { + string[] architectures = ["x64", "arm64", "arm", "x86", "loongarch64", "riscv64"]; + foreach (object[] stdArch in new MockTarget.StdArch()) + { + foreach (string archName in architectures) + { + yield return [stdArch[0], archName]; + } + } + } + + public static IEnumerable TargetArchitectures_SpRange() + { + foreach (object[] archData in TargetArchitectures()) + { + yield return [archData[0], archData[1], (ulong)0x6000, System.HResults.S_OK]; + yield return [archData[0], archData[1], (ulong)0x2000, CorDbgHResults.CORDBG_E_NON_MATCHING_CONTEXT]; + yield return [archData[0], archData[1], (ulong)0x8000, CorDbgHResults.CORDBG_E_NON_MATCHING_CONTEXT]; + } + } + + [Theory] + [MemberData(nameof(TargetArchitectures_SpRange))] + public void CheckContext_WithControlFlag_ValidatesSpRange(MockTarget.Architecture arch, string targetArch, ulong sp, int expectedHr) + { + const ulong ThreadAddr = 0x1000; + var (dacDbi, target) = CreateCheckContextDacDbi(arch, targetArch, ThreadAddr, stackBase: 0x8000, stackLimit: 0x4000); + + IPlatformAgnosticContext ctx = IPlatformAgnosticContext.GetContextForPlatform(target); + ctx.RawContextFlags = ctx.ContextControlFlags; + ctx.StackPointer = new TargetPointer(sp); + byte[] bytes = ctx.GetBytes(); + + fixed (byte* pCtx = bytes) + { + int hr = dacDbi.CheckContext(ThreadAddr, (nint)pCtx); + Assert.Equal(expectedHr, hr); + } + } + + [Theory] + [MemberData(nameof(TargetArchitectures))] + public void CheckContext_NoControlFlag_SkipsSpCheck(MockTarget.Architecture arch, string targetArch) + { + const ulong ThreadAddr = 0x1000; + var mockThread = new Mock(); + + var target = new TestPlaceholderTarget.Builder(arch) + .AddGlobalStrings((Constants.Globals.Architecture, targetArch)) + .AddContract(version: "c1") + .AddMockContract(mockThread) + .Build(); + var dacDbi = new DacDbiImpl(target, legacyObj: null); + + IPlatformAgnosticContext ctx = IPlatformAgnosticContext.GetContextForPlatform(target); + ctx.RawContextFlags = 0; + ctx.StackPointer = new TargetPointer(0x2000); + byte[] bytes = ctx.GetBytes(); + + fixed (byte* pCtx = bytes) + { + int hr = dacDbi.CheckContext(ThreadAddr, (nint)pCtx); + Assert.Equal(System.HResults.S_OK, hr); + } + + mockThread.Verify( + t => t.GetStackLimitData(It.IsAny(), out It.Ref.IsAny, out It.Ref.IsAny, out It.Ref.IsAny), + Times.Never); + } + + private static (DacDbiImpl DacDbi, Target Target) CreateCheckContextDacDbi(MockTarget.Architecture arch, string targetArch, ulong threadAddr, ulong stackBase, ulong stackLimit) + { + var mockThread = new Mock(); + mockThread + .Setup(t => t.GetStackLimitData(new TargetPointer(threadAddr), out It.Ref.IsAny, out It.Ref.IsAny, out It.Ref.IsAny)) + .Callback(new GetStackLimitDataCallback((TargetPointer _, out TargetPointer sb, out TargetPointer sl, out TargetPointer fa) => + { + sb = new TargetPointer(stackBase); + sl = new TargetPointer(stackLimit); + fa = TargetPointer.Null; + })); + + var target = new TestPlaceholderTarget.Builder(arch) + .AddGlobalStrings((Constants.Globals.Architecture, targetArch)) + .AddContract(version: "c1") + .AddMockContract(mockThread) + .Build(); + + return (new DacDbiImpl(target, legacyObj: null), target); + } + + private delegate void GetStackLimitDataCallback(TargetPointer threadPointer, out TargetPointer stackBase, out TargetPointer stackLimit, out TargetPointer frameAddress); } From f01998519aa2d361551f072f79170ea7b740f8fe Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Fri, 22 May 2026 14:34:52 -0700 Subject: [PATCH 2/2] Update clrconfigvalues.h --- src/coreclr/inc/clrconfigvalues.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/coreclr/inc/clrconfigvalues.h b/src/coreclr/inc/clrconfigvalues.h index c9dd7c485c99c0..3d457c8fd5460b 100644 --- a/src/coreclr/inc/clrconfigvalues.h +++ b/src/coreclr/inc/clrconfigvalues.h @@ -197,7 +197,6 @@ CONFIG_DWORD_INFO(INTERNAL_EnCResolveField, W("EnCResolveField"), 0, "Allows bre CONFIG_DWORD_INFO(INTERNAL_EncResumeInUpdatedFunction, W("EncResumeInUpdatedFunction"), 0, "Allows breaking when execution resumes in a new EnC version of a function") CONFIG_DWORD_INFO(INTERNAL_DbgAssertOnDebuggeeDebugBreak, W("DbgAssertOnDebuggeeDebugBreak"), 0, "If non-zero causes the managed-only debugger to assert on unhandled breakpoints in the debuggee") RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_DbgDontResumeThreadsOnUnhandledException, W("UNSUPPORTED_DbgDontResumeThreadsOnUnhandledException"), 0, "If non-zero, then don't try to unsuspend threads after continuing a 2nd-chance native exception") -RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_DbgSkipStackCheck, W("DbgSkipStackCheck"), 0, "Skip the stack pointer check during stackwalking") #ifdef DACCESS_COMPILE CONFIG_DWORD_INFO(INTERNAL_DumpGeneration_IntentionallyCorruptDataFromTarget, W("IntentionallyCorruptDataFromTarget"), 0, "Intentionally fakes bad data retrieved from target to try and break dump generation.") #endif