From ac16b5fde49fd6d76deca29a8fea466dd9477ee9 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Wed, 29 Apr 2026 15:25:50 +0200 Subject: [PATCH 1/5] [interp] Advance past owning InterpreterFrame at StackFrameIterator::Init call sites Follow-up to #126953. StackFrameIterator::Init asserts that the seed CONTEXT does not reference interpreted code, but that assert cannot hold on debugger paths whose CONTEXT was set up by InterpreterFrame::SetContextToInterpMethodContextFrame. Per the design discussed in dotnet/runtime#126953, callers must advance pStartFrame past the owning InterpreterFrame before constructing the iterator from an interpreted-IP CONTEXT. A new helper InterpreterFrame::TryGetOwningFrameFromContext returns the owning InterpreterFrame from the first-arg register slot when the IP lies in interpreted code, otherwise NULL. The three Init call sites that supply a CONTEXT (Thread::StackWalkFramesEx, ClrDataStackWalk::Init, DacDbiInterfaceImpl::IsThreadAtGCSafePlace) use the helper to compute pStartFrame. ResetRegDisp's existing inline interpreter handling also routes through the helper. The assert in Init now checks the actual caller-side invariant. When the IP is interpreted, m_crawl.pFrame must not be the owning InterpreterFrame. The next frame may legitimately be a different InterpreterFrame (for example, interp -> JIT -> interp). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/daccess/dacdbiimpl.cpp | 12 ++++++++- src/coreclr/debug/daccess/stack.cpp | 12 ++++++++- src/coreclr/vm/frames.cpp | 24 ++++++++++++++++++ src/coreclr/vm/frames.h | 8 ++++++ src/coreclr/vm/stackwalk.cpp | 31 ++++++++++++++++++------ 5 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index bd31eee7c233bf..aea42ea093f889 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -5656,8 +5656,18 @@ BOOL DacDbiInterfaceImpl::IsThreadAtGCSafePlace(VMPTR_Thread vmThread) ULONG32 flags = (QUICKUNWIND | HANDLESKIPPEDFRAMES | DISABLE_MISSING_FRAME_DETECTION); + PTR_Frame pStartFrame = pThread->GetFrame(); +#ifdef FEATURE_INTERPRETER + PTR_InterpreterFrame pOwningInterpFrame = + InterpreterFrame::TryGetOwningFrameFromContext(rd.pCurrentContext); + if (pOwningInterpFrame != NULL) + { + pStartFrame = pOwningInterpFrame->PtrNextFrame(); + } +#endif // FEATURE_INTERPRETER + StackFrameIterator iter; - iter.Init(pThread, pThread->GetFrame(), &rd, flags); + iter.Init(pThread, pStartFrame, &rd, flags); CrawlFrame * pCF = &(iter.m_crawl); if (pCF->IsFrameless() && pCF->IsActiveFunc()) diff --git a/src/coreclr/debug/daccess/stack.cpp b/src/coreclr/debug/daccess/stack.cpp index 0e56a57cb6de7f..cccb53a7ad97ca 100644 --- a/src/coreclr/debug/daccess/stack.cpp +++ b/src/coreclr/debug/daccess/stack.cpp @@ -489,7 +489,17 @@ ClrDataStackWalk::Init(void) iterFlags |= FUNCTIONSONLY; } - m_frameIter.Init(m_thread, NULL, &m_regDisp, iterFlags); + PTR_Frame pStartFrame = NULL; +#ifdef FEATURE_INTERPRETER + PTR_InterpreterFrame pOwningInterpFrame = + InterpreterFrame::TryGetOwningFrameFromContext(m_regDisp.pCurrentContext); + if (pOwningInterpFrame != NULL) + { + pStartFrame = pOwningInterpFrame->PtrNextFrame(); + } +#endif // FEATURE_INTERPRETER + + m_frameIter.Init(m_thread, pStartFrame, &m_regDisp, iterFlags); if (m_frameIter.GetFrameState() == StackFrameIterator::SFITER_UNINITIALIZED) { return E_FAIL; diff --git a/src/coreclr/vm/frames.cpp b/src/coreclr/vm/frames.cpp index 18045f0b7ac46c..e48c1989157fe7 100644 --- a/src/coreclr/vm/frames.cpp +++ b/src/coreclr/vm/frames.cpp @@ -1823,6 +1823,30 @@ void InterpreterFrame::SetContextToInterpMethodContextFrame(T_CONTEXT * pContext } } +// static +PTR_InterpreterFrame InterpreterFrame::TryGetOwningFrameFromContext(T_CONTEXT * pContext) +{ + LIMITED_METHOD_DAC_CONTRACT; + + if (pContext == NULL) + { + return NULL; + } + + EECodeInfo codeInfo; + codeInfo.Init(::GetIP(pContext)); + if (!codeInfo.IsInterpretedCode()) + { + return NULL; + } + + PTR_InterpreterFrame pOwningInterpFrame = + dac_cast((TADDR)GetFirstArgReg(pContext)); + _ASSERTE(pOwningInterpFrame != NULL); + _ASSERTE(pOwningInterpFrame->GetFrameIdentifier() == FrameIdentifier::InterpreterFrame); + return pOwningInterpFrame; +} + void InterpreterFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats) { SyncRegDisplayToCurrentContext(pRD); diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index f3fccab5615efa..1eea56a424dc4b 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -2239,6 +2239,14 @@ class InterpreterFrame : public FramedMethodFrame void SetContextToInterpMethodContextFrame(T_CONTEXT * pContext); + // If the IP in the supplied CONTEXT lies in interpreted code (i.e. the CONTEXT + // was set up by SetContextToInterpMethodContextFrame), returns the owning + // InterpreterFrame extracted from the first-arg register slot. Otherwise returns + // NULL. Callers that build a StackFrameIterator from such a CONTEXT should pass + // pOwner->PtrNextFrame() as the start frame to skip the InterpreterFrame that + // owns the executing InterpMethodContextFrame chain. + static PTR_InterpreterFrame TryGetOwningFrameFromContext(T_CONTEXT * pContext); + #if defined(HOST_AMD64) && defined(HOST_WINDOWS) void SetInterpExecMethodSSP(TADDR ssp) { diff --git a/src/coreclr/vm/stackwalk.cpp b/src/coreclr/vm/stackwalk.cpp index 00f2123f005fe0..874122f0928a40 100644 --- a/src/coreclr/vm/stackwalk.cpp +++ b/src/coreclr/vm/stackwalk.cpp @@ -818,6 +818,18 @@ StackWalkAction Thread::StackWalkFramesEx( #endif SET_THREAD_TYPE_STACKWALKER(this); +#ifdef FEATURE_INTERPRETER + // Advance pStartFrame past the owning InterpreterFrame when the CONTEXT + // references interpreted code, unless the caller already starts past it. + PTR_InterpreterFrame pOwningInterpFrame = + InterpreterFrame::TryGetOwningFrameFromContext(pRD->pCurrentContext); + if (pOwningInterpFrame != NULL && + (pStartFrame == NULL || dac_cast(pStartFrame) <= dac_cast(pOwningInterpFrame))) + { + pStartFrame = pOwningInterpFrame->PtrNextFrame(); + } +#endif // FEATURE_INTERPRETER + StackFrameIterator iter; if (iter.Init(this, pStartFrame, pRD, flags) == TRUE) { @@ -1088,7 +1100,15 @@ BOOL StackFrameIterator::Init(Thread * pThread, // process the REGDISPLAY and stop at the first frame ProcessIp(GetControlPC(m_crawl.pRD)); #ifdef FEATURE_INTERPRETER - _ASSERTE(!m_crawl.codeInfo.IsInterpretedCode()); + // Tripwire: callers must advance pStartFrame past the owning InterpreterFrame + // when the CONTEXT references interpreted code. + if (m_crawl.codeInfo.IsInterpretedCode()) + { + PTR_InterpreterFrame pOwning = + InterpreterFrame::TryGetOwningFrameFromContext(m_crawl.pRD->pCurrentContext); + _ASSERTE(pOwning != NULL); + _ASSERTE(m_crawl.pFrame != dac_cast(pOwning)); + } #endif // FEATURE_INTERPRETER if (m_crawl.isFrameless && !!(m_crawl.pRD->pCurrentContext->ContextFlags & CONTEXT_EXCEPTION_ACTIVE)) { @@ -1166,14 +1186,11 @@ BOOL StackFrameIterator::ResetRegDisp(PREGDISPLAY pRegDisp, #ifdef FEATURE_INTERPRETER if (m_crawl.codeInfo.IsInterpretedCode()) { - // The CONTEXT carries the owning InterpreterFrame in the first-arg register - // (set by InterpreterFrame::SetContextToInterpMethodContextFrame). Advance - // m_crawl.pFrame past it so the iterator does not re-enter the same - // InterpMethodContextFrame chain via the explicit frame link. + // Advance past the owning InterpreterFrame so we don't re-enter its + // InterpMethodContextFrame chain via the explicit Frame link. PTR_InterpreterFrame pOwningInterpFrame = - dac_cast((TADDR)GetFirstArgReg(m_crawl.pRD->pCurrentContext)); + InterpreterFrame::TryGetOwningFrameFromContext(m_crawl.pRD->pCurrentContext); _ASSERTE(pOwningInterpFrame != NULL); - _ASSERTE(pOwningInterpFrame->GetFrameIdentifier() == FrameIdentifier::InterpreterFrame); m_crawl.pFrame = pOwningInterpFrame->PtrNextFrame(); } else From 3d053721470d5a7cdaa5a3ae23b33f0622fa985a Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Wed, 29 Apr 2026 15:46:06 +0200 Subject: [PATCH 2/5] Refactor: Simplify assignment of owning InterpreterFrame in stack-related functions --- src/coreclr/debug/daccess/dacdbiimpl.cpp | 3 +-- src/coreclr/debug/daccess/stack.cpp | 3 +-- src/coreclr/vm/frames.cpp | 3 +-- src/coreclr/vm/frames.h | 7 ------- src/coreclr/vm/stackwalk.cpp | 8 +++----- 5 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index aea42ea093f889..ea185b3932b07f 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -5658,8 +5658,7 @@ BOOL DacDbiInterfaceImpl::IsThreadAtGCSafePlace(VMPTR_Thread vmThread) PTR_Frame pStartFrame = pThread->GetFrame(); #ifdef FEATURE_INTERPRETER - PTR_InterpreterFrame pOwningInterpFrame = - InterpreterFrame::TryGetOwningFrameFromContext(rd.pCurrentContext); + PTR_InterpreterFrame pOwningInterpFrame = InterpreterFrame::TryGetOwningFrameFromContext(rd.pCurrentContext); if (pOwningInterpFrame != NULL) { pStartFrame = pOwningInterpFrame->PtrNextFrame(); diff --git a/src/coreclr/debug/daccess/stack.cpp b/src/coreclr/debug/daccess/stack.cpp index cccb53a7ad97ca..901e893dbd77eb 100644 --- a/src/coreclr/debug/daccess/stack.cpp +++ b/src/coreclr/debug/daccess/stack.cpp @@ -491,8 +491,7 @@ ClrDataStackWalk::Init(void) PTR_Frame pStartFrame = NULL; #ifdef FEATURE_INTERPRETER - PTR_InterpreterFrame pOwningInterpFrame = - InterpreterFrame::TryGetOwningFrameFromContext(m_regDisp.pCurrentContext); + PTR_InterpreterFrame pOwningInterpFrame =InterpreterFrame::TryGetOwningFrameFromContext(m_regDisp.pCurrentContext); if (pOwningInterpFrame != NULL) { pStartFrame = pOwningInterpFrame->PtrNextFrame(); diff --git a/src/coreclr/vm/frames.cpp b/src/coreclr/vm/frames.cpp index e48c1989157fe7..f552bea915a2b4 100644 --- a/src/coreclr/vm/frames.cpp +++ b/src/coreclr/vm/frames.cpp @@ -1840,8 +1840,7 @@ PTR_InterpreterFrame InterpreterFrame::TryGetOwningFrameFromContext(T_CONTEXT * return NULL; } - PTR_InterpreterFrame pOwningInterpFrame = - dac_cast((TADDR)GetFirstArgReg(pContext)); + PTR_InterpreterFrame pOwningInterpFrame = dac_cast((TADDR)GetFirstArgReg(pContext)); _ASSERTE(pOwningInterpFrame != NULL); _ASSERTE(pOwningInterpFrame->GetFrameIdentifier() == FrameIdentifier::InterpreterFrame); return pOwningInterpFrame; diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 1eea56a424dc4b..2f15074c8365fb 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -2238,13 +2238,6 @@ class InterpreterFrame : public FramedMethodFrame PTR_InterpMethodContextFrame GetTopInterpMethodContextFrame(); void SetContextToInterpMethodContextFrame(T_CONTEXT * pContext); - - // If the IP in the supplied CONTEXT lies in interpreted code (i.e. the CONTEXT - // was set up by SetContextToInterpMethodContextFrame), returns the owning - // InterpreterFrame extracted from the first-arg register slot. Otherwise returns - // NULL. Callers that build a StackFrameIterator from such a CONTEXT should pass - // pOwner->PtrNextFrame() as the start frame to skip the InterpreterFrame that - // owns the executing InterpMethodContextFrame chain. static PTR_InterpreterFrame TryGetOwningFrameFromContext(T_CONTEXT * pContext); #if defined(HOST_AMD64) && defined(HOST_WINDOWS) diff --git a/src/coreclr/vm/stackwalk.cpp b/src/coreclr/vm/stackwalk.cpp index 874122f0928a40..0315a20c5e3484 100644 --- a/src/coreclr/vm/stackwalk.cpp +++ b/src/coreclr/vm/stackwalk.cpp @@ -1100,12 +1100,11 @@ BOOL StackFrameIterator::Init(Thread * pThread, // process the REGDISPLAY and stop at the first frame ProcessIp(GetControlPC(m_crawl.pRD)); #ifdef FEATURE_INTERPRETER - // Tripwire: callers must advance pStartFrame past the owning InterpreterFrame + // Callers must advance pStartFrame past the owning InterpreterFrame // when the CONTEXT references interpreted code. if (m_crawl.codeInfo.IsInterpretedCode()) { - PTR_InterpreterFrame pOwning = - InterpreterFrame::TryGetOwningFrameFromContext(m_crawl.pRD->pCurrentContext); + PTR_InterpreterFrame pOwning = InterpreterFrame::TryGetOwningFrameFromContext(m_crawl.pRD->pCurrentContext); _ASSERTE(pOwning != NULL); _ASSERTE(m_crawl.pFrame != dac_cast(pOwning)); } @@ -1188,8 +1187,7 @@ BOOL StackFrameIterator::ResetRegDisp(PREGDISPLAY pRegDisp, { // Advance past the owning InterpreterFrame so we don't re-enter its // InterpMethodContextFrame chain via the explicit Frame link. - PTR_InterpreterFrame pOwningInterpFrame = - InterpreterFrame::TryGetOwningFrameFromContext(m_crawl.pRD->pCurrentContext); + PTR_InterpreterFrame pOwningInterpFrame = InterpreterFrame::TryGetOwningFrameFromContext(m_crawl.pRD->pCurrentContext); _ASSERTE(pOwningInterpFrame != NULL); m_crawl.pFrame = pOwningInterpFrame->PtrNextFrame(); } From 936dd54f650d4dc27692997242918df8ae945bf8 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Thu, 30 Apr 2026 11:58:30 +0200 Subject: [PATCH 3/5] Move InterpreterFrame auto-advance into StackFrameIterator::Init Address PR feedback from @noahfalk and @janvorli: rather than have every caller advance pStartFrame past the owning InterpreterFrame, do it inside StackFrameIterator::Init when the caller passes pFrame==NULL. - Init: when the CONTEXT references interpreted code, extract the owning InterpreterFrame from the first-arg register slot directly (no EECodeInfo lookup; we already have m_crawl.codeInfo populated by ProcessIp). If pFrame was NULL, auto-advance m_crawl.pFrame to the next Frame past the owner. If the caller passed an explicit pFrame, validate it is strictly past the owner (asserted with > since the Frame chain on this stack is laid out so a Frame closer to the leaf has a lower address than its caller's owning Frame). - ResetRegDisp: same inline fast-path; do not re-prove IsInterpretedCode via a helper that redoes EECodeInfo.Init. - Revert callsite advance in DacDbiInterfaceImpl::IsThreadAtGCSafePlace (now passes NULL), ClrDataStackWalk::Init (already passed NULL), and Thread::StackWalkFramesEx (no longer pre-advances). - Delete InterpreterFrame::TryGetOwningFrameFromContext helper and its declaration in frames.h; no remaining callers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/debug/daccess/dacdbiimpl.cpp | 11 +------ src/coreclr/debug/daccess/stack.cpp | 11 +------ src/coreclr/vm/frames.cpp | 23 -------------- src/coreclr/vm/frames.h | 1 - src/coreclr/vm/stackwalk.cpp | 40 +++++++++++++----------- 5 files changed, 23 insertions(+), 63 deletions(-) diff --git a/src/coreclr/debug/daccess/dacdbiimpl.cpp b/src/coreclr/debug/daccess/dacdbiimpl.cpp index ea185b3932b07f..0eb96e0af27b9b 100644 --- a/src/coreclr/debug/daccess/dacdbiimpl.cpp +++ b/src/coreclr/debug/daccess/dacdbiimpl.cpp @@ -5656,17 +5656,8 @@ BOOL DacDbiInterfaceImpl::IsThreadAtGCSafePlace(VMPTR_Thread vmThread) ULONG32 flags = (QUICKUNWIND | HANDLESKIPPEDFRAMES | DISABLE_MISSING_FRAME_DETECTION); - PTR_Frame pStartFrame = pThread->GetFrame(); -#ifdef FEATURE_INTERPRETER - PTR_InterpreterFrame pOwningInterpFrame = InterpreterFrame::TryGetOwningFrameFromContext(rd.pCurrentContext); - if (pOwningInterpFrame != NULL) - { - pStartFrame = pOwningInterpFrame->PtrNextFrame(); - } -#endif // FEATURE_INTERPRETER - StackFrameIterator iter; - iter.Init(pThread, pStartFrame, &rd, flags); + iter.Init(pThread, NULL, &rd, flags); CrawlFrame * pCF = &(iter.m_crawl); if (pCF->IsFrameless() && pCF->IsActiveFunc()) diff --git a/src/coreclr/debug/daccess/stack.cpp b/src/coreclr/debug/daccess/stack.cpp index 901e893dbd77eb..0e56a57cb6de7f 100644 --- a/src/coreclr/debug/daccess/stack.cpp +++ b/src/coreclr/debug/daccess/stack.cpp @@ -489,16 +489,7 @@ ClrDataStackWalk::Init(void) iterFlags |= FUNCTIONSONLY; } - PTR_Frame pStartFrame = NULL; -#ifdef FEATURE_INTERPRETER - PTR_InterpreterFrame pOwningInterpFrame =InterpreterFrame::TryGetOwningFrameFromContext(m_regDisp.pCurrentContext); - if (pOwningInterpFrame != NULL) - { - pStartFrame = pOwningInterpFrame->PtrNextFrame(); - } -#endif // FEATURE_INTERPRETER - - m_frameIter.Init(m_thread, pStartFrame, &m_regDisp, iterFlags); + m_frameIter.Init(m_thread, NULL, &m_regDisp, iterFlags); if (m_frameIter.GetFrameState() == StackFrameIterator::SFITER_UNINITIALIZED) { return E_FAIL; diff --git a/src/coreclr/vm/frames.cpp b/src/coreclr/vm/frames.cpp index f552bea915a2b4..18045f0b7ac46c 100644 --- a/src/coreclr/vm/frames.cpp +++ b/src/coreclr/vm/frames.cpp @@ -1823,29 +1823,6 @@ void InterpreterFrame::SetContextToInterpMethodContextFrame(T_CONTEXT * pContext } } -// static -PTR_InterpreterFrame InterpreterFrame::TryGetOwningFrameFromContext(T_CONTEXT * pContext) -{ - LIMITED_METHOD_DAC_CONTRACT; - - if (pContext == NULL) - { - return NULL; - } - - EECodeInfo codeInfo; - codeInfo.Init(::GetIP(pContext)); - if (!codeInfo.IsInterpretedCode()) - { - return NULL; - } - - PTR_InterpreterFrame pOwningInterpFrame = dac_cast((TADDR)GetFirstArgReg(pContext)); - _ASSERTE(pOwningInterpFrame != NULL); - _ASSERTE(pOwningInterpFrame->GetFrameIdentifier() == FrameIdentifier::InterpreterFrame); - return pOwningInterpFrame; -} - void InterpreterFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats) { SyncRegDisplayToCurrentContext(pRD); diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index 2f15074c8365fb..f3fccab5615efa 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -2238,7 +2238,6 @@ class InterpreterFrame : public FramedMethodFrame PTR_InterpMethodContextFrame GetTopInterpMethodContextFrame(); void SetContextToInterpMethodContextFrame(T_CONTEXT * pContext); - static PTR_InterpreterFrame TryGetOwningFrameFromContext(T_CONTEXT * pContext); #if defined(HOST_AMD64) && defined(HOST_WINDOWS) void SetInterpExecMethodSSP(TADDR ssp) diff --git a/src/coreclr/vm/stackwalk.cpp b/src/coreclr/vm/stackwalk.cpp index 0315a20c5e3484..53a1acdd102817 100644 --- a/src/coreclr/vm/stackwalk.cpp +++ b/src/coreclr/vm/stackwalk.cpp @@ -818,18 +818,6 @@ StackWalkAction Thread::StackWalkFramesEx( #endif SET_THREAD_TYPE_STACKWALKER(this); -#ifdef FEATURE_INTERPRETER - // Advance pStartFrame past the owning InterpreterFrame when the CONTEXT - // references interpreted code, unless the caller already starts past it. - PTR_InterpreterFrame pOwningInterpFrame = - InterpreterFrame::TryGetOwningFrameFromContext(pRD->pCurrentContext); - if (pOwningInterpFrame != NULL && - (pStartFrame == NULL || dac_cast(pStartFrame) <= dac_cast(pOwningInterpFrame))) - { - pStartFrame = pOwningInterpFrame->PtrNextFrame(); - } -#endif // FEATURE_INTERPRETER - StackFrameIterator iter; if (iter.Init(this, pStartFrame, pRD, flags) == TRUE) { @@ -1100,13 +1088,25 @@ BOOL StackFrameIterator::Init(Thread * pThread, // process the REGDISPLAY and stop at the first frame ProcessIp(GetControlPC(m_crawl.pRD)); #ifdef FEATURE_INTERPRETER - // Callers must advance pStartFrame past the owning InterpreterFrame - // when the CONTEXT references interpreted code. if (m_crawl.codeInfo.IsInterpretedCode()) { - PTR_InterpreterFrame pOwning = InterpreterFrame::TryGetOwningFrameFromContext(m_crawl.pRD->pCurrentContext); + // CONTEXT is in interpreted code; the first-arg register holds the owning + // InterpreterFrame. Skip past it so we don't re-enter its frame chain. + PTR_InterpreterFrame pOwning = + dac_cast((TADDR)GetFirstArgReg(m_crawl.pRD->pCurrentContext)); _ASSERTE(pOwning != NULL); - _ASSERTE(m_crawl.pFrame != dac_cast(pOwning)); + _ASSERTE(pOwning->GetFrameIdentifier() == FrameIdentifier::InterpreterFrame); + + if (pFrame == NULL) + { + m_crawl.pFrame = pOwning->PtrNextFrame(); + } + else + { + // Explicit pFrame must already be past the owner (callee Frames have + // lower addresses than their callers). + _ASSERTE(dac_cast(m_crawl.pFrame) > dac_cast(pOwning)); + } } #endif // FEATURE_INTERPRETER if (m_crawl.isFrameless && !!(m_crawl.pRD->pCurrentContext->ContextFlags & CONTEXT_EXCEPTION_ACTIVE)) @@ -1185,10 +1185,12 @@ BOOL StackFrameIterator::ResetRegDisp(PREGDISPLAY pRegDisp, #ifdef FEATURE_INTERPRETER if (m_crawl.codeInfo.IsInterpretedCode()) { - // Advance past the owning InterpreterFrame so we don't re-enter its - // InterpMethodContextFrame chain via the explicit Frame link. - PTR_InterpreterFrame pOwningInterpFrame = InterpreterFrame::TryGetOwningFrameFromContext(m_crawl.pRD->pCurrentContext); + // CONTEXT is in interpreted code; advance past the owning InterpreterFrame + // (in the first-arg register) so we don't re-enter its frame chain. + PTR_InterpreterFrame pOwningInterpFrame = + dac_cast((TADDR)GetFirstArgReg(m_crawl.pRD->pCurrentContext)); _ASSERTE(pOwningInterpFrame != NULL); + _ASSERTE(pOwningInterpFrame->GetFrameIdentifier() == FrameIdentifier::InterpreterFrame); m_crawl.pFrame = pOwningInterpFrame->PtrNextFrame(); } else From f99ac1988d17664a8e1f88442a4371ebd56b7295 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Thu, 30 Apr 2026 12:05:04 +0200 Subject: [PATCH 4/5] Clarify comment on InterpreterFrame handling in StackFrameIterator::Init --- src/coreclr/vm/stackwalk.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/stackwalk.cpp b/src/coreclr/vm/stackwalk.cpp index 53a1acdd102817..30dac8d01095c5 100644 --- a/src/coreclr/vm/stackwalk.cpp +++ b/src/coreclr/vm/stackwalk.cpp @@ -1090,8 +1090,8 @@ BOOL StackFrameIterator::Init(Thread * pThread, #ifdef FEATURE_INTERPRETER if (m_crawl.codeInfo.IsInterpretedCode()) { - // CONTEXT is in interpreted code; the first-arg register holds the owning - // InterpreterFrame. Skip past it so we don't re-enter its frame chain. + // CONTEXT is in interpreted code where the first-arg register holds the owning InterpreterFrame. + // Skip past it so we don't re-enter its frame chain. PTR_InterpreterFrame pOwning = dac_cast((TADDR)GetFirstArgReg(m_crawl.pRD->pCurrentContext)); _ASSERTE(pOwning != NULL); From 5e95f680c0337d0ce66035ddf94074034076a1a6 Mon Sep 17 00:00:00 2001 From: Milos Kotlar Date: Thu, 30 Apr 2026 12:06:43 +0200 Subject: [PATCH 5/5] Update comment --- src/coreclr/vm/stackwalk.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/coreclr/vm/stackwalk.cpp b/src/coreclr/vm/stackwalk.cpp index 30dac8d01095c5..f8e344552c303e 100644 --- a/src/coreclr/vm/stackwalk.cpp +++ b/src/coreclr/vm/stackwalk.cpp @@ -1103,8 +1103,7 @@ BOOL StackFrameIterator::Init(Thread * pThread, } else { - // Explicit pFrame must already be past the owner (callee Frames have - // lower addresses than their callers). + // Explicit pFrame must already be past the owner (callee Frames have lower addresses than their callers). _ASSERTE(dac_cast(m_crawl.pFrame) > dac_cast(pOwning)); } } @@ -1185,8 +1184,8 @@ BOOL StackFrameIterator::ResetRegDisp(PREGDISPLAY pRegDisp, #ifdef FEATURE_INTERPRETER if (m_crawl.codeInfo.IsInterpretedCode()) { - // CONTEXT is in interpreted code; advance past the owning InterpreterFrame - // (in the first-arg register) so we don't re-enter its frame chain. + // CONTEXT is in interpreted code where the first-arg register holds the owning InterpreterFrame. + // Skip past it so we don't re-enter its frame chain. PTR_InterpreterFrame pOwningInterpFrame = dac_cast((TADDR)GetFirstArgReg(m_crawl.pRD->pCurrentContext)); _ASSERTE(pOwningInterpFrame != NULL);