-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Description
The IsTailCall function in src/coreclr/debug/ee/controller.cpp assumes that given a call IP, we can immediately determine the destination MethodDesc that will be invoked. However, this assumption breaks for cross ReadyToRun (R2R) image tail calls.
Specifically, if ReadyToRun image A has method Foo1() which tail-calls to Foo2() in image B, then Foo1() may have a call to one of the tail-call helpers at the end that is indirected through an ExternalMethodFixup. The StubManager for ExternalMethodFixup does not immediately figure out the destination MethodDesc - it uses callbacks that pass content to TraceManager() to determine where execution will go next.
This means the debugger may not correctly handle stepping through tail calls that cross ReadyToRun image boundaries.
Background
This issue was discovered as part of PR #123640, which fixed an access violation (AV) in IsTailCall when trace.GetAddress() returns NULL. The AV was fixed by adding a NULL check before calling GetNativeCodeMethodDesc(), but the underlying stepping correctness issue remains.
Related changes:
- PR Update stepping through ExternalMethodFixup under the Debugger #108942 introduced
TRACE_MULTICAST_DELEGATE_HELPERtrace type - PR Update stepping through MulticastDelegate under the Debugger #108414 introduced
TRACE_EXTERNAL_METHOD_FIXUPtrace type
These trace types set address = NULL by design because:
TRACE_UNJITTED_METHOD- TheMethodDescfield describes a method that will be jitted and executed in the future, code address not yet known.TRACE_MULTICAST_DELEGATE_HELPERandTRACE_EXTERNAL_METHOD_FIXUP- A callback can be turned on and the content of that callback can be passed toTraceManager()to determine where execution will go next.- Other trace types - The address is some IP that will be executed in the future where a breakpoint can be placed and then further actions taken from there.
Current Behavior
When stepping through a tail call that crosses R2R image boundaries (via ExternalMethodFixup), the debugger may not correctly step into the target method because IsTailCall cannot determine the destination MethodDesc at the time of the call.
Expected Behavior
The debugger should correctly step through tail calls regardless of whether they cross ReadyToRun image boundaries.
Affected Code
src/coreclr/debug/ee/controller.cpp-IsTailCallfunction (lines ~5820-5860)src/coreclr/vm/stubmgr.cpp-ExternalMethodFixupStubManager
Potential Solutions
This will require redesigning how IsTailCall determines the target method for calls that go through ExternalMethodFixup stubs. Possible approaches include:
- Enhancing the
StubManagerto provide target method information forExternalMethodFixup - Using the callback mechanism (
TraceManager()) withinIsTailCallto resolve the target - Special-casing R2R cross-image tail calls in the stepping logic
References
- PR Fix AV in IsTailCall by checking for NULL trace address #123640 - Fix AV in IsTailCall by checking for NULL trace address
- PR Update stepping through ExternalMethodFixup under the Debugger #108942 - Introduced
TRACE_MULTICAST_DELEGATE_HELPERtrace type - PR Update stepping through MulticastDelegate under the Debugger #108414 - Introduced
TRACE_EXTERNAL_METHOD_FIXUPtrace type - Comment discussion