Fix interpreter EH when exception escapes filter funclet#120811
Merged
janvorli merged 3 commits intodotnet:mainfrom Oct 17, 2025
Merged
Fix interpreter EH when exception escapes filter funclet#120811janvorli merged 3 commits intodotnet:mainfrom
janvorli merged 3 commits intodotnet:mainfrom
Conversation
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.
Contributor
|
Tagging subscribers to this area: @BrzVlad, @janvorli, @kg |
Contributor
There was a problem hiding this comment.
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
SfiNextWorkerto 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 |
src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/AsmOffsets.cs
Show resolved
Hide resolved
davidwrighton
approved these changes
Oct 16, 2025
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.