From adff60cf87f05797819f8909b5f42721af5dd883 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Mon, 20 Apr 2026 15:01:26 -0700 Subject: [PATCH 1/8] adding getcontext APIs --- docs/design/datacontracts/StackWalk.md | 9 ++ src/coreclr/debug/daccess/dacdbiimpl.cpp | 52 +------- .../debug/daccess/dacdbiimplstackwalk.cpp | 8 +- .../Contracts/IStackWalk.cs | 1 + .../Contracts/IThread.cs | 9 ++ .../StackWalk/Context/AMD64Context.cs | 4 +- .../StackWalk/Context/ARM64Context.cs | 7 +- .../Contracts/StackWalk/Context/ARMContext.cs | 4 +- .../StackWalk/Context/ContextHolder.cs | 3 +- .../Context/IPlatformAgnosticContext.cs | 3 +- .../StackWalk/Context/IPlatformContext.cs | 3 +- .../StackWalk/Context/LoongArch64Context.cs | 7 +- .../StackWalk/Context/RISCV64Context.cs | 4 +- .../Contracts/StackWalk/Context/X86Context.cs | 4 +- .../Contracts/StackWalk/StackWalk_1.cs | 52 +++----- .../Contracts/Thread_1.cs | 35 +++++ .../Dbi/DacDbiImpl.cs | 76 ++++++++++- .../Dbi/IDacDbiInterface.cs | 4 +- .../DacDbi/DacDbiStackWalkDumpTests.cs | 125 ++++++++++++++++++ .../tests/DumpTests/StackWalkDumpTests.cs | 55 ++++++++ 20 files changed, 361 insertions(+), 104 deletions(-) create mode 100644 src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 9d254591ddb0ac..7bdd83db79cee0 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -28,6 +28,10 @@ TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle); // Gets the instruction pointer from the current frame's context. TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle); + +// Compares two raw thread contexts for equality of the control registers (stack pointer, frame pointer, instruction pointer). +// Returns true if SP, FP, and IP are equal. Does not compare any other registers. +bool AreContextsEqual(byte[] context1, byte[] context2); ``` ## Version 1 @@ -399,6 +403,11 @@ TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle) TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle) ``` +`AreContextsEqual` compares two raw thread contexts for equality of the platform-independent control registers: stack pointer (SP), frame pointer (FP), and instruction pointer (IP). Returns `true` if all three match, `false` otherwise. Other register values are not compared. Both byte arrays must be at least as large as the platform context size; otherwise an `ArgumentOutOfRangeException` is thrown. +```csharp +bool AreContextsEqual(byte[] context1, byte[] context2) +``` + ### x86 Specifics The x86 platform has some major differences to other platforms. In general this stems from the platform being older and not having a defined unwinding codes. Instead, to unwind managed frames, we rely on GCInfo associated with JITted code. For the unwind, we do not defer to a 'Windows like' native unwinder, instead the custom unwinder implementation was ported to managed code. diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index 163213084a3e3a..e25e7e01c63147 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -5886,58 +5886,10 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetContext(VMPTR_Thread vmThread, { // If the filter context is NULL, then we use the true context of the thread. pContextBuffer->ContextFlags = DT_CONTEXT_ALL; - HRESULT hr = m_pTarget->GetThreadContext(pThread->GetOSThreadId(), + IfFailThrow(m_pTarget->GetThreadContext(pThread->GetOSThreadId(), pContextBuffer->ContextFlags, sizeof(DT_CONTEXT), - reinterpret_cast(pContextBuffer)); - if (hr == E_NOTIMPL) - { - // GetThreadContext is not implemented on this data target. - // That's why we have to make do with context we can obtain from Frames explicitly stored in Thread object. - // It suffices for managed debugging stackwalk. - REGDISPLAY tmpRd = {}; - T_CONTEXT tmpContext = {}; - FillRegDisplay(&tmpRd, &tmpContext); - - // Going through thread Frames and looking for first (deepest one) one that - // that has context available for stackwalking (SP and PC) - // For example: RedirectedThreadFrame, InlinedCallFrame, DynamicHelperFrame - Frame *frame = pThread->GetFrame(); - - while (frame != NULL && frame != FRAME_TOP) - { -#ifdef FEATURE_INTERPRETER - if (frame->GetFrameIdentifier() == FrameIdentifier::InterpreterFrame) - { - PTR_InterpreterFrame pInterpreterFrame = dac_cast(frame); - pInterpreterFrame->SetContextToInterpMethodContextFrame(&tmpContext); - CopyMemory(pContextBuffer, &tmpContext, sizeof(*pContextBuffer)); - return S_OK; - } -#endif // FEATURE_INTERPRETER - frame->UpdateRegDisplay(&tmpRd); - if (GetRegdisplaySP(&tmpRd) != 0 && GetControlPC(&tmpRd) != 0) - { - UpdateContextFromRegDisp(&tmpRd, &tmpContext); - CopyMemory(pContextBuffer, &tmpContext, sizeof(*pContextBuffer)); - pContextBuffer->ContextFlags = DT_CONTEXT_CONTROL - #if defined(TARGET_AMD64) || defined(TARGET_ARM) - | DT_CONTEXT_INTEGER // DT_CONTEXT_INTEGER is needed to include the frame register on ARM32 and AMD64 architectures - // DT_CONTEXT_CONTROL already includes the frame register for X86 and ARM64 architectures - #endif - ; - return S_OK; - } - frame = frame->Next(); - } - - // It looks like this thread is not running managed code. - ZeroMemory(pContextBuffer, sizeof(*pContextBuffer)); - } - else - { - IfFailThrow(hr); - } + reinterpret_cast(pContextBuffer))); } else { diff --git a/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp b/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp index 659c9f08ca2ea9..5959c39df7ae15 100644 --- a/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp +++ b/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp @@ -734,7 +734,6 @@ FramePointer DacDbiInterfaceImpl::GetFramePointerWorker(StackFrameIterator * pIt } // Return TRUE if the specified CONTEXT is the CONTEXT of the leaf frame. -// @dbgtodo filter CONTEXT - Currently we check for the filter CONTEXT first. HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::IsLeafFrame(VMPTR_Thread vmThread, const DT_CONTEXT * pContext, OUT BOOL * pResult) { DD_ENTER_MAY_THROW; @@ -744,7 +743,12 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::IsLeafFrame(VMPTR_Thread vmThread { DT_CONTEXT ctxLeaf; - IfFailThrow(GetContext(vmThread, &ctxLeaf)); + Thread * pThread = vmThread.GetDacPtr(); + ctxLeaf.ContextFlags = DT_CONTEXT_ALL; + IfFailThrow(m_pTarget->GetThreadContext(pThread->GetOSThreadId(), + ctxLeaf.ContextFlags, + sizeof(DT_CONTEXT), + reinterpret_cast(&ctxLeaf))); // Call a platform-specific helper to compare the two contexts. *pResult = CompareControlRegisters(pContext, &ctxLeaf); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs index 137cb75f45eafb..a2f2d73a8de12c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs @@ -33,6 +33,7 @@ public interface IStackWalk : IContract TargetPointer GetMethodDescPtr(TargetPointer framePtr) => throw new NotImplementedException(); TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); + bool AreContextsEqual(byte[] context1, byte[] context2) => throw new NotImplementedException(); } public struct StackWalk : IStackWalk diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs index 8d30a898632f76..1f9a73a9cc4916 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs @@ -5,6 +5,14 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts; +[Flags] +public enum ThreadContextSource +{ + None = 0, + Debugger = 1, + Profiler = 2, +} + public record struct ThreadStoreData( int ThreadCount, TargetPointer FirstThread, @@ -60,6 +68,7 @@ void GetStackLimitData(TargetPointer threadPointer, out TargetPointer stackBase, TargetPointer GetThreadLocalStaticBase(TargetPointer threadPointer, TargetPointer tlsIndexPtr) => throw new NotImplementedException(); TargetPointer GetCurrentExceptionHandle(TargetPointer threadPointer) => throw new NotImplementedException(); byte[] GetWatsonBuckets(TargetPointer threadPointer) => throw new NotImplementedException(); + byte[] GetContext(TargetPointer threadPointer, ThreadContextSource contextSource, uint contextFlags) => throw new NotImplementedException(); } public readonly struct Thread : IThread 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 8ab2e2468da526..2ef6e567420edc 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,7 +31,9 @@ public enum ContextFlagsValues : uint } public readonly uint Size => 0x4d0; - public readonly uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; public readonly int StackPointerRegister => 4; 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 f88f9a9ecfb62b..84f62284e89853 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,10 +38,9 @@ public enum ContextFlagsValues : uint public readonly uint Size => 0x390; - public readonly uint DefaultContextFlags => (uint)(ContextFlagsValues.CONTEXT_CONTROL | - ContextFlagsValues.CONTEXT_INTEGER | - ContextFlagsValues.CONTEXT_FLOATING_POINT | - ContextFlagsValues.CONTEXT_DEBUG_REGISTERS); + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; public readonly int StackPointerRegister => 31; 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 abbcd8805257a8..8fedf1cdd5a83e 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,7 +29,9 @@ public enum ContextFlagsValues : uint } public readonly uint Size => 0x1a0; - public readonly uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; public readonly int StackPointerRegister => 13; 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 c2bffd2ad361b8..c952ea17a50829 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,7 +12,8 @@ public sealed class ContextHolder : IPlatformAgnosticContext, IEquatable Context.Size; - public uint DefaultContextFlags => Context.DefaultContextFlags; + public uint FullContextFlags => Context.FullContextFlags; + public uint AllContextFlags => Context.AllContextFlags; public int StackPointerRegister => Context.StackPointerRegister; 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 c95012b12b74a8..6d42bba8fff30d 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,7 +8,8 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; public interface IPlatformAgnosticContext { public abstract uint Size { get; } - public abstract uint DefaultContextFlags { get; } + public abstract uint FullContextFlags { get; } + public abstract uint AllContextFlags { get; } public int StackPointerRegister { 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 df26023ee54a4f..01ef3f60aed42a 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,7 +6,8 @@ namespace Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; public interface IPlatformContext { uint Size { get; } - uint DefaultContextFlags { get; } + uint FullContextFlags { get; } + uint AllContextFlags { get; } int StackPointerRegister { 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 6acf124a0d11c1..5fbe851e1b7105 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,10 +36,9 @@ public enum ContextFlagsValues : uint public readonly uint Size => 0x320; - public readonly uint DefaultContextFlags => (uint)(ContextFlagsValues.CONTEXT_CONTROL | - ContextFlagsValues.CONTEXT_INTEGER | - ContextFlagsValues.CONTEXT_FLOATING_POINT | - ContextFlagsValues.CONTEXT_DEBUG_REGISTERS); + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; public readonly int StackPointerRegister => 3; 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 d401d90d89cda3..e4afafa0ecfece 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,7 +36,9 @@ public enum ContextFlagsValues : uint public readonly uint Size => 0x220; - public readonly uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; public readonly int StackPointerRegister => 2; 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 505da9a8d52889..3a32e77bb76df8 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,7 +38,9 @@ public enum ContextFlagsValues : uint } public readonly uint Size => 0x2cc; - public readonly uint DefaultContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; + public readonly uint FullContextFlags => (uint)ContextFlagsValues.CONTEXT_FULL; + + public readonly uint AllContextFlags => (uint)ContextFlagsValues.CONTEXT_ALL; public readonly int StackPointerRegister => 4; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs index e7a3222806464b..cfa62d2277a976 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -686,6 +686,22 @@ TargetPointer IStackWalk.GetInstructionPointer(IStackDataFrameHandle stackDataFr return handle.Context.InstructionPointer; } + bool IStackWalk.AreContextsEqual(byte[] context1, byte[] context2) + { + IPlatformAgnosticContext ctx1 = IPlatformAgnosticContext.GetContextForPlatform(_target); + IPlatformAgnosticContext ctx2 = IPlatformAgnosticContext.GetContextForPlatform(_target); + + ArgumentOutOfRangeException.ThrowIfLessThan((uint)context1.Length, ctx1.Size, nameof(context1)); + ArgumentOutOfRangeException.ThrowIfLessThan((uint)context2.Length, ctx2.Size, nameof(context2)); + + ctx1.FillFromBuffer(context1); + ctx2.FillFromBuffer(context2); + + return ctx1.StackPointer == ctx2.StackPointer + && ctx1.FramePointer == ctx2.FramePointer + && ctx1.InstructionPointer == ctx2.InstructionPointer; + } + string IStackWalk.GetFrameName(TargetPointer frameIdentifier) => FrameIterator.GetFrameName(_target, frameIdentifier); @@ -760,37 +776,11 @@ private bool IsManaged(TargetPointer ip, [NotNullWhen(true)] out CodeBlockHandle private void FillContextFromThread(IPlatformAgnosticContext context, ThreadData threadData) { - byte[] bytes = new byte[context.Size]; - Span buffer = new Span(bytes); - - // Match the native DacStackReferenceWalker behavior: if the thread has a - // FilterContext or ProfilerFilterContext set, use that instead of calling - // GetThreadContext. During debugger breaks, GC stress redirection, or - // profiler stack walks, these contexts hold the correct managed frame state. - Data.Thread thread = _target.ProcessedData.GetOrAdd(threadData.ThreadAddress); - - TargetPointer filterContext = thread.DebuggerFilterContext; - if (filterContext == TargetPointer.Null) - filterContext = thread.ProfilerFilterContext; - - if (filterContext != TargetPointer.Null) - { - _target.ReadBuffer(filterContext.Value, buffer); - context.FillFromBuffer(buffer); - return; - } - - // The underlying ICLRDataTarget.GetThreadContext has some variance depending on the host. - // SOS's managed implementation sets the ContextFlags to platform specific values defined in ThreadService.cs (diagnostics repo) - // SOS's native implementation keeps the ContextFlags passed into this function. - // To match the DAC behavior, the DefaultContextFlags are what the DAC passes in in DacGetThreadContext. - // In most implementations, this will be overridden by the host, but in some cases, it may not be. - if (!_target.TryGetThreadContext(threadData.OSId.Value, context.DefaultContextFlags, buffer)) - { - throw new InvalidOperationException($"GetThreadContext failed for thread {threadData.OSId.Value}"); - } - - context.FillFromBuffer(buffer); + byte[] bytes = _target.Contracts.Thread.GetContext( + threadData.ThreadAddress, + ThreadContextSource.Debugger | ThreadContextSource.Profiler, + context.FullContextFlags); + context.FillFromBuffer(bytes); } private static StackDataFrameHandle AssertCorrectHandle(IStackDataFrameHandle stackDataFrameHandle) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index b811041fb1a856..1bf2496f0ef36f 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; namespace Microsoft.Diagnostics.DataContractReader.Contracts; @@ -284,4 +285,38 @@ byte[] IThread.GetWatsonBuckets(TargetPointer threadPointer) _target.ReadBuffer(readFrom, rval); return rval; } + + byte[] IThread.GetContext(TargetPointer threadPointer, ThreadContextSource contextSource, uint contextFlags) + { + IPlatformAgnosticContext context = IPlatformAgnosticContext.GetContextForPlatform(_target); + byte[] bytes = new byte[context.Size]; + Span buffer = new Span(bytes); + + Data.Thread thread = _target.ProcessedData.GetOrAdd(threadPointer); + + TargetPointer filterContext = TargetPointer.Null; + + if (contextSource.HasFlag(ThreadContextSource.Debugger)) + filterContext = thread.DebuggerFilterContext; + + if (filterContext == TargetPointer.Null && contextSource.HasFlag(ThreadContextSource.Profiler)) + filterContext = thread.ProfilerFilterContext; + + if (filterContext != TargetPointer.Null) + { + _target.ReadBuffer(filterContext.Value, buffer); + context.FillFromBuffer(buffer); + + return context.GetBytes(); + } + + if (!_target.TryGetThreadContext(thread.OSId.Value, contextFlags, buffer)) + { + throw new InvalidOperationException($"GetThreadContext failed for thread {thread.OSId.Value}"); + } + + context.FillFromBuffer(buffer); + + return context.GetBytes(); + } } 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..71373801687666 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 @@ -8,6 +8,7 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; namespace Microsoft.Diagnostics.DataContractReader.Legacy; @@ -666,11 +667,78 @@ public int GetStackParameterSize(ulong controlPC, uint* pRetVal) public int GetFramePointer(nuint pSFIHandle, ulong* pRetVal) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetFramePointer(pSFIHandle, pRetVal) : HResults.E_NOTIMPL; - public int IsLeafFrame(ulong vmThread, nint pContext, Interop.BOOL* pResult) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.IsLeafFrame(vmThread, pContext, pResult) : HResults.E_NOTIMPL; + public int IsLeafFrame(ulong vmThread, byte* pContext, Interop.BOOL* pResult) + { + *pResult = Interop.BOOL.FALSE; + int hr = HResults.S_OK; + try + { + IStackWalk sw = _target.Contracts.StackWalk; + + uint allFlags = IPlatformAgnosticContext.GetContextForPlatform(_target).AllContextFlags; + byte[] leafContext = _target.Contracts.Thread.GetContext(new TargetPointer(vmThread), ThreadContextSource.None, allFlags); + + // Read the given context from the native buffer. + byte[] givenContext = new byte[leafContext.Length]; + new Span(pContext, givenContext.Length).CopyTo(givenContext); + + *pResult = sw.AreContextsEqual(givenContext, leafContext) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + Interop.BOOL resultLocal; + int hrLocal = _legacy.IsLeafFrame(vmThread, pContext, &resultLocal); + Debug.ValidateHResult(hr, hrLocal); + if (hr == HResults.S_OK) + Debug.Assert(*pResult == resultLocal, $"cDAC: {*pResult}, DAC: {resultLocal}"); + } +#endif + return hr; + } - public int GetContext(ulong vmThread, nint pContextBuffer) - => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.GetContext(vmThread, pContextBuffer) : HResults.E_NOTIMPL; + public int GetContext(ulong vmThread, byte* pContextBuffer) + { + int hr = HResults.S_OK; + try + { + uint allFlags = IPlatformAgnosticContext.GetContextForPlatform(_target).AllContextFlags; + byte[] context = _target.Contracts.Thread.GetContext(new TargetPointer(vmThread), ThreadContextSource.Debugger, allFlags); + + context.AsSpan().CopyTo(new Span(pContextBuffer, context.Length)); + } + catch (System.Exception ex) + { + hr = ex.HResult; + } +#if DEBUG + if (_legacy is not null) + { + uint contextSize = IPlatformAgnosticContext.GetContextForPlatform(_target).Size; + byte[] localContextBuf = new byte[contextSize]; + fixed (byte* pLocal = localContextBuf) + { + int hrLocal = _legacy.GetContext(vmThread, pLocal); + Debug.ValidateHResult(hr, hrLocal); + + if (hr == HResults.S_OK) + { + IPlatformAgnosticContext contextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target); + IPlatformAgnosticContext localContextStruct = IPlatformAgnosticContext.GetContextForPlatform(_target); + contextStruct.FillFromBuffer(new Span(pContextBuffer, (int)contextSize)); + localContextStruct.FillFromBuffer(localContextBuf); + + Debug.Assert(contextStruct.Equals(localContextStruct)); + } + } + } +#endif + return hr; + } public int ConvertContextToDebuggerRegDisplay(nint pInContext, nint pOutDRD, Interop.BOOL fActive) => LegacyFallbackHelper.CanFallback() && _legacy is not null ? _legacy.ConvertContextToDebuggerRegDisplay(pInContext, pOutDRD, fActive) : HResults.E_NOTIMPL; diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs index 3accd937a1f35c..c87c5b172f31d4 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs @@ -316,10 +316,10 @@ public unsafe partial interface IDacDbiInterface int GetFramePointer(nuint pSFIHandle, ulong* pRetVal); [PreserveSig] - int IsLeafFrame(ulong vmThread, nint pContext, Interop.BOOL* pResult); + int IsLeafFrame(ulong vmThread, byte* pContext, Interop.BOOL* pResult); [PreserveSig] - int GetContext(ulong vmThread, nint pContextBuffer); + int GetContext(ulong vmThread, byte* pContextBuffer); [PreserveSig] int ConvertContextToDebuggerRegDisplay(nint pInContext, nint pOutDRD, Interop.BOOL fActive); diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs new file mode 100644 index 00000000000000..6360be41be1a34 --- /dev/null +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs @@ -0,0 +1,125 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using Microsoft.Diagnostics.DataContractReader.Contracts; +using Microsoft.Diagnostics.DataContractReader.Contracts.StackWalkHelpers; +using Microsoft.Diagnostics.DataContractReader.Legacy; +using Xunit; + +namespace Microsoft.Diagnostics.DataContractReader.DumpTests; + +/// +/// Dump-based integration tests for DacDbiImpl stack walk methods (IsLeafFrame, GetContext). +/// Uses the StackWalk debuggee (full dump). +/// +public class DacDbiStackWalkDumpTests : DumpTestBase +{ + protected override string DebuggeeName => "StackWalk"; + protected override string DumpType => "full"; + + private DacDbiImpl CreateDacDbi() => new DacDbiImpl(Target, legacyObj: null); + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public unsafe void GetContext_Succeeds_ForCrashingThread(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); + uint contextSize = IPlatformAgnosticContext.GetContextForPlatform(Target).Size; + byte[] contextBuffer = new byte[contextSize]; + + fixed (byte* pContext = contextBuffer) + { + int hr = dbi.GetContext(crashingThread.ThreadAddress, pContext); + Assert.Equal(System.HResults.S_OK, hr); + } + + IPlatformAgnosticContext ctx = IPlatformAgnosticContext.GetContextForPlatform(Target); + ctx.FillFromBuffer(contextBuffer); + Assert.NotEqual(TargetPointer.Null, ctx.InstructionPointer); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public unsafe void GetContext_MatchesContractGetContext(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); + uint contextSize = IPlatformAgnosticContext.GetContextForPlatform(Target).Size; + + byte[] dbiContextBuffer = new byte[contextSize]; + fixed (byte* pContext = dbiContextBuffer) + { + int hr = dbi.GetContext(crashingThread.ThreadAddress, pContext); + Assert.Equal(System.HResults.S_OK, hr); + } + + uint allFlags = IPlatformAgnosticContext.GetContextForPlatform(Target).AllContextFlags; + byte[] contractContext = Target.Contracts.Thread.GetContext(crashingThread.ThreadAddress, ThreadContextSource.Debugger, allFlags); + + IPlatformAgnosticContext dbiCtx = IPlatformAgnosticContext.GetContextForPlatform(Target); + IPlatformAgnosticContext contractCtx = IPlatformAgnosticContext.GetContextForPlatform(Target); + dbiCtx.FillFromBuffer(dbiContextBuffer); + contractCtx.FillFromBuffer(contractContext); + + Assert.Equal(contractCtx.InstructionPointer, dbiCtx.InstructionPointer); + Assert.Equal(contractCtx.StackPointer, dbiCtx.StackPointer); + Assert.Equal(contractCtx.FramePointer, dbiCtx.FramePointer); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public unsafe void IsLeafFrame_TrueForLeafContext(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); + + uint allFlags = IPlatformAgnosticContext.GetContextForPlatform(Target).AllContextFlags; + byte[] leafContext = Target.Contracts.Thread.GetContext(crashingThread.ThreadAddress, ThreadContextSource.None, allFlags); + + Interop.BOOL result; + fixed (byte* pContext = leafContext) + { + int hr = dbi.IsLeafFrame(crashingThread.ThreadAddress, pContext, &result); + Assert.Equal(System.HResults.S_OK, hr); + } + + Assert.Equal(Interop.BOOL.TRUE, result); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public unsafe void IsLeafFrame_FalseForNonLeafContext(TestConfiguration config) + { + InitializeDumpTest(config); + DacDbiImpl dbi = CreateDacDbi(); + + ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); + + IStackWalk sw = Target.Contracts.StackWalk; + var frames = sw.CreateStackWalk(crashingThread).ToList(); + Assert.True(frames.Count >= 2, "Expected at least 2 frames to test non-leaf"); + + byte[] nonLeafContext = sw.GetRawContext(frames[1]); + + Interop.BOOL result; + fixed (byte* pContext = nonLeafContext) + { + int hr = dbi.IsLeafFrame(crashingThread.ThreadAddress, pContext, &result); + Assert.Equal(System.HResults.S_OK, hr); + } + + Assert.Equal(Interop.BOOL.FALSE, result); + } +} diff --git a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs index 65c0b9597ffa8f..3908c90e327692 100644 --- a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs @@ -332,4 +332,59 @@ public unsafe void VarargPInvoke_GetCodeHeaderDataWithInvalidPrecodeAddress(Test Assert.Fail("Expected to find a frame with a valid entry point"); } + + // ========== GetContext and AreContextsEqual API tests ========== + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public void GetContext_ReturnsNonEmptyContext(TestConfiguration config) + { + InitializeDumpTest(config); + + ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); + uint allFlags = Contracts.StackWalkHelpers.IPlatformAgnosticContext.GetContextForPlatform(Target).AllContextFlags; + byte[] context = Target.Contracts.Thread.GetContext(crashingThread.ThreadAddress, ThreadContextSource.None, allFlags); + + Assert.NotNull(context); + Assert.True(context.Length > 0, "Expected non-empty context"); + + var ctx = Contracts.StackWalkHelpers.IPlatformAgnosticContext.GetContextForPlatform(Target); + ctx.FillFromBuffer(context); + Assert.NotEqual(TargetPointer.Null, ctx.InstructionPointer); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public void AreContextsEqual_TrueForSameContext(TestConfiguration config) + { + InitializeDumpTest(config); + IStackWalk stackWalk = Target.Contracts.StackWalk; + + ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); + uint allFlags = Contracts.StackWalkHelpers.IPlatformAgnosticContext.GetContextForPlatform(Target).AllContextFlags; + byte[] context = Target.Contracts.Thread.GetContext(crashingThread.ThreadAddress, ThreadContextSource.None, allFlags); + + Assert.True(stackWalk.AreContextsEqual(context, context)); + } + + [ConditionalTheory] + [MemberData(nameof(TestConfigurations))] + [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] + public void AreContextsEqual_FalseForDifferentFrames(TestConfiguration config) + { + InitializeDumpTest(config); + IStackWalk stackWalk = Target.Contracts.StackWalk; + + ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); + + List frames = stackWalk.CreateStackWalk(crashingThread).ToList(); + Assert.True(frames.Count >= 2, "Expected at least 2 frames"); + + byte[] context1 = stackWalk.GetRawContext(frames[0]); + byte[] context2 = stackWalk.GetRawContext(frames[1]); + + Assert.False(stackWalk.AreContextsEqual(context1, context2)); + } } From d36ff82f0464dc30bc1a656f288379b986789662 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Mon, 20 Apr 2026 18:14:40 -0700 Subject: [PATCH 2/8] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/design/datacontracts/StackWalk.md | 5 +++-- .../Contracts/StackWalk/StackWalk_1.cs | 13 ++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 7bdd83db79cee0..55bc89bd79f4ac 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -29,8 +29,9 @@ TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle); // Gets the instruction pointer from the current frame's context. TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle); -// Compares two raw thread contexts for equality of the control registers (stack pointer, frame pointer, instruction pointer). -// Returns true if SP, FP, and IP are equal. Does not compare any other registers. +// Compares two raw thread contexts for equality of the architecture's control registers. +// Typically this means stack pointer, frame pointer, and instruction pointer; however, on ARM32 +// the native DAC compares only SP and IP/PC and does not require frame pointer equality. bool AreContextsEqual(byte[] context1, byte[] context2); ``` diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs index cfa62d2277a976..3250edfe279a42 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -697,9 +697,16 @@ bool IStackWalk.AreContextsEqual(byte[] context1, byte[] context2) ctx1.FillFromBuffer(context1); ctx2.FillFromBuffer(context2); - return ctx1.StackPointer == ctx2.StackPointer - && ctx1.FramePointer == ctx2.FramePointer - && ctx1.InstructionPointer == ctx2.InstructionPointer; + return _target.Architecture switch + { + RuntimeInfoArchitecture.Arm => + ctx1.StackPointer == ctx2.StackPointer + && ctx1.InstructionPointer == ctx2.InstructionPointer, + _ => + ctx1.StackPointer == ctx2.StackPointer + && ctx1.FramePointer == ctx2.FramePointer + && ctx1.InstructionPointer == ctx2.InstructionPointer, + }; } string IStackWalk.GetFrameName(TargetPointer frameIdentifier) From b830e958064356c59d5349727b71e9f46624cf86 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 21 Apr 2026 09:24:41 -0700 Subject: [PATCH 3/8] code review --- docs/design/datacontracts/StackWalk.md | 10 ------ .../Contracts/IStackWalk.cs | 1 - .../Contracts/StackWalk/StackWalk_1.cs | 23 ------------ .../Dbi/DacDbiImpl.cs | 12 ++++--- .../DacDbi/DacDbiStackWalkDumpTests.cs | 20 +++++++++-- .../tests/DumpTests/StackWalkDumpTests.cs | 36 +------------------ 6 files changed, 26 insertions(+), 76 deletions(-) diff --git a/docs/design/datacontracts/StackWalk.md b/docs/design/datacontracts/StackWalk.md index 55bc89bd79f4ac..9d254591ddb0ac 100644 --- a/docs/design/datacontracts/StackWalk.md +++ b/docs/design/datacontracts/StackWalk.md @@ -28,11 +28,6 @@ TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle); // Gets the instruction pointer from the current frame's context. TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle); - -// Compares two raw thread contexts for equality of the architecture's control registers. -// Typically this means stack pointer, frame pointer, and instruction pointer; however, on ARM32 -// the native DAC compares only SP and IP/PC and does not require frame pointer equality. -bool AreContextsEqual(byte[] context1, byte[] context2); ``` ## Version 1 @@ -404,11 +399,6 @@ TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle) TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle) ``` -`AreContextsEqual` compares two raw thread contexts for equality of the platform-independent control registers: stack pointer (SP), frame pointer (FP), and instruction pointer (IP). Returns `true` if all three match, `false` otherwise. Other register values are not compared. Both byte arrays must be at least as large as the platform context size; otherwise an `ArgumentOutOfRangeException` is thrown. -```csharp -bool AreContextsEqual(byte[] context1, byte[] context2) -``` - ### x86 Specifics The x86 platform has some major differences to other platforms. In general this stems from the platform being older and not having a defined unwinding codes. Instead, to unwind managed frames, we rely on GCInfo associated with JITted code. For the unwind, we do not defer to a 'Windows like' native unwinder, instead the custom unwinder implementation was ported to managed code. diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs index a2f2d73a8de12c..137cb75f45eafb 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs @@ -33,7 +33,6 @@ public interface IStackWalk : IContract TargetPointer GetMethodDescPtr(TargetPointer framePtr) => throw new NotImplementedException(); TargetPointer GetMethodDescPtr(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); TargetPointer GetInstructionPointer(IStackDataFrameHandle stackDataFrameHandle) => throw new NotImplementedException(); - bool AreContextsEqual(byte[] context1, byte[] context2) => throw new NotImplementedException(); } public struct StackWalk : IStackWalk diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs index 3250edfe279a42..44a8171c227cb7 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -686,29 +686,6 @@ TargetPointer IStackWalk.GetInstructionPointer(IStackDataFrameHandle stackDataFr return handle.Context.InstructionPointer; } - bool IStackWalk.AreContextsEqual(byte[] context1, byte[] context2) - { - IPlatformAgnosticContext ctx1 = IPlatformAgnosticContext.GetContextForPlatform(_target); - IPlatformAgnosticContext ctx2 = IPlatformAgnosticContext.GetContextForPlatform(_target); - - ArgumentOutOfRangeException.ThrowIfLessThan((uint)context1.Length, ctx1.Size, nameof(context1)); - ArgumentOutOfRangeException.ThrowIfLessThan((uint)context2.Length, ctx2.Size, nameof(context2)); - - ctx1.FillFromBuffer(context1); - ctx2.FillFromBuffer(context2); - - return _target.Architecture switch - { - RuntimeInfoArchitecture.Arm => - ctx1.StackPointer == ctx2.StackPointer - && ctx1.InstructionPointer == ctx2.InstructionPointer, - _ => - ctx1.StackPointer == ctx2.StackPointer - && ctx1.FramePointer == ctx2.FramePointer - && ctx1.InstructionPointer == ctx2.InstructionPointer, - }; - } - string IStackWalk.GetFrameName(TargetPointer frameIdentifier) => FrameIterator.GetFrameName(_target, frameIdentifier); 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 71373801687666..462c80484e78ae 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 @@ -673,16 +673,20 @@ public int IsLeafFrame(ulong vmThread, byte* pContext, Interop.BOOL* pResult) int hr = HResults.S_OK; try { - IStackWalk sw = _target.Contracts.StackWalk; - - uint allFlags = IPlatformAgnosticContext.GetContextForPlatform(_target).AllContextFlags; + IPlatformAgnosticContext leafCtx = IPlatformAgnosticContext.GetContextForPlatform(_target); + uint allFlags = leafCtx.AllContextFlags; byte[] leafContext = _target.Contracts.Thread.GetContext(new TargetPointer(vmThread), ThreadContextSource.None, allFlags); + leafCtx.FillFromBuffer(leafContext); // Read the given context from the native buffer. + IPlatformAgnosticContext givenCtx = IPlatformAgnosticContext.GetContextForPlatform(_target); byte[] givenContext = new byte[leafContext.Length]; new Span(pContext, givenContext.Length).CopyTo(givenContext); + givenCtx.FillFromBuffer(givenContext); - *pResult = sw.AreContextsEqual(givenContext, leafContext) ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; + *pResult = givenCtx.StackPointer == leafCtx.StackPointer + && givenCtx.InstructionPointer == leafCtx.InstructionPointer + ? Interop.BOOL.TRUE : Interop.BOOL.FALSE; } catch (System.Exception ex) { diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs index 6360be41be1a34..d804dd87b71628 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs @@ -107,11 +107,25 @@ public unsafe void IsLeafFrame_FalseForNonLeafContext(TestConfiguration config) ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); + uint allFlags = IPlatformAgnosticContext.GetContextForPlatform(Target).AllContextFlags; + byte[] leafContext = Target.Contracts.Thread.GetContext(crashingThread.ThreadAddress, ThreadContextSource.None, allFlags); + IPlatformAgnosticContext leafCtx = IPlatformAgnosticContext.GetContextForPlatform(Target); + leafCtx.FillFromBuffer(leafContext); + IStackWalk sw = Target.Contracts.StackWalk; - var frames = sw.CreateStackWalk(crashingThread).ToList(); - Assert.True(frames.Count >= 2, "Expected at least 2 frames to test non-leaf"); - byte[] nonLeafContext = sw.GetRawContext(frames[1]); + // Find a frame whose SP+IP differs from the leaf context + byte[]? nonLeafContext = sw.CreateStackWalk(crashingThread) + .Select(sw.GetRawContext) + .FirstOrDefault(ctx => + { + IPlatformAgnosticContext frameCtx = IPlatformAgnosticContext.GetContextForPlatform(Target); + frameCtx.FillFromBuffer(ctx); + return frameCtx.StackPointer != leafCtx.StackPointer + || frameCtx.InstructionPointer != leafCtx.InstructionPointer; + }); + + Assert.NotNull(nonLeafContext); Interop.BOOL result; fixed (byte* pContext = nonLeafContext) diff --git a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs index 3908c90e327692..d5a867c06f050f 100644 --- a/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs @@ -333,7 +333,7 @@ public unsafe void VarargPInvoke_GetCodeHeaderDataWithInvalidPrecodeAddress(Test Assert.Fail("Expected to find a frame with a valid entry point"); } - // ========== GetContext and AreContextsEqual API tests ========== + // ========== GetContext API tests ========== [ConditionalTheory] [MemberData(nameof(TestConfigurations))] @@ -353,38 +353,4 @@ public void GetContext_ReturnsNonEmptyContext(TestConfiguration config) ctx.FillFromBuffer(context); Assert.NotEqual(TargetPointer.Null, ctx.InstructionPointer); } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] - public void AreContextsEqual_TrueForSameContext(TestConfiguration config) - { - InitializeDumpTest(config); - IStackWalk stackWalk = Target.Contracts.StackWalk; - - ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); - uint allFlags = Contracts.StackWalkHelpers.IPlatformAgnosticContext.GetContextForPlatform(Target).AllContextFlags; - byte[] context = Target.Contracts.Thread.GetContext(crashingThread.ThreadAddress, ThreadContextSource.None, allFlags); - - Assert.True(stackWalk.AreContextsEqual(context, context)); - } - - [ConditionalTheory] - [MemberData(nameof(TestConfigurations))] - [SkipOnVersion("net10.0", "InlinedCallFrame.Datum was added after net10.0")] - public void AreContextsEqual_FalseForDifferentFrames(TestConfiguration config) - { - InitializeDumpTest(config); - IStackWalk stackWalk = Target.Contracts.StackWalk; - - ThreadData crashingThread = DumpTestHelpers.FindFailFastThread(Target); - - List frames = stackWalk.CreateStackWalk(crashingThread).ToList(); - Assert.True(frames.Count >= 2, "Expected at least 2 frames"); - - byte[] context1 = stackWalk.GetRawContext(frames[0]); - byte[] context2 = stackWalk.GetRawContext(frames[1]); - - Assert.False(stackWalk.AreContextsEqual(context1, context2)); - } } From 7b678c184893000cd39e123fcf638e64854c85ea Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Tue, 21 Apr 2026 09:37:43 -0700 Subject: [PATCH 4/8] Update src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Contracts/StackWalk/StackWalk_1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs index 44a8171c227cb7..fc50b2bca85dc9 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -763,7 +763,7 @@ private void FillContextFromThread(IPlatformAgnosticContext context, ThreadData byte[] bytes = _target.Contracts.Thread.GetContext( threadData.ThreadAddress, ThreadContextSource.Debugger | ThreadContextSource.Profiler, - context.FullContextFlags); + context.AllContextFlags); context.FillFromBuffer(bytes); } From 7a3c16a72e3f0752c85109db330dfebf14368c70 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Tue, 21 Apr 2026 11:56:01 -0700 Subject: [PATCH 5/8] code review --- src/coreclr/debug/inc/amd64/primitives.h | 4 +--- src/coreclr/debug/inc/arm/primitives.h | 3 --- src/coreclr/debug/inc/arm64/primitives.h | 5 +---- src/coreclr/debug/inc/i386/primitives.h | 4 +--- src/coreclr/debug/inc/loongarch64/primitives.h | 5 +---- src/coreclr/debug/inc/riscv64/primitives.h | 5 +---- .../Contracts/Thread_1.cs | 8 ++------ .../Dbi/DacDbiImpl.cs | 4 +--- .../tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs | 1 - 9 files changed, 8 insertions(+), 31 deletions(-) diff --git a/src/coreclr/debug/inc/amd64/primitives.h b/src/coreclr/debug/inc/amd64/primitives.h index 83afbbbba7d327..678badee1136a9 100644 --- a/src/coreclr/debug/inc/amd64/primitives.h +++ b/src/coreclr/debug/inc/amd64/primitives.h @@ -147,14 +147,12 @@ inline void CORDbgSetSP(DT_CONTEXT *context, LPVOID rsp) #define CORDbgSetFP(context, rbp) #define CORDbgGetFP(context) 0 -// compare the RIP, RSP, and RBP inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * pCtx2) { LIMITED_METHOD_DAC_CONTRACT; if ((pCtx1->Rip == pCtx2->Rip) && - (pCtx1->Rsp == pCtx2->Rsp) && - (pCtx1->Rbp == pCtx2->Rbp)) + (pCtx1->Rsp == pCtx2->Rsp)) { return TRUE; } diff --git a/src/coreclr/debug/inc/arm/primitives.h b/src/coreclr/debug/inc/arm/primitives.h index 269281eb006bed..bb585412a26e02 100644 --- a/src/coreclr/debug/inc/arm/primitives.h +++ b/src/coreclr/debug/inc/arm/primitives.h @@ -117,13 +117,10 @@ inline LPVOID CORDbgGetFP(DT_CONTEXT* context) return (LPVOID)(UINT_PTR)0; } -// compare the EIP, ESP, and EBP inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * pCtx2) { LIMITED_METHOD_DAC_CONTRACT; - // @ARMTODO: Sort out frame registers - if ((pCtx1->Pc == pCtx2->Pc) && (pCtx1->Sp == pCtx2->Sp)) { diff --git a/src/coreclr/debug/inc/arm64/primitives.h b/src/coreclr/debug/inc/arm64/primitives.h index 5f8b5262d993e4..cf4023e7ab2ca9 100644 --- a/src/coreclr/debug/inc/arm64/primitives.h +++ b/src/coreclr/debug/inc/arm64/primitives.h @@ -132,11 +132,8 @@ inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * { LIMITED_METHOD_DAC_CONTRACT; - // @ARMTODO: Sort out frame registers - if ((pCtx1->Pc == pCtx2->Pc) && - (pCtx1->Sp == pCtx2->Sp) && - (pCtx1->Fp == pCtx2->Fp)) + (pCtx1->Sp == pCtx2->Sp)) { return TRUE; } diff --git a/src/coreclr/debug/inc/i386/primitives.h b/src/coreclr/debug/inc/i386/primitives.h index 757614e185fa00..d0c55986ea96e1 100644 --- a/src/coreclr/debug/inc/i386/primitives.h +++ b/src/coreclr/debug/inc/i386/primitives.h @@ -108,14 +108,12 @@ inline LPVOID CORDbgGetFP(DT_CONTEXT* context) return (LPVOID)(UINT_PTR)context->Ebp; } -// compare the EIP, ESP, and EBP inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * pCtx2) { LIMITED_METHOD_DAC_CONTRACT; if ((pCtx1->Eip == pCtx2->Eip) && - (pCtx1->Esp == pCtx2->Esp) && - (pCtx1->Ebp == pCtx2->Ebp)) + (pCtx1->Esp == pCtx2->Esp)) { return TRUE; } diff --git a/src/coreclr/debug/inc/loongarch64/primitives.h b/src/coreclr/debug/inc/loongarch64/primitives.h index cfe46955fe7c8a..f6309552e5ebf3 100644 --- a/src/coreclr/debug/inc/loongarch64/primitives.h +++ b/src/coreclr/debug/inc/loongarch64/primitives.h @@ -118,11 +118,8 @@ inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * { LIMITED_METHOD_DAC_CONTRACT; - // TODO-LoongArch64: Sort out frame registers - if ((pCtx1->Pc == pCtx2->Pc) && - (pCtx1->Sp == pCtx2->Sp) && - (pCtx1->Fp == pCtx2->Fp)) + (pCtx1->Sp == pCtx2->Sp)) { return TRUE; } diff --git a/src/coreclr/debug/inc/riscv64/primitives.h b/src/coreclr/debug/inc/riscv64/primitives.h index 17ace22981c77d..ed4f15d6018a63 100644 --- a/src/coreclr/debug/inc/riscv64/primitives.h +++ b/src/coreclr/debug/inc/riscv64/primitives.h @@ -119,11 +119,8 @@ inline BOOL CompareControlRegisters(const DT_CONTEXT * pCtx1, const DT_CONTEXT * { LIMITED_METHOD_DAC_CONTRACT; - // TODO-RISCV64: Sort out frame registers - if ((pCtx1->Pc == pCtx2->Pc) && - (pCtx1->Sp == pCtx2->Sp) && - (pCtx1->Fp == pCtx2->Fp)) + (pCtx1->Sp == pCtx2->Sp)) { return TRUE; } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index 1bf2496f0ef36f..a00ba512e35488 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -305,9 +305,7 @@ byte[] IThread.GetContext(TargetPointer threadPointer, ThreadContextSource conte if (filterContext != TargetPointer.Null) { _target.ReadBuffer(filterContext.Value, buffer); - context.FillFromBuffer(buffer); - - return context.GetBytes(); + return bytes; } if (!_target.TryGetThreadContext(thread.OSId.Value, contextFlags, buffer)) @@ -315,8 +313,6 @@ byte[] IThread.GetContext(TargetPointer threadPointer, ThreadContextSource conte throw new InvalidOperationException($"GetThreadContext failed for thread {thread.OSId.Value}"); } - context.FillFromBuffer(buffer); - - return context.GetBytes(); + return bytes; } } 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 462c80484e78ae..af0f1c85939f27 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 @@ -680,9 +680,7 @@ public int IsLeafFrame(ulong vmThread, byte* pContext, Interop.BOOL* pResult) // Read the given context from the native buffer. IPlatformAgnosticContext givenCtx = IPlatformAgnosticContext.GetContextForPlatform(_target); - byte[] givenContext = new byte[leafContext.Length]; - new Span(pContext, givenContext.Length).CopyTo(givenContext); - givenCtx.FillFromBuffer(givenContext); + givenCtx.FillFromBuffer(new Span(pContext, leafContext.Length)); *pResult = givenCtx.StackPointer == leafCtx.StackPointer && givenCtx.InstructionPointer == leafCtx.InstructionPointer diff --git a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs index d804dd87b71628..14b061abe0fc8e 100644 --- a/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs +++ b/src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiStackWalkDumpTests.cs @@ -71,7 +71,6 @@ public unsafe void GetContext_MatchesContractGetContext(TestConfiguration config Assert.Equal(contractCtx.InstructionPointer, dbiCtx.InstructionPointer); Assert.Equal(contractCtx.StackPointer, dbiCtx.StackPointer); - Assert.Equal(contractCtx.FramePointer, dbiCtx.FramePointer); } [ConditionalTheory] From 2ae61f99e031ee186a156a3ea9a3d05f62dbc5eb Mon Sep 17 00:00:00 2001 From: rcj1 Date: Wed, 22 Apr 2026 10:30:48 -0700 Subject: [PATCH 6/8] fix --- .../Contracts/StackWalk/StackWalk_1.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs index fc50b2bca85dc9..22b6e63edc1792 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -49,6 +49,12 @@ private record StackDataFrameHandle( bool IsActiveFrame = false) : IStackDataFrameHandle { } + private enum ContextFlags + { + Full = 0x1, + All = 0x2, + } + private class StackWalkData(IPlatformAgnosticContext context, StackWalkState state, FrameIterator frameIter, ThreadData threadData) { public IPlatformAgnosticContext Context { get; set; } = context; @@ -106,7 +112,7 @@ public StackDataFrameHandle ToDataFrame() } IEnumerable IStackWalk.CreateStackWalk(ThreadData threadData) - => CreateStackWalkCore(threadData, skipInitialFrames: false); + => CreateStackWalkCore(threadData, skipInitialFrames: false, flags: ContextFlags.All); /// /// Core stack walk implementation. @@ -121,10 +127,16 @@ IEnumerable IStackWalk.CreateStackWalk(ThreadData threadD /// Must be false for ClrDataStackWalk, which advances the cDAC and legacy DAC in /// lockstep and must yield the same frame sequence (including initial skipped frames). /// - private IEnumerable CreateStackWalkCore(ThreadData threadData, bool skipInitialFrames) + private IEnumerable CreateStackWalkCore(ThreadData threadData, bool skipInitialFrames, ContextFlags flags) { IPlatformAgnosticContext context = IPlatformAgnosticContext.GetContextForPlatform(_target); - FillContextFromThread(context, threadData); + uint contextFlags = flags switch + { + ContextFlags.Full => context.FullContextFlags, + ContextFlags.All => context.AllContextFlags, + _ => throw new ArgumentOutOfRangeException(nameof(flags)), + }; + FillContextFromThread(context, threadData, contextFlags); StackWalkState state = IsManaged(context.InstructionPointer, out _) ? StackWalkState.SW_FRAMELESS : StackWalkState.SW_FRAME; FrameIterator frameIterator = new(_target, threadData); @@ -176,7 +188,7 @@ private IEnumerable CreateStackWalkCore(ThreadData thread IReadOnlyList IStackWalk.WalkStackReferences(ThreadData threadData) { - IEnumerable stackFrames = CreateStackWalkCore(threadData, skipInitialFrames: true); + IEnumerable stackFrames = CreateStackWalkCore(threadData, skipInitialFrames: true, flags: ContextFlags.Full); IEnumerable frames = stackFrames.Select(AssertCorrectHandle); IEnumerable gcFrames = Filter(frames); @@ -758,12 +770,12 @@ private bool IsManaged(TargetPointer ip, [NotNullWhen(true)] out CodeBlockHandle return false; } - private void FillContextFromThread(IPlatformAgnosticContext context, ThreadData threadData) + private void FillContextFromThread(IPlatformAgnosticContext context, ThreadData threadData, uint flags) { byte[] bytes = _target.Contracts.Thread.GetContext( threadData.ThreadAddress, ThreadContextSource.Debugger | ThreadContextSource.Profiler, - context.AllContextFlags); + flags); context.FillFromBuffer(bytes); } From 59b3e653eab48744d3e0d3ae032bb40938d07d08 Mon Sep 17 00:00:00 2001 From: Rachel Jarvi Date: Wed, 22 Apr 2026 10:54:21 -0700 Subject: [PATCH 7/8] Update ARM64Context.cs --- .../Contracts/StackWalk/Context/ARM64Context.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 84f62284e89853..1f9508e517e1cb 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 @@ -24,7 +24,7 @@ public enum ContextFlagsValues : uint CONTEXT_X18 = CONTEXT_ARM64 | 0x10, CONTEXT_XSTATE = CONTEXT_ARM64 | 0x20, CONTEXT_FULL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT, - CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS | CONTEXT_X18, + CONTEXT_ALL = CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG_REGISTERS, // // This flag is set by the unwinder if it has unwound to a call From 7bae28a40be8131894a22157bd1b6bbee993eb19 Mon Sep 17 00:00:00 2001 From: rcj1 Date: Fri, 24 Apr 2026 11:24:04 -0700 Subject: [PATCH 8/8] address code review --- docs/design/datacontracts/Thread.md | 35 ++++++++++++++++++- .../vm/datadescriptor/datadescriptor.inc | 3 -- src/coreclr/vm/threads.h | 3 -- .../Contracts/IThread.cs | 1 - .../Contracts/StackWalk/StackWalk_1.cs | 2 +- .../Contracts/Thread_1.cs | 3 -- .../Data/Thread.cs | 2 -- .../MockDescriptors/MockDescriptors.Thread.cs | 16 +++------ src/native/managed/cdac/tests/ThreadTests.cs | 26 ++------------ 9 files changed, 42 insertions(+), 49 deletions(-) diff --git a/docs/design/datacontracts/Thread.md b/docs/design/datacontracts/Thread.md index f2e07c8debe428..6dc9596712f18c 100644 --- a/docs/design/datacontracts/Thread.md +++ b/docs/design/datacontracts/Thread.md @@ -5,6 +5,13 @@ This contract is for reading and iterating the threads of the process. ## APIs of contract ``` csharp +[Flags] +enum ThreadContextSource +{ + None = 0, + Debugger = 1, +} + record struct ThreadStoreData ( int ThreadCount, TargetPointer FirstThread, @@ -52,7 +59,8 @@ ThreadStoreCounts GetThreadCounts(); ThreadData GetThreadData(TargetPointer threadPointer); void GetStackLimitData(TargetPointer threadPointer, out TargetPointer stackBase, out TargetPointer stackLimit, out TargetPointer frameAddress); TargetPointer IdToThread(uint id); -TargetPointer GetThreadLocalStaticBase(TargetPointer threadPointer, int indexOffset, int indexType); +TargetPointer GetThreadLocalStaticBase(TargetPointer threadPointer, TargetPointer tlsIndexPtr); +byte[] GetContext(TargetPointer threadPointer, ThreadContextSource contextSource, uint contextFlags); ``` ## Version 1 @@ -107,6 +115,7 @@ The contract additionally depends on these data descriptors | `Thread` | `CurrentCustomDebuggerNotification` | Handle to the current custom debugger notification object | | `Thread` | `LinkNext` | Pointer to get next thread | | `Thread` | `ExceptionTracker` | Pointer to exception tracking information | +| `Thread` | `DebuggerFilterContext` | Pointer to the debugger filter context for the thread | | `Thread` | `RuntimeThreadLocals` | Pointer to some thread-local storage | | `Thread` | `ThreadLocalDataPtr` | Pointer to thread local data structure | | `Thread` | `UEWatsonBucketTrackerBuckets` | Pointer to thread Watson buckets data (optional, Windows only) | @@ -329,4 +338,28 @@ byte[] IThread.GetWatsonBuckets(TargetPointer threadPointer) return span.ToArray(); } +byte[] IThread.GetContext(TargetPointer threadPointer, ThreadContextSource contextSource, uint contextFlags) +{ + // Allocate a context buffer for the target platform + IPlatformAgnosticContext context = IPlatformAgnosticContext.GetContextForPlatform(target); + byte[] bytes = new byte[context.Size]; + + TargetPointer filterContext = TargetPointer.Null; + + if (contextSource.HasFlag(ThreadContextSource.Debugger)) + filterContext = target.ReadPointer(threadPointer + /* Thread::DebuggerFilterContext offset */); + + if (filterContext != TargetPointer.Null) + { + // Use the filter context directly + target.ReadBuffer(filterContext, bytes); + return bytes; + } + + // Fall back to the OS thread context + ulong osId = target.ReadNUInt(threadPointer + /* Thread::OSId offset */); + target.GetThreadContext(osId, contextFlags, bytes); + return bytes; +} + ``` diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index a13b9b275ce31c..31f30fee63c005 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -45,9 +45,6 @@ CDAC_TYPE_FIELD(Thread, T_POINTER, CachedStackBase, cdac_data::CachedSta CDAC_TYPE_FIELD(Thread, T_POINTER, CachedStackLimit, cdac_data::CachedStackLimit) CDAC_TYPE_FIELD(Thread, T_POINTER, ExceptionTracker, cdac_data::ExceptionTracker) CDAC_TYPE_FIELD(Thread, T_POINTER, DebuggerFilterContext, cdac_data::DebuggerFilterContext) -#ifdef PROFILING_SUPPORTED -CDAC_TYPE_FIELD(Thread, T_POINTER, ProfilerFilterContext, cdac_data::ProfilerFilterContext) -#endif // PROFILING_SUPPORTED CDAC_TYPE_FIELD(Thread, T_POINTER, ExposedObject, cdac_data::ExposedObject) CDAC_TYPE_FIELD(Thread, T_POINTER, LastThrownObject, cdac_data::LastThrownObject) CDAC_TYPE_FIELD(Thread, T_UINT32, LastThrownObjectIsUnhandled, cdac_data::LastThrownObjectIsUnhandled) diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index bb17344a8fe625..d9d8130b7f2e3c 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -3769,9 +3769,6 @@ struct cdac_data "Thread::m_ExceptionState is of type ThreadExceptionState"); static constexpr size_t ExceptionTracker = offsetof(Thread, m_ExceptionState) + offsetof(ThreadExceptionState, m_pCurrentTracker); static constexpr size_t DebuggerFilterContext = offsetof(Thread, m_debuggerFilterContext); -#ifdef PROFILING_SUPPORTED - static constexpr size_t ProfilerFilterContext = offsetof(Thread, m_pProfilerFilterContext); -#endif // PROFILING_SUPPORTED #ifndef TARGET_UNIX static constexpr size_t UEWatsonBucketTrackerBuckets = offsetof(Thread, m_ExceptionState) + offsetof(ThreadExceptionState, m_UEWatsonBucketTracker) + offsetof(EHWatsonBucketTracker, m_WatsonUnhandledInfo.m_pUnhandledBuckets); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs index 1f9a73a9cc4916..08a6d8d291850c 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IThread.cs @@ -10,7 +10,6 @@ public enum ThreadContextSource { None = 0, Debugger = 1, - Profiler = 2, } public record struct ThreadStoreData( diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs index 22b6e63edc1792..3a09197254291a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs @@ -774,7 +774,7 @@ private void FillContextFromThread(IPlatformAgnosticContext context, ThreadData { byte[] bytes = _target.Contracts.Thread.GetContext( threadData.ThreadAddress, - ThreadContextSource.Debugger | ThreadContextSource.Profiler, + ThreadContextSource.Debugger, flags); context.FillFromBuffer(bytes); } diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs index a00ba512e35488..013729f378d1fc 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/Thread_1.cs @@ -299,9 +299,6 @@ byte[] IThread.GetContext(TargetPointer threadPointer, ThreadContextSource conte if (contextSource.HasFlag(ThreadContextSource.Debugger)) filterContext = thread.DebuggerFilterContext; - if (filterContext == TargetPointer.Null && contextSource.HasFlag(ThreadContextSource.Profiler)) - filterContext = thread.ProfilerFilterContext; - if (filterContext != TargetPointer.Null) { _target.ReadBuffer(filterContext.Value, buffer); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs index 7f77498aa24cd5..10fd2d9c07bfe8 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Thread.cs @@ -35,7 +35,6 @@ public Thread(Target target, TargetPointer address) UEWatsonBucketTrackerBuckets = target.ReadPointerFieldOrNull(address, type, nameof(UEWatsonBucketTrackerBuckets)); ThreadLocalDataPtr = target.ReadPointerField(address, type, nameof(ThreadLocalDataPtr)); DebuggerFilterContext = target.ReadPointerField(address, type, nameof(DebuggerFilterContext)); - ProfilerFilterContext = target.ReadPointerFieldOrNull(address, type, nameof(ProfilerFilterContext)); CurrentCustomDebuggerNotification = target.ReadPointerField(address, type, nameof(CurrentCustomDebuggerNotification)); } @@ -55,6 +54,5 @@ public Thread(Target target, TargetPointer address) public TargetPointer UEWatsonBucketTrackerBuckets { get; init; } public TargetPointer ThreadLocalDataPtr { get; init; } public TargetPointer DebuggerFilterContext { get; init; } - public TargetPointer ProfilerFilterContext { get; init; } public TargetPointer CurrentCustomDebuggerNotification { get; init; } } diff --git a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs index 6f9760394731e6..c978e941c0540e 100644 --- a/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs +++ b/src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.Thread.cs @@ -187,9 +187,8 @@ internal sealed class MockThread : TypedView private const string ThreadLocalDataPtrFieldName = "ThreadLocalDataPtr"; private const string UEWatsonBucketTrackerBucketsFieldName = "UEWatsonBucketTrackerBuckets"; private const string DebuggerFilterContextFieldName = "DebuggerFilterContext"; - private const string ProfilerFilterContextFieldName = "ProfilerFilterContext"; - public static Layout CreateLayout(MockTarget.Architecture architecture, bool hasProfilingSupport = true) + public static Layout CreateLayout(MockTarget.Architecture architecture) { SequentialLayoutBuilder layoutBuilder = new SequentialLayoutBuilder("Thread", architecture) .AddUInt32Field(IdFieldName) @@ -210,11 +209,6 @@ public static Layout CreateLayout(MockTarget.Architecture architectu .AddPointerField(UEWatsonBucketTrackerBucketsFieldName) .AddPointerField(DebuggerFilterContextFieldName); - if (hasProfilingSupport) - { - layoutBuilder.AddPointerField(ProfilerFilterContextFieldName); - } - return layoutBuilder.Build(); } @@ -309,19 +303,19 @@ internal sealed class MockThreadBuilder private MockThread? _previousThread; - public MockThreadBuilder(MockMemorySpace.Builder builder, bool hasProfilingSupport = true) - : this(builder, (DefaultAllocationRangeStart, DefaultAllocationRangeEnd), hasProfilingSupport) + public MockThreadBuilder(MockMemorySpace.Builder builder) + : this(builder, (DefaultAllocationRangeStart, DefaultAllocationRangeEnd)) { } - public MockThreadBuilder(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocationRange, bool hasProfilingSupport = true) + public MockThreadBuilder(MockMemorySpace.Builder builder, (ulong Start, ulong End) allocationRange) { Builder = builder; _allocator = Builder.CreateAllocator(allocationRange.Start, allocationRange.End); TargetTestHelpers helpers = builder.TargetTestHelpers; ExceptionInfoLayout = MockExceptionInfo.CreateLayout(helpers.Arch); - ThreadLayout = MockThread.CreateLayout(helpers.Arch, hasProfilingSupport); + ThreadLayout = MockThread.CreateLayout(helpers.Arch); ThreadStoreLayout = MockThreadStore.CreateLayout(helpers.Arch); GCAllocContextLayout = MockGCAllocContext.CreateLayout(helpers.Arch); EEAllocContextLayout = MockEEAllocContext.CreateLayout(helpers.Arch, GCAllocContextLayout); diff --git a/src/native/managed/cdac/tests/ThreadTests.cs b/src/native/managed/cdac/tests/ThreadTests.cs index a9960aaf5d9c0b..d314c9f5cf2432 100644 --- a/src/native/managed/cdac/tests/ThreadTests.cs +++ b/src/native/managed/cdac/tests/ThreadTests.cs @@ -12,11 +12,10 @@ public unsafe class ThreadTests { private static TestPlaceholderTarget CreateTarget( MockTarget.Architecture arch, - Action configure, - bool hasProfilingSupport = true) + Action configure) { TestPlaceholderTarget.Builder targetBuilder = new(arch); - MockThreadBuilder threadBuilder = new(targetBuilder.MemoryBuilder, hasProfilingSupport: hasProfilingSupport); + MockThreadBuilder threadBuilder = new(targetBuilder.MemoryBuilder); configure(threadBuilder); TestPlaceholderTarget target = targetBuilder @@ -277,25 +276,4 @@ public void GetCurrentExceptionHandle_HandlePointsToNull(MockTarget.Architecture TargetPointer thrownObjectHandle = contract.GetCurrentExceptionHandle(new TargetPointer(thread!.Address)); Assert.Equal(TargetPointer.Null, thrownObjectHandle); } - - [Theory] - [ClassData(typeof(MockTarget.StdArch))] - public void GetThreadData_NoProfilerFilterContext(MockTarget.Architecture arch) - { - const uint id = 1; - const ulong osId = 1234; - MockThread? thread = null; - - TestPlaceholderTarget target = CreateTarget( - arch, - threadBuilder => thread = threadBuilder.AddThread(id, osId), - hasProfilingSupport: false); - - IThread contract = target.Contracts.Thread; - Assert.NotNull(contract); - - ThreadData data = contract.GetThreadData(new TargetPointer(thread!.Address)); - Assert.Equal(id, data.Id); - Assert.Equal(new TargetNUInt(osId), data.OSId); - } }