Skip to content

[cDAC] Add interpreter support for stack walking and diagnostics#126520

Open
max-charlamb wants to merge 17 commits into
dotnet:mainfrom
max-charlamb:dev/max-charlamb/cdac-interpreter-support
Open

[cDAC] Add interpreter support for stack walking and diagnostics#126520
max-charlamb wants to merge 17 commits into
dotnet:mainfrom
max-charlamb:dev/max-charlamb/cdac-interpreter-support

Conversation

@max-charlamb
Copy link
Copy Markdown
Member

@max-charlamb max-charlamb commented Apr 3, 2026

Summary

Adds interpreter support to the cDAC (contract-based Data Access Component), enabling diagnostic tools to correctly walk stacks containing interpreter frames, resolve interpreter precodes, and retrieve method information for interpreted methods.

Changes

Native Data Descriptors

  • Added interpreter type descriptors to datadescriptor.inc: InterpreterRealCodeHeader, InterpreterPrecodeData, InterpByteCodeStart, InterpMethod, InterpMethodContextFrame, InterpreterFrame
  • Added InterpreterFrame to frames.h explicit frame list for cDAC visibility

Execution Manager - Interpreter JIT Manager

  • New ExecutionManagerCore.InterpreterJitManager handles code address lookups for interpreter code heaps
  • GetCodeBlockHandle now searches interpreter code heaps when JIT heaps don't contain the address
  • GetMethodDesc resolves MethodDesc from interpreter code headers

Precode Resolution (GetInterpreterCodeFromInterpreterPrecodeIfPresent)

  • New API on IPrecodeStubs matching the native DAC pattern: each call site resolves interpreter precodes before passing addresses to ExecutionManager
  • Passthrough semantics: returns original address if not an interpreter precode
  • NOTHROW contract via VirtualReadException catch
  • Applied at 4 call sites: GetMethodDescData, CopyNativeCodeVersionToReJitData, GetTieredVersions, GetILAddressMap

Stack Walking

  • FrameIterator handles InterpreterFrame - extracts MethodDesc and native code pointer from InterpMethodContextFrame
  • StackWalk_1 resolves interpreter frames during enumeration

RuntimeTypeSystem

  • MethodValidation updated to handle interpreter method descriptors (IsInterpreterStub flag, chunk validation)

Documentation

  • Updated ExecutionManager.md, PrecodeStubs.md, StackWalk.md with interpreter support details

Tests

  • Unit tests: ExecutionManagerTests (interpreter JIT manager), FrameIteratorTests (interpreter frame handling), PrecodeStubsTests (interpreter precode resolution), MethodDescTests (interpreter method validation)
  • Dump tests: InterpreterStackDumpTests - 3 integration tests using a mixed JIT/interpreter stack debuggee (InterpreterStack) that validates interleaved frame layout, precode resolution, and thread enumeration
  • All 1647 unit tests pass, all 358 dump tests pass (218 passed, 140 skipped)

This comment was marked as outdated.

Copilot AI review requested due to automatic review settings April 8, 2026 14:54
@max-charlamb max-charlamb force-pushed the dev/max-charlamb/cdac-interpreter-support branch from 7e1ec2a to cdaefe0 Compare April 8, 2026 14:54

This comment was marked as outdated.

Copilot AI review requested due to automatic review settings April 8, 2026 17:28

This comment was marked as outdated.

Copilot AI review requested due to automatic review settings April 8, 2026 20:29

This comment was marked as outdated.

This comment was marked as outdated.

@max-charlamb max-charlamb force-pushed the dev/max-charlamb/cdac-interpreter-support branch from 129584c to c19d421 Compare May 7, 2026 17:22
Copilot AI review requested due to automatic review settings May 7, 2026 17:28
@max-charlamb max-charlamb force-pushed the dev/max-charlamb/cdac-interpreter-support branch from c19d421 to d600dfb Compare May 7, 2026 17:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 67 out of 68 changed files in this pull request and generated 1 comment.

Comment thread src/native/managed/cdac/tests/DumpTests/DumpTests.targets
@max-charlamb max-charlamb force-pushed the dev/max-charlamb/cdac-interpreter-support branch from d600dfb to 57325e4 Compare May 7, 2026 17:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated 5 comments.

Comment thread src/native/managed/cdac/tests/PrecodeStubsTests.cs
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated 3 comments.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 62 out of 63 changed files in this pull request and generated 6 comments.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (3)

src/native/managed/cdac/tests/DumpTests/ISOSDacInterfaceTests.cs:1

  • ConditionalTheory is used but this file doesn’t import Microsoft.DotNet.XUnitExtensions, so it will fail to compile unless there’s a global using elsewhere. Add using Microsoft.DotNet.XUnitExtensions; (and optionally simplify SkipTestException usage) to make the dependency explicit.
    src/native/managed/cdac/tests/DumpTests/DumpTests.targets:1
  • _DebuggeeEnvVars is already escaped earlier, but it is escaped again when passed through nested MSBuild calls. This double-escaping prevents semicolon-separated env var lists (e.g. A=B;C=D) from being restored correctly when later unescaped for Exec EnvironmentVariables (you’d end up with %3B instead of ;). Pass the value through without re-escaping (or unescape before re-escaping) so multi-variable lists remain semicolon-delimited at the final Exec.
    src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/ARM64/ARM64Unwinder.cs:1
  • Similar to the ARM change, returning false on missing unwind info risks leaving the context unchanged if the caller doesn’t propagate this failure (most of the stack walk logic expects unwind errors to surface as exceptions). To avoid stalls or partial walks, either throw an InvalidOperationException here (as before, or consistent with other unwinders) or update the unwind call sites to handle a false return deterministically.
// Licensed to the .NET Foundation under one or more agreements.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated 5 comments.

max-charlamb and others added 16 commits May 12, 2026 17:08
Add cDAC contracts and implementations to walk interpreter frames during
stack walks and identify interpreter-managed methods. The cDAC stack
walker now mirrors the native DAC behavior:

* Yields `InterpreterFrame` as a runtime frame marker (pMD = NULL),
  matching the native DAC.
* Implements interpreter virtual unwind by following the
  `InterpMethodContextFrame.pParent` chain so each interpreted method
  is yielded as a frameless frame.
* Resolves the top `InterpMethodContextFrame` from the
  `InterpreterFrame` in the same way as the native runtime
  (`GetTopInterpMethodContextFrame`).
* Adds `InterpreterJitManager` for interpreter `CodeBlockHandle`
  resolution and a precode-stubs fallthrough that recognizes
  `InterpreterPrecode`.

Includes data descriptors for `InterpreterFrame`,
`InterpMethodContextFrame`, `InterpMethod`, `InterpByteCodeStart`,
`InterpreterPrecodeData`, and `InterpreterRealCodeHeader`, plus
documentation updates and unit tests covering the new contract behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add dump-test infrastructure for the interpreter scenarios and tests
that exercise the cDAC interpreter stack walker against real coredumps:

* `InterpreterStack` debuggee: single-threaded debuggee with a
  JIT->interpreter->JIT->interpreter call chain that triggers a
  `FailFast` from inside an interpreted method.
* `InterpreterStackDoubleWalk` debuggee: multi-threaded debuggee
  where a worker thread is parked deep in an interpreted call chain
  while the main thread captures the dump. This exercises walking a
  thread other than the crashing thread and asserts the cDAC walker
  does not produce duplicated `InterpreterFrame` markers.

The new dump tests verify the interleaved JIT/interpreter frame layout,
the absence of doubled `InterpreterFrame` markers, and that
`DumpTestStackWalker` adjacency assertions hold across the full
interpreter call chain.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When CheckForSkippedFrames clones the context for an interpreter IP and
calls Unwind, the interpreter's GetUnwindInfo returns TargetPointer.Null.
AMD64Unwinder already guards against this by checking for null and returning
false; ARM and ARM64 unwinders did not, and crashed reading RuntimeFunction
at address 0 (VirtualReadException at 0x00000000). Add the same null guard
to ARM and ARM64 to make all three platforms behaviorally consistent.

Also remove StackWalk_NoDoubledInterpreterFrames_WithDebuggerFilterContext
since it cannot run in CI (depends on a manually-collected cdb dump in the
local repo) and the scenario is invalid per native PR dotnet#126953.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Restore single ternary at StackWalk_1.cs (was needlessly verbose)
- Remove unrelated infinite-loop guard around Unwind
- Replace unicode arrows/em-dashes with -> and -- in code/comments/docs
- Drop direct line-number references in comments
- Make FrameHelpers access modifiers consistent: public for externally
  callable methods, private for helpers

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add SetContextToInterpMethodContextFrame and VirtualUnwindInterpreterCallFrame helpers in FrameHelpers, named to mirror native (frames.cpp / eetwain.cpp)

- Add IsFaulting field on InterpreterFrame and Stack field on InterpMethodContextFrame; thread Data.InterpreterFrame.Address through

- Add RawContextFlags abstraction across all platform contexts so the helper can OR in CONTEXT_EXCEPTION_ACTIVE for faulting top frames

- Gate IExecutionManager.IsFunclet on JitType.Interpreter so interpreter code (no native unwind info) reports false without throwing in GetFuncletStartAddress

- Remove flaky InterpreterStackDoubleWalk debuggee + tests (timing-sensitive SpinStep loop)

- Address Copilot bot feedback: MSBuild escaping in DumpTests.targets, funclet null-safety for interpreter

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pe; hide GetFrameHandler behind helper

- SOSDacImpl.GetCodeHeaderData: when JIT type is Interpreter, decode the
  GC info using DecodeInterpreterGCInfo instead of DecodePlatformSpecificGCInfo.
  This mirrors native ClrDataAccess::GetCodeHeaderData routing through
  EECodeInfo::GetCodeManager()->GetFunctionSize, where interpreter code goes
  through InterpreterCodeManager::GetFunctionSize / InterpreterGcInfoDecoder.

- FrameHelpers: make GetFrameHandler private and add a semantic helper
  ApplyInterpreterFrameTransition(context, interpreterFrameAddress) that
  encapsulates reading the InterpreterFrame as a FramedMethodFrame and
  invoking HandleTransitionFrame on it. Update StackWalk_1.InterpreterVirtualUnwind
  to use the new helper instead of reaching into the frame-handler dispatch
  directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds an opt-in Interpreter SOS test leg to runtime-diagnostics.yml that
exercises the diagnostics-side SOSInterpreterTests against a Checked CoreCLR
drop. FEATURE_INTERPRETER is only compiled into Debug/Checked CoreCLR, so the
existing Release build leg cannot run this test.

Changes:

* eng/pipelines/diagnostics/runtime-diag-job.yml: adds a testInterpreter
  parameter (default false). When true, _TestInterpreterArgs resolves to
  -testInterpreter and is forwarded to the diagnostics build script alongside
  the existing -useCdac / -noFallback flags. Default-false invocation is
  identical to today.

* eng/pipelines/runtime-diagnostics.yml:
  - New build leg that produces a Checked CoreCLR (libs/SDK stay Release) under
    a distinct artifact name (..._coreclr_Checked).
  - New Interpreter test job that depends on the Checked build leg, downloads
    its artifact, and is the only job that sets testInterpreter: true.
  - The cDAC / cDAC_no_fallback / DAC test jobs and the existing Release build
    leg are unchanged.

Tested with a manual ADO queue using diagnosticsBranch pointed at the
companion diagnostics PR (dotnet/diagnostics#5829).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…CallFrame handlers

Mirror the per-architecture native InlinedCallFrame::UpdateRegDisplay_Impl
logic in the corresponding cDAC frame handlers:

- AMD64FrameHandler.HandleInlinedCallFrame: sets Rcx (Windows) or Rdi
  (Unix SysV) to the InterpreterFrame address when the next Frame is an
  InterpreterFrame, matching src/coreclr/vm/amd64/cgenamd64.cpp:212-218.
- ARM64FrameHandler.HandleInlinedCallFrame: sets X0 to the InterpreterFrame
  address when the next Frame is an InterpreterFrame, matching
  src/coreclr/vm/arm64/stubs.cpp:408-414.

ARM, x86, LoongArch64, and RISCV64 native InlinedCallFrame::UpdateRegDisplay_Impl
do NOT perform this update, so the corresponding cDAC handlers (or BaseFrameHandler
inheritance) do not either.

A protected GetNextFrame helper is added to BaseFrameHandler so each
handler can inspect the chain itself without changing the
IPlatformFrameHandler interface (handles the FRAME_TOP all-ones
terminator). BaseFrameHandler also constructs its own FrameHelpers from
the target so derived handlers can inline calls to GetFrameType.

Without this update, cDAC reports the thread's literal saved Rcx for frames
between an InlinedCallFrame and its successor InterpreterFrame, while the
legacy DAC reports the InterpreterFrame address. This trips
Debug.Assert(contextStruct.Equals(localContextStruct)) in
ClrDataStackWalk.GetContext during !ClrStack on a thread captured during a
P/Invoke from interpreted code.

Verified locally against the SOS.InterpreterStackTest.Heap.dmp captured in
CI: !ClrStack and !PrintException both succeed with the cDAC parity check
enabled, walking through [InlinedCallFrame], JIT IL stub frames,
[InterpreterFrame: ...], and the interpreted method frames in correct
order. All 2081 cDAC unit tests continue to pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The cDAC's InterpreterJitManager (added in this PR) registers interpreter
code as code blocks with an interpreter-format DebugInfo, and
DebugInfo_2.GetMethodNativeMap successfully decodes that DebugInfo into
an OffsetMapping list. This means cDAC produces real, correct IL offsets
for interpreter pseudo-IPs (e.g. for !ClrStack -lines source-line
resolution), where the legacy DAC bails out early with E_INVALIDARG
because ExecutionManager::GetNativeCodeVersion(address) returns null for
interpreter IPs (daccess.cpp:5660-5666).

Switch the parity validation in GetILOffsetsByAddress from the default
AllowDivergentFailures (which rejects cDAC-success vs DAC-failure) to
AllowCdacSuccess so cDAC's better behavior is permitted. Gate the
offset-comparison block on hrLocal == S_OK as well, so we don't
spuriously compare against a zero-initialized localOffsetsNeeded /
localIlOffsets when only cDAC succeeded.

Verified locally against the SOS.InterpreterStackTest.Heap.dmp from CI:
!ClrStack -lines now resolves source lines for the JIT-compiled
EH frames AND for the interpreter frames (InterpTestMethodThrow @ 19,
InterpTestMethodRunNested @ 13, InterpreterStackTestApp.Main @ 12),
with the cDAC parity check enabled. All 2081 cDAC unit tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds ISOSDacInterfaceTests dump test class mirroring ISOSDacInterface13Tests, with a GetCodeHeaderData test that validates TYPE_INTERPRETER routing through the interpreter precode unwrap path.

Also switches the InterpreterStack debuggee from a full dump to a heap dump (107 MB -> 16 MB) since the heap dump captures all memory needed for interpreter stack walking and code-header lookup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Linux createdump segfaults during heap-dump region enumeration when the
interpreter is active, so InterpreterStack switches to full dumps.

Tracked by dotnet#128044.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-bit targets

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep the original catch (VirtualReadException) clause; the NotImplementedException widening will be handled separately.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Missed during the rebase onto main (which removed JitType in favor of the unified CodeKind enum). Updates AssertInterpreted/AssertJitted to call IExecutionManager.GetCodeKind with the resolved code pointer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 63 out of 64 changed files in this pull request and generated 4 comments.

Comment thread src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.cs Outdated
Comment thread docs/design/datacontracts/ExecutionManager.md Outdated
- MockDescriptors.ExecutionManager.cs: Remove AddRangeListSection helper
  that created an invalid RangeSection (RangeList flag set but RangeList
  and JitManager pointers null). Switch the one caller in
  GetCodeBlockHandle_InterpreterPrecode_ReturnsNull to use the
  AddRangeListRangeSection helper which properly initializes RangeList
  and JitManager.
- ExecutionManager.md: Fix missing space after backticked method name
  in the NonVirtualEntry2MethodDesc paragraph.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

5 participants