dotnet / dotnet/runtime

Debugger::FuncEvalAbort and Thread::UserAbort have incompatible GC contracts

Open
#133,536 1 comment 1 reaction 2 assignees Claimed by @jkoritzinsky View on GitHub
area-Diagnostics-coreclr
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### Description

`Debugger::FuncEvalAbort` declares `GC_NOTRIGGER`, but directly calls `Thread::UserAbort`, which declares `GC_TRIGGERS`. On a Checked CoreCLR build, aborting an active debugger function evaluation hits a GC contract violation before the abort completes. The debuggee terminates, and the debugger subsequently receives `CORDBG_E_PROCESS_TERMINATED`.

`Debugger::FuncEvalRudeAbort` has the same caller/callee mismatch in source. The failures described below were observed on the **safe-abort** path; rude-abort behavior has not been established by these results.

### Reproduction Steps

1. Configure the MDbg/xUnit debugger test harness to use a private Windows x64 **Checked** CoreCLR through CoreRun, with the matching `mscordbi.dll` and runtime symbols, rather than an installed runtime.
2. Run either existing test with the CoreCLR/ProjectK configuration:
- `FuncEval.InfiniteLoopASyncAbortTest(config: projectk.prebuilt.12.0.0)`
- `FuncEval.EscapeSecondPass(config: projectk.prebuilt.12.0.0)`
3. The tests start a function evaluation, continue the debuggee, and asynchronously call `ICorDebugEval::Abort` while the evaluation is active. Both hit the contract violation below.

These failures were captured in a local baseline run before the pending func-eval invocation conversion, with contract checking left enabled.

### Expected behavior

The abort request should follow the existing abort/timeout protocol without violating GC contracts or terminating the debuggee. These test scenarios expect an `EvalComplete` callback.

### Actual behavior

Both tests report the same assertion. Excerpt with machine-specific paths and addresses removed:

```text
CONTRACT VIOLATION by Thread::UserAbort at src\coreclr\vm\threadsuspend.cpp:1160

GC_TRIGGERS encountered in a GC_NOTRIGGER scope

CONTRACT in Thread::UserAbort at threadsuspend.cpp:1160
VIOLATED--> CONTRACT in Debugger::FuncEvalAbort at debugger.cpp:14340
CONTRACT in Debugger::HandleIPCEvent at debugger.cpp:10063
CONTRACT in DebuggerRCThread::HandleRSEA at rcthread.cpp:784
CONTRACT in DebuggerRCThread::MainLoop at rcthread.cpp:854
CONTRACT in DebuggerRCThread::ThreadProc at rcthread.cpp:584
```

Abbreviated native stack:

```text
CORECLR! CONTRACT_ASSERT
CORECLR! EEContract::DoChecks
CORECLR! Thread::UserAbort
CORECLR! Debugger::FuncEvalAbort
CORECLR! Debugger::HandleIPCEvent
CORECLR! HandleIPCEventWrapper
CORECLR! DebuggerRCThread::HandleRSEA
CORECLR! DebuggerRCThread::MainLoop
CORECLR! DebuggerRCThread::ThreadProc
```

The abort worker then reports:

```text
ICDEval::Abort returned an unexpected HResult:
System.Runtime.InteropServices.COMException (0x80131301):
Process was terminated. (CORDBG_E_PROCESS_TERMINATED)
```

### Regression?

Unknown when introduced; no comparison with a previous released runtime has been performed. The failure predates the pending func-eval invocation conversion. The baseline's `debugger.cpp` and `threadsuspend.cpp` are identical to the versions in upstream commit `80a8cdbdb9c36ccab97374afcb2f0491ae58a94d`, linked below.

### Known Workarounds

None established. The failures were not suppressed or bypassed by disabling contracts.

### Configuration

- Windows, x64.
- Local Checked CoreCLR baseline; System.Private.CoreLib product version `12.0.0-dev`.
- CoreCLR, DBI, and DAC loaded from the same pinned baseline; MDbg/xUnit CoreCLR/ProjectK configuration.
- Baseline runtime commit: `491ca5dd30a9991873d2554eccb4e12af355f1de` (local development commit; the two relevant native source files are unchanged from the public upstream commit above).
- Whether this reproduces on other platforms or configurations has not been determined.

### Other information

The incompatible contracts and calls are visible in the public source:

| Method | Contract and call |
| --- | --- |
| [`Debugger::FuncEvalAbort`](https://github.com/dotnet/runtime/blob/80a8cdbdb9c36ccab97374afcb2f0491ae58a94d/src/coreclr/debug/ee/debugger.cpp#L14330-L14382) | `GC_NOTRIGGER`; calls `UserAbort(EEPolicy::TA_Safe, FUNC_EVAL_DEFAULT_TIMEOUT_VALUE)`. |
| [`Thread::UserAbort`](https://github.com/dotnet/runtime/blob/80a8cdbdb9c36ccab97374afcb2f0491ae58a94d/src/coreclr/vm/threadsuspend.cpp#L1152-L1205) | `GC_TRIGGERS; // For GetXxxException`. |
| [`Debugger::FuncEvalRudeAbort`](https://github.com/dotnet/runtime/blob/80a8cdbdb9c36ccab97374afcb2f0491ae58a94d/src/coreclr/debug/ee/debugger.cpp#L14393-L14449) | `GC_NOTRIGGER`; calls `UserAbort(EEPolicy::TA_Rude, FUNC_EVAL_DEFAULT_TIMEOUT_VALUE)`. |

The fix should reconcile the abort contracts with the debugger helper-thread constraints, including the rude-abort sibling, rather than simply suppressing the assertion. `UserAbort` also has a self-abort path that materializes and raises an exception, so weakening its GC contract needs an audit of the supported paths. Regression coverage should exercise safe and rude func-eval aborts with Checked contracts enabled.

> [!NOTE]
> This issue was generated with GitHub Copilot from source inspection and captured local debugger-test results.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.