Skip to content

[interp] Fix doubled interpreter frames in DAC/DBI stack walks#126953

Open
kotlarmilos wants to merge 5 commits intodotnet:mainfrom
kotlarmilos:interp-fix-stackwalking
Open

[interp] Fix doubled interpreter frames in DAC/DBI stack walks#126953
kotlarmilos wants to merge 5 commits intodotnet:mainfrom
kotlarmilos:interp-fix-stackwalking

Conversation

@kotlarmilos
Copy link
Copy Markdown
Member

@kotlarmilos kotlarmilos commented Apr 15, 2026

Description

When DAC/DBI seeds StackFrameIterator from a CONTEXT pointing into interpreted code, m_crawl.pFrame is initialized to the thread's top explicit Frame, which is the InterpreterFrame that owns the executing InterpMethodContextFrame chain.

ResetRegDisp reads the owning InterpreterFrame* from the CONTEXT's first-arg register and advances m_crawl.pFrame past it before ProcessCurrentFrame runs.

Tests

Fixes the following interpreter debugger test failures:

  • StackWalking.NestedException
  • StackWalking.ChildParentTest

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes two interpreter-specific debugger stack-walk issues in CoreCLR that caused duplicated frames and incorrect source line mapping when unwinding interpreter call frames.

Changes:

  • Adjust StackFrameIterator to advance the explicit frame chain past InterpreterFrame after the DummyCallerIP transition to native code, preventing the debugger path from re-entering the interpreter chain and duplicating frames.
  • Adjust interpreter unwinding to set the caller IP to (pFrame->ip - 1) so return-address mapping lands within the call instruction range (aligning with JIT-style return-address adjustment).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/coreclr/vm/stackwalk.cpp Skips past InterpreterFrame in the explicit frame chain after transitioning out of the last interpreted frame, avoiding duplicate interpreter frame reporting in debugger walks.
src/coreclr/vm/eetwain.cpp Adjusts the unwound interpreter caller IP by -1 to correct non-leaf frame line mapping in stack traces/debugger views.

@kotlarmilos kotlarmilos changed the title [Interpreter] Fix double frames and line number off-by-one in debugger stack walks [clr-ios] Fix double frames and line number off-by-one in debugger stack walks Apr 15, 2026
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Fix two issues in the StackFrameIterator for FEATURE_INTERPRETER:

1. Double-frame bug (stackwalk.cpp): After the DummyCallerIP transition from
   the last interpreter frame to native code, advance m_crawl.pFrame past the
   InterpreterFrame. Without this, the DAC/DBI debugger path (which starts
   with context already pointing into the interpreter chain) would encounter
   the InterpreterFrame again and re-enter the chain, reporting each
   interpreter frame twice. The GC path is unaffected because it already
   advances pFrame when entering the chain via the explicit frame handler.

2. Line number off-by-one (eetwain.cpp): In VirtualUnwindInterpreterCallFrame,
   subtract 1 from pFrame->ip when unwinding to a parent frame. The
   interpreter stores ip after advancing past the call instruction (ip += N),
   so the raw IP points to the start of the next instruction. Subtracting 1
   places the IP within the call instruction, ensuring the debug info maps to
   the correct IL offset and source line for non-leaf frames. This is
   analogous to how JIT return addresses are adjusted for native code.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos kotlarmilos force-pushed the interp-fix-stackwalking branch from 8113c17 to 123c90a Compare April 16, 2026 09:02
Comment thread src/coreclr/vm/eetwain.cpp Outdated
Copilot AI review requested due to automatic review settings April 16, 2026 17:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/vm/eetwain.cpp Outdated
The stack walker should return the real IP. IP adjustment for call sites
belongs on the consumer side (EH already does it; debugger-side handling
for the interpreter line-number off-by-one is tracked separately).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos kotlarmilos changed the title [clr-ios] Fix double frames and line number off-by-one in debugger stack walks [interp] Fix doubled interpreter frames in debugger stack walks Apr 22, 2026
@kotlarmilos kotlarmilos requested a review from janvorli April 22, 2026 13:06
@kotlarmilos kotlarmilos marked this pull request as ready for review April 22, 2026 13:06
@kotlarmilos kotlarmilos requested review from Copilot and noahfalk April 22, 2026 13:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread src/coreclr/vm/stackwalk.cpp Outdated
Comment thread src/coreclr/vm/stackwalk.cpp Outdated
// frame, because the stack frame iterator assumes that when it is walking interpreted frames, it has
// already processed the interpreter frame. Without this skip, the stack walk would end up walking the
// interpreted frames twice.
m_crawl.GotoNextFrame();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normal invariant enforced by NextRaw() is that m_crawl moves past the InterpreterFrame before calling ProcessCurrentFrame() with a CONTEXT pointing at interpreted code. Also in general it is the caller's job to advance the Frame, not ProcessCurrentFrame() (the comments on this method say this is called after we've stopped). I'd suggest:

  1. In StackFrameIterator::Init() assert that the CONTEXT doesn't point at interpreted code.
  2. In StackFrameIterator::ResetRegDisp - there is already a bunch of logic that tries to start the Frame pointer at the correct position in the Frame chain. Currently that logic uses stack pointer comparisons but it probably needs to be updated to handle CONTEXTs that reference interpreter code. I'm guessing we'd need to map the interpreter code to its logically associated InterpreterFrame, then advance the Frame pointer to the next Frame in the chain.
  3. Remove this code here because now ResetRegDisp() will have initialized the state correctly.

Copy link
Copy Markdown
Member Author

@kotlarmilos kotlarmilos Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - Init asserts the CONTEXT isn't interpreted, ResetRegDisp reads the owning InterpreterFrame* from the CONTEXT's first-arg register and advances m_crawl.pFrame past it before ProcessCurrentFrame runs.

Move the doubled-interpreter-frames fix from ProcessCurrentFrame to
ResetRegDisp so the StackFrameIterator starts from a correct state
when DAC/DBI seeds it with a CONTEXT pointing into interpreted code.

- Init: assert the CONTEXT does not reference interpreted code; that
  path is for OS-thread-top contexts and only DAC/DBI seeds interpreter
  contexts (via ResetRegDisp).
- ResetRegDisp: when CONTEXT references interpreted code, locate the
  owning InterpreterFrame whose InterpMethodContextFrame chain contains
  the SP, and advance m_crawl.pFrame past it.
- ProcessCurrentFrame: drop the post-hoc workaround that skipped the
  re-encountered InterpreterFrame; no longer needed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@kotlarmilos kotlarmilos changed the title [interp] Fix doubled interpreter frames in debugger stack walks [interp] Fix doubled interpreter frames in DAC/DBI stack walks Apr 28, 2026
@kotlarmilos kotlarmilos requested a review from noahfalk April 28, 2026 12:58
When the StackFrameIterator is seeded from a CONTEXT pointing into
interpreted code, InterpreterFrame::SetContextToInterpMethodContextFrame
already writes the owning InterpreterFrame pointer into the first-arg
register. Read it from the CONTEXT instead of searching the explicit
Frame chain for an InterpMethodContextFrame whose address matches SP.

Drops the chain search and removes the implicit assumption that the
seeded context always belongs to the topmost InterpreterFrame, which
also generalizes correctly to mid-stack seeds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 28, 2026 14:47
Copy link
Copy Markdown
Member

@janvorli janvorli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you!

Copy link
Copy Markdown
Member

@noahfalk noahfalk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants