Skip to content

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract - #129456

Merged
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength
Jun 17, 2026
Merged

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract#129456
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength

Conversation

@max-charlamb

Copy link
Copy Markdown
Member

Note

This PR was authored with assistance from GitHub Copilot.

Summary

Fixes a cDAC GetCodeHeaderData failure that surfaced as Unable to get codeHeader information when SOS ran !clru against an IL stub MethodDesc on Windows x86 with cDAC enabled (the new default behavior on .NET 11 introduced by dotnet/diagnostics#5874).

The CI failure that motivated this is SOSMethodTests.VarargPInvokeInteropMD on x86 in dotnet/diagnostics: !IP2MD returned the IL stub MethodDesc correctly, but the immediate follow-up !clru <MD> printed only Unable to get codeHeader information. x64 / arm64 / .NET 8/9/10 were unaffected.

Root cause

x86 uses a fundamentally different GC info encoding from every other architecture: the legacy bit-packed InfoHdr byte-stream format from src/coreclr/vm/gc_unwind_x86.inl and src/coreclr/inc/gcdecoder.cpp (USE_GC_INFO_DECODER is defined for every target except x86, see eetwain.h:34).

The cDAC GCInfo contract registered IGCInfo implementations for X64/Arm64/Arm/LoongArch64/RiscV64 but not X86 -- so on x86 it fell through to default(GCInfo) and threw NotImplementedException from the interface's default DecodePlatformSpecificGCInfo. Any SOS path that needed method size on x86 (!clru, GetCodeHeaderData, GetMethodRegionInfo) failed.

Approach

The cDAC already had a substantial x86 InfoHdr decoder under Contracts/StackWalk/Context/X86/GCInfoDecoding/, used by the x86 stack walker. Rather than write a parallel decoder, this PR relocates that existing decoder under the GCInfo contract so there is one canonical x86 GC info implementation shared between SOS callers and the stack walker -- mirroring how the other architectures' decoders are structured.

Changes

  • MoveContracts/StackWalk/Context/X86/GCInfoDecoding/*Contracts/GCInfo/X86/* (6 files, tracked as renames). Rename namespace StackWalkHelpers.X86GCInfoHelpers.X86.
  • Rename the moved class GCInfoX86GCInfo to avoid collision with the empty Contracts.GCInfo IGCInfo fallback struct.
  • Make relativeOffset ctor arg optional. Implement IGCInfoDecoder directly on X86GCInfo: GetCodeLength / GetStackBaseRegister / GetSizeOfStackParameterArea are wired up. GetInterruptibleRanges and EnumerateLiveSlots throw NotSupportedException (future work, needed for !gcroot / !clrstack -l etc.).
  • Add GCInfoX86_1 IGCInfo for x86; register it in CoreCLRContracts.cs for RuntimeInfoArchitecture.X86.
  • Update ExecutionManagerCore.GetStackParameterSize to delegate to IGCInfo.GetSizeOfStackParameterArea (one source of truth).
  • X86Unwinder continues to construct X86GCInfo directly because it needs offset-bound state (IsInProlog / IsInEpilog / PushedArgSize) not exposed through IGCInfoDecoder.

Tests

  • New VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize regression test in cdac/tests/DumpTests/StackWalkDumpTests.cs -- asserts the IL stub path returns S_OK with non-zero MethodSize. Runs against the existing cdac-dump-helix windows_x86 matrix on every PR (no pipeline changes needed).
  • Existing 2509 cDAC unit tests still pass.
  • Validated end-to-end against SOSMethodTests.VarargPInvokeInteropMD x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.

Docs

  • docs/design/datacontracts/GCInfo.md -- intro now reflects partial x86 support; GetSizeOfStackParameterArea API documented; per-method status notes for x86.
  • docs/design/datacontracts/StackWalk.md -- x86 section points at the consolidated decoder location and explains how it's shared.

Out of scope (future work)

  • GetInterruptibleRanges and EnumerateLiveSlots for x86. The underlying transition data is decoded but the adapter to the cDAC IGCInfoDecoder shape is not wired up yet. This is what's needed to unblock !gcroot, !clrstack -l, !pe on x86 cDAC. The two pre-existing [SkipOnArch("x86", "GCInfo decoder does not support x86")] markers in StackReferenceDumpTests.cs should become removable once that lands.

…ntract
Fixes the cDAC `GetCodeHeaderData` failure that surfaced as
`Unable to get codeHeader information` when SOS ran `!clru` against an
IL stub MethodDesc on Windows x86, .NET 11, with cDAC enabled (the new
default behavior introduced by dotnet/diagnostics#5874).
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
`src/coreclr/vm/gc_unwind_x86.inl` and `src/coreclr/inc/gcdecoder.cpp`
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`). The GCInfo contract had no x86 implementation, so it
fell through to `default(GCInfo)` and threw `NotImplementedException`
from the interface's default `DecodePlatformSpecificGCInfo`. Any
SOS path that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86
stack walker. This change relocates that decoder under the `GCInfo`
contract so there is a single canonical x86 GC info implementation
shared between SOS callers and the stack walker.
Changes:
* Move `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` ->
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename
namespace `StackWalkHelpers.X86` -> `GCInfoHelpers.X86`.
* Rename the moved class `GCInfo` -> `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges`
and `EnumerateLiveSlots` throw `NotSupportedException` (future work,
needed for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because
it needs offset-bound state (`IsInProlog`/`IsInEpilog`/`PushedArgSize`)
not exposed through `IGCInfoDecoder`.
Tests:
* Add `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test that asserts the IL stub path returns S_OK with
non-zero MethodSize. Runs against the existing cdac-dump-helix
`windows_x86` matrix on every PR.
Docs:
* Update `docs/design/datacontracts/GCInfo.md` to reflect partial x86
support and document `GetSizeOfStackParameterArea`.
* Update `docs/design/datacontracts/StackWalk.md` x86 section to
point at the consolidated decoder location.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an x86 implementation of the cDAC IGCInfo contract by reusing the existing managed x86 InfoHdr decoder (relocated under the GCInfo contract) so SOS callers can query method size / stack parameter area on Windows x86, and includes a dump-based regression test plus documentation updates.

Changes:

  • Register a new x86 IGCInfo implementation (GCInfoX86_1) and wire it up for RuntimeInfoArchitecture.X86.
  • Consolidate x86 GCInfo decoding under the GCInfo contract and update the x86 unwinder and ExecutionManager to use the shared decoder / contract APIs.
  • Add a dump-based regression test for ISOSDacInterface.GetCodeHeaderData on an IL stub and update design docs.

Reviewed changes

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

Show a summary per file
FileDescription
src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.csAdds regression coverage validating GetCodeHeaderData returns a non-zero method size for an IL stub.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.csRegisters GCInfoX86_1 for x86 targets.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.csUpdates x86 unwinder to use the relocated/renamed X86GCInfo type.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/InfoHdr.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfoTargetExtensions.csMoves x86 GCInfo decoder support extensions under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.csRenames GCInfoX86GCInfo, makes relativeOffset optional, and implements IGCInfoDecoder queries needed by SOS/ExecutionManager.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/CallPattern.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.csIntroduces the x86 IGCInfo implementation backed by X86GCInfo.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.csDelegates x86 stack parameter size computation to the IGCInfo contract for a single source of truth.
docs/design/datacontracts/StackWalk.mdDocuments the consolidated location and sharing model for the x86 GCInfo decoder.
docs/design/datacontracts/GCInfo.mdUpdates contract API docs (but the intro still incorrectly claims x86 is unsupported).

Comment threaddocs/design/datacontracts/GCInfo.md
Tweaks the second intro paragraph in GCInfo.md to say x86 is "partially
supported" rather than "not currently supported", reflecting the IGCInfo
implementation added in the first commit.
Addresses dotnet#129456 (comment)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlamb marked this pull request as ready for review June 16, 2026 13:20
CopilotAI review requested due to automatic review settings June 16, 2026 15:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 13 out of 13 changed files in this pull request and generated 1 comment.

Comment threaddocs/design/datacontracts/GCInfo.md Outdated
Address PR feedback: the IGCInfo contract was conflating two distinct
concepts under one API name. GcInfoDecoder::GetSizeOfStackParameterArea
returns the outgoing-argument scratch area (_fixedStackParameterScratchArea,
platform-specific, used by the GC scanner). EECodeManager::GetStackParameterSize
returns the x86 __stdcall callee-popped argument size (used by the debugger
to adjust SP after return).
Keep IGCInfo.GetSizeOfStackParameterArea as the GcInfoDecoder scratch-area
accessor (consumed by GcScanner). On x86 it now throws NotSupportedException
since x86 has no separate scratch-area concept.
Add IGCInfo.GetCalleePoppedArgumentsSize mirroring EECodeManager::GetStackParameterSize:
0 by default, x86 returns varargs ? 0 : argCount * ptrSize.
ExecutionManagerCore.GetStackParameterSize now routes through this new method.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… x86 GCInfo
The previous test passed the IL stub's stable entry point
(GetMethodEntryPointIfExists) to GetCodeHeaderData. On x86 r2r that resolves
to a precode, so SOSDacImpl.GetCodeHeaderData takes the NonVirtualEntry2MethodDesc
fallback (MethodSize=0, TYPE_UNKNOWN) and never calls IGCInfo.GetCodeLength -
the exact path the regression was about.
Pass the live instruction pointer of a Frameless IL stub frame instead. The
IP is guaranteed to be inside the JIT-emitted code body, so GetCodeHeaderData
takes the full path through IGCInfo.GetCodeLength and the test actually
exercises the x86 X86GCInfo decoder it claims to cover.
Validated locally against the x86 dumps from the original failing CI run:
all 452 dump tests pass (previously 1 failure).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:35

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • GetSizeOfStackParameterArea is invoked by the managed GC scanner (Contracts/StackWalk/GC/GcScanner.cs) before live-slot enumeration. On x86 there’s no separate scratch/outgoing area, so returning 0 is a sensible, non-throwing default; throwing here causes x86 GC scanning callers to fail early even when they could proceed (or fail later for the actual missing feature).
    src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:32
  • X86GCInfo (and related x86 decoder helpers) are public and were also renamed/moved from the prior StackWalkHelpers.X86.GCInfo name/namespace. If these assemblies are consumed externally, that’s a breaking API change and expands public surface area. Consider making these helpers internal (they appear to be used only within this assembly), or providing a compatibility shim/type-forwarding stub for the old public type name/namespace.

Comment threaddocs/design/datacontracts/StackWalk.md Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • IGCInfoDecoder.GetSizeOfStackParameterArea is used unconditionally by GcScanner.EnumGcRefsForManagedFrame (StackWalk/GC/GcScanner.cs:48-50). Throwing here will immediately abort managed-frame GC scanning on x86 (even before EnumerateLiveSlots), which makes the method unusable and can lead to hard-to-diagnose partial results (exceptions are swallowed per-frame in StackWalk_1). Since x86 has no separate scratch/outgoing-arg area concept, returning 0 is a safe representation and avoids unnecessary exceptions.

@max-charlamb
max-charlamb enabled auto-merge (squash) June 17, 2026 16:42
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

/ba-g known helix infra issues causing timeouts on arm64 and osx

@max-charlamb
max-charlamb merged commit 9451fcf into dotnet:mainJun 17, 2026
68 of 77 checks passed
@max-charlamb
max-charlamb deleted the cdac-x86-gcinfo-codelength branch June 17, 2026 18:08
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…ntract (#129456)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
## Summary
Fixes a cDAC `GetCodeHeaderData` failure that surfaced as `Unable to get
codeHeader information` when SOS ran `!clru` against an IL stub
MethodDesc on **Windows x86** with cDAC enabled (the new default
behavior on .NET 11 introduced by
[dotnet/diagnostics#5874](dotnet/diagnostics#5874)).
The CI failure that motivated this is
`SOSMethodTests.VarargPInvokeInteropMD` on x86 in dotnet/diagnostics:
`!IP2MD` returned the IL stub MethodDesc correctly, but the immediate
follow-up `!clru <MD>` printed only `Unable to get codeHeader
information`. x64 / arm64 / .NET 8/9/10 were unaffected.
## Root cause
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
[`src/coreclr/vm/gc_unwind_x86.inl`](https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/gc_unwind_x86.inl)
and
[`src/coreclr/inc/gcdecoder.cpp`](https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/gcdecoder.cpp)
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`).
The cDAC `GCInfo` contract registered IGCInfo implementations for
X64/Arm64/Arm/LoongArch64/RiscV64 but **not** X86 -- so on x86 it fell
through to `default(GCInfo)` and threw `NotImplementedException` from
the interface's default `DecodePlatformSpecificGCInfo`. Any SOS path
that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
## Approach
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86 stack
walker. Rather than write a parallel decoder, this PR **relocates** that
existing decoder under the `GCInfo` contract so there is **one canonical
x86 GC info implementation** shared between SOS callers and the stack
walker -- mirroring how the other architectures' decoders are
structured.
## Changes
* **Move** `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` →
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename namespace
`StackWalkHelpers.X86` → `GCInfoHelpers.X86`.
* **Rename** the moved class `GCInfo` → `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges` and
`EnumerateLiveSlots` throw `NotSupportedException` (future work, needed
for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because it
needs offset-bound state (`IsInProlog` / `IsInEpilog` / `PushedArgSize`)
not exposed through `IGCInfoDecoder`.
## Tests
* New `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test in `cdac/tests/DumpTests/StackWalkDumpTests.cs` --
asserts the IL stub path returns S_OK with non-zero `MethodSize`. Runs
against the existing cdac-dump-helix `windows_x86` matrix on every PR
(no pipeline changes needed).
* Existing 2509 cDAC unit tests still pass.
* Validated end-to-end against `SOSMethodTests.VarargPInvokeInteropMD`
x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.
## Docs
* `docs/design/datacontracts/GCInfo.md` -- intro now reflects partial
x86 support; `GetSizeOfStackParameterArea` API documented; per-method
status notes for x86.
* `docs/design/datacontracts/StackWalk.md` -- x86 section points at the
consolidated decoder location and explains how it's shared.
## Out of scope (future work)
* `GetInterruptibleRanges` and `EnumerateLiveSlots` for x86. The
underlying transition data is decoded but the adapter to the cDAC
`IGCInfoDecoder` shape is not wired up yet. This is what's needed to
unblock `!gcroot`, `!clrstack -l`, `!pe` on x86 cDAC. The two
pre-existing `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers in `StackReferenceDumpTests.cs` should become removable
once that lands.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@max-charlamb@steveisok@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract by max-charlamb · Pull Request #129456 · dotnet/runtime · GitHub
Skip to content

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract - #129456

Merged
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength
Jun 17, 2026
Merged

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract#129456
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength

Conversation

@max-charlamb

Copy link
Copy Markdown
Member

Note

This PR was authored with assistance from GitHub Copilot.

Summary

Fixes a cDAC GetCodeHeaderData failure that surfaced as Unable to get codeHeader information when SOS ran !clru against an IL stub MethodDesc on Windows x86 with cDAC enabled (the new default behavior on .NET 11 introduced by dotnet/diagnostics#5874).

The CI failure that motivated this is SOSMethodTests.VarargPInvokeInteropMD on x86 in dotnet/diagnostics: !IP2MD returned the IL stub MethodDesc correctly, but the immediate follow-up !clru <MD> printed only Unable to get codeHeader information. x64 / arm64 / .NET 8/9/10 were unaffected.

Root cause

x86 uses a fundamentally different GC info encoding from every other architecture: the legacy bit-packed InfoHdr byte-stream format from src/coreclr/vm/gc_unwind_x86.inl and src/coreclr/inc/gcdecoder.cpp (USE_GC_INFO_DECODER is defined for every target except x86, see eetwain.h:34).

The cDAC GCInfo contract registered IGCInfo implementations for X64/Arm64/Arm/LoongArch64/RiscV64 but not X86 -- so on x86 it fell through to default(GCInfo) and threw NotImplementedException from the interface's default DecodePlatformSpecificGCInfo. Any SOS path that needed method size on x86 (!clru, GetCodeHeaderData, GetMethodRegionInfo) failed.

Approach

The cDAC already had a substantial x86 InfoHdr decoder under Contracts/StackWalk/Context/X86/GCInfoDecoding/, used by the x86 stack walker. Rather than write a parallel decoder, this PR relocates that existing decoder under the GCInfo contract so there is one canonical x86 GC info implementation shared between SOS callers and the stack walker -- mirroring how the other architectures' decoders are structured.

Changes

  • MoveContracts/StackWalk/Context/X86/GCInfoDecoding/*Contracts/GCInfo/X86/* (6 files, tracked as renames). Rename namespace StackWalkHelpers.X86GCInfoHelpers.X86.
  • Rename the moved class GCInfoX86GCInfo to avoid collision with the empty Contracts.GCInfo IGCInfo fallback struct.
  • Make relativeOffset ctor arg optional. Implement IGCInfoDecoder directly on X86GCInfo: GetCodeLength / GetStackBaseRegister / GetSizeOfStackParameterArea are wired up. GetInterruptibleRanges and EnumerateLiveSlots throw NotSupportedException (future work, needed for !gcroot / !clrstack -l etc.).
  • Add GCInfoX86_1 IGCInfo for x86; register it in CoreCLRContracts.cs for RuntimeInfoArchitecture.X86.
  • Update ExecutionManagerCore.GetStackParameterSize to delegate to IGCInfo.GetSizeOfStackParameterArea (one source of truth).
  • X86Unwinder continues to construct X86GCInfo directly because it needs offset-bound state (IsInProlog / IsInEpilog / PushedArgSize) not exposed through IGCInfoDecoder.

Tests

  • New VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize regression test in cdac/tests/DumpTests/StackWalkDumpTests.cs -- asserts the IL stub path returns S_OK with non-zero MethodSize. Runs against the existing cdac-dump-helix windows_x86 matrix on every PR (no pipeline changes needed).
  • Existing 2509 cDAC unit tests still pass.
  • Validated end-to-end against SOSMethodTests.VarargPInvokeInteropMD x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.

Docs

  • docs/design/datacontracts/GCInfo.md -- intro now reflects partial x86 support; GetSizeOfStackParameterArea API documented; per-method status notes for x86.
  • docs/design/datacontracts/StackWalk.md -- x86 section points at the consolidated decoder location and explains how it's shared.

Out of scope (future work)

  • GetInterruptibleRanges and EnumerateLiveSlots for x86. The underlying transition data is decoded but the adapter to the cDAC IGCInfoDecoder shape is not wired up yet. This is what's needed to unblock !gcroot, !clrstack -l, !pe on x86 cDAC. The two pre-existing [SkipOnArch("x86", "GCInfo decoder does not support x86")] markers in StackReferenceDumpTests.cs should become removable once that lands.

…ntract
Fixes the cDAC `GetCodeHeaderData` failure that surfaced as
`Unable to get codeHeader information` when SOS ran `!clru` against an
IL stub MethodDesc on Windows x86, .NET 11, with cDAC enabled (the new
default behavior introduced by dotnet/diagnostics#5874).
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
`src/coreclr/vm/gc_unwind_x86.inl` and `src/coreclr/inc/gcdecoder.cpp`
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`). The GCInfo contract had no x86 implementation, so it
fell through to `default(GCInfo)` and threw `NotImplementedException`
from the interface's default `DecodePlatformSpecificGCInfo`. Any
SOS path that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86
stack walker. This change relocates that decoder under the `GCInfo`
contract so there is a single canonical x86 GC info implementation
shared between SOS callers and the stack walker.
Changes:
* Move `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` ->
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename
namespace `StackWalkHelpers.X86` -> `GCInfoHelpers.X86`.
* Rename the moved class `GCInfo` -> `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges`
and `EnumerateLiveSlots` throw `NotSupportedException` (future work,
needed for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because
it needs offset-bound state (`IsInProlog`/`IsInEpilog`/`PushedArgSize`)
not exposed through `IGCInfoDecoder`.
Tests:
* Add `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test that asserts the IL stub path returns S_OK with
non-zero MethodSize. Runs against the existing cdac-dump-helix
`windows_x86` matrix on every PR.
Docs:
* Update `docs/design/datacontracts/GCInfo.md` to reflect partial x86
support and document `GetSizeOfStackParameterArea`.
* Update `docs/design/datacontracts/StackWalk.md` x86 section to
point at the consolidated decoder location.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an x86 implementation of the cDAC IGCInfo contract by reusing the existing managed x86 InfoHdr decoder (relocated under the GCInfo contract) so SOS callers can query method size / stack parameter area on Windows x86, and includes a dump-based regression test plus documentation updates.

Changes:

  • Register a new x86 IGCInfo implementation (GCInfoX86_1) and wire it up for RuntimeInfoArchitecture.X86.
  • Consolidate x86 GCInfo decoding under the GCInfo contract and update the x86 unwinder and ExecutionManager to use the shared decoder / contract APIs.
  • Add a dump-based regression test for ISOSDacInterface.GetCodeHeaderData on an IL stub and update design docs.

Reviewed changes

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

Show a summary per file
FileDescription
src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.csAdds regression coverage validating GetCodeHeaderData returns a non-zero method size for an IL stub.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.csRegisters GCInfoX86_1 for x86 targets.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.csUpdates x86 unwinder to use the relocated/renamed X86GCInfo type.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/InfoHdr.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfoTargetExtensions.csMoves x86 GCInfo decoder support extensions under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.csRenames GCInfoX86GCInfo, makes relativeOffset optional, and implements IGCInfoDecoder queries needed by SOS/ExecutionManager.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/CallPattern.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.csIntroduces the x86 IGCInfo implementation backed by X86GCInfo.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.csDelegates x86 stack parameter size computation to the IGCInfo contract for a single source of truth.
docs/design/datacontracts/StackWalk.mdDocuments the consolidated location and sharing model for the x86 GCInfo decoder.
docs/design/datacontracts/GCInfo.mdUpdates contract API docs (but the intro still incorrectly claims x86 is unsupported).

Comment threaddocs/design/datacontracts/GCInfo.md
Tweaks the second intro paragraph in GCInfo.md to say x86 is "partially
supported" rather than "not currently supported", reflecting the IGCInfo
implementation added in the first commit.
Addresses dotnet#129456 (comment)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlamb marked this pull request as ready for review June 16, 2026 13:20
CopilotAI review requested due to automatic review settings June 16, 2026 15:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 13 out of 13 changed files in this pull request and generated 1 comment.

Comment threaddocs/design/datacontracts/GCInfo.md Outdated
Address PR feedback: the IGCInfo contract was conflating two distinct
concepts under one API name. GcInfoDecoder::GetSizeOfStackParameterArea
returns the outgoing-argument scratch area (_fixedStackParameterScratchArea,
platform-specific, used by the GC scanner). EECodeManager::GetStackParameterSize
returns the x86 __stdcall callee-popped argument size (used by the debugger
to adjust SP after return).
Keep IGCInfo.GetSizeOfStackParameterArea as the GcInfoDecoder scratch-area
accessor (consumed by GcScanner). On x86 it now throws NotSupportedException
since x86 has no separate scratch-area concept.
Add IGCInfo.GetCalleePoppedArgumentsSize mirroring EECodeManager::GetStackParameterSize:
0 by default, x86 returns varargs ? 0 : argCount * ptrSize.
ExecutionManagerCore.GetStackParameterSize now routes through this new method.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… x86 GCInfo
The previous test passed the IL stub's stable entry point
(GetMethodEntryPointIfExists) to GetCodeHeaderData. On x86 r2r that resolves
to a precode, so SOSDacImpl.GetCodeHeaderData takes the NonVirtualEntry2MethodDesc
fallback (MethodSize=0, TYPE_UNKNOWN) and never calls IGCInfo.GetCodeLength -
the exact path the regression was about.
Pass the live instruction pointer of a Frameless IL stub frame instead. The
IP is guaranteed to be inside the JIT-emitted code body, so GetCodeHeaderData
takes the full path through IGCInfo.GetCodeLength and the test actually
exercises the x86 X86GCInfo decoder it claims to cover.
Validated locally against the x86 dumps from the original failing CI run:
all 452 dump tests pass (previously 1 failure).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:35

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • GetSizeOfStackParameterArea is invoked by the managed GC scanner (Contracts/StackWalk/GC/GcScanner.cs) before live-slot enumeration. On x86 there’s no separate scratch/outgoing area, so returning 0 is a sensible, non-throwing default; throwing here causes x86 GC scanning callers to fail early even when they could proceed (or fail later for the actual missing feature).
    src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:32
  • X86GCInfo (and related x86 decoder helpers) are public and were also renamed/moved from the prior StackWalkHelpers.X86.GCInfo name/namespace. If these assemblies are consumed externally, that’s a breaking API change and expands public surface area. Consider making these helpers internal (they appear to be used only within this assembly), or providing a compatibility shim/type-forwarding stub for the old public type name/namespace.

Comment threaddocs/design/datacontracts/StackWalk.md Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • IGCInfoDecoder.GetSizeOfStackParameterArea is used unconditionally by GcScanner.EnumGcRefsForManagedFrame (StackWalk/GC/GcScanner.cs:48-50). Throwing here will immediately abort managed-frame GC scanning on x86 (even before EnumerateLiveSlots), which makes the method unusable and can lead to hard-to-diagnose partial results (exceptions are swallowed per-frame in StackWalk_1). Since x86 has no separate scratch/outgoing-arg area concept, returning 0 is a safe representation and avoids unnecessary exceptions.

@max-charlamb
max-charlamb enabled auto-merge (squash) June 17, 2026 16:42
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

/ba-g known helix infra issues causing timeouts on arm64 and osx

@max-charlamb
max-charlamb merged commit 9451fcf into dotnet:mainJun 17, 2026
68 of 77 checks passed
@max-charlamb
max-charlamb deleted the cdac-x86-gcinfo-codelength branch June 17, 2026 18:08
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…ntract (#129456)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
## Summary
Fixes a cDAC `GetCodeHeaderData` failure that surfaced as `Unable to get
codeHeader information` when SOS ran `!clru` against an IL stub
MethodDesc on **Windows x86** with cDAC enabled (the new default
behavior on .NET 11 introduced by
[dotnet/diagnostics#5874](dotnet/diagnostics#5874)).
The CI failure that motivated this is
`SOSMethodTests.VarargPInvokeInteropMD` on x86 in dotnet/diagnostics:
`!IP2MD` returned the IL stub MethodDesc correctly, but the immediate
follow-up `!clru <MD>` printed only `Unable to get codeHeader
information`. x64 / arm64 / .NET 8/9/10 were unaffected.
## Root cause
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
[`src/coreclr/vm/gc_unwind_x86.inl`](https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/gc_unwind_x86.inl)
and
[`src/coreclr/inc/gcdecoder.cpp`](https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/gcdecoder.cpp)
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`).
The cDAC `GCInfo` contract registered IGCInfo implementations for
X64/Arm64/Arm/LoongArch64/RiscV64 but **not** X86 -- so on x86 it fell
through to `default(GCInfo)` and threw `NotImplementedException` from
the interface's default `DecodePlatformSpecificGCInfo`. Any SOS path
that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
## Approach
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86 stack
walker. Rather than write a parallel decoder, this PR **relocates** that
existing decoder under the `GCInfo` contract so there is **one canonical
x86 GC info implementation** shared between SOS callers and the stack
walker -- mirroring how the other architectures' decoders are
structured.
## Changes
* **Move** `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` →
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename namespace
`StackWalkHelpers.X86` → `GCInfoHelpers.X86`.
* **Rename** the moved class `GCInfo` → `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges` and
`EnumerateLiveSlots` throw `NotSupportedException` (future work, needed
for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because it
needs offset-bound state (`IsInProlog` / `IsInEpilog` / `PushedArgSize`)
not exposed through `IGCInfoDecoder`.
## Tests
* New `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test in `cdac/tests/DumpTests/StackWalkDumpTests.cs` --
asserts the IL stub path returns S_OK with non-zero `MethodSize`. Runs
against the existing cdac-dump-helix `windows_x86` matrix on every PR
(no pipeline changes needed).
* Existing 2509 cDAC unit tests still pass.
* Validated end-to-end against `SOSMethodTests.VarargPInvokeInteropMD`
x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.
## Docs
* `docs/design/datacontracts/GCInfo.md` -- intro now reflects partial
x86 support; `GetSizeOfStackParameterArea` API documented; per-method
status notes for x86.
* `docs/design/datacontracts/StackWalk.md` -- x86 section points at the
consolidated decoder location and explains how it's shared.
## Out of scope (future work)
* `GetInterruptibleRanges` and `EnumerateLiveSlots` for x86. The
underlying transition data is decoded but the adapter to the cDAC
`IGCInfoDecoder` shape is not wired up yet. This is what's needed to
unblock `!gcroot`, `!clrstack -l`, `!pe` on x86 cDAC. The two
pre-existing `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers in `StackReferenceDumpTests.cs` should become removable
once that lands.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@max-charlamb@steveisok@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract by max-charlamb · Pull Request #129456 · dotnet/runtime · GitHub
Skip to content

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract - #129456

Merged
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength
Jun 17, 2026
Merged

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract#129456
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength

Conversation

@max-charlamb

Copy link
Copy Markdown
Member

Note

This PR was authored with assistance from GitHub Copilot.

Summary

Fixes a cDAC GetCodeHeaderData failure that surfaced as Unable to get codeHeader information when SOS ran !clru against an IL stub MethodDesc on Windows x86 with cDAC enabled (the new default behavior on .NET 11 introduced by dotnet/diagnostics#5874).

The CI failure that motivated this is SOSMethodTests.VarargPInvokeInteropMD on x86 in dotnet/diagnostics: !IP2MD returned the IL stub MethodDesc correctly, but the immediate follow-up !clru <MD> printed only Unable to get codeHeader information. x64 / arm64 / .NET 8/9/10 were unaffected.

Root cause

x86 uses a fundamentally different GC info encoding from every other architecture: the legacy bit-packed InfoHdr byte-stream format from src/coreclr/vm/gc_unwind_x86.inl and src/coreclr/inc/gcdecoder.cpp (USE_GC_INFO_DECODER is defined for every target except x86, see eetwain.h:34).

The cDAC GCInfo contract registered IGCInfo implementations for X64/Arm64/Arm/LoongArch64/RiscV64 but not X86 -- so on x86 it fell through to default(GCInfo) and threw NotImplementedException from the interface's default DecodePlatformSpecificGCInfo. Any SOS path that needed method size on x86 (!clru, GetCodeHeaderData, GetMethodRegionInfo) failed.

Approach

The cDAC already had a substantial x86 InfoHdr decoder under Contracts/StackWalk/Context/X86/GCInfoDecoding/, used by the x86 stack walker. Rather than write a parallel decoder, this PR relocates that existing decoder under the GCInfo contract so there is one canonical x86 GC info implementation shared between SOS callers and the stack walker -- mirroring how the other architectures' decoders are structured.

Changes

  • MoveContracts/StackWalk/Context/X86/GCInfoDecoding/*Contracts/GCInfo/X86/* (6 files, tracked as renames). Rename namespace StackWalkHelpers.X86GCInfoHelpers.X86.
  • Rename the moved class GCInfoX86GCInfo to avoid collision with the empty Contracts.GCInfo IGCInfo fallback struct.
  • Make relativeOffset ctor arg optional. Implement IGCInfoDecoder directly on X86GCInfo: GetCodeLength / GetStackBaseRegister / GetSizeOfStackParameterArea are wired up. GetInterruptibleRanges and EnumerateLiveSlots throw NotSupportedException (future work, needed for !gcroot / !clrstack -l etc.).
  • Add GCInfoX86_1 IGCInfo for x86; register it in CoreCLRContracts.cs for RuntimeInfoArchitecture.X86.
  • Update ExecutionManagerCore.GetStackParameterSize to delegate to IGCInfo.GetSizeOfStackParameterArea (one source of truth).
  • X86Unwinder continues to construct X86GCInfo directly because it needs offset-bound state (IsInProlog / IsInEpilog / PushedArgSize) not exposed through IGCInfoDecoder.

Tests

  • New VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize regression test in cdac/tests/DumpTests/StackWalkDumpTests.cs -- asserts the IL stub path returns S_OK with non-zero MethodSize. Runs against the existing cdac-dump-helix windows_x86 matrix on every PR (no pipeline changes needed).
  • Existing 2509 cDAC unit tests still pass.
  • Validated end-to-end against SOSMethodTests.VarargPInvokeInteropMD x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.

Docs

  • docs/design/datacontracts/GCInfo.md -- intro now reflects partial x86 support; GetSizeOfStackParameterArea API documented; per-method status notes for x86.
  • docs/design/datacontracts/StackWalk.md -- x86 section points at the consolidated decoder location and explains how it's shared.

Out of scope (future work)

  • GetInterruptibleRanges and EnumerateLiveSlots for x86. The underlying transition data is decoded but the adapter to the cDAC IGCInfoDecoder shape is not wired up yet. This is what's needed to unblock !gcroot, !clrstack -l, !pe on x86 cDAC. The two pre-existing [SkipOnArch("x86", "GCInfo decoder does not support x86")] markers in StackReferenceDumpTests.cs should become removable once that lands.

…ntract
Fixes the cDAC `GetCodeHeaderData` failure that surfaced as
`Unable to get codeHeader information` when SOS ran `!clru` against an
IL stub MethodDesc on Windows x86, .NET 11, with cDAC enabled (the new
default behavior introduced by dotnet/diagnostics#5874).
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
`src/coreclr/vm/gc_unwind_x86.inl` and `src/coreclr/inc/gcdecoder.cpp`
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`). The GCInfo contract had no x86 implementation, so it
fell through to `default(GCInfo)` and threw `NotImplementedException`
from the interface's default `DecodePlatformSpecificGCInfo`. Any
SOS path that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86
stack walker. This change relocates that decoder under the `GCInfo`
contract so there is a single canonical x86 GC info implementation
shared between SOS callers and the stack walker.
Changes:
* Move `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` ->
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename
namespace `StackWalkHelpers.X86` -> `GCInfoHelpers.X86`.
* Rename the moved class `GCInfo` -> `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges`
and `EnumerateLiveSlots` throw `NotSupportedException` (future work,
needed for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because
it needs offset-bound state (`IsInProlog`/`IsInEpilog`/`PushedArgSize`)
not exposed through `IGCInfoDecoder`.
Tests:
* Add `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test that asserts the IL stub path returns S_OK with
non-zero MethodSize. Runs against the existing cdac-dump-helix
`windows_x86` matrix on every PR.
Docs:
* Update `docs/design/datacontracts/GCInfo.md` to reflect partial x86
support and document `GetSizeOfStackParameterArea`.
* Update `docs/design/datacontracts/StackWalk.md` x86 section to
point at the consolidated decoder location.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an x86 implementation of the cDAC IGCInfo contract by reusing the existing managed x86 InfoHdr decoder (relocated under the GCInfo contract) so SOS callers can query method size / stack parameter area on Windows x86, and includes a dump-based regression test plus documentation updates.

Changes:

  • Register a new x86 IGCInfo implementation (GCInfoX86_1) and wire it up for RuntimeInfoArchitecture.X86.
  • Consolidate x86 GCInfo decoding under the GCInfo contract and update the x86 unwinder and ExecutionManager to use the shared decoder / contract APIs.
  • Add a dump-based regression test for ISOSDacInterface.GetCodeHeaderData on an IL stub and update design docs.

Reviewed changes

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

Show a summary per file
FileDescription
src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.csAdds regression coverage validating GetCodeHeaderData returns a non-zero method size for an IL stub.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.csRegisters GCInfoX86_1 for x86 targets.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.csUpdates x86 unwinder to use the relocated/renamed X86GCInfo type.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/InfoHdr.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfoTargetExtensions.csMoves x86 GCInfo decoder support extensions under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.csRenames GCInfoX86GCInfo, makes relativeOffset optional, and implements IGCInfoDecoder queries needed by SOS/ExecutionManager.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/CallPattern.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.csIntroduces the x86 IGCInfo implementation backed by X86GCInfo.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.csDelegates x86 stack parameter size computation to the IGCInfo contract for a single source of truth.
docs/design/datacontracts/StackWalk.mdDocuments the consolidated location and sharing model for the x86 GCInfo decoder.
docs/design/datacontracts/GCInfo.mdUpdates contract API docs (but the intro still incorrectly claims x86 is unsupported).

Comment threaddocs/design/datacontracts/GCInfo.md
Tweaks the second intro paragraph in GCInfo.md to say x86 is "partially
supported" rather than "not currently supported", reflecting the IGCInfo
implementation added in the first commit.
Addresses dotnet#129456 (comment)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlamb marked this pull request as ready for review June 16, 2026 13:20
CopilotAI review requested due to automatic review settings June 16, 2026 15:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 13 out of 13 changed files in this pull request and generated 1 comment.

Comment threaddocs/design/datacontracts/GCInfo.md Outdated
Address PR feedback: the IGCInfo contract was conflating two distinct
concepts under one API name. GcInfoDecoder::GetSizeOfStackParameterArea
returns the outgoing-argument scratch area (_fixedStackParameterScratchArea,
platform-specific, used by the GC scanner). EECodeManager::GetStackParameterSize
returns the x86 __stdcall callee-popped argument size (used by the debugger
to adjust SP after return).
Keep IGCInfo.GetSizeOfStackParameterArea as the GcInfoDecoder scratch-area
accessor (consumed by GcScanner). On x86 it now throws NotSupportedException
since x86 has no separate scratch-area concept.
Add IGCInfo.GetCalleePoppedArgumentsSize mirroring EECodeManager::GetStackParameterSize:
0 by default, x86 returns varargs ? 0 : argCount * ptrSize.
ExecutionManagerCore.GetStackParameterSize now routes through this new method.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… x86 GCInfo
The previous test passed the IL stub's stable entry point
(GetMethodEntryPointIfExists) to GetCodeHeaderData. On x86 r2r that resolves
to a precode, so SOSDacImpl.GetCodeHeaderData takes the NonVirtualEntry2MethodDesc
fallback (MethodSize=0, TYPE_UNKNOWN) and never calls IGCInfo.GetCodeLength -
the exact path the regression was about.
Pass the live instruction pointer of a Frameless IL stub frame instead. The
IP is guaranteed to be inside the JIT-emitted code body, so GetCodeHeaderData
takes the full path through IGCInfo.GetCodeLength and the test actually
exercises the x86 X86GCInfo decoder it claims to cover.
Validated locally against the x86 dumps from the original failing CI run:
all 452 dump tests pass (previously 1 failure).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:35

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • GetSizeOfStackParameterArea is invoked by the managed GC scanner (Contracts/StackWalk/GC/GcScanner.cs) before live-slot enumeration. On x86 there’s no separate scratch/outgoing area, so returning 0 is a sensible, non-throwing default; throwing here causes x86 GC scanning callers to fail early even when they could proceed (or fail later for the actual missing feature).
    src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:32
  • X86GCInfo (and related x86 decoder helpers) are public and were also renamed/moved from the prior StackWalkHelpers.X86.GCInfo name/namespace. If these assemblies are consumed externally, that’s a breaking API change and expands public surface area. Consider making these helpers internal (they appear to be used only within this assembly), or providing a compatibility shim/type-forwarding stub for the old public type name/namespace.

Comment threaddocs/design/datacontracts/StackWalk.md Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • IGCInfoDecoder.GetSizeOfStackParameterArea is used unconditionally by GcScanner.EnumGcRefsForManagedFrame (StackWalk/GC/GcScanner.cs:48-50). Throwing here will immediately abort managed-frame GC scanning on x86 (even before EnumerateLiveSlots), which makes the method unusable and can lead to hard-to-diagnose partial results (exceptions are swallowed per-frame in StackWalk_1). Since x86 has no separate scratch/outgoing-arg area concept, returning 0 is a safe representation and avoids unnecessary exceptions.

@max-charlamb
max-charlamb enabled auto-merge (squash) June 17, 2026 16:42
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

/ba-g known helix infra issues causing timeouts on arm64 and osx

@max-charlamb
max-charlamb merged commit 9451fcf into dotnet:mainJun 17, 2026
68 of 77 checks passed
@max-charlamb
max-charlamb deleted the cdac-x86-gcinfo-codelength branch June 17, 2026 18:08
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…ntract (#129456)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
## Summary
Fixes a cDAC `GetCodeHeaderData` failure that surfaced as `Unable to get
codeHeader information` when SOS ran `!clru` against an IL stub
MethodDesc on **Windows x86** with cDAC enabled (the new default
behavior on .NET 11 introduced by
[dotnet/diagnostics#5874](dotnet/diagnostics#5874)).
The CI failure that motivated this is
`SOSMethodTests.VarargPInvokeInteropMD` on x86 in dotnet/diagnostics:
`!IP2MD` returned the IL stub MethodDesc correctly, but the immediate
follow-up `!clru <MD>` printed only `Unable to get codeHeader
information`. x64 / arm64 / .NET 8/9/10 were unaffected.
## Root cause
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
[`src/coreclr/vm/gc_unwind_x86.inl`](https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/gc_unwind_x86.inl)
and
[`src/coreclr/inc/gcdecoder.cpp`](https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/gcdecoder.cpp)
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`).
The cDAC `GCInfo` contract registered IGCInfo implementations for
X64/Arm64/Arm/LoongArch64/RiscV64 but **not** X86 -- so on x86 it fell
through to `default(GCInfo)` and threw `NotImplementedException` from
the interface's default `DecodePlatformSpecificGCInfo`. Any SOS path
that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
## Approach
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86 stack
walker. Rather than write a parallel decoder, this PR **relocates** that
existing decoder under the `GCInfo` contract so there is **one canonical
x86 GC info implementation** shared between SOS callers and the stack
walker -- mirroring how the other architectures' decoders are
structured.
## Changes
* **Move** `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` →
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename namespace
`StackWalkHelpers.X86` → `GCInfoHelpers.X86`.
* **Rename** the moved class `GCInfo` → `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges` and
`EnumerateLiveSlots` throw `NotSupportedException` (future work, needed
for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because it
needs offset-bound state (`IsInProlog` / `IsInEpilog` / `PushedArgSize`)
not exposed through `IGCInfoDecoder`.
## Tests
* New `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test in `cdac/tests/DumpTests/StackWalkDumpTests.cs` --
asserts the IL stub path returns S_OK with non-zero `MethodSize`. Runs
against the existing cdac-dump-helix `windows_x86` matrix on every PR
(no pipeline changes needed).
* Existing 2509 cDAC unit tests still pass.
* Validated end-to-end against `SOSMethodTests.VarargPInvokeInteropMD`
x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.
## Docs
* `docs/design/datacontracts/GCInfo.md` -- intro now reflects partial
x86 support; `GetSizeOfStackParameterArea` API documented; per-method
status notes for x86.
* `docs/design/datacontracts/StackWalk.md` -- x86 section points at the
consolidated decoder location and explains how it's shared.
## Out of scope (future work)
* `GetInterruptibleRanges` and `EnumerateLiveSlots` for x86. The
underlying transition data is decoded but the adapter to the cDAC
`IGCInfoDecoder` shape is not wired up yet. This is what's needed to
unblock `!gcroot`, `!clrstack -l`, `!pe` on x86 cDAC. The two
pre-existing `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers in `StackReferenceDumpTests.cs` should become removable
once that lands.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@max-charlamb@steveisok@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract by max-charlamb · Pull Request #129456 · dotnet/runtime · GitHub
Skip to content

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract - #129456

Merged
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength
Jun 17, 2026
Merged

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract#129456
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength

Conversation

@max-charlamb

Copy link
Copy Markdown
Member

Note

This PR was authored with assistance from GitHub Copilot.

Summary

Fixes a cDAC GetCodeHeaderData failure that surfaced as Unable to get codeHeader information when SOS ran !clru against an IL stub MethodDesc on Windows x86 with cDAC enabled (the new default behavior on .NET 11 introduced by dotnet/diagnostics#5874).

The CI failure that motivated this is SOSMethodTests.VarargPInvokeInteropMD on x86 in dotnet/diagnostics: !IP2MD returned the IL stub MethodDesc correctly, but the immediate follow-up !clru <MD> printed only Unable to get codeHeader information. x64 / arm64 / .NET 8/9/10 were unaffected.

Root cause

x86 uses a fundamentally different GC info encoding from every other architecture: the legacy bit-packed InfoHdr byte-stream format from src/coreclr/vm/gc_unwind_x86.inl and src/coreclr/inc/gcdecoder.cpp (USE_GC_INFO_DECODER is defined for every target except x86, see eetwain.h:34).

The cDAC GCInfo contract registered IGCInfo implementations for X64/Arm64/Arm/LoongArch64/RiscV64 but not X86 -- so on x86 it fell through to default(GCInfo) and threw NotImplementedException from the interface's default DecodePlatformSpecificGCInfo. Any SOS path that needed method size on x86 (!clru, GetCodeHeaderData, GetMethodRegionInfo) failed.

Approach

The cDAC already had a substantial x86 InfoHdr decoder under Contracts/StackWalk/Context/X86/GCInfoDecoding/, used by the x86 stack walker. Rather than write a parallel decoder, this PR relocates that existing decoder under the GCInfo contract so there is one canonical x86 GC info implementation shared between SOS callers and the stack walker -- mirroring how the other architectures' decoders are structured.

Changes

  • MoveContracts/StackWalk/Context/X86/GCInfoDecoding/*Contracts/GCInfo/X86/* (6 files, tracked as renames). Rename namespace StackWalkHelpers.X86GCInfoHelpers.X86.
  • Rename the moved class GCInfoX86GCInfo to avoid collision with the empty Contracts.GCInfo IGCInfo fallback struct.
  • Make relativeOffset ctor arg optional. Implement IGCInfoDecoder directly on X86GCInfo: GetCodeLength / GetStackBaseRegister / GetSizeOfStackParameterArea are wired up. GetInterruptibleRanges and EnumerateLiveSlots throw NotSupportedException (future work, needed for !gcroot / !clrstack -l etc.).
  • Add GCInfoX86_1 IGCInfo for x86; register it in CoreCLRContracts.cs for RuntimeInfoArchitecture.X86.
  • Update ExecutionManagerCore.GetStackParameterSize to delegate to IGCInfo.GetSizeOfStackParameterArea (one source of truth).
  • X86Unwinder continues to construct X86GCInfo directly because it needs offset-bound state (IsInProlog / IsInEpilog / PushedArgSize) not exposed through IGCInfoDecoder.

Tests

  • New VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize regression test in cdac/tests/DumpTests/StackWalkDumpTests.cs -- asserts the IL stub path returns S_OK with non-zero MethodSize. Runs against the existing cdac-dump-helix windows_x86 matrix on every PR (no pipeline changes needed).
  • Existing 2509 cDAC unit tests still pass.
  • Validated end-to-end against SOSMethodTests.VarargPInvokeInteropMD x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.

Docs

  • docs/design/datacontracts/GCInfo.md -- intro now reflects partial x86 support; GetSizeOfStackParameterArea API documented; per-method status notes for x86.
  • docs/design/datacontracts/StackWalk.md -- x86 section points at the consolidated decoder location and explains how it's shared.

Out of scope (future work)

  • GetInterruptibleRanges and EnumerateLiveSlots for x86. The underlying transition data is decoded but the adapter to the cDAC IGCInfoDecoder shape is not wired up yet. This is what's needed to unblock !gcroot, !clrstack -l, !pe on x86 cDAC. The two pre-existing [SkipOnArch("x86", "GCInfo decoder does not support x86")] markers in StackReferenceDumpTests.cs should become removable once that lands.

…ntract
Fixes the cDAC `GetCodeHeaderData` failure that surfaced as
`Unable to get codeHeader information` when SOS ran `!clru` against an
IL stub MethodDesc on Windows x86, .NET 11, with cDAC enabled (the new
default behavior introduced by dotnet/diagnostics#5874).
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
`src/coreclr/vm/gc_unwind_x86.inl` and `src/coreclr/inc/gcdecoder.cpp`
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`). The GCInfo contract had no x86 implementation, so it
fell through to `default(GCInfo)` and threw `NotImplementedException`
from the interface's default `DecodePlatformSpecificGCInfo`. Any
SOS path that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86
stack walker. This change relocates that decoder under the `GCInfo`
contract so there is a single canonical x86 GC info implementation
shared between SOS callers and the stack walker.
Changes:
* Move `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` ->
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename
namespace `StackWalkHelpers.X86` -> `GCInfoHelpers.X86`.
* Rename the moved class `GCInfo` -> `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges`
and `EnumerateLiveSlots` throw `NotSupportedException` (future work,
needed for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because
it needs offset-bound state (`IsInProlog`/`IsInEpilog`/`PushedArgSize`)
not exposed through `IGCInfoDecoder`.
Tests:
* Add `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test that asserts the IL stub path returns S_OK with
non-zero MethodSize. Runs against the existing cdac-dump-helix
`windows_x86` matrix on every PR.
Docs:
* Update `docs/design/datacontracts/GCInfo.md` to reflect partial x86
support and document `GetSizeOfStackParameterArea`.
* Update `docs/design/datacontracts/StackWalk.md` x86 section to
point at the consolidated decoder location.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an x86 implementation of the cDAC IGCInfo contract by reusing the existing managed x86 InfoHdr decoder (relocated under the GCInfo contract) so SOS callers can query method size / stack parameter area on Windows x86, and includes a dump-based regression test plus documentation updates.

Changes:

  • Register a new x86 IGCInfo implementation (GCInfoX86_1) and wire it up for RuntimeInfoArchitecture.X86.
  • Consolidate x86 GCInfo decoding under the GCInfo contract and update the x86 unwinder and ExecutionManager to use the shared decoder / contract APIs.
  • Add a dump-based regression test for ISOSDacInterface.GetCodeHeaderData on an IL stub and update design docs.

Reviewed changes

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

Show a summary per file
FileDescription
src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.csAdds regression coverage validating GetCodeHeaderData returns a non-zero method size for an IL stub.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.csRegisters GCInfoX86_1 for x86 targets.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.csUpdates x86 unwinder to use the relocated/renamed X86GCInfo type.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/InfoHdr.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfoTargetExtensions.csMoves x86 GCInfo decoder support extensions under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.csRenames GCInfoX86GCInfo, makes relativeOffset optional, and implements IGCInfoDecoder queries needed by SOS/ExecutionManager.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/CallPattern.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.csIntroduces the x86 IGCInfo implementation backed by X86GCInfo.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.csDelegates x86 stack parameter size computation to the IGCInfo contract for a single source of truth.
docs/design/datacontracts/StackWalk.mdDocuments the consolidated location and sharing model for the x86 GCInfo decoder.
docs/design/datacontracts/GCInfo.mdUpdates contract API docs (but the intro still incorrectly claims x86 is unsupported).

Comment threaddocs/design/datacontracts/GCInfo.md
Tweaks the second intro paragraph in GCInfo.md to say x86 is "partially
supported" rather than "not currently supported", reflecting the IGCInfo
implementation added in the first commit.
Addresses dotnet#129456 (comment)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlamb marked this pull request as ready for review June 16, 2026 13:20
CopilotAI review requested due to automatic review settings June 16, 2026 15:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 13 out of 13 changed files in this pull request and generated 1 comment.

Comment threaddocs/design/datacontracts/GCInfo.md Outdated
Address PR feedback: the IGCInfo contract was conflating two distinct
concepts under one API name. GcInfoDecoder::GetSizeOfStackParameterArea
returns the outgoing-argument scratch area (_fixedStackParameterScratchArea,
platform-specific, used by the GC scanner). EECodeManager::GetStackParameterSize
returns the x86 __stdcall callee-popped argument size (used by the debugger
to adjust SP after return).
Keep IGCInfo.GetSizeOfStackParameterArea as the GcInfoDecoder scratch-area
accessor (consumed by GcScanner). On x86 it now throws NotSupportedException
since x86 has no separate scratch-area concept.
Add IGCInfo.GetCalleePoppedArgumentsSize mirroring EECodeManager::GetStackParameterSize:
0 by default, x86 returns varargs ? 0 : argCount * ptrSize.
ExecutionManagerCore.GetStackParameterSize now routes through this new method.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… x86 GCInfo
The previous test passed the IL stub's stable entry point
(GetMethodEntryPointIfExists) to GetCodeHeaderData. On x86 r2r that resolves
to a precode, so SOSDacImpl.GetCodeHeaderData takes the NonVirtualEntry2MethodDesc
fallback (MethodSize=0, TYPE_UNKNOWN) and never calls IGCInfo.GetCodeLength -
the exact path the regression was about.
Pass the live instruction pointer of a Frameless IL stub frame instead. The
IP is guaranteed to be inside the JIT-emitted code body, so GetCodeHeaderData
takes the full path through IGCInfo.GetCodeLength and the test actually
exercises the x86 X86GCInfo decoder it claims to cover.
Validated locally against the x86 dumps from the original failing CI run:
all 452 dump tests pass (previously 1 failure).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:35

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • GetSizeOfStackParameterArea is invoked by the managed GC scanner (Contracts/StackWalk/GC/GcScanner.cs) before live-slot enumeration. On x86 there’s no separate scratch/outgoing area, so returning 0 is a sensible, non-throwing default; throwing here causes x86 GC scanning callers to fail early even when they could proceed (or fail later for the actual missing feature).
    src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:32
  • X86GCInfo (and related x86 decoder helpers) are public and were also renamed/moved from the prior StackWalkHelpers.X86.GCInfo name/namespace. If these assemblies are consumed externally, that’s a breaking API change and expands public surface area. Consider making these helpers internal (they appear to be used only within this assembly), or providing a compatibility shim/type-forwarding stub for the old public type name/namespace.

Comment threaddocs/design/datacontracts/StackWalk.md Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • IGCInfoDecoder.GetSizeOfStackParameterArea is used unconditionally by GcScanner.EnumGcRefsForManagedFrame (StackWalk/GC/GcScanner.cs:48-50). Throwing here will immediately abort managed-frame GC scanning on x86 (even before EnumerateLiveSlots), which makes the method unusable and can lead to hard-to-diagnose partial results (exceptions are swallowed per-frame in StackWalk_1). Since x86 has no separate scratch/outgoing-arg area concept, returning 0 is a safe representation and avoids unnecessary exceptions.

@max-charlamb
max-charlamb enabled auto-merge (squash) June 17, 2026 16:42
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

/ba-g known helix infra issues causing timeouts on arm64 and osx

@max-charlamb
max-charlamb merged commit 9451fcf into dotnet:mainJun 17, 2026
68 of 77 checks passed
@max-charlamb
max-charlamb deleted the cdac-x86-gcinfo-codelength branch June 17, 2026 18:08
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…ntract (#129456)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
## Summary
Fixes a cDAC `GetCodeHeaderData` failure that surfaced as `Unable to get
codeHeader information` when SOS ran `!clru` against an IL stub
MethodDesc on **Windows x86** with cDAC enabled (the new default
behavior on .NET 11 introduced by
[dotnet/diagnostics#5874](dotnet/diagnostics#5874)).
The CI failure that motivated this is
`SOSMethodTests.VarargPInvokeInteropMD` on x86 in dotnet/diagnostics:
`!IP2MD` returned the IL stub MethodDesc correctly, but the immediate
follow-up `!clru <MD>` printed only `Unable to get codeHeader
information`. x64 / arm64 / .NET 8/9/10 were unaffected.
## Root cause
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
[`src/coreclr/vm/gc_unwind_x86.inl`](https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/gc_unwind_x86.inl)
and
[`src/coreclr/inc/gcdecoder.cpp`](https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/gcdecoder.cpp)
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`).
The cDAC `GCInfo` contract registered IGCInfo implementations for
X64/Arm64/Arm/LoongArch64/RiscV64 but **not** X86 -- so on x86 it fell
through to `default(GCInfo)` and threw `NotImplementedException` from
the interface's default `DecodePlatformSpecificGCInfo`. Any SOS path
that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
## Approach
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86 stack
walker. Rather than write a parallel decoder, this PR **relocates** that
existing decoder under the `GCInfo` contract so there is **one canonical
x86 GC info implementation** shared between SOS callers and the stack
walker -- mirroring how the other architectures' decoders are
structured.
## Changes
* **Move** `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` →
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename namespace
`StackWalkHelpers.X86` → `GCInfoHelpers.X86`.
* **Rename** the moved class `GCInfo` → `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges` and
`EnumerateLiveSlots` throw `NotSupportedException` (future work, needed
for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because it
needs offset-bound state (`IsInProlog` / `IsInEpilog` / `PushedArgSize`)
not exposed through `IGCInfoDecoder`.
## Tests
* New `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test in `cdac/tests/DumpTests/StackWalkDumpTests.cs` --
asserts the IL stub path returns S_OK with non-zero `MethodSize`. Runs
against the existing cdac-dump-helix `windows_x86` matrix on every PR
(no pipeline changes needed).
* Existing 2509 cDAC unit tests still pass.
* Validated end-to-end against `SOSMethodTests.VarargPInvokeInteropMD`
x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.
## Docs
* `docs/design/datacontracts/GCInfo.md` -- intro now reflects partial
x86 support; `GetSizeOfStackParameterArea` API documented; per-method
status notes for x86.
* `docs/design/datacontracts/StackWalk.md` -- x86 section points at the
consolidated decoder location and explains how it's shared.
## Out of scope (future work)
* `GetInterruptibleRanges` and `EnumerateLiveSlots` for x86. The
underlying transition data is decoded but the adapter to the cDAC
`IGCInfoDecoder` shape is not wired up yet. This is what's needed to
unblock `!gcroot`, `!clrstack -l`, `!pe` on x86 cDAC. The two
pre-existing `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers in `StackReferenceDumpTests.cs` should become removable
once that lands.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@max-charlamb@steveisok@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' [cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract by max-charlamb · Pull Request #129456 · dotnet/runtime · GitHub
Skip to content

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract - #129456

Merged
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength
Jun 17, 2026
Merged

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract#129456
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength

Conversation

@max-charlamb

Copy link
Copy Markdown
Member

Note

This PR was authored with assistance from GitHub Copilot.

Summary

Fixes a cDAC GetCodeHeaderData failure that surfaced as Unable to get codeHeader information when SOS ran !clru against an IL stub MethodDesc on Windows x86 with cDAC enabled (the new default behavior on .NET 11 introduced by dotnet/diagnostics#5874).

The CI failure that motivated this is SOSMethodTests.VarargPInvokeInteropMD on x86 in dotnet/diagnostics: !IP2MD returned the IL stub MethodDesc correctly, but the immediate follow-up !clru <MD> printed only Unable to get codeHeader information. x64 / arm64 / .NET 8/9/10 were unaffected.

Root cause

x86 uses a fundamentally different GC info encoding from every other architecture: the legacy bit-packed InfoHdr byte-stream format from src/coreclr/vm/gc_unwind_x86.inl and src/coreclr/inc/gcdecoder.cpp (USE_GC_INFO_DECODER is defined for every target except x86, see eetwain.h:34).

The cDAC GCInfo contract registered IGCInfo implementations for X64/Arm64/Arm/LoongArch64/RiscV64 but not X86 -- so on x86 it fell through to default(GCInfo) and threw NotImplementedException from the interface's default DecodePlatformSpecificGCInfo. Any SOS path that needed method size on x86 (!clru, GetCodeHeaderData, GetMethodRegionInfo) failed.

Approach

The cDAC already had a substantial x86 InfoHdr decoder under Contracts/StackWalk/Context/X86/GCInfoDecoding/, used by the x86 stack walker. Rather than write a parallel decoder, this PR relocates that existing decoder under the GCInfo contract so there is one canonical x86 GC info implementation shared between SOS callers and the stack walker -- mirroring how the other architectures' decoders are structured.

Changes

  • MoveContracts/StackWalk/Context/X86/GCInfoDecoding/*Contracts/GCInfo/X86/* (6 files, tracked as renames). Rename namespace StackWalkHelpers.X86GCInfoHelpers.X86.
  • Rename the moved class GCInfoX86GCInfo to avoid collision with the empty Contracts.GCInfo IGCInfo fallback struct.
  • Make relativeOffset ctor arg optional. Implement IGCInfoDecoder directly on X86GCInfo: GetCodeLength / GetStackBaseRegister / GetSizeOfStackParameterArea are wired up. GetInterruptibleRanges and EnumerateLiveSlots throw NotSupportedException (future work, needed for !gcroot / !clrstack -l etc.).
  • Add GCInfoX86_1 IGCInfo for x86; register it in CoreCLRContracts.cs for RuntimeInfoArchitecture.X86.
  • Update ExecutionManagerCore.GetStackParameterSize to delegate to IGCInfo.GetSizeOfStackParameterArea (one source of truth).
  • X86Unwinder continues to construct X86GCInfo directly because it needs offset-bound state (IsInProlog / IsInEpilog / PushedArgSize) not exposed through IGCInfoDecoder.

Tests

  • New VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize regression test in cdac/tests/DumpTests/StackWalkDumpTests.cs -- asserts the IL stub path returns S_OK with non-zero MethodSize. Runs against the existing cdac-dump-helix windows_x86 matrix on every PR (no pipeline changes needed).
  • Existing 2509 cDAC unit tests still pass.
  • Validated end-to-end against SOSMethodTests.VarargPInvokeInteropMD x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.

Docs

  • docs/design/datacontracts/GCInfo.md -- intro now reflects partial x86 support; GetSizeOfStackParameterArea API documented; per-method status notes for x86.
  • docs/design/datacontracts/StackWalk.md -- x86 section points at the consolidated decoder location and explains how it's shared.

Out of scope (future work)

  • GetInterruptibleRanges and EnumerateLiveSlots for x86. The underlying transition data is decoded but the adapter to the cDAC IGCInfoDecoder shape is not wired up yet. This is what's needed to unblock !gcroot, !clrstack -l, !pe on x86 cDAC. The two pre-existing [SkipOnArch("x86", "GCInfo decoder does not support x86")] markers in StackReferenceDumpTests.cs should become removable once that lands.

…ntract
Fixes the cDAC `GetCodeHeaderData` failure that surfaced as
`Unable to get codeHeader information` when SOS ran `!clru` against an
IL stub MethodDesc on Windows x86, .NET 11, with cDAC enabled (the new
default behavior introduced by dotnet/diagnostics#5874).
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
`src/coreclr/vm/gc_unwind_x86.inl` and `src/coreclr/inc/gcdecoder.cpp`
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`). The GCInfo contract had no x86 implementation, so it
fell through to `default(GCInfo)` and threw `NotImplementedException`
from the interface's default `DecodePlatformSpecificGCInfo`. Any
SOS path that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86
stack walker. This change relocates that decoder under the `GCInfo`
contract so there is a single canonical x86 GC info implementation
shared between SOS callers and the stack walker.
Changes:
* Move `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` ->
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename
namespace `StackWalkHelpers.X86` -> `GCInfoHelpers.X86`.
* Rename the moved class `GCInfo` -> `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges`
and `EnumerateLiveSlots` throw `NotSupportedException` (future work,
needed for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because
it needs offset-bound state (`IsInProlog`/`IsInEpilog`/`PushedArgSize`)
not exposed through `IGCInfoDecoder`.
Tests:
* Add `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test that asserts the IL stub path returns S_OK with
non-zero MethodSize. Runs against the existing cdac-dump-helix
`windows_x86` matrix on every PR.
Docs:
* Update `docs/design/datacontracts/GCInfo.md` to reflect partial x86
support and document `GetSizeOfStackParameterArea`.
* Update `docs/design/datacontracts/StackWalk.md` x86 section to
point at the consolidated decoder location.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an x86 implementation of the cDAC IGCInfo contract by reusing the existing managed x86 InfoHdr decoder (relocated under the GCInfo contract) so SOS callers can query method size / stack parameter area on Windows x86, and includes a dump-based regression test plus documentation updates.

Changes:

  • Register a new x86 IGCInfo implementation (GCInfoX86_1) and wire it up for RuntimeInfoArchitecture.X86.
  • Consolidate x86 GCInfo decoding under the GCInfo contract and update the x86 unwinder and ExecutionManager to use the shared decoder / contract APIs.
  • Add a dump-based regression test for ISOSDacInterface.GetCodeHeaderData on an IL stub and update design docs.

Reviewed changes

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

Show a summary per file
FileDescription
src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.csAdds regression coverage validating GetCodeHeaderData returns a non-zero method size for an IL stub.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.csRegisters GCInfoX86_1 for x86 targets.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.csUpdates x86 unwinder to use the relocated/renamed X86GCInfo type.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/InfoHdr.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfoTargetExtensions.csMoves x86 GCInfo decoder support extensions under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.csRenames GCInfoX86GCInfo, makes relativeOffset optional, and implements IGCInfoDecoder queries needed by SOS/ExecutionManager.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/CallPattern.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.csIntroduces the x86 IGCInfo implementation backed by X86GCInfo.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.csDelegates x86 stack parameter size computation to the IGCInfo contract for a single source of truth.
docs/design/datacontracts/StackWalk.mdDocuments the consolidated location and sharing model for the x86 GCInfo decoder.
docs/design/datacontracts/GCInfo.mdUpdates contract API docs (but the intro still incorrectly claims x86 is unsupported).

Comment threaddocs/design/datacontracts/GCInfo.md
Tweaks the second intro paragraph in GCInfo.md to say x86 is "partially
supported" rather than "not currently supported", reflecting the IGCInfo
implementation added in the first commit.
Addresses dotnet#129456 (comment)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlamb marked this pull request as ready for review June 16, 2026 13:20
CopilotAI review requested due to automatic review settings June 16, 2026 15:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 13 out of 13 changed files in this pull request and generated 1 comment.

Comment threaddocs/design/datacontracts/GCInfo.md Outdated
Address PR feedback: the IGCInfo contract was conflating two distinct
concepts under one API name. GcInfoDecoder::GetSizeOfStackParameterArea
returns the outgoing-argument scratch area (_fixedStackParameterScratchArea,
platform-specific, used by the GC scanner). EECodeManager::GetStackParameterSize
returns the x86 __stdcall callee-popped argument size (used by the debugger
to adjust SP after return).
Keep IGCInfo.GetSizeOfStackParameterArea as the GcInfoDecoder scratch-area
accessor (consumed by GcScanner). On x86 it now throws NotSupportedException
since x86 has no separate scratch-area concept.
Add IGCInfo.GetCalleePoppedArgumentsSize mirroring EECodeManager::GetStackParameterSize:
0 by default, x86 returns varargs ? 0 : argCount * ptrSize.
ExecutionManagerCore.GetStackParameterSize now routes through this new method.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… x86 GCInfo
The previous test passed the IL stub's stable entry point
(GetMethodEntryPointIfExists) to GetCodeHeaderData. On x86 r2r that resolves
to a precode, so SOSDacImpl.GetCodeHeaderData takes the NonVirtualEntry2MethodDesc
fallback (MethodSize=0, TYPE_UNKNOWN) and never calls IGCInfo.GetCodeLength -
the exact path the regression was about.
Pass the live instruction pointer of a Frameless IL stub frame instead. The
IP is guaranteed to be inside the JIT-emitted code body, so GetCodeHeaderData
takes the full path through IGCInfo.GetCodeLength and the test actually
exercises the x86 X86GCInfo decoder it claims to cover.
Validated locally against the x86 dumps from the original failing CI run:
all 452 dump tests pass (previously 1 failure).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:35

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • GetSizeOfStackParameterArea is invoked by the managed GC scanner (Contracts/StackWalk/GC/GcScanner.cs) before live-slot enumeration. On x86 there’s no separate scratch/outgoing area, so returning 0 is a sensible, non-throwing default; throwing here causes x86 GC scanning callers to fail early even when they could proceed (or fail later for the actual missing feature).
    src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:32
  • X86GCInfo (and related x86 decoder helpers) are public and were also renamed/moved from the prior StackWalkHelpers.X86.GCInfo name/namespace. If these assemblies are consumed externally, that’s a breaking API change and expands public surface area. Consider making these helpers internal (they appear to be used only within this assembly), or providing a compatibility shim/type-forwarding stub for the old public type name/namespace.

Comment threaddocs/design/datacontracts/StackWalk.md Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • IGCInfoDecoder.GetSizeOfStackParameterArea is used unconditionally by GcScanner.EnumGcRefsForManagedFrame (StackWalk/GC/GcScanner.cs:48-50). Throwing here will immediately abort managed-frame GC scanning on x86 (even before EnumerateLiveSlots), which makes the method unusable and can lead to hard-to-diagnose partial results (exceptions are swallowed per-frame in StackWalk_1). Since x86 has no separate scratch/outgoing-arg area concept, returning 0 is a safe representation and avoids unnecessary exceptions.

@max-charlamb
max-charlamb enabled auto-merge (squash) June 17, 2026 16:42
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

/ba-g known helix infra issues causing timeouts on arm64 and osx

@max-charlamb
max-charlamb merged commit 9451fcf into dotnet:mainJun 17, 2026
68 of 77 checks passed
@max-charlamb
max-charlamb deleted the cdac-x86-gcinfo-codelength branch June 17, 2026 18:08
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…ntract (#129456)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
## Summary
Fixes a cDAC `GetCodeHeaderData` failure that surfaced as `Unable to get
codeHeader information` when SOS ran `!clru` against an IL stub
MethodDesc on **Windows x86** with cDAC enabled (the new default
behavior on .NET 11 introduced by
[dotnet/diagnostics#5874](dotnet/diagnostics#5874)).
The CI failure that motivated this is
`SOSMethodTests.VarargPInvokeInteropMD` on x86 in dotnet/diagnostics:
`!IP2MD` returned the IL stub MethodDesc correctly, but the immediate
follow-up `!clru <MD>` printed only `Unable to get codeHeader
information`. x64 / arm64 / .NET 8/9/10 were unaffected.
## Root cause
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
[`src/coreclr/vm/gc_unwind_x86.inl`](https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/gc_unwind_x86.inl)
and
[`src/coreclr/inc/gcdecoder.cpp`](https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/gcdecoder.cpp)
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`).
The cDAC `GCInfo` contract registered IGCInfo implementations for
X64/Arm64/Arm/LoongArch64/RiscV64 but **not** X86 -- so on x86 it fell
through to `default(GCInfo)` and threw `NotImplementedException` from
the interface's default `DecodePlatformSpecificGCInfo`. Any SOS path
that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
## Approach
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86 stack
walker. Rather than write a parallel decoder, this PR **relocates** that
existing decoder under the `GCInfo` contract so there is **one canonical
x86 GC info implementation** shared between SOS callers and the stack
walker -- mirroring how the other architectures' decoders are
structured.
## Changes
* **Move** `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` →
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename namespace
`StackWalkHelpers.X86` → `GCInfoHelpers.X86`.
* **Rename** the moved class `GCInfo` → `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges` and
`EnumerateLiveSlots` throw `NotSupportedException` (future work, needed
for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because it
needs offset-bound state (`IsInProlog` / `IsInEpilog` / `PushedArgSize`)
not exposed through `IGCInfoDecoder`.
## Tests
* New `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test in `cdac/tests/DumpTests/StackWalkDumpTests.cs` --
asserts the IL stub path returns S_OK with non-zero `MethodSize`. Runs
against the existing cdac-dump-helix `windows_x86` matrix on every PR
(no pipeline changes needed).
* Existing 2509 cDAC unit tests still pass.
* Validated end-to-end against `SOSMethodTests.VarargPInvokeInteropMD`
x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.
## Docs
* `docs/design/datacontracts/GCInfo.md` -- intro now reflects partial
x86 support; `GetSizeOfStackParameterArea` API documented; per-method
status notes for x86.
* `docs/design/datacontracts/StackWalk.md` -- x86 section points at the
consolidated decoder location and explains how it's shared.
## Out of scope (future work)
* `GetInterruptibleRanges` and `EnumerateLiveSlots` for x86. The
underlying transition data is decoded but the adapter to the cDAC
`IGCInfoDecoder` shape is not wired up yet. This is what's needed to
unblock `!gcroot`, `!clrstack -l`, `!pe` on x86 cDAC. The two
pre-existing `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers in `StackReferenceDumpTests.cs` should become removable
once that lands.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@max-charlamb@steveisok@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract by max-charlamb · Pull Request #129456 · dotnet/runtime · GitHub
Skip to content

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract - #129456

Merged
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength
Jun 17, 2026
Merged

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract#129456
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength

Conversation

@max-charlamb

Copy link
Copy Markdown
Member

Note

This PR was authored with assistance from GitHub Copilot.

Summary

Fixes a cDAC GetCodeHeaderData failure that surfaced as Unable to get codeHeader information when SOS ran !clru against an IL stub MethodDesc on Windows x86 with cDAC enabled (the new default behavior on .NET 11 introduced by dotnet/diagnostics#5874).

The CI failure that motivated this is SOSMethodTests.VarargPInvokeInteropMD on x86 in dotnet/diagnostics: !IP2MD returned the IL stub MethodDesc correctly, but the immediate follow-up !clru <MD> printed only Unable to get codeHeader information. x64 / arm64 / .NET 8/9/10 were unaffected.

Root cause

x86 uses a fundamentally different GC info encoding from every other architecture: the legacy bit-packed InfoHdr byte-stream format from src/coreclr/vm/gc_unwind_x86.inl and src/coreclr/inc/gcdecoder.cpp (USE_GC_INFO_DECODER is defined for every target except x86, see eetwain.h:34).

The cDAC GCInfo contract registered IGCInfo implementations for X64/Arm64/Arm/LoongArch64/RiscV64 but not X86 -- so on x86 it fell through to default(GCInfo) and threw NotImplementedException from the interface's default DecodePlatformSpecificGCInfo. Any SOS path that needed method size on x86 (!clru, GetCodeHeaderData, GetMethodRegionInfo) failed.

Approach

The cDAC already had a substantial x86 InfoHdr decoder under Contracts/StackWalk/Context/X86/GCInfoDecoding/, used by the x86 stack walker. Rather than write a parallel decoder, this PR relocates that existing decoder under the GCInfo contract so there is one canonical x86 GC info implementation shared between SOS callers and the stack walker -- mirroring how the other architectures' decoders are structured.

Changes

  • MoveContracts/StackWalk/Context/X86/GCInfoDecoding/*Contracts/GCInfo/X86/* (6 files, tracked as renames). Rename namespace StackWalkHelpers.X86GCInfoHelpers.X86.
  • Rename the moved class GCInfoX86GCInfo to avoid collision with the empty Contracts.GCInfo IGCInfo fallback struct.
  • Make relativeOffset ctor arg optional. Implement IGCInfoDecoder directly on X86GCInfo: GetCodeLength / GetStackBaseRegister / GetSizeOfStackParameterArea are wired up. GetInterruptibleRanges and EnumerateLiveSlots throw NotSupportedException (future work, needed for !gcroot / !clrstack -l etc.).
  • Add GCInfoX86_1 IGCInfo for x86; register it in CoreCLRContracts.cs for RuntimeInfoArchitecture.X86.
  • Update ExecutionManagerCore.GetStackParameterSize to delegate to IGCInfo.GetSizeOfStackParameterArea (one source of truth).
  • X86Unwinder continues to construct X86GCInfo directly because it needs offset-bound state (IsInProlog / IsInEpilog / PushedArgSize) not exposed through IGCInfoDecoder.

Tests

  • New VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize regression test in cdac/tests/DumpTests/StackWalkDumpTests.cs -- asserts the IL stub path returns S_OK with non-zero MethodSize. Runs against the existing cdac-dump-helix windows_x86 matrix on every PR (no pipeline changes needed).
  • Existing 2509 cDAC unit tests still pass.
  • Validated end-to-end against SOSMethodTests.VarargPInvokeInteropMD x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.

Docs

  • docs/design/datacontracts/GCInfo.md -- intro now reflects partial x86 support; GetSizeOfStackParameterArea API documented; per-method status notes for x86.
  • docs/design/datacontracts/StackWalk.md -- x86 section points at the consolidated decoder location and explains how it's shared.

Out of scope (future work)

  • GetInterruptibleRanges and EnumerateLiveSlots for x86. The underlying transition data is decoded but the adapter to the cDAC IGCInfoDecoder shape is not wired up yet. This is what's needed to unblock !gcroot, !clrstack -l, !pe on x86 cDAC. The two pre-existing [SkipOnArch("x86", "GCInfo decoder does not support x86")] markers in StackReferenceDumpTests.cs should become removable once that lands.

…ntract
Fixes the cDAC `GetCodeHeaderData` failure that surfaced as
`Unable to get codeHeader information` when SOS ran `!clru` against an
IL stub MethodDesc on Windows x86, .NET 11, with cDAC enabled (the new
default behavior introduced by dotnet/diagnostics#5874).
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
`src/coreclr/vm/gc_unwind_x86.inl` and `src/coreclr/inc/gcdecoder.cpp`
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`). The GCInfo contract had no x86 implementation, so it
fell through to `default(GCInfo)` and threw `NotImplementedException`
from the interface's default `DecodePlatformSpecificGCInfo`. Any
SOS path that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86
stack walker. This change relocates that decoder under the `GCInfo`
contract so there is a single canonical x86 GC info implementation
shared between SOS callers and the stack walker.
Changes:
* Move `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` ->
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename
namespace `StackWalkHelpers.X86` -> `GCInfoHelpers.X86`.
* Rename the moved class `GCInfo` -> `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges`
and `EnumerateLiveSlots` throw `NotSupportedException` (future work,
needed for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because
it needs offset-bound state (`IsInProlog`/`IsInEpilog`/`PushedArgSize`)
not exposed through `IGCInfoDecoder`.
Tests:
* Add `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test that asserts the IL stub path returns S_OK with
non-zero MethodSize. Runs against the existing cdac-dump-helix
`windows_x86` matrix on every PR.
Docs:
* Update `docs/design/datacontracts/GCInfo.md` to reflect partial x86
support and document `GetSizeOfStackParameterArea`.
* Update `docs/design/datacontracts/StackWalk.md` x86 section to
point at the consolidated decoder location.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an x86 implementation of the cDAC IGCInfo contract by reusing the existing managed x86 InfoHdr decoder (relocated under the GCInfo contract) so SOS callers can query method size / stack parameter area on Windows x86, and includes a dump-based regression test plus documentation updates.

Changes:

  • Register a new x86 IGCInfo implementation (GCInfoX86_1) and wire it up for RuntimeInfoArchitecture.X86.
  • Consolidate x86 GCInfo decoding under the GCInfo contract and update the x86 unwinder and ExecutionManager to use the shared decoder / contract APIs.
  • Add a dump-based regression test for ISOSDacInterface.GetCodeHeaderData on an IL stub and update design docs.

Reviewed changes

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

Show a summary per file
FileDescription
src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.csAdds regression coverage validating GetCodeHeaderData returns a non-zero method size for an IL stub.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.csRegisters GCInfoX86_1 for x86 targets.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.csUpdates x86 unwinder to use the relocated/renamed X86GCInfo type.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/InfoHdr.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfoTargetExtensions.csMoves x86 GCInfo decoder support extensions under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.csRenames GCInfoX86GCInfo, makes relativeOffset optional, and implements IGCInfoDecoder queries needed by SOS/ExecutionManager.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/CallPattern.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.csIntroduces the x86 IGCInfo implementation backed by X86GCInfo.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.csDelegates x86 stack parameter size computation to the IGCInfo contract for a single source of truth.
docs/design/datacontracts/StackWalk.mdDocuments the consolidated location and sharing model for the x86 GCInfo decoder.
docs/design/datacontracts/GCInfo.mdUpdates contract API docs (but the intro still incorrectly claims x86 is unsupported).

Comment threaddocs/design/datacontracts/GCInfo.md
Tweaks the second intro paragraph in GCInfo.md to say x86 is "partially
supported" rather than "not currently supported", reflecting the IGCInfo
implementation added in the first commit.
Addresses dotnet#129456 (comment)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlamb marked this pull request as ready for review June 16, 2026 13:20
CopilotAI review requested due to automatic review settings June 16, 2026 15:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 13 out of 13 changed files in this pull request and generated 1 comment.

Comment threaddocs/design/datacontracts/GCInfo.md Outdated
Address PR feedback: the IGCInfo contract was conflating two distinct
concepts under one API name. GcInfoDecoder::GetSizeOfStackParameterArea
returns the outgoing-argument scratch area (_fixedStackParameterScratchArea,
platform-specific, used by the GC scanner). EECodeManager::GetStackParameterSize
returns the x86 __stdcall callee-popped argument size (used by the debugger
to adjust SP after return).
Keep IGCInfo.GetSizeOfStackParameterArea as the GcInfoDecoder scratch-area
accessor (consumed by GcScanner). On x86 it now throws NotSupportedException
since x86 has no separate scratch-area concept.
Add IGCInfo.GetCalleePoppedArgumentsSize mirroring EECodeManager::GetStackParameterSize:
0 by default, x86 returns varargs ? 0 : argCount * ptrSize.
ExecutionManagerCore.GetStackParameterSize now routes through this new method.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… x86 GCInfo
The previous test passed the IL stub's stable entry point
(GetMethodEntryPointIfExists) to GetCodeHeaderData. On x86 r2r that resolves
to a precode, so SOSDacImpl.GetCodeHeaderData takes the NonVirtualEntry2MethodDesc
fallback (MethodSize=0, TYPE_UNKNOWN) and never calls IGCInfo.GetCodeLength -
the exact path the regression was about.
Pass the live instruction pointer of a Frameless IL stub frame instead. The
IP is guaranteed to be inside the JIT-emitted code body, so GetCodeHeaderData
takes the full path through IGCInfo.GetCodeLength and the test actually
exercises the x86 X86GCInfo decoder it claims to cover.
Validated locally against the x86 dumps from the original failing CI run:
all 452 dump tests pass (previously 1 failure).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:35

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • GetSizeOfStackParameterArea is invoked by the managed GC scanner (Contracts/StackWalk/GC/GcScanner.cs) before live-slot enumeration. On x86 there’s no separate scratch/outgoing area, so returning 0 is a sensible, non-throwing default; throwing here causes x86 GC scanning callers to fail early even when they could proceed (or fail later for the actual missing feature).
    src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:32
  • X86GCInfo (and related x86 decoder helpers) are public and were also renamed/moved from the prior StackWalkHelpers.X86.GCInfo name/namespace. If these assemblies are consumed externally, that’s a breaking API change and expands public surface area. Consider making these helpers internal (they appear to be used only within this assembly), or providing a compatibility shim/type-forwarding stub for the old public type name/namespace.

Comment threaddocs/design/datacontracts/StackWalk.md Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • IGCInfoDecoder.GetSizeOfStackParameterArea is used unconditionally by GcScanner.EnumGcRefsForManagedFrame (StackWalk/GC/GcScanner.cs:48-50). Throwing here will immediately abort managed-frame GC scanning on x86 (even before EnumerateLiveSlots), which makes the method unusable and can lead to hard-to-diagnose partial results (exceptions are swallowed per-frame in StackWalk_1). Since x86 has no separate scratch/outgoing-arg area concept, returning 0 is a safe representation and avoids unnecessary exceptions.

@max-charlamb
max-charlamb enabled auto-merge (squash) June 17, 2026 16:42
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

/ba-g known helix infra issues causing timeouts on arm64 and osx

@max-charlamb
max-charlamb merged commit 9451fcf into dotnet:mainJun 17, 2026
68 of 77 checks passed
@max-charlamb
max-charlamb deleted the cdac-x86-gcinfo-codelength branch June 17, 2026 18:08
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…ntract (#129456)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
## Summary
Fixes a cDAC `GetCodeHeaderData` failure that surfaced as `Unable to get
codeHeader information` when SOS ran `!clru` against an IL stub
MethodDesc on **Windows x86** with cDAC enabled (the new default
behavior on .NET 11 introduced by
[dotnet/diagnostics#5874](dotnet/diagnostics#5874)).
The CI failure that motivated this is
`SOSMethodTests.VarargPInvokeInteropMD` on x86 in dotnet/diagnostics:
`!IP2MD` returned the IL stub MethodDesc correctly, but the immediate
follow-up `!clru <MD>` printed only `Unable to get codeHeader
information`. x64 / arm64 / .NET 8/9/10 were unaffected.
## Root cause
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
[`src/coreclr/vm/gc_unwind_x86.inl`](https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/gc_unwind_x86.inl)
and
[`src/coreclr/inc/gcdecoder.cpp`](https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/gcdecoder.cpp)
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`).
The cDAC `GCInfo` contract registered IGCInfo implementations for
X64/Arm64/Arm/LoongArch64/RiscV64 but **not** X86 -- so on x86 it fell
through to `default(GCInfo)` and threw `NotImplementedException` from
the interface's default `DecodePlatformSpecificGCInfo`. Any SOS path
that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
## Approach
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86 stack
walker. Rather than write a parallel decoder, this PR **relocates** that
existing decoder under the `GCInfo` contract so there is **one canonical
x86 GC info implementation** shared between SOS callers and the stack
walker -- mirroring how the other architectures' decoders are
structured.
## Changes
* **Move** `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` →
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename namespace
`StackWalkHelpers.X86` → `GCInfoHelpers.X86`.
* **Rename** the moved class `GCInfo` → `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges` and
`EnumerateLiveSlots` throw `NotSupportedException` (future work, needed
for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because it
needs offset-bound state (`IsInProlog` / `IsInEpilog` / `PushedArgSize`)
not exposed through `IGCInfoDecoder`.
## Tests
* New `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test in `cdac/tests/DumpTests/StackWalkDumpTests.cs` --
asserts the IL stub path returns S_OK with non-zero `MethodSize`. Runs
against the existing cdac-dump-helix `windows_x86` matrix on every PR
(no pipeline changes needed).
* Existing 2509 cDAC unit tests still pass.
* Validated end-to-end against `SOSMethodTests.VarargPInvokeInteropMD`
x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.
## Docs
* `docs/design/datacontracts/GCInfo.md` -- intro now reflects partial
x86 support; `GetSizeOfStackParameterArea` API documented; per-method
status notes for x86.
* `docs/design/datacontracts/StackWalk.md` -- x86 section points at the
consolidated decoder location and explains how it's shared.
## Out of scope (future work)
* `GetInterruptibleRanges` and `EnumerateLiveSlots` for x86. The
underlying transition data is decoded but the adapter to the cDAC
`IGCInfoDecoder` shape is not wired up yet. This is what's needed to
unblock `!gcroot`, `!clrstack -l`, `!pe` on x86 cDAC. The two
pre-existing `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers in `StackReferenceDumpTests.cs` should become removable
once that lands.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@max-charlamb@steveisok@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' [cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract by max-charlamb · Pull Request #129456 · dotnet/runtime · GitHub
Skip to content

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract - #129456

Merged
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength
Jun 17, 2026
Merged

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract#129456
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength

Conversation

@max-charlamb

Copy link
Copy Markdown
Member

Note

This PR was authored with assistance from GitHub Copilot.

Summary

Fixes a cDAC GetCodeHeaderData failure that surfaced as Unable to get codeHeader information when SOS ran !clru against an IL stub MethodDesc on Windows x86 with cDAC enabled (the new default behavior on .NET 11 introduced by dotnet/diagnostics#5874).

The CI failure that motivated this is SOSMethodTests.VarargPInvokeInteropMD on x86 in dotnet/diagnostics: !IP2MD returned the IL stub MethodDesc correctly, but the immediate follow-up !clru <MD> printed only Unable to get codeHeader information. x64 / arm64 / .NET 8/9/10 were unaffected.

Root cause

x86 uses a fundamentally different GC info encoding from every other architecture: the legacy bit-packed InfoHdr byte-stream format from src/coreclr/vm/gc_unwind_x86.inl and src/coreclr/inc/gcdecoder.cpp (USE_GC_INFO_DECODER is defined for every target except x86, see eetwain.h:34).

The cDAC GCInfo contract registered IGCInfo implementations for X64/Arm64/Arm/LoongArch64/RiscV64 but not X86 -- so on x86 it fell through to default(GCInfo) and threw NotImplementedException from the interface's default DecodePlatformSpecificGCInfo. Any SOS path that needed method size on x86 (!clru, GetCodeHeaderData, GetMethodRegionInfo) failed.

Approach

The cDAC already had a substantial x86 InfoHdr decoder under Contracts/StackWalk/Context/X86/GCInfoDecoding/, used by the x86 stack walker. Rather than write a parallel decoder, this PR relocates that existing decoder under the GCInfo contract so there is one canonical x86 GC info implementation shared between SOS callers and the stack walker -- mirroring how the other architectures' decoders are structured.

Changes

  • MoveContracts/StackWalk/Context/X86/GCInfoDecoding/*Contracts/GCInfo/X86/* (6 files, tracked as renames). Rename namespace StackWalkHelpers.X86GCInfoHelpers.X86.
  • Rename the moved class GCInfoX86GCInfo to avoid collision with the empty Contracts.GCInfo IGCInfo fallback struct.
  • Make relativeOffset ctor arg optional. Implement IGCInfoDecoder directly on X86GCInfo: GetCodeLength / GetStackBaseRegister / GetSizeOfStackParameterArea are wired up. GetInterruptibleRanges and EnumerateLiveSlots throw NotSupportedException (future work, needed for !gcroot / !clrstack -l etc.).
  • Add GCInfoX86_1 IGCInfo for x86; register it in CoreCLRContracts.cs for RuntimeInfoArchitecture.X86.
  • Update ExecutionManagerCore.GetStackParameterSize to delegate to IGCInfo.GetSizeOfStackParameterArea (one source of truth).
  • X86Unwinder continues to construct X86GCInfo directly because it needs offset-bound state (IsInProlog / IsInEpilog / PushedArgSize) not exposed through IGCInfoDecoder.

Tests

  • New VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize regression test in cdac/tests/DumpTests/StackWalkDumpTests.cs -- asserts the IL stub path returns S_OK with non-zero MethodSize. Runs against the existing cdac-dump-helix windows_x86 matrix on every PR (no pipeline changes needed).
  • Existing 2509 cDAC unit tests still pass.
  • Validated end-to-end against SOSMethodTests.VarargPInvokeInteropMD x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.

Docs

  • docs/design/datacontracts/GCInfo.md -- intro now reflects partial x86 support; GetSizeOfStackParameterArea API documented; per-method status notes for x86.
  • docs/design/datacontracts/StackWalk.md -- x86 section points at the consolidated decoder location and explains how it's shared.

Out of scope (future work)

  • GetInterruptibleRanges and EnumerateLiveSlots for x86. The underlying transition data is decoded but the adapter to the cDAC IGCInfoDecoder shape is not wired up yet. This is what's needed to unblock !gcroot, !clrstack -l, !pe on x86 cDAC. The two pre-existing [SkipOnArch("x86", "GCInfo decoder does not support x86")] markers in StackReferenceDumpTests.cs should become removable once that lands.

…ntract
Fixes the cDAC `GetCodeHeaderData` failure that surfaced as
`Unable to get codeHeader information` when SOS ran `!clru` against an
IL stub MethodDesc on Windows x86, .NET 11, with cDAC enabled (the new
default behavior introduced by dotnet/diagnostics#5874).
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
`src/coreclr/vm/gc_unwind_x86.inl` and `src/coreclr/inc/gcdecoder.cpp`
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`). The GCInfo contract had no x86 implementation, so it
fell through to `default(GCInfo)` and threw `NotImplementedException`
from the interface's default `DecodePlatformSpecificGCInfo`. Any
SOS path that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86
stack walker. This change relocates that decoder under the `GCInfo`
contract so there is a single canonical x86 GC info implementation
shared between SOS callers and the stack walker.
Changes:
* Move `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` ->
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename
namespace `StackWalkHelpers.X86` -> `GCInfoHelpers.X86`.
* Rename the moved class `GCInfo` -> `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges`
and `EnumerateLiveSlots` throw `NotSupportedException` (future work,
needed for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because
it needs offset-bound state (`IsInProlog`/`IsInEpilog`/`PushedArgSize`)
not exposed through `IGCInfoDecoder`.
Tests:
* Add `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test that asserts the IL stub path returns S_OK with
non-zero MethodSize. Runs against the existing cdac-dump-helix
`windows_x86` matrix on every PR.
Docs:
* Update `docs/design/datacontracts/GCInfo.md` to reflect partial x86
support and document `GetSizeOfStackParameterArea`.
* Update `docs/design/datacontracts/StackWalk.md` x86 section to
point at the consolidated decoder location.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an x86 implementation of the cDAC IGCInfo contract by reusing the existing managed x86 InfoHdr decoder (relocated under the GCInfo contract) so SOS callers can query method size / stack parameter area on Windows x86, and includes a dump-based regression test plus documentation updates.

Changes:

  • Register a new x86 IGCInfo implementation (GCInfoX86_1) and wire it up for RuntimeInfoArchitecture.X86.
  • Consolidate x86 GCInfo decoding under the GCInfo contract and update the x86 unwinder and ExecutionManager to use the shared decoder / contract APIs.
  • Add a dump-based regression test for ISOSDacInterface.GetCodeHeaderData on an IL stub and update design docs.

Reviewed changes

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

Show a summary per file
FileDescription
src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.csAdds regression coverage validating GetCodeHeaderData returns a non-zero method size for an IL stub.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.csRegisters GCInfoX86_1 for x86 targets.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.csUpdates x86 unwinder to use the relocated/renamed X86GCInfo type.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/InfoHdr.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfoTargetExtensions.csMoves x86 GCInfo decoder support extensions under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.csRenames GCInfoX86GCInfo, makes relativeOffset optional, and implements IGCInfoDecoder queries needed by SOS/ExecutionManager.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/CallPattern.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.csIntroduces the x86 IGCInfo implementation backed by X86GCInfo.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.csDelegates x86 stack parameter size computation to the IGCInfo contract for a single source of truth.
docs/design/datacontracts/StackWalk.mdDocuments the consolidated location and sharing model for the x86 GCInfo decoder.
docs/design/datacontracts/GCInfo.mdUpdates contract API docs (but the intro still incorrectly claims x86 is unsupported).

Comment threaddocs/design/datacontracts/GCInfo.md
Tweaks the second intro paragraph in GCInfo.md to say x86 is "partially
supported" rather than "not currently supported", reflecting the IGCInfo
implementation added in the first commit.
Addresses dotnet#129456 (comment)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlamb marked this pull request as ready for review June 16, 2026 13:20
CopilotAI review requested due to automatic review settings June 16, 2026 15:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 13 out of 13 changed files in this pull request and generated 1 comment.

Comment threaddocs/design/datacontracts/GCInfo.md Outdated
Address PR feedback: the IGCInfo contract was conflating two distinct
concepts under one API name. GcInfoDecoder::GetSizeOfStackParameterArea
returns the outgoing-argument scratch area (_fixedStackParameterScratchArea,
platform-specific, used by the GC scanner). EECodeManager::GetStackParameterSize
returns the x86 __stdcall callee-popped argument size (used by the debugger
to adjust SP after return).
Keep IGCInfo.GetSizeOfStackParameterArea as the GcInfoDecoder scratch-area
accessor (consumed by GcScanner). On x86 it now throws NotSupportedException
since x86 has no separate scratch-area concept.
Add IGCInfo.GetCalleePoppedArgumentsSize mirroring EECodeManager::GetStackParameterSize:
0 by default, x86 returns varargs ? 0 : argCount * ptrSize.
ExecutionManagerCore.GetStackParameterSize now routes through this new method.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… x86 GCInfo
The previous test passed the IL stub's stable entry point
(GetMethodEntryPointIfExists) to GetCodeHeaderData. On x86 r2r that resolves
to a precode, so SOSDacImpl.GetCodeHeaderData takes the NonVirtualEntry2MethodDesc
fallback (MethodSize=0, TYPE_UNKNOWN) and never calls IGCInfo.GetCodeLength -
the exact path the regression was about.
Pass the live instruction pointer of a Frameless IL stub frame instead. The
IP is guaranteed to be inside the JIT-emitted code body, so GetCodeHeaderData
takes the full path through IGCInfo.GetCodeLength and the test actually
exercises the x86 X86GCInfo decoder it claims to cover.
Validated locally against the x86 dumps from the original failing CI run:
all 452 dump tests pass (previously 1 failure).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:35

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • GetSizeOfStackParameterArea is invoked by the managed GC scanner (Contracts/StackWalk/GC/GcScanner.cs) before live-slot enumeration. On x86 there’s no separate scratch/outgoing area, so returning 0 is a sensible, non-throwing default; throwing here causes x86 GC scanning callers to fail early even when they could proceed (or fail later for the actual missing feature).
    src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:32
  • X86GCInfo (and related x86 decoder helpers) are public and were also renamed/moved from the prior StackWalkHelpers.X86.GCInfo name/namespace. If these assemblies are consumed externally, that’s a breaking API change and expands public surface area. Consider making these helpers internal (they appear to be used only within this assembly), or providing a compatibility shim/type-forwarding stub for the old public type name/namespace.

Comment threaddocs/design/datacontracts/StackWalk.md Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • IGCInfoDecoder.GetSizeOfStackParameterArea is used unconditionally by GcScanner.EnumGcRefsForManagedFrame (StackWalk/GC/GcScanner.cs:48-50). Throwing here will immediately abort managed-frame GC scanning on x86 (even before EnumerateLiveSlots), which makes the method unusable and can lead to hard-to-diagnose partial results (exceptions are swallowed per-frame in StackWalk_1). Since x86 has no separate scratch/outgoing-arg area concept, returning 0 is a safe representation and avoids unnecessary exceptions.

@max-charlamb
max-charlamb enabled auto-merge (squash) June 17, 2026 16:42
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

/ba-g known helix infra issues causing timeouts on arm64 and osx

@max-charlamb
max-charlamb merged commit 9451fcf into dotnet:mainJun 17, 2026
68 of 77 checks passed
@max-charlamb
max-charlamb deleted the cdac-x86-gcinfo-codelength branch June 17, 2026 18:08
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…ntract (#129456)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
## Summary
Fixes a cDAC `GetCodeHeaderData` failure that surfaced as `Unable to get
codeHeader information` when SOS ran `!clru` against an IL stub
MethodDesc on **Windows x86** with cDAC enabled (the new default
behavior on .NET 11 introduced by
[dotnet/diagnostics#5874](dotnet/diagnostics#5874)).
The CI failure that motivated this is
`SOSMethodTests.VarargPInvokeInteropMD` on x86 in dotnet/diagnostics:
`!IP2MD` returned the IL stub MethodDesc correctly, but the immediate
follow-up `!clru <MD>` printed only `Unable to get codeHeader
information`. x64 / arm64 / .NET 8/9/10 were unaffected.
## Root cause
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
[`src/coreclr/vm/gc_unwind_x86.inl`](https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/gc_unwind_x86.inl)
and
[`src/coreclr/inc/gcdecoder.cpp`](https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/gcdecoder.cpp)
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`).
The cDAC `GCInfo` contract registered IGCInfo implementations for
X64/Arm64/Arm/LoongArch64/RiscV64 but **not** X86 -- so on x86 it fell
through to `default(GCInfo)` and threw `NotImplementedException` from
the interface's default `DecodePlatformSpecificGCInfo`. Any SOS path
that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
## Approach
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86 stack
walker. Rather than write a parallel decoder, this PR **relocates** that
existing decoder under the `GCInfo` contract so there is **one canonical
x86 GC info implementation** shared between SOS callers and the stack
walker -- mirroring how the other architectures' decoders are
structured.
## Changes
* **Move** `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` →
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename namespace
`StackWalkHelpers.X86` → `GCInfoHelpers.X86`.
* **Rename** the moved class `GCInfo` → `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges` and
`EnumerateLiveSlots` throw `NotSupportedException` (future work, needed
for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because it
needs offset-bound state (`IsInProlog` / `IsInEpilog` / `PushedArgSize`)
not exposed through `IGCInfoDecoder`.
## Tests
* New `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test in `cdac/tests/DumpTests/StackWalkDumpTests.cs` --
asserts the IL stub path returns S_OK with non-zero `MethodSize`. Runs
against the existing cdac-dump-helix `windows_x86` matrix on every PR
(no pipeline changes needed).
* Existing 2509 cDAC unit tests still pass.
* Validated end-to-end against `SOSMethodTests.VarargPInvokeInteropMD`
x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.
## Docs
* `docs/design/datacontracts/GCInfo.md` -- intro now reflects partial
x86 support; `GetSizeOfStackParameterArea` API documented; per-method
status notes for x86.
* `docs/design/datacontracts/StackWalk.md` -- x86 section points at the
consolidated decoder location and explains how it's shared.
## Out of scope (future work)
* `GetInterruptibleRanges` and `EnumerateLiveSlots` for x86. The
underlying transition data is decoded but the adapter to the cDAC
`IGCInfoDecoder` shape is not wired up yet. This is what's needed to
unblock `!gcroot`, `!clrstack -l`, `!pe` on x86 cDAC. The two
pre-existing `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers in `StackReferenceDumpTests.cs` should become removable
once that lands.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@max-charlamb@steveisok@hoyosjs
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); [cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract by max-charlamb · Pull Request #129456 · dotnet/runtime · GitHub
Skip to content

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract - #129456

Merged
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength
Jun 17, 2026
Merged

[cdac] Implement IGCInfo for x86; consolidate decoder under GCInfo contract#129456
max-charlamb merged 6 commits into
dotnet:mainfrom
max-charlamb:cdac-x86-gcinfo-codelength

Conversation

@max-charlamb

Copy link
Copy Markdown
Member

Note

This PR was authored with assistance from GitHub Copilot.

Summary

Fixes a cDAC GetCodeHeaderData failure that surfaced as Unable to get codeHeader information when SOS ran !clru against an IL stub MethodDesc on Windows x86 with cDAC enabled (the new default behavior on .NET 11 introduced by dotnet/diagnostics#5874).

The CI failure that motivated this is SOSMethodTests.VarargPInvokeInteropMD on x86 in dotnet/diagnostics: !IP2MD returned the IL stub MethodDesc correctly, but the immediate follow-up !clru <MD> printed only Unable to get codeHeader information. x64 / arm64 / .NET 8/9/10 were unaffected.

Root cause

x86 uses a fundamentally different GC info encoding from every other architecture: the legacy bit-packed InfoHdr byte-stream format from src/coreclr/vm/gc_unwind_x86.inl and src/coreclr/inc/gcdecoder.cpp (USE_GC_INFO_DECODER is defined for every target except x86, see eetwain.h:34).

The cDAC GCInfo contract registered IGCInfo implementations for X64/Arm64/Arm/LoongArch64/RiscV64 but not X86 -- so on x86 it fell through to default(GCInfo) and threw NotImplementedException from the interface's default DecodePlatformSpecificGCInfo. Any SOS path that needed method size on x86 (!clru, GetCodeHeaderData, GetMethodRegionInfo) failed.

Approach

The cDAC already had a substantial x86 InfoHdr decoder under Contracts/StackWalk/Context/X86/GCInfoDecoding/, used by the x86 stack walker. Rather than write a parallel decoder, this PR relocates that existing decoder under the GCInfo contract so there is one canonical x86 GC info implementation shared between SOS callers and the stack walker -- mirroring how the other architectures' decoders are structured.

Changes

  • MoveContracts/StackWalk/Context/X86/GCInfoDecoding/*Contracts/GCInfo/X86/* (6 files, tracked as renames). Rename namespace StackWalkHelpers.X86GCInfoHelpers.X86.
  • Rename the moved class GCInfoX86GCInfo to avoid collision with the empty Contracts.GCInfo IGCInfo fallback struct.
  • Make relativeOffset ctor arg optional. Implement IGCInfoDecoder directly on X86GCInfo: GetCodeLength / GetStackBaseRegister / GetSizeOfStackParameterArea are wired up. GetInterruptibleRanges and EnumerateLiveSlots throw NotSupportedException (future work, needed for !gcroot / !clrstack -l etc.).
  • Add GCInfoX86_1 IGCInfo for x86; register it in CoreCLRContracts.cs for RuntimeInfoArchitecture.X86.
  • Update ExecutionManagerCore.GetStackParameterSize to delegate to IGCInfo.GetSizeOfStackParameterArea (one source of truth).
  • X86Unwinder continues to construct X86GCInfo directly because it needs offset-bound state (IsInProlog / IsInEpilog / PushedArgSize) not exposed through IGCInfoDecoder.

Tests

  • New VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize regression test in cdac/tests/DumpTests/StackWalkDumpTests.cs -- asserts the IL stub path returns S_OK with non-zero MethodSize. Runs against the existing cdac-dump-helix windows_x86 matrix on every PR (no pipeline changes needed).
  • Existing 2509 cDAC unit tests still pass.
  • Validated end-to-end against SOSMethodTests.VarargPInvokeInteropMD x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.

Docs

  • docs/design/datacontracts/GCInfo.md -- intro now reflects partial x86 support; GetSizeOfStackParameterArea API documented; per-method status notes for x86.
  • docs/design/datacontracts/StackWalk.md -- x86 section points at the consolidated decoder location and explains how it's shared.

Out of scope (future work)

  • GetInterruptibleRanges and EnumerateLiveSlots for x86. The underlying transition data is decoded but the adapter to the cDAC IGCInfoDecoder shape is not wired up yet. This is what's needed to unblock !gcroot, !clrstack -l, !pe on x86 cDAC. The two pre-existing [SkipOnArch("x86", "GCInfo decoder does not support x86")] markers in StackReferenceDumpTests.cs should become removable once that lands.

…ntract
Fixes the cDAC `GetCodeHeaderData` failure that surfaced as
`Unable to get codeHeader information` when SOS ran `!clru` against an
IL stub MethodDesc on Windows x86, .NET 11, with cDAC enabled (the new
default behavior introduced by dotnet/diagnostics#5874).
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
`src/coreclr/vm/gc_unwind_x86.inl` and `src/coreclr/inc/gcdecoder.cpp`
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`). The GCInfo contract had no x86 implementation, so it
fell through to `default(GCInfo)` and threw `NotImplementedException`
from the interface's default `DecodePlatformSpecificGCInfo`. Any
SOS path that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86
stack walker. This change relocates that decoder under the `GCInfo`
contract so there is a single canonical x86 GC info implementation
shared between SOS callers and the stack walker.
Changes:
* Move `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` ->
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename
namespace `StackWalkHelpers.X86` -> `GCInfoHelpers.X86`.
* Rename the moved class `GCInfo` -> `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges`
and `EnumerateLiveSlots` throw `NotSupportedException` (future work,
needed for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because
it needs offset-bound state (`IsInProlog`/`IsInEpilog`/`PushedArgSize`)
not exposed through `IGCInfoDecoder`.
Tests:
* Add `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test that asserts the IL stub path returns S_OK with
non-zero MethodSize. Runs against the existing cdac-dump-helix
`windows_x86` matrix on every PR.
Docs:
* Update `docs/design/datacontracts/GCInfo.md` to reflect partial x86
support and document `GetSizeOfStackParameterArea`.
* Update `docs/design/datacontracts/StackWalk.md` x86 section to
point at the consolidated decoder location.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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.

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an x86 implementation of the cDAC IGCInfo contract by reusing the existing managed x86 InfoHdr decoder (relocated under the GCInfo contract) so SOS callers can query method size / stack parameter area on Windows x86, and includes a dump-based regression test plus documentation updates.

Changes:

  • Register a new x86 IGCInfo implementation (GCInfoX86_1) and wire it up for RuntimeInfoArchitecture.X86.
  • Consolidate x86 GCInfo decoding under the GCInfo contract and update the x86 unwinder and ExecutionManager to use the shared decoder / contract APIs.
  • Add a dump-based regression test for ISOSDacInterface.GetCodeHeaderData on an IL stub and update design docs.

Reviewed changes

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

Show a summary per file
FileDescription
src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.csAdds regression coverage validating GetCodeHeaderData returns a non-zero method size for an IL stub.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.csRegisters GCInfoX86_1 for x86 targets.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/Context/X86/X86Unwinder.csUpdates x86 unwinder to use the relocated/renamed X86GCInfo type.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/InfoHdr.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfoTargetExtensions.csMoves x86 GCInfo decoder support extensions under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.csRenames GCInfoX86GCInfo, makes relativeOffset optional, and implements IGCInfoDecoder queries needed by SOS/ExecutionManager.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/CallPattern.csMoves x86 GCInfo decoder support type under GCInfoHelpers.X86 namespace.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoX86_1.csIntroduces the x86 IGCInfo implementation backed by X86GCInfo.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.csDelegates x86 stack parameter size computation to the IGCInfo contract for a single source of truth.
docs/design/datacontracts/StackWalk.mdDocuments the consolidated location and sharing model for the x86 GCInfo decoder.
docs/design/datacontracts/GCInfo.mdUpdates contract API docs (but the intro still incorrectly claims x86 is unsupported).

Comment threaddocs/design/datacontracts/GCInfo.md
Tweaks the second intro paragraph in GCInfo.md to say x86 is "partially
supported" rather than "not currently supported", reflecting the IGCInfo
implementation added in the first commit.
Addresses dotnet#129456 (comment)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlamb marked this pull request as ready for review June 16, 2026 13:20
CopilotAI review requested due to automatic review settings June 16, 2026 15:06

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 13 out of 13 changed files in this pull request and generated 1 comment.

Comment threaddocs/design/datacontracts/GCInfo.md Outdated
Address PR feedback: the IGCInfo contract was conflating two distinct
concepts under one API name. GcInfoDecoder::GetSizeOfStackParameterArea
returns the outgoing-argument scratch area (_fixedStackParameterScratchArea,
platform-specific, used by the GC scanner). EECodeManager::GetStackParameterSize
returns the x86 __stdcall callee-popped argument size (used by the debugger
to adjust SP after return).
Keep IGCInfo.GetSizeOfStackParameterArea as the GcInfoDecoder scratch-area
accessor (consumed by GcScanner). On x86 it now throws NotSupportedException
since x86 has no separate scratch-area concept.
Add IGCInfo.GetCalleePoppedArgumentsSize mirroring EECodeManager::GetStackParameterSize:
0 by default, x86 returns varargs ? 0 : argCount * ptrSize.
ExecutionManagerCore.GetStackParameterSize now routes through this new method.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… x86 GCInfo
The previous test passed the IL stub's stable entry point
(GetMethodEntryPointIfExists) to GetCodeHeaderData. On x86 r2r that resolves
to a precode, so SOSDacImpl.GetCodeHeaderData takes the NonVirtualEntry2MethodDesc
fallback (MethodSize=0, TYPE_UNKNOWN) and never calls IGCInfo.GetCodeLength -
the exact path the regression was about.
Pass the live instruction pointer of a Frameless IL stub frame instead. The
IP is guaranteed to be inside the JIT-emitted code body, so GetCodeHeaderData
takes the full path through IGCInfo.GetCodeLength and the test actually
exercises the x86 X86GCInfo decoder it claims to cover.
Validated locally against the x86 dumps from the original failing CI run:
all 452 dump tests pass (previously 1 failure).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:35

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • GetSizeOfStackParameterArea is invoked by the managed GC scanner (Contracts/StackWalk/GC/GcScanner.cs) before live-slot enumeration. On x86 there’s no separate scratch/outgoing area, so returning 0 is a sensible, non-throwing default; throwing here causes x86 GC scanning callers to fail early even when they could proceed (or fail later for the actual missing feature).
    src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:32
  • X86GCInfo (and related x86 decoder helpers) are public and were also renamed/moved from the prior StackWalkHelpers.X86.GCInfo name/namespace. If these assemblies are consumed externally, that’s a breaking API change and expands public surface area. Consider making these helpers internal (they appear to be used only within this assembly), or providing a compatibility shim/type-forwarding stub for the old public type name/namespace.

Comment threaddocs/design/datacontracts/StackWalk.md Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings June 17, 2026 14:44

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 16 out of 16 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCInfo.cs:261

  • IGCInfoDecoder.GetSizeOfStackParameterArea is used unconditionally by GcScanner.EnumGcRefsForManagedFrame (StackWalk/GC/GcScanner.cs:48-50). Throwing here will immediately abort managed-frame GC scanning on x86 (even before EnumerateLiveSlots), which makes the method unusable and can lead to hard-to-diagnose partial results (exceptions are swallowed per-frame in StackWalk_1). Since x86 has no separate scratch/outgoing-arg area concept, returning 0 is a safe representation and avoids unnecessary exceptions.

@max-charlamb
max-charlamb enabled auto-merge (squash) June 17, 2026 16:42
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

/ba-g known helix infra issues causing timeouts on arm64 and osx

@max-charlamb
max-charlamb merged commit 9451fcf into dotnet:mainJun 17, 2026
68 of 77 checks passed
@max-charlamb
max-charlamb deleted the cdac-x86-gcinfo-codelength branch June 17, 2026 18:08
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 18, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview6 milestone Jun 18, 2026
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 25, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
…ots stackref tests
Builds on the partial x86 IGCInfo support added in dotnet#129456 by porting the
remaining decoder pieces required for GC-root scanning on x86, so that
`IStackWalk.WalkStackReferences` returns live frame slots on x86 cDAC.
The x86 GC info uses the legacy bit-packed `InfoHdr` byte-stream encoding
(`src/coreclr/vm/gc_unwind_x86.inl`, `src/coreclr/inc/gcdecoder.cpp`)
instead of the modern `GcInfoDecoder` shared by other architectures, so
the implementation lives entirely on the existing `X86GCInfo` decoder
under `Contracts/GCInfo/X86/`.
Changes
-------
* `X86GCInfo`: add `UntrackedSlots` lazy property +
`DecodeUntrackedSlots()` -- delta-decoded signed varints with the
double-align-frame rebase from `gc_unwind_x86.inl:3467`.
* `X86GCInfo`: add `VarPtrLifetimes` lazy property +
`DecodeVarPtrLifetimes()` -- triplets of (varOffs, begOffs delta,
endOffs delta) for EBP-frame tracked locals.
* Two new public record types `UntrackedSlot` and `VarPtrLifetime`
capture the decoded entries.
* `IsCodeOffsetInProlog` / `IsCodeOffsetInEpilog` helpers
(offset-parameterised, so EnumerateLiveSlots can answer for any
instruction offset without re-constructing X86GCInfo).
* `RegMaskToRegisterNumber` helper maps the single-bit `RegMask`
flags-enum values to the x86 ModRM register numbers used by
`X86Context.TryReadRegister` and `LiveSlot.RegisterNumber`.
* Implement `IGCInfoDecoder.EnumerateLiveSlots(uint offset, options)`:
early-return empty in prolog/epilog (or aborted+non-interruptible),
emit untracked locals (suppressed for filter funclets), emit VarPtr
lifetimes covering `offset`, walk `Transitions` up to `offset`
accumulating live registers + pushed pointer args, and emit a
partially-interruptible `GcTransitionCall` exactly at `offset`.
* Flip `IGCInfoDecoder.GetSizeOfStackParameterArea` from
`NotSupportedException` to `return 0` for x86 -- x86 has no
separate outgoing-argument scratch area; per-offset transitions
report pushed args directly, so the GcScanner scratch-area filter is
a no-op (correct).
* Remove the `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers on `GCRoots_WalkStackReferences_FindsRefs` and
`GCRoots_RefsPointToValidObjects`.
* `DumpTests.targets`: add optional `DebuggeeFilter=<Name>` to
restrict `GenerateAllDumps` to a single debuggee. Useful for
iterative local x86 work where some other debuggee's publish may
fail.
* `docs/design/datacontracts/GCInfo.md`: enumerate which
`IGCInfoDecoder` APIs are wired up on x86.
Out of scope (deferred)
-----------------------
* `GetInterruptibleRanges` for x86 -- the only consumer is the
catch-handler PC override in `StackWalk_1`; no x86-relevant
scenarios today.
* "this"-pointer special-case reporting for synchronized methods
(VarPtr 0x2 bit currently masked out).
* IPtrMask interior-pointer bitmaps for pushed args (uses the simpler
per-push `Iptr` flag).
* Funclet handling beyond the existing `IsParentOfFuncletStackFrame`
caller-side early-skip.
* Finer `IsActiveFrame` register filter precision.
Validation
----------
* All 2525 cDAC unit tests pass.
* The two unblocked `GCRoots_*` tests pass against a freshly
generated x86 GCRoots dump.
* Broader `DumpTests` x86 sweep: 34 pass / 46 fail / 830 skip --
net +2 vs. before this change (the two GCRoots tests), zero
regressions. The 46 pre-existing failures are all unrelated to
GCInfo (`ThreadDumpTests` / `ComWrappersDumpTests` /
`RuntimeInfoDumpTests` / `WorkstationGCDumpTests` and similar).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Pure file rename (git mv) -- the class was renamed `GCInfo` -> `X86GCInfo`
in dotnet#129456 to avoid collision with the empty IGCInfo fallback struct, but
the file kept the old name. Bring the filename in line.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
max-charlamb pushed a commit to max-charlamb/runtime that referenced this pull request Jun 27, 2026
Reverts commit cb82b21 to keep the file name as `GCInfo.cs`. Even
though the class inside is `X86GCInfo` (renamed in dotnet#129456 to avoid
colliding with the empty `Contracts.GCInfo` IGCInfo fallback struct),
the file name churn shows up as a delete+add in the PR diff, which is
harder to review. The C# class name does not need to match the file
name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
…ntract (#129456)
> [!NOTE]
> This PR was authored with assistance from GitHub Copilot.
## Summary
Fixes a cDAC `GetCodeHeaderData` failure that surfaced as `Unable to get
codeHeader information` when SOS ran `!clru` against an IL stub
MethodDesc on **Windows x86** with cDAC enabled (the new default
behavior on .NET 11 introduced by
[dotnet/diagnostics#5874](dotnet/diagnostics#5874)).
The CI failure that motivated this is
`SOSMethodTests.VarargPInvokeInteropMD` on x86 in dotnet/diagnostics:
`!IP2MD` returned the IL stub MethodDesc correctly, but the immediate
follow-up `!clru <MD>` printed only `Unable to get codeHeader
information`. x64 / arm64 / .NET 8/9/10 were unaffected.
## Root cause
x86 uses a fundamentally different GC info encoding from every other
architecture: the legacy bit-packed `InfoHdr` byte-stream format from
[`src/coreclr/vm/gc_unwind_x86.inl`](https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/gc_unwind_x86.inl)
and
[`src/coreclr/inc/gcdecoder.cpp`](https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/gcdecoder.cpp)
(`USE_GC_INFO_DECODER` is defined for every target except x86, see
`eetwain.h:34`).
The cDAC `GCInfo` contract registered IGCInfo implementations for
X64/Arm64/Arm/LoongArch64/RiscV64 but **not** X86 -- so on x86 it fell
through to `default(GCInfo)` and threw `NotImplementedException` from
the interface's default `DecodePlatformSpecificGCInfo`. Any SOS path
that needed method size on x86 (`!clru`, `GetCodeHeaderData`,
`GetMethodRegionInfo`) failed.
## Approach
The cDAC already had a substantial x86 `InfoHdr` decoder under
`Contracts/StackWalk/Context/X86/GCInfoDecoding/`, used by the x86 stack
walker. Rather than write a parallel decoder, this PR **relocates** that
existing decoder under the `GCInfo` contract so there is **one canonical
x86 GC info implementation** shared between SOS callers and the stack
walker -- mirroring how the other architectures' decoders are
structured.
## Changes
* **Move** `Contracts/StackWalk/Context/X86/GCInfoDecoding/*` →
`Contracts/GCInfo/X86/*` (6 files, tracked as renames). Rename namespace
`StackWalkHelpers.X86` → `GCInfoHelpers.X86`.
* **Rename** the moved class `GCInfo` → `X86GCInfo` to avoid collision
with the empty `Contracts.GCInfo` IGCInfo fallback struct.
* Make `relativeOffset` ctor arg optional. Implement `IGCInfoDecoder`
directly on `X86GCInfo`: `GetCodeLength` / `GetStackBaseRegister` /
`GetSizeOfStackParameterArea` are wired up. `GetInterruptibleRanges` and
`EnumerateLiveSlots` throw `NotSupportedException` (future work, needed
for `!gcroot` / `!clrstack -l` etc.).
* Add `GCInfoX86_1` IGCInfo for x86; register it in
`CoreCLRContracts.cs` for `RuntimeInfoArchitecture.X86`.
* Update `ExecutionManagerCore.GetStackParameterSize` to delegate to
`IGCInfo.GetSizeOfStackParameterArea` (one source of truth).
* `X86Unwinder` continues to construct `X86GCInfo` directly because it
needs offset-bound state (`IsInProlog` / `IsInEpilog` / `PushedArgSize`)
not exposed through `IGCInfoDecoder`.
## Tests
* New `VarargPInvoke_GetCodeHeaderDataForILStub_ReturnsMethodSize`
regression test in `cdac/tests/DumpTests/StackWalkDumpTests.cs` --
asserts the IL stub path returns S_OK with non-zero `MethodSize`. Runs
against the existing cdac-dump-helix `windows_x86` matrix on every PR
(no pipeline changes needed).
* Existing 2509 cDAC unit tests still pass.
* Validated end-to-end against `SOSMethodTests.VarargPInvokeInteropMD`
x86 .NET 11prev6 cDAC: 4/4 pass after this fix; failed before.
## Docs
* `docs/design/datacontracts/GCInfo.md` -- intro now reflects partial
x86 support; `GetSizeOfStackParameterArea` API documented; per-method
status notes for x86.
* `docs/design/datacontracts/StackWalk.md` -- x86 section points at the
consolidated decoder location and explains how it's shared.
## Out of scope (future work)
* `GetInterruptibleRanges` and `EnumerateLiveSlots` for x86. The
underlying transition data is decoded but the adapter to the cDAC
`IGCInfoDecoder` shape is not wired up yet. This is what's needed to
unblock `!gcroot`, `!clrstack -l`, `!pe` on x86 cDAC. The two
pre-existing `[SkipOnArch("x86", "GCInfo decoder does not support
x86")]` markers in `StackReferenceDumpTests.cs` should become removable
once that lands.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 19, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@max-charlamb@steveisok@hoyosjs