Skip to content

[Interpreter] Install the resume-after-catch handler in ThrowControlForThread - #135533

Draft
pcshrosbree wants to merge 3 commits into
dotnet:mainfrom
pcshrosbree:interp-abort-throw-control
Draft

pcshrosbree wants to merge 3 commits into
dotnet:mainfrom
pcshrosbree:interp-abort-throw-control

Conversation

@pcshrosbree

@pcshrosbree pcshrosbree commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #135532.

Draft, stacked on #135939. The first two commits are #135939; only the last commit belongs to this PR. I'll rebase once #135939 is in.

ThrowControlForThread raises the thread abort that compiled code raises again at the end of a catch handler. Before this change, when an interpreted frame further up caught the abort, the ResumeAfterCatchException used to resume that frame encountered the managed frames present when the thread was redirected. Now ThrowControlForThread installs the resume-after-catch handler, like IL_Throw and HandleManagedFault.

On Windows x64 with shadow stacks, the handler's rethrow searched for a "precise" SSP on the assumption that the slot below the SSP holds a return address in the context's frame. A redirected thread's frame was interrupted rather than stopped at a call, and its recorded SSP is exact already, so the search moved it past the correct position. The new INSTALL_RESUME_AFTER_CATCH_HANDLER_WITH_INTERRUPTED_CONTEXT marks the context as interrupted, so that RethrowResumeAfterCatchExceptionSkipManagedFrames uses the recorded SSP without searching for another one. The same mismatch caused #135530, fixed by #135531.

Tests: a CancelItself scenario in src/tests/Regressions/coreclr/GitHub_135463 (the action cancels its own token), and ControlledExecutionTests.CancelItselfOutsideOfTryCatchFinally enabled again.

Validation of the new scenario (Checked; linux-x64, osx-arm64, windows-x64 with CET enabled):

#135939 alone #135939 + this PR
JIT pass pass
DOTNET_InterpMode=1, with and without TieredCompilation=0 linux/osx: terminate with ResumeAfterCatchException; windows: assert pass
DOTNET_InterpMode 2 and 3 pass pass

Additional validation:

  • Under the JIT, the new scenario also passes with TieredCompilation=0, JITMinOpts, JitStress 1 and 2, ReadyToRun=0, and GCStress 0x3 and 0xC on all three platforms. Its TestEnclosingTry is skipped under the JIT, as in [Interpreter] Raise a pending thread abort again at the resume point after a catch #135939.
  • With this PR, all 10 ControlledExecutionTests pass when run as a class in one process, and CancelItselfOutsideOfTryCatchFinally also passes when run alone in its own process. Both runs pass on all three platforms under the JIT and DOTNET_InterpMode 1, 2 and 3.
  • With [Interpreter] Raise a pending thread abort again at the resume point after a catch #135939 alone, CancelItselfOutsideOfTryCatchFinally fails under mode 1: it terminates with ResumeAfterCatchException on linux-x64 and osx-arm64, and fails a Checked consistency check on windows-x64.
  • With the handler but without the interrupted-context marking, windows-x64 asserts *(ULONG64*)targetSSP == pContext->Rip in ExecuteFunctionBelowContext.

Note

This PR was prepared with AI assistance (Anthropic Claude, checked by OpenAI Codex) at my request. I reviewed it before posting it.

@github-actions github-actions Bot added the area-ExceptionHandling-coreclr only use for closed issues label Oct 9, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Oct 9, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

When interpreted code catches a ThreadAbortException, raise the abort
again where the execution resumes after the catch, as
EECodeManager::ResumeAfterCatch does for compiled code:

- Set up Thread::m_OSContext for the resume point before calling
  COMPlusCheckForAbort, which expects its caller to do that (dotnet#135463).
- Dispatch the abort from a try block of its own, outside the C++ catch
  block for ResumeAfterCatchException, so that the execution can resume
  again when the abort is caught again in the same InterpExecMethod.
- Make the catch leave island the resume point. Within a leave, every
  edge into a catch leave island uses INTOP_LEAVE_CATCH; every other
  edge, including the final edge to the leave target, uses INTOP_BR.
  The catch therefore ends with INTOP_LEAVE_CATCH to its island. The
  island is inside every try region that encloses the catch's try
  region. The leave target need not be inside all of those regions
  (dotnet#135871).
- Mark the frame faulting before the dispatch, as INTOP_THROW does, so
  that the handlers are looked up at the resume IP itself rather than
  at the instruction before it.

Add the test GitHub_135463. Its two dotnet#135871 cases run only when the
test is interpreted, because jitted code gets them wrong until dotnet#135637
is fixed. Enable
ControlledExecutionTests.CancelInTryAndExitCatchNormally again, and stop
skipping test135530 under the interpreter.

Fixes dotnet#135463
Fixes dotnet#135871
Remove the goto at the end of the catch of ResumeAfterCatchException,
which only jumped to the next statement. Move the block that raises the
abort again to just after the resume code, so that a pending abort
falls through into it instead of jumping to a label after EXIT_FRAME.
No change in behavior.
When compiled code raises a thread abort again at the end of a catch
handler and an interpreted frame catches it, the ResumeAfterCatchException
that resumes that frame ran into the managed frames ThrowControlForThread
was redirected from. Install the handler there, as IL_Throw and
HandleManagedFault do.

On Windows x64 with shadow stacks, the frame of a redirected thread was
interrupted rather than stopped at a call and its recorded SSP is exact,
so tell the rethrow not to search for a more precise one.

Enable ControlledExecutionTests.CancelItselfOutsideOfTryCatchFinally again
and add the scenario to the GitHub_135463 regression test.

Fixes dotnet#135532

This branch has not been deployed

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

Labels

area-CodeGen-Interpreter-coreclr community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Interpreter] ResumeAfterCatchException escapes ThrowControlForThread when compiled code raises a thread abort again and an interpreted frame catches it

2 participants