[clr-ios] Fix exception handling for debugger interception#126955
[clr-ios] Fix exception handling for debugger interception#126955kotlarmilos wants to merge 10 commits intodotnet:mainfrom
Conversation
…ogic for clarity in DebuggerEval handling
…r-funceval # Conflicts: # src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DebuggerEval.cs
…ec.h include - Restore m_bypassAddress/m_bypassOpcode in EX_CATCH before EX_RETHROW so bypass state is not corrupted if func-eval throws - Remove unused interpexec.h include from debugger.cpp (GetInterpThreadContext is declared on Thread, not in interpexec.h) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Skip controlPC -1 adjustment for interpreter methods (bytecode IP already points to the correct instruction boundary) - Detect native transitions past last interpreter frame in SfiNext - Skip InterpreterFrame chain entries when checking for unhandled exceptions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Fixes interpreter-related exception/debugger interaction issues (notably exception interception via SetIP) by adjusting EH stack-walking semantics for interpreted frames and enabling interpreter breakpoint handling to resume from debugger-modified IP, with supporting debugger/func-eval plumbing.
Changes:
- Adjust EH stack-walk control PC handling and native-transition detection for interpreted frames, including skipping
InterpreterFrameentries when checking for unhandled exceptions. - Update interpreter breakpoint handling to run pending interpreter func-evals and honor debugger
SetIPchanges when resuming execution. - Rename the func-eval flag from “during exception” to “uses hijack” across debugger + CDAC surface area.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DebuggerEval.cs | Renames CDAC-exposed flag to EvalUsesHijack in the reader contract. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs | Uses EvalUsesHijack to decide whether to update unwind context from DebuggerEval. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/BaseFrameHandler.cs | Uses EvalUsesHijack to decide whether to update unwind context from DebuggerEval. |
| src/coreclr/vm/interpexec.cpp | Returns updated bytecode IP from breakpoint callback and executes pending interpreter func-evals. |
| src/coreclr/vm/exceptionhandling.cpp | Interpreter-aware EH stackwalk changes (controlPC adjust, transition detection, frame-chain skipping). |
| src/coreclr/vm/dbginterface.h | Adds interpreter-specific debugger hook to execute pending interpreter func-evals. |
| src/coreclr/vm/datadescriptor/datadescriptor.inc | Updates CDAC data descriptor field name to EvalUsesHijack. |
| src/coreclr/debug/ee/funceval.cpp | Switches logic from m_evalDuringException to m_evalUsesHijack. |
| src/coreclr/debug/ee/debugger.h | Renames DebuggerEval state to m_evalUsesHijack and adjusts Init behavior. |
| src/coreclr/debug/ee/debugger.cpp | Interpreter-aware func-eval setup + new ExecutePendingInterpreterFuncEval implementation; updates to new flag name. |
| Target.TypeInfo type = target.GetTypeInfo(DataType.DebuggerEval); | ||
| TargetContext = address + (ulong)type.Fields[nameof(TargetContext)].Offset; | ||
| EvalDuringException = target.ReadField<byte>(address, type, nameof(EvalDuringException)) != 0; | ||
| EvalUsesHijack = target.ReadField<byte>(address, type, nameof(EvalUsesHijack)) != 0; |
There was a problem hiding this comment.
The CDAC field rename from EvalDuringException to EvalUsesHijack is a breaking contract change for DataContractReader when analyzing dumps from older runtimes (the old field name won’t exist, and ReadField will throw). To preserve backward compatibility, read EvalUsesHijack via ReadFieldOrDefault and, if it’s missing, fall back to reading EvalDuringException (also via ReadFieldOrDefault) and derive EvalUsesHijack = !EvalDuringException.
| EvalUsesHijack = target.ReadField<byte>(address, type, nameof(EvalUsesHijack)) != 0; | |
| byte? evalUsesHijack = target.ReadFieldOrDefault<byte>(address, type, nameof(EvalUsesHijack)); | |
| if (evalUsesHijack is not null) | |
| { | |
| EvalUsesHijack = evalUsesHijack != 0; | |
| } | |
| else | |
| { | |
| byte? evalDuringException = target.ReadFieldOrDefault<byte>(address, type, "EvalDuringException"); | |
| EvalUsesHijack = evalDuringException is null || evalDuringException == 0; | |
| } |
|
Tagging subscribers to 'os-ios': @vitek-karas, @kotlarmilos, @steveisok, @akoeplinger |
Description
Fix exception handling paths for the interpreter debugger: skip the
STACKWALK_CONTROLPC_ADJUST_OFFSETadjustment for interpreter methods since the bytecode IP already points at the correct instruction boundary, detect interpreter-to-native transitions past the last interpreter frame inSfiNextWorker, and skipInterpreterFrameentries in the unhandled-exception frame chain walk.Follow-up to #126576
This fixes the following interpreter debugger test failure:
Exceptions.InterceptTest