Skip to content

cDAC: recover exact generic context for transition frames - #136673

Closed
lewing wants to merge 3 commits into
mainfrom
lewing-cdac-transition-frame-generic-context
Closed

lewing wants to merge 3 commits into
mainfrom
lewing-cdac-transition-frame-generic-context

Conversation

@lewing

@lewing lewing commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #136667, as requested in #136667 (comment): the cDAC should keep working when ReadyToRun call targets have not loaded their signature types.

Background

#135805 lets a ReadyToRun call target that has an import or exact-entry GCRefMap skip loading its signature value types. #136667 changed native TransitionFrame::GetParamTypeArg to find the generic-context argument through the frame's GCRefMap. The ArgIterator path needs the return type, which may not be loaded.

The cDAC's IStackWalk.GetExactGenericArgsToken returned TargetPointer.Null for every non-frameless frame. Native CrawlFrame::GetExactGenericArgsToken returns a token for transition frames.

Change

For a capital-F transition frame (Frame/SkippedFrame state), GetExactGenericArgsToken now follows native CrawlFrame::GetExactGenericArgsToken:

  • ThisPtr context: reads the receiver from the transition block's first argument slot and returns its MethodTable. It returns Null on x64, where native CrawlFrame::GetThisPointer does not recover a transition frame's receiver.
  • InstArgMethodDesc / InstArgMethodTable context: reads the MethodParam/TypeParam slot from the frame's GCRefMap without inspecting the signature:
    • ExternalMethodFrame uses FindGCRefMap on its Indirection.
    • PrestubMethodFrame and CallCountingHelperFrame use the same method-entry map resolution as GC scanning.
  • No map, or a map without an instantiation token: decodes the same tokens from CallingConvention.TryComputeArgGCRefMapBlob. It returns Null instead of throwing when the target lacks the signature layout.
  • StubDispatchFrame: uses only the signature path, as native does, because dispatch maps omit the instantiation argument.

The logic lives in FrameHelpers, alongside the other per-frame helpers. GcScanner's transition-block slot-address helpers moved there too, so both callers share them. docs/design/datacontracts/StackWalk.md replaces the note that explicit FramedMethodFrame recovery was out of scope with a description of the algorithm.

GC scanning already had parity: GcScanner uses the frame's GCRefMap before the signature for these frame types. It is unchanged apart from the helper move.

Current reach: no DAC-shaped API exposes this, natively or in the cDAC. Native CrawlFrame::GetExactGenericArgsToken handles transition frames, but its DAC callers don't pass them a transition frame:

  • DacDbi fills the token only for kManagedStackFrame. It reports transition frames as kExplicitFrame/cStubFrame internal frames, which carry no token. The cDAC DacDbiImpl matches this.
  • Native IXCLRDataFrame2::GetExactGenericArgsToken, which dump enumeration uses, reads the TYPECTXT_ILNUM variable from JIT debug info rather than calling CrawlFrame. The cDAC ClrDataFrame still returns E_NOTIMPL.

The in-process debugger (debug/ee/frameinfo.cpp) does call CrawlFrame::GetExactGenericArgsToken on transition frames, and #136667 already covers it. This change makes the cDAC IStackWalk contract match CrawlFrame. That keeps the contract correct in this restricted ReadyToRun scenario for any cDAC consumer that later surfaces transition frames, but it does not change current DAC-visible behavior.

Validation

  • ./dotnet.sh build /t:Build;Test src/native/managed/cdac/tests/UnitTests/Microsoft.Diagnostics.DataContractReader.Tests.csproj -c Release -p:RuntimeConfiguration=Checked -p:LibrariesConfiguration=Release: passed, 3938 total, 0 failed (osx-arm64). The run includes 33 new StackWalkTests cases:
    • a GCRefMap token with no signature available, for each map-backed frame type;
    • signature fallback when the map is missing or lacks an instantiation token, including StubDispatchFrame ignoring its map and resolving its MethodDesc from the representative MethodTable;
    • Null when neither source has the token, the method isn't shared, or the frame has no MethodDesc;
    • the this-derived context on x86 versus x64;
    • wasm and ARM64 slot mapping, where ARM64's FirstGCRefMapSlot (X8) differs from the argument registers;
    • a real wasm CreateStackWalk reaching IStackWalk.GetExactGenericArgsToken through a Frame-state handle: a transition frame returns its token, an InlinedCallFrame returns Null.
  • Mutation checks: with the GCRefMap branch disabled, exactly the 5 x86 map-only cases fail; with StackWalk_1's Frame routing removed, only the stack-walk transition-frame case fails.

Note

This PR description and the code changes were generated with GitHub Copilot.

Mirror native CrawlFrame::GetExactGenericArgsToken for capital-F transition
frames. Use the receiver's MethodTable for a this-derived context (except on
x64, where native does not recover it). For an instantiation argument, read
the MethodParam/TypeParam slot from the ExternalMethodFrame,
PrestubMethodFrame or CallCountingHelperFrame GCRefMap before falling back to
the signature-based layout, so a ReadyToRun target with unloaded signature
types still yields the token. StubDispatchFrame keeps using the signature, as
native does.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 5 pipeline(s).
11 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: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

@lewing
lewing requested a review from davidwrighton October 30, 2026 01:01
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@davidwrighton

Copy link
Copy Markdown
Member

Hmm. If there are no consequences of a bug does it need squishing? @max-charlamb @rcj1

@lewing

lewing commented Oct 30, 2026

Copy link
Copy Markdown
Member Author

I'm still digging a bit

…stub dispatch

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lewing

lewing commented Oct 30, 2026

Copy link
Copy Markdown
Member Author

Dug in: there are no consequences today. Native DacDbi never fills exactGenericArgsToken for transition frames; it reports them as cStubFrame internal frames. Native IXCLRDataFrame2::GetExactGenericArgsToken, which dump enumeration uses, reads TYPECTXT_ILNUM from JIT debug info rather than CrawlFrame. The cDAC matches both, so no DAC or DBI client can observe the restricted ReadyToRun case. The only CrawlFrame consumer of transition frames is the runtime's own debugger stack walk (debug/ee/frameinfo.cpp), which #136667 already fixes.

This PR only brings the cDAC IStackWalk contract in line with CrawlFrame for a future consumer, so I'm closing it. It can be revived as groundwork if @max-charlamb / @rcj1 prefer.

Note

This comment was drafted with GitHub Copilot.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants