From 97fd5b58cdce2d4f204d5b2bbd49db0e2ea90eec Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 31 Jul 2026 11:55:24 -0700 Subject: [PATCH] Wasm: stop R2R stack walk at reverse-pinvoke frames Fixes a failure seen in #131493 (enable SPC R2R). We were walking off the end of the managed part of the shadow stack at a reverse pinvoke boundary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 89caa9c8-5b0f-4fcc-a8c4-726ac8535110 --- src/coreclr/vm/wasm/helpers.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 3d6c96c5bb8ea7..fab4c3e819a6b8 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -1761,7 +1761,7 @@ TADDR GetWasmVirtualIPFromStackPointer(TADDR sp) } } -static void WasmUnwindStackFrameCore(TADDR* pSP, TADDR* pIP, UINT_PTR ImageBase, PRUNTIME_FUNCTION FunctionEntry) +static void WasmUnwindStackFrameCore(TADDR* pSP, TADDR* pIP, UINT_PTR ImageBase, PRUNTIME_FUNCTION FunctionEntry, bool callerIsNative = false) { TADDR sp = *pSP; TADDR fp = GetWasmFramePointerFromStackPointer_Internal(sp); @@ -1774,7 +1774,9 @@ static void WasmUnwindStackFrameCore(TADDR* pSP, TADDR* pIP, UINT_PTR ImageBase, { PTR_BYTE pUnwindData = dac_cast(FunctionEntry->UnwindData + ImageBase); *pSP = fp + DecodeULEB128AsU32(&pUnwindData); // Unwind the frame pointer to the callers stack pointer - *pIP = GetWasmVirtualIPFromStackPointer(*pSP); + // A reverse-pinvoke frame's caller is native, not an R2R shadow frame; leave the IP unset so the walk + // continues via the Frame chain instead of reading the native caller SP as a shadow frame. + *pIP = callerIsNative ? 0 : GetWasmVirtualIPFromStackPointer(*pSP); } } @@ -1846,7 +1848,20 @@ RtlVirtualUnwind ( *HandlerData = 0; *EstablisherFrame = 0; - WasmUnwindStackFrameCore((TADDR*)&ContextRecord->InterpreterSP, (TADDR*)&ContextRecord->InterpreterIP, ImageBase, FunctionEntry); + // Reverse-pinvoke frames (UnmanagedCallersOnly methods and reverse P/Invoke IL stubs) are called from + // native code, so terminate the R2R walk at this boundary. + bool callerIsNative = false; + { + EECodeInfo codeInfo; + codeInfo.Init((PCODE)ControlPc); + if (codeInfo.IsValid()) + { + GcInfoDecoder gcInfoDecoder(codeInfo.GetGCInfoToken(), DECODE_REVERSE_PINVOKE_VAR); + callerIsNative = gcInfoDecoder.GetReversePInvokeFrameStackSlot() != NO_REVERSE_PINVOKE_FRAME; + } + } + + WasmUnwindStackFrameCore((TADDR*)&ContextRecord->InterpreterSP, (TADDR*)&ContextRecord->InterpreterIP, ImageBase, FunctionEntry, callerIsNative); if (ContextRecord->InterpreterSP != 0) {