Skip to content

Conversation

janvorli
Copy link
Member

The EH was crashing with interpreter when there was an unhandled exception in a filter funclet. It was asserting on x64 windows due to SSP not being correct, but the actual issue was more involved than just ensuring the SSP was correct.
The problem happens due to the way the stack walking handles unwinding from the first interpreted frame. To better explain what was wrong, let me describe hat mechanism. That transition sets the IP to a special value InterpreterFrame::DummyCallerIP and SP to the address of the InterpreterFrame. The frame state is SFITER_NATIVE_MARKER_FRAME. When the stack frame iterator moves to the next frame, the state is SFITER_FRAME_FUNCTION, the explicit frame is the InterpreterFrame and the REGDISPLAY is set to the native context that was in REGDISPLAY before we switched to iterating over the interpreter frames.

The SfiNext in the EH needs to handle the case when it gets the SFITER_NATIVE_MARKER_FRAME described above. It can either be transitioning to a managed caller of the interpreted core or it could have been a call to a funclet that doesn't have transition frame stored in the intepreter frame, so it doesn't have any context to move to. This doesn't cause any problem for catch or finally funclets, as that means a collided unwind that is detected and the REGDISPLAY is overwritten by the REGDISPLAY from the stack frame iterator of the exception that we've collided with.
The only problem is for filter funclet, where we need to return that frame from the SfiNext and the EH uses its SP in the second pass to know when the 2nd pass is finished.
The bug was that we have moved the stack frame iterator from the SFITER_NATIVE_MARKER_FRAME, which got REGDISPLAY with SP that was actually smaller than the SP of the last reported interpreted frame. That caused the 2nd pass to terminate prematurely on the very first frame, thinking it found the target frame. Thus finallys were not called and also the context was wrong. That lead to the assert in CallCatchFunclet.

There was also a secondary problem that we were not saving and restoring SSP when the state moved from the special state with IP set to InterpreterFrame::DummyCallerIP and the SSP was the SSP in the InterpExecMethod, instead of the SSP in the DispatchManagedException that the rest of the context was pointing to.

This change fixes both these issues and unhandled exceptions in filter funclets are correctly swallowed now.

The EH was crashing with interpreter when there was an unhandled
exception in a filter funclet. It was asserting on x64 windows due
to SSP not being correct, but the actual issue was more involved than
just ensuring the SSP was correct.
The problem happens due to the way the stack walking handles unwinding
from the first interpreted frame. To better explain what was wrong, let
me describe hat mechanism. That transition sets the IP to a
special value InterpreterFrame::DummyCallerIP and SP to the address of
the InterpreterFrame. The frame state is SFITER_NATIVE_MARKER_FRAME.
When the stack frame iterator moves to the next frame, the state is
SFITER_FRAME_FUNCTION, the explicit frame is the InterpreterFrame and
the REGDISPLAY is set to the native context that was in REGDISPLAY
before we switched to iterating over the interpreter frames.

The SfiNext in the EH needs to handle the case when it gets the
SFITER_NATIVE_MARKER_FRAME described above. It can either be
transitioning to a managed caller of the interpreted core or it could
have been a call to a funclet that doesn't have transition frame stored
in the intepreter frame, so it doesn't have any context to move to.
This doesn't cause any problem for catch or finally funclets, as that
means a collided unwind that is detected and the REGDISPLAY is
overwritten by the REGDISPLAY from the stack frame iterator of the
exception that we've collided with.
The only problem is for filter funclet, where we need to return that
frame from the SfiNext and the EH uses its SP in the second pass to know
when the 2nd pass is finished.
The bug was that we have moved the stack frame iterator from the
SFITER_NATIVE_MARKER_FRAME, which got REGDISPLAY with SP that was
actually smaller than the SP of the last reported interpreted frame.
That caused the 2nd pass to terminate prematurely on the very first
frame, thinking it found the target frame. Thus finallys were not called
and also the context was wrong. That lead to the assert in
CallCatchFunclet.

There was also a secondary problem that we were not saving and restoring
SSP when the state moved from the special state with IP set to
InterpreterFrame::DummyCallerIP and the SSP was the SSP in the
InterpExecMethod, instead of the SSP in the DispatchManagedException
that the rest of the context was pointing to.

This change fixes both these issues and unhandled exceptions in filter
funclets are correctly swallowed now.
@janvorli janvorli added this to the 11.0.0 milestone Oct 16, 2025
@janvorli janvorli self-assigned this Oct 16, 2025
@Copilot Copilot AI review requested due to automatic review settings October 16, 2025 16:55
Copy link
Contributor

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

Copy link
Contributor

@Copilot 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 fixes a crash in the exception handling (EH) system when an unhandled exception escapes from a filter funclet in the interpreter. The issue involved incorrect stack pointer handling during unwinding and missing SSP (shadow stack pointer) save/restore logic.

Key Changes:

  • Added SSP save/restore for interpreted frames on x64 Windows to maintain correct shadow stack state across frame transitions
  • Fixed premature termination of the second pass of exception handling by properly detecting when unwinding from an interpreted filter funclet
  • Corrected control flow logic in SfiNextWorker to handle cases where filter funclets have no transition frame

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/coreclr/vm/stackwalk.h Adds storage for SSP in StackFrameIterator for x64 Windows with interpreter enabled
src/coreclr/vm/stackwalk.cpp Implements SSP save/restore logic when transitioning between interpreted and native frames
src/coreclr/vm/exceptionhandling.cpp Fixes unwinding logic for interpreted filter funclets and corrects frame iteration
src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/AsmOffsets.cs Updates structure size constants to account for new SSP field

@janvorli janvorli merged commit 335820f into dotnet:main Oct 17, 2025
97 checks passed
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.

2 participants