Skip to content

[clr-ios] Advance past owning InterpreterFrame at StackFrameIterator::Init call sites#127560

Open
kotlarmilos wants to merge 5 commits intodotnet:mainfrom
kotlarmilos:interp-stackwalkiter-init-callerfix
Open

[clr-ios] Advance past owning InterpreterFrame at StackFrameIterator::Init call sites#127560
kotlarmilos wants to merge 5 commits intodotnet:mainfrom
kotlarmilos:interp-stackwalkiter-init-callerfix

Conversation

@kotlarmilos
Copy link
Copy Markdown
Member

@kotlarmilos kotlarmilos commented Apr 29, 2026

Follow-up to #126953

Description

When a thread is suspended inside the interpreter, InterpreterFrame::SetContextToInterpMethodContextFrame rewrites its CONTEXT so that the IP points at the executing interpreted bytecode and the first-arg register holds the owning InterpreterFrame*. Debugger code paths that walk such a thread forwards this CONTEXT to StackFrameIterator::Init.

Currently, Init fails with _ASSERTE(!m_crawl.codeInfo.IsInterpretedCode()). The assert checks a property of the IP, but the IP points at interpreter code in the cases above, so the assert fires on every such walk.

Approach

StackFrameIterator::Init resolves the start frame itself when the supplied CONTEXT references interpreted code, so debugger DAC callers no longer need to recompute it.

cc @noahfalk @janvorli

…Init call sites

Follow-up to dotnet#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#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>
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @BrzVlad, @janvorli, @kg
See info in area-owners.md if you want to be subscribed.

@kotlarmilos kotlarmilos added this to the 11.0.0 milestone Apr 29, 2026
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

This PR updates CoreCLR interpreter/debugger stack-walking so that StackFrameIterator::Init can be seeded from a CONTEXT whose IP is in interpreted code, by requiring (and enforcing via assertions) that callers advance the start Frame past the owning InterpreterFrame. This prevents checked-build aborts when stackwalking interpreter-suspended threads while preserving the “tripwire” behavior for future call sites.

Changes:

  • Introduces InterpreterFrame::TryGetOwningFrameFromContext(T_CONTEXT*) to recover the owning InterpreterFrame from the first-arg register when the IP is interpreted.
  • Updates StackFrameIterator::Init / ResetRegDisp and key debugger/DAC call sites to skip the owning InterpreterFrame when starting from an interpreted-IP CONTEXT.
  • Adjusts the existing Init assertion to validate the caller-side invariant (“start frame must not be the owning InterpreterFrame”) instead of asserting interpreted code can never occur.
Show a summary per file
File Description
src/coreclr/vm/stackwalk.cpp Advances pStartFrame past the owning InterpreterFrame for interpreted-IP contexts; updates iterator assertions and reuses the new helper in ResetRegDisp.
src/coreclr/vm/frames.h Declares InterpreterFrame::TryGetOwningFrameFromContext and documents the intended calling protocol for stackwalk seeding.
src/coreclr/vm/frames.cpp Implements TryGetOwningFrameFromContext by identifying interpreted IPs and extracting the owning frame from the first-arg register slot.
src/coreclr/debug/daccess/stack.cpp Updates DAC stackwalk initialization to pass a start frame that skips the owning InterpreterFrame for interpreted-IP contexts.
src/coreclr/debug/daccess/dacdbiimpl.cpp Updates the DAC DBI “GC safe place” probe to seed stackwalks past the owning InterpreterFrame when needed.

Copilot's findings

  • Files reviewed: 5/5 changed files
  • Comments generated: 3

Comment thread src/coreclr/vm/stackwalk.cpp Outdated
Comment thread src/coreclr/vm/stackwalk.cpp
Comment thread src/coreclr/vm/frames.h Outdated
@kotlarmilos kotlarmilos changed the title [interp] Advance past owning InterpreterFrame at StackFrameIterator::Init call sites [clr-ios] Advance past owning InterpreterFrame at StackFrameIterator::Init call sites Apr 29, 2026
@kotlarmilos kotlarmilos changed the title [clr-ios] Advance past owning InterpreterFrame at StackFrameIterator::Init call sites [clr-ios] Advance past owning InterpreterFrame at StackFrameIterator::Init call sites Apr 29, 2026
Comment thread src/coreclr/debug/daccess/dacdbiimpl.cpp Outdated
Comment thread src/coreclr/vm/stackwalk.cpp Outdated
Copilot AI review requested due to automatic review settings April 30, 2026 10:02
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>
@kotlarmilos kotlarmilos force-pushed the interp-stackwalkiter-init-callerfix branch from bf6c98b to 936dd54 Compare April 30, 2026 10:04
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.

Copilot's findings

  • Files reviewed: 2/2 changed files
  • Comments generated: 1

Comment thread src/coreclr/vm/stackwalk.cpp
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!

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