From 8e97fa53ab8cff33ea8087782b918ebc94de84f9 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 29 Jun 2026 17:36:58 -0700 Subject: [PATCH 1/2] Fix stackwalking handling of FP on Wasm in R2R code Add setting of the various InstructionSets so that R2R code is used Adjust CLRTest.CrossGen.targets to specify the JitWasmNyi flags so that tests generally pass --- src/coreclr/vm/codeman.cpp | 5 +++++ src/coreclr/vm/excep.cpp | 4 ++-- src/coreclr/vm/wasm/helpers.cpp | 6 ++++++ src/tests/Common/CLRTest.CrossGen.targets | 8 ++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 02645c9e9a0a08..6f7712d1fdbe40 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -1515,6 +1515,11 @@ void EEJitManager::SetCpuInfo() } #endif +#if defined(TARGET_WASM) + CPUCompileFlags.Set(InstructionSet_WasmBase); + CPUCompileFlags.Set(InstructionSet_PackedSimd); +#endif + #if defined(TARGET_X86) || defined(TARGET_AMD64) CPUCompileFlags.Set(InstructionSet_VectorT128); diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index 0bc243d9650e18..905be8e332348e 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -10728,7 +10728,7 @@ void SoftwareExceptionFrame::UpdateContextFromTransitionBlock(TransitionBlock *p } #elif defined(TARGET_WASM) - +TADDR GetWasmFramePointerFromStackPointer(TADDR sp); void SoftwareExceptionFrame::UpdateContextFromTransitionBlock(TransitionBlock *pTransitionBlock) { LIMITED_METHOD_CONTRACT; @@ -10740,7 +10740,7 @@ void SoftwareExceptionFrame::UpdateContextFromTransitionBlock(TransitionBlock *p if (pTransitionBlock != nullptr) { m_Context.InterpreterSP = pTransitionBlock->m_StackPointer; - m_Context.InterpreterFP = 0; + m_Context.InterpreterFP = GetWasmFramePointerFromStackPointer(m_Context.InterpreterSP); m_Context.InterpreterIP = GetWasmVirtualIPFromStackPointer(pTransitionBlock->m_StackPointer); m_ReturnAddress = m_Context.InterpreterIP; m_Context.InterpreterWalkFramePointer = 0; diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 8abf0c736818f2..277455dce4ff0f 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -520,12 +520,16 @@ void FaultingExceptionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool u PORTABILITY_ASSERT("FaultingExceptionFrame::UpdateRegDisplay_Impl is not implemented on wasm"); } +TADDR GetWasmFramePointerFromStackPointer(TADDR sp); + void TransitionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats) { pRD->IsCallerContextValid = FALSE; pRD->pCurrentContext->InterpreterIP = GetReturnAddress(); pRD->pCurrentContext->InterpreterSP = GetSP(); + // Recover the frame pointer so GC-info readers can locate frame slots. + pRD->pCurrentContext->InterpreterFP = GetWasmFramePointerFromStackPointer(GetSP()); SyncRegDisplayToCurrentContext(pRD); @@ -1514,6 +1518,7 @@ RtlVirtualUnwind ( PTR_BYTE pUnwindData = dac_cast(FunctionEntry->UnwindData + ImageBase); ContextRecord->InterpreterSP = fp + DecodeULEB128AsU32(&pUnwindData); // Unwind the frame pointer to the callers stack pointer ContextRecord->InterpreterIP = GetWasmVirtualIPFromStackPointer(ContextRecord->InterpreterSP); + ContextRecord->InterpreterFP = GetWasmFramePointerFromStackPointer(ContextRecord->InterpreterSP); } else { @@ -1521,6 +1526,7 @@ RtlVirtualUnwind ( _ASSERTE(FALSE); ContextRecord->InterpreterIP = 0; ContextRecord->InterpreterSP = 0; + ContextRecord->InterpreterFP = 0; } return nullptr; diff --git a/src/tests/Common/CLRTest.CrossGen.targets b/src/tests/Common/CLRTest.CrossGen.targets index 3cdbf07518e9e8..06439b9f381982 100644 --- a/src/tests/Common/CLRTest.CrossGen.targets +++ b/src/tests/Common/CLRTest.CrossGen.targets @@ -130,6 +130,10 @@ if [ ! -z ${RunCrossGen2+x} ]%3B then echo --targetos:$(TargetOS) >> "$__ResponseFile" echo --verify-type-and-field-layout >> "$__ResponseFile" echo --method-layout:random >> "$__ResponseFile" + if [ "$(CrossGen2OutputFormat)" == "wasm" ]; then + echo --codegenopt:JitWasmNyiToR2RUnsupported=1 >> "$__ResponseFile" + echo --codegenopt:JitWasmSimdNyiToR2RUnsupported=1 >> "$__ResponseFile" + fi if [ ! -z ${CrossGen2SynthesizePgo+x} ]%3B then echo --synthesize-random-mibc >> "$__ResponseFile" echo --embed-pgo-data >> "$__ResponseFile" @@ -339,6 +343,10 @@ if defined RunCrossGen2 ( echo --targetos:$(TargetOS)>>!__ResponseFile! echo --verify-type-and-field-layout>>!__ResponseFile! echo --method-layout:random>>!__ResponseFile! + if "$(CrossGen2OutputFormat)"=="wasm" ( + echo "--codegenopt:JitWasmNyiToR2RUnsupported=1">>!__ResponseFile! + echo "--codegenopt:JitWasmSimdNyiToR2RUnsupported=1">>!__ResponseFile! + ) if not "$(CrossGen2OutputFormat)"=="" ( echo -f:$(CrossGen2OutputFormat)>>!__ResponseFile! ) From f61912dc9dead67b2be74ac5a23253358a05964f Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 30 Jun 2026 14:54:48 -0700 Subject: [PATCH 2/2] Fix OOB crash in TransitionFrame::UpdateRegDisplay_Impl on wasm The R2R FP stackwalking change started decoding the frame pointer from TransitionFrame::GetSP() unconditionally. For transitions out of interpreted code GetSP() returns the address just past the TransitionBlock (the outgoing argument area) rather than a real R2R stack pointer, so feeding it into GetWasmFramePointerFromStackPointer followed a garbage chained pointer and trapped with 'memory access out of bounds' during exception dispatch. Only decode InterpreterFP when the TransitionBlock records a trusted R2R stack pointer (both m_ReturnAddress and m_StackPointer set), mirroring the trusted-SP condition in TransitionFrame::GetSP. Otherwise leave it 0, which restores the previous safe behavior for interpreter transitions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/vm/wasm/helpers.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index 277455dce4ff0f..9fd937234028d7 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -527,9 +527,19 @@ void TransitionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFl pRD->IsCallerContextValid = FALSE; pRD->pCurrentContext->InterpreterIP = GetReturnAddress(); - pRD->pCurrentContext->InterpreterSP = GetSP(); - // Recover the frame pointer so GC-info readers can locate frame slots. - pRD->pCurrentContext->InterpreterFP = GetWasmFramePointerFromStackPointer(GetSP()); + TADDR sp = GetSP(); + pRD->pCurrentContext->InterpreterSP = sp; + + // Recover the frame pointer so GC-info readers can locate frame slots, but only when + // the stack pointer refers to a real R2R frame. When this frame represents a transition + // out of interpreted code, GetSP() returns the address just past the TransitionBlock (the + // outgoing argument area) rather than a frame pointer (see TransitionFrame::GetSP). Decoding + // that would dereference arbitrary memory, so leave the frame pointer as 0 in that case. + TransitionBlock* pTransitionBlock = (TransitionBlock*)GetTransitionBlock(); + bool hasR2RStackPointer = (pTransitionBlock != NULL) && + (pTransitionBlock->m_ReturnAddress != 0) && + (pTransitionBlock->m_StackPointer != 0); + pRD->pCurrentContext->InterpreterFP = hasR2RStackPointer ? GetWasmFramePointerFromStackPointer(sp) : 0; SyncRegDisplayToCurrentContext(pRD);