From dad7203463051bef17c7e3ab7cbeffd017153bb6 Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Mon, 20 Apr 2026 16:45:55 -0400 Subject: [PATCH 1/2] Fix x86 DAC stack walk regression from Environment.CallEntryPoint skip Don't skip Environment.CallEntryPoint in UnwindStackWalkFrame with continue. On x86, GetFrameWorker unwinds one frame ahead to compute the frame pointer. Skipping this frame produces an incorrect frame pointer for the caller, breaking post-step stack walks (NoCurrentFrameException). The frame is still hidden from debugger UI via kRuntimeEntryPointFrame classification and S_FALSE return in GetFrameWorker. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp b/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp index 659c9f08ca2ea9..b412060622bd88 100644 --- a/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp +++ b/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp @@ -308,12 +308,12 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::UnwindStackWalkFrame(StackWalkHan continue; } - // Skip the runtime helper that invokes the main program entrypoint, the debuggers do not want to see it. - // Environment.CallEntryPoint - if (pMD == g_pEnvironmentCallEntryPointMethodDesc) - { - continue; - } + // Note: Environment.CallEntryPoint is NOT skipped here with continue. + // On x86, GetFrameWorker() unwinds one frame ahead to compute the frame pointer + // (see rsstackwalk.cpp). If we skip this frame, the unwind lands on the wrong + // frame, producing an incorrect frame pointer for the caller (e.g., Main). + // Instead, we let it be enumerated; GetStackWalkCurrentFrameInfo classifies it + // as kRuntimeEntryPointFrame and GetFrameWorker returns S_FALSE to hide it. fIsAtEndOfStack = FALSE; } From ac3c73c3f7a8157435e5dcb5a7196cd95d83898b Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Tue, 21 Apr 2026 18:00:54 -0400 Subject: [PATCH 2/2] Remove continue skips for EH frames in UnwindStackWalkFrame Address jkotas feedback: the same x86 GetFrameWorker one-frame-ahead reasoning applies to exception handling frames. Move all runtime-internal frame hiding to GetStackWalkCurrentFrameInfo + GetFrameWorker(S_FALSE). Also add g_pStackFrameIteratorClass to GetStackWalkCurrentFrameInfo classification, which was previously only handled by the continue skip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../debug/daccess/dacdbiimplstackwalk.cpp | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp b/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp index b412060622bd88..c63d28d2d1f06c 100644 --- a/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp +++ b/src/coreclr/debug/daccess/dacdbiimplstackwalk.cpp @@ -297,24 +297,12 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::UnwindStackWalkFrame(StackWalkHan } else if (pIter->GetFrameState() == StackFrameIterator::SFITER_FRAMELESS_METHOD) { - MethodDesc *pMD = pIter->m_crawl.GetFunction(); - MethodTable *pMT = pMD->GetMethodTable(); - - // Skip the exception handling managed code, the debugger clients are not supposed to see them - // EH.DispatchEx, EH.RhThrowEx, EH.RhThrowHwEx, ExceptionServices.InternalCalls.SfiInit, ExceptionServices.InternalCalls.SfiNext - // and System.Runtime.StackFrameIterator.* - if (pMT == g_pEHClass || pMT == g_pExceptionServicesInternalCallsClass || pMT == g_pStackFrameIteratorClass) - { - continue; - } - - // Note: Environment.CallEntryPoint is NOT skipped here with continue. - // On x86, GetFrameWorker() unwinds one frame ahead to compute the frame pointer - // (see rsstackwalk.cpp). If we skip this frame, the unwind lands on the wrong - // frame, producing an incorrect frame pointer for the caller (e.g., Main). - // Instead, we let it be enumerated; GetStackWalkCurrentFrameInfo classifies it - // as kRuntimeEntryPointFrame and GetFrameWorker returns S_FALSE to hide it. - + // Runtime-internal frames (exception handling helpers, entry point, etc.) are NOT + // skipped here with continue. On x86, GetFrameWorker() unwinds one frame ahead to + // compute the frame pointer (see rsstackwalk.cpp). Skipping frames here would cause + // the unwind to land on the wrong frame, producing incorrect frame pointers. + // Instead, GetStackWalkCurrentFrameInfo classifies these frames and GetFrameWorker + // returns S_FALSE to hide them from debugger clients. fIsAtEndOfStack = FALSE; } else @@ -425,7 +413,8 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetStackWalkCurrentFrameInfo(Stac { MethodDesc *pMD = pIter->m_crawl.GetFunction(); // EH.DispatchEx, EH.RhThrowEx, EH.RhThrowHwEx, ExceptionServices.InternalCalls.SfiInit, ExceptionServices.InternalCalls.SfiNext - if (pMD->GetMethodTable() == g_pEHClass || pMD->GetMethodTable() == g_pExceptionServicesInternalCallsClass) + // and System.Runtime.StackFrameIterator.* + if (pMD->GetMethodTable() == g_pEHClass || pMD->GetMethodTable() == g_pExceptionServicesInternalCallsClass || pMD->GetMethodTable() == g_pStackFrameIteratorClass) { ftResult = kManagedExceptionHandlingCodeFrame; }