Skip to content

[cDAC] Fix native code version lookup and caller SP for interpreted frames - #136362

Merged
steveisok merged 2 commits into
dotnet:mainfrom
steveisok:cdac-interpreter-code-version-and-caller-sp
Oct 23, 2026
Merged

steveisok merged 2 commits into
dotnet:mainfrom
steveisok:cdac-interpreter-code-version-and-caller-sp

Conversation

@steveisok

Copy link
Copy Markdown
Member

Independent of #136361 in code, but end-to-end testing on iOS needs #136361, otherwise in-app debugging never attaches.

Summary

With the cDAC as the in-app IDacDbiInterface on CoreCLR iOS, where Debug
app code runs on the interpreter, the debugger never reported
Debugger.Break. Two cDAC divergences from the native DAC cause this, one per
commit.

1. Map entry point to code start when finding a native code version

CodeVersions_1.GetSpecificNativeCodeVersion compared the MethodDesc native
code slot with the code-block start. For interpreted methods the slot holds an
InterpreterPrecode and the code block starts at the bytecode, so the two
never matched.

  • GetNativeCodeVersionForIP returned an invalid handle.
  • DebugInfo_1.GetMethodVarInfo threw "No NativeCodeVersion found for native
    code …" (0x80131509).
  • GetNativeCodeSequencePointsAndVarInfo failed, the DBI abandoned the frame,
    and the stop was dropped.

The fix maps the default and explicit versions' native code through
IExecutionManager.GetDiagnosticCodeStartFromEntryPoint before comparing.
That's the helper #135430 added for GetMethodVarInfo; it covers interpreter
precodes and portable entry points. It matches the native DAC's
GetInterpreterCodeFromEntryPointIfPresent(NativeCodeVersion::GetNativeCode())
pattern. #135430 alone isn't enough, because the throw happens before its
offset mapping.

Tests and docs:

  • CodeVersionsTests.GetNativeCodeVersion_Interpreted_MapsPrecodeToBytecode
    (4 architectures).
  • The mock AddCodeBlock gets a default identity
    GetDiagnosticCodeStartFromEntryPoint.
  • CodeVersions.md pseudo-code updated.

2. Compute caller SP for interpreted frames like the native code manager

StackWalk_1.CallerStackPointer backs GetRuntimeFramePointer on
caller-SP architectures such as ARM64, and GetFuncletRootId
(fpParentOrSelf). It always ran the OS unwinder, which has no unwind info for
bytecode, so fp/fpParentOrSelf came back as the frame's own SP.

For interpreter code it now uses the new
FrameHelpers.InterpreterCallerStackPointer, which returns:

  • the parent InterpMethodContextFrame, including the zeroed root frame
    (ip == NULL) of an interpreter activation, or
  • the owning InterpreterFrame when there is no parent.

This is the SP that native VirtualUnwindInterpreterCallFrame (eetwain.cpp)
gives the caller context. The walker's own unwind path (Next) is unchanged.

Tests and docs:

  • StackWalkTests.CallerStackPointer_InterpretedFrame_IsParentInterpMethodContextFrame
    and StackWalkTests.CallerStackPointer_InterpretedFrameWithRootParent_IsRootFrameAddress.
  • StackWalk.md updated.

Validation

  • Unit tests (Microsoft.Diagnostics.DataContractReader.Tests):
    • 3,406/3,406 pass after commit 1, and 3,408/3,408 after commit 2.
    • Each new test fails without its own commit's product change and passes
      with it.
    • Microsoft.Diagnostics.DataContractReader.Usage.Tests: 4/4 after each
      commit.
  • iOS Simulator (iossimulator-arm64, interpreted Debug app, VS Code .NET MAUI
    F5 flow, DOTNET_ENABLE_CDAC=1, with Fix TryGetSymbol on iOS, tvOS and Mac Catalyst #136361 applied):
    • Debugger.Break stops, and stepping, Locals and caller inspection all
      work.
    • A diagnosis-only trace compared each cDAC result with the legacy DAC for
      the same calls during the whole workflow:
      • GetNativeCodeSequencePointsAndVarInfo: 8/8 calls match in HRESULT,
        variable count and sequence-point count;
      • GetStackWalkCurrentFrameInfo: 0 mismatches in 975 compared frame
        fields
        (including fp, fpParentOrSelf, method token and
        MethodDesc, native start and offset, and context) across 288 frames,
        covering every frame token in the session.
    • Without these commits, the same scenario never reports Debugger.Break.

steveisok and others added 2 commits October 23, 2026 07:27
GetSpecificNativeCodeVersion compared the MethodDesc native code slot (an
InterpreterPrecode for interpreted methods) with the code-block start (the
bytecode), so it never matched. GetNativeCodeVersionForIP then returned an
invalid handle, and GetNativeCodeSequencePointsAndVarInfo failed because
DebugInfo's GetMethodVarInfo threw "No NativeCodeVersion found". Map the
default and explicit versions' native code through
GetDiagnosticCodeStartFromEntryPoint first, as the native DAC does with
GetInterpreterCodeFromEntryPointIfPresent.

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

CallerStackPointer always ran the OS unwinder, which has no unwind info for
bytecode, so on caller-SP architectures such as ARM64 fp/fpParentOrSelf came
back as the frame's own SP. For interpreter code, use the parent
InterpMethodContextFrame (including the zeroed root frame), or the owning
InterpreterFrame when there is no parent, matching
VirtualUnwindInterpreterCallFrame in eetwain.cpp.

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.

@steveisok
steveisok requested review from a team and matouskozak October 23, 2026 12:19
@steveisok

Copy link
Copy Markdown
Member Author

/ba-g SOS test failures will be fixed by dotnet/diagnostics#6143

@steveisok
steveisok merged commit 714f7ab into dotnet:main Oct 23, 2026
79 of 87 checks passed
steveisok added a commit to dotnet/diagnostics that referenced this pull request Oct 23, 2026
…ion (#6143)

## Problem

The SOS Helix legs of dotnet/runtime's `runtime-diagnostics` pipeline
are **blocked**. Every SOS work item fails before any test runs. This
affects all runtime PRs since roughly 2026-10-23 16:00 UTC. Example:
https://dev.azure.com/dnceng-public/public/_build/results?buildId=1646832
(dotnet/runtime#136362). Each work item exits 3 with:

```
The Helix-provisioned runtime was not found at '<HELIX_CORRELATION_PAYLOAD>/dotnet-cli/shared/Microsoft.NETCore.App/11.0.0-rtm.26514.118'.
```

## Root cause

For private runtime testing (`PrivateBuildTesting=true` +
`LiveRuntimeDir`), `src/tests/SOS.Tests/HelixPayload.targets` passes
`--runtime-version "%(RuntimeDownload)"` to
`runtests.sh`/`runtests.cmd`. They use that value as the
`shared/Microsoft.NETCore.App/<version>` folder to overlay the private
runtime onto.

`RuntimeDownload` (`MicrosoftNETCorePlatformsVersion`) is the
build/download version. The installed folder, however, is named after
the product version (`Runtime` = `MicrosoftNETCoreAppRefVersion`). These
two values were identical until #6136 moved .NET 11 to stabilized RTM
versioning, so the bug went unnoticed:

| | before #6136 | after #6136 |
|---|---|---|
| `RuntimeDownload` | `11.0.0-rtm.26480.113` | `11.0.0-rtm.26514.118` |
| `Runtime` | `11.0.0-rtm.26480.113` | `11.0.0` |

How the folder gets its name: the Helix SDK's `AddAdditionalRuntimes`
target (`tools/dotnet-cli/DotNetCli.targets`) resolves the archive with
`FindDotNetCliPackage` and extracts it into `dotnet-cli` without
changing its layout. The resolved archive,
`ci.dot.net/public/Runtime/11.0.0-rtm.26514.118/dotnet-runtime-11.0.0-linux-x64.tar.gz`
(its `productVersion.txt` is `11.0.0`), contains
`shared/Microsoft.NETCore.App/11.0.0/`.

## Fix

- Keep downloading by `RuntimeDownload` (`RequiredRuntimeVersions` is
unchanged), but pass `%(HelixWorkItem.Runtime)` as `--runtime-version`.
- Fail early with a clear error if a private runtime run has no
`Runtime` metadata.
- Document the build-version vs. product-version distinction in the
SOS.Tests README.

I checked the other places that use these versions and they need no
change: the SOS harness metadata, CommonTestRunner and DbgShim already
configure by `Runtime`, the managed Helix projects use the stable
`TestHostRuntimeVersion`, and macOS picks `--fx-version` from `dotnet
--list-runtimes`.

## Validation

- I ran `CreateHelixPayload` through a scratch MSBuild project that
imports `eng/Version.Details.props`, `eng/Versions.props` and
`SOS.Tests/HelixPayload.targets`, with payload staging stubbed out:
- Private, linux-x64 — before: `--runtime-version
"11.0.0-rtm.26514.118"`; after: `--runtime-version "11.0.0"`.
`RequiredRuntimeVersions` stays `10.0.10;11.0.0-rtm.26514.118`.
- Private, win-x64 (`runtests.cmd`) — after: `--runtime-version
"11.0.0"`.
- Non-private matrix (Net11/10/9/8) — the generated work items are
unchanged.
  - Private run with empty `Runtime` — the new error is raised.
- I ran `runtests.sh` against a fake correlation payload that copies the
real archive layout (`shared/Microsoft.NETCore.App/11.0.0`). With the
old value it reproduces the exact CI error. With the new value it
overlays the runtime and continues.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f9525fb2-c2dd-4524-8de0-24a2d0b0f6a7
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Oct 26, 2026
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