Skip to content

[cDAC][wasm] Stack walk never terminates on an R2R InlinedCallFrame (INLINED_PINVOKE_FROM_R2R sentinel copied as IP) #135036

Description

@lewing

Description

On WASM, an active InlinedCallFrame pushed by R2R code stores the sentinel INLINED_PINVOKE_FROM_R2R (1, src/coreclr/vm/frames.h) in m_pCallerReturnAddress. Native InlinedCallFrame::UpdateRegDisplay_Impl (src/coreclr/vm/wasm/helpers.cpp) treats that as a marker: it sets SP from CallSiteSP and derives IP via GetWasmVirtualIPFromStackPointer(CallSiteSP).

On main, the cDAC's WasmFrameHandler.HandleInlinedCallFrame calls BaseFrameHandler.HandleInlinedCallFrame, which copies CallerReturnAddress verbatim. The context IP becomes 0x1, which is not managed code, so in StackWalk_1.Next():

  • case StackWalkState.Frame: isActiveICF is true (return address ≠ 0), so FrameIter.Next() is not called.
  • UpdateState: IP 0x1 is not managed → State = Frame again, on the same frame.

The IEnumerable never terminates. It keeps yielding the same Frame (same frame address, ip 0x1, same SP) forever. A consumer that enumerates without a cap hangs.

On a live browser-wasm target paused during a [JSExport] call, this triggered on the very first walk. Seeding with CreateStackWalk(threadData) gives InitialNativeContext from the InlinedCallFrame, then an endless run of Frame [InlinedCallFrame] ip=0x1 sp=0x31B5B0.

Status

The open PR #133890 already handles CallerReturnAddress == INLINED_PINVOKE_FROM_R2R in WasmFrameHandler. #133890 depends on #133086, so this issue tracks the gap in case that stack is split or delayed. It would be worth landing the sentinel handling on its own.

Separately, consider a progress guard in StackWalk_1 (an identical frame yielded twice in a row → Error/terminate). Then a future frame-handler gap degrades to a truncated walk instead of a non-terminating enumeration.

Repro

Notes

As a workaround, Blazor-Playground/nesm detects the stall, rebuilds the context the native way (SP = CallSiteSP, IP = virtual IP from the R2R frame record), and re-seeds with CreateStackWalk(threadData, context, isFirst: false). The stitched walk then continues correctly through R2R CoreLib frames, the InterpreterFrame and the interpreted JavaScriptExports.CallJSExport.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions