[cdac] x86 GCInfo decoder + PromoteCallerStack -> GCRefMap; enforce 0 known issues on windows-x86/x64#129858
Draft
max-charlamb wants to merge 1 commit into
Draft
[cdac] x86 GCInfo decoder + PromoteCallerStack -> GCRefMap; enforce 0 known issues on windows-x86/x64#129858max-charlamb wants to merge 1 commit into
max-charlamb wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR expands the cDAC stack-walking and stress infrastructure by wiring Windows x86 transition-frame caller scanning to the shared ICallingConvention/GCRefMap machinery, and by extending the in-proc cdacstress harness to support multiple sub-checks (GCREFS + ARGITER) with richer test/debuggee coverage.
Changes:
- Reworked caller-stack promotion to optionally synthesize and decode GCRefMap blobs via a new
ICallingConvention.TryComputeArgGCRefMapBlobpath and shared token enumeration. - Extended cdacstress configuration/telemetry (flag layout,
[GC_STATS]/[ARG_STATS]) and updated managed stress harness parsing/assertions; added multiple new stress debuggees. - Refactored/shared calling convention code (ArgIterator/TransitionBlock/ITypeHandle) for reuse by cDAC and ReadyToRun tooling.
Reviewed changes
Copilot reviewed 60 out of 60 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/native/managed/cdac/tests/StressTests/RunStressTests.ps1 | Updates stress flag documentation/defaults to new WHERE/WHAT/MODIFIER layout. |
| src/native/managed/cdac/tests/StressTests/README.md | Documents new flag regions, sub-check markers, and updated defaults. |
| src/native/managed/cdac/tests/StressTests/known-issues.md | Updates defaults and adds documentation for an intermittent x86 flake. |
| src/native/managed/cdac/tests/StressTests/Debuggees/VarArgs/VarArgs.csproj | Adds VarArgs debuggee project file. |
| src/native/managed/cdac/tests/StressTests/Debuggees/VarArgs/Program.cs | Adds varargs-focused ARGITER debuggee. |
| src/native/managed/cdac/tests/StressTests/Debuggees/StructScenarios/Program.cs | Adds nested-struct scenario to stress ArgIterator/GCDesc paths. |
| src/native/managed/cdac/tests/StressTests/Debuggees/CrossModule/Program.cs | Adds cross-module signature/type-resolution debuggee. |
| src/native/managed/cdac/tests/StressTests/Debuggees/CrossModule/Lib/Types.cs | Adds library types used by CrossModule debuggee to force cross-module resolution. |
| src/native/managed/cdac/tests/StressTests/Debuggees/CrossModule/Lib/CrossModuleLib.csproj | Adds library project for CrossModule debuggee. |
| src/native/managed/cdac/tests/StressTests/Debuggees/CrossModule/CrossModule.csproj | Adds main CrossModule debuggee project file + ProjectReference wiring. |
| src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/Program.cs | Adds comprehensive ARGITER signature-shape coverage debuggee. |
| src/native/managed/cdac/tests/StressTests/Debuggees/CallSignatures/CallSignatures.csproj | Adds CallSignatures debuggee project file and warning suppressions. |
| src/native/managed/cdac/tests/StressTests/CdacStressTests.cs | Replaces prior basic test entrypoint with expanded debuggee catalog and GCREFS/ARGITER theories. |
| src/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs | Adds per-mode stress runner and stricter assertions/target detection. |
| src/native/managed/cdac/tests/StressTests/CdacStressResults.cs | Adds parsing of [GC_STATS] / [ARG_STATS] and captures ARGITER divergence lines. |
| src/native/managed/cdac/tests/StressTests/BasicCdacStressTests.cs | Removes the older stress test harness class (superseded by CdacStressTests). |
| src/native/managed/cdac/tests/DumpTests/StackReferenceDumpTests.cs | Removes x86 skip now that x86 GCInfo decoding is supported. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/StressTestApi/CdacStressApi.cs | Adds legacy DAC request handlers for cdacstress private opcodes, including ARGITER blob request. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.IXCLRDataProcess.cs | Routes stress private requests to the new CdacStressApi helper. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/RuntimeTypeSystemHelpers/MethodTableFlags_1.cs | Extends MethodTable flags helpers (shared instantiation + byreflike). |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Microsoft.Diagnostics.DataContractReader.Contracts.csproj | Links in shared CallingConvention/ArgIterator sources into Contracts build. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/DataType.cs | Adds PInvokeCalliFrame to the cDAC type discriminator enum. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/TransitionBlock.cs | Adds OffsetOfArgs field address to support correct x86 GCRefMap pos->addr mapping. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/PInvokeCalliFrame.cs | Adds cDAC data type to expose VASigCookiePtr for PInvokeCalliFrame. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/CoreCLRContracts.cs | Registers the new ICallingConvention contract implementation. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs | Swallows NotImplementedException per-frame to allow partial stack-walk results. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.cs | Reworks caller-stack scanning to use synthesized GCRefMap blobs on Windows x86/x64; factors shared token iteration; fixes x86 DynamicHelperFrame arg-reg offsets; corrects x86 pos->addr mapping. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.cs | Adds a byte[]-backed decoder constructor for host-synthesized blobs. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/FrameHandling/X86FrameHandler.cs | Computes x86 transition-frame caller SP by decoding stack-pop prefix (or VASigCookie for PInvokeCalliFrame). |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/RuntimeTypeSystem_1.cs | Implements new RTS helpers (byreflike, unboxing stub, approx field type handle). |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCTransition.cs | Fixes ctor assignments for IsThis/Iptr fields. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/X86/GCArgTable.cs | Fixes several x86 arg table decoding issues and removes debug prints; adjusts stack-depth transition deltas. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/CdacTypeHandle.cs | Adds cDAC-backed ITypeHandle implementation for shared ArgIterator. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/CallingConvention/ArgumentLocation.cs | Adds internal argument-location record for encoder bookkeeping. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IRuntimeTypeSystem.cs | Adds new contract APIs (byreflike, unboxing stub, approx field type handle). |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/ICallingConvention.cs | Introduces new calling-convention contract + public surface. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/ContractRegistry.cs | Exposes CallingConvention contract property on registry. |
| src/coreclr/vm/frames.h | Exposes PInvokeCalliFrame::m_pVASigCookie via cdac_data specialization. |
| src/coreclr/vm/datadescriptor/datadescriptor.inc | Adds PInvokeCalliFrame descriptor and registers CallingConvention global contract. |
| src/coreclr/vm/cdacstress.cpp | Adds WHAT/WHERE/MODIFIER flag regions, ARGITER sub-check (runtime-vs-cDAC blob comparison), and emits [GC_STATS]/[ARG_STATS]. |
| src/coreclr/tools/Common/CallingConvention/TransitionBlock.cs | Moves TransitionBlock into Internal.CallingConvention and refactors to use ITypeHandle. |
| src/coreclr/tools/Common/CallingConvention/SystemVAmd64PassingDescriptor.cs | Adds extracted SystemV descriptor types for shared use. |
| src/coreclr/tools/Common/CallingConvention/ITypeHandle.cs | Adds shared type abstraction for calling convention computation. |
| src/coreclr/tools/Common/CallingConvention/FpStructInRegistersInfo.cs | Adds extracted RISC-V/LoongArch FP struct classification types for shared use. |
| src/coreclr/tools/Common/CallingConvention/ArgIterator.cs | Refactors ArgIterator into Internal.CallingConvention and reworks it to use ITypeHandle + shared TransitionBlock. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/WasmLowering.ReadyToRun.cs | Updates Wasm lowering code to use new ITypeHandle shape. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj | Links in shared CallingConvention sources and adjusts ReadyToRun file list. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.cs | Updates using/aliasing for new ArgIterator namespace. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.cs | Updates using/aliasing for new ArgIterator namespace. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.cs | Updates using/aliasing for new ArgIterator namespace. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/TypeHandle.cs | Adds crossgen2-backed ITypeHandle implementation. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/GCRefMapBuilder.cs | Updates builder to use shared TransitionBlock/ArgIterator/ITypeHandle. |
| src/coreclr/inc/dacprivate.h | Adds new private request opcode and request struct for ARGITER blob retrieval (CDAC_STRESS). |
| src/coreclr/inc/clrconfigvalues.h | Simplifies CdacStress config description now that layout is more complex. |
| eng/pipelines/runtime-diagnostics.yml | Adds windows_x86 to runtime-diagnostics pipeline parameter default list. |
| docs/design/datacontracts/RuntimeTypeSystem.md | Documents new RTS APIs and flags. |
| docs/design/datacontracts/GCInfo.md | Updates x86 behavior documentation and adds x86 specifics section. |
| docs/design/datacontracts/CallingConvention.md | Adds new CallingConvention contract design doc. |
Comment on lines
+8
to
+14
| public interface ICallingConvention : IContract | ||
| { | ||
| static string IContract.Name => nameof(CallingConvention); | ||
|
|
||
| bool TryComputeArgGCRefMapBlob(MethodDescHandle methodDesc, out byte[] blob) | ||
| => throw new NotImplementedException(); | ||
| } |
Comment on lines
+65
to
+66
| if (inBuffer is null || inSize < (uint)Unsafe.SizeOf<DacStressArgGCRefMapRequest>()) | ||
| return HResults.E_INVALIDARG; |
Comment on lines
+1
to
+9
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <LangVersion>latest</LangVersion> | ||
| <!-- This debuggee deliberately defines many "test scaffolding" types and | ||
| enums where StyleCop's one-value-per-line / no-unassigned-fields | ||
| conventions don't add value. Extend the parent NoWarn. --> | ||
| <NoWarn>$(NoWarn);SA1136;CS0649</NoWarn> | ||
| </PropertyGroup> | ||
| </Project> |
Comment on lines
+62
to
+86
| public int GetSize() | ||
| { | ||
| // Constructed pointer/array/byref args always occupy one TADDR slot | ||
| // in the transition block (the actual pointee is reached via the | ||
| // pointer value, not stored inline). When _kindOverride is set, the | ||
| // underlying TypeHandle may be null (uncached PTR), so GetBaseSize | ||
| // would fault. | ||
| if (_kindOverride is CdacCorElementType.Ptr | ||
| or CdacCorElementType.Byref | ||
| or CdacCorElementType.SzArray | ||
| or CdacCorElementType.Array) | ||
| { | ||
| return PointerSize; | ||
| } | ||
|
|
||
| if (_typeHandle.IsNull) | ||
| return 0; | ||
|
|
||
| // GetBaseSize returns the full object size including object header and padding. | ||
| // For value types used in calling convention, we need the unboxed size. | ||
| // BaseSize = ObjHeader + MethodTable* + unboxed fields, aligned to pointer size. | ||
| // Unboxed size = BaseSize - 2 * PointerSize (subtract ObjHeader + MT pointer). | ||
| uint baseSize = Rts.GetBaseSize(_typeHandle); | ||
| return (int)(baseSize - (uint)(2 * PointerSize)); | ||
| } |
7648cd9 to
8bed0e2
Compare
This was referenced Jun 25, 2026
Open
8bed0e2 to
2869d31
Compare
Comment on lines
+167
to
+169
| /// the test. On other targets we still tolerate KnownIssues because | ||
| /// <c>PromoteCallerStack</c> falls back to <c>RecordDeferredFrame</c> | ||
| /// there (see <c>GcScanner.PromoteCallerStack</c>). |
Comment on lines
+358
to
+382
| IRuntimeInfo runtimeInfo = _target.Contracts.RuntimeInfo; | ||
| RuntimeInfoArchitecture arch = runtimeInfo.GetTargetArchitecture(); | ||
| bool supportedByCallingConvention = | ||
| runtimeInfo.GetTargetOperatingSystem() == RuntimeInfoOperatingSystem.Windows | ||
| && arch is RuntimeInfoArchitecture.X86 or RuntimeInfoArchitecture.X64; | ||
|
|
||
| if (!supportedByCallingConvention) | ||
| { | ||
| scanContext.RecordDeferredFrame(frameAddress); | ||
| return; | ||
| } | ||
|
|
||
| Data.FramedMethodFrame fmf = _target.ProcessedData.GetOrAdd<Data.FramedMethodFrame>(frameAddress); | ||
| if (fmf.MethodDescPtr == TargetPointer.Null) | ||
| { | ||
| scanContext.RecordDeferredFrame(frameAddress); | ||
| return; | ||
| } | ||
|
|
||
| MethodDescHandle md = _target.Contracts.RuntimeTypeSystem.GetMethodDescHandle(fmf.MethodDescPtr); | ||
| if (!_target.Contracts.CallingConvention.TryComputeArgGCRefMapBlob(md, out byte[] blob) || blob is null || blob.Length == 0) | ||
| { | ||
| scanContext.RecordDeferredFrame(frameAddress); | ||
| return; | ||
| } |
Contributor
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
2869d31 to
2c648d9
Compare
- Add x86 IGCInfoDecoder (EnumerateLiveSlots, GetInterruptibleRanges) - Rewire GcScanner.PromoteCallerStack to use ICallingConvention.TryComputeArgGCRefMapBlob - Centralize x86 cbStackPop handling inside EnumerateGCRefMapTokens - Encapsulate x86 ENUM_ARGUMENT_REGISTERS_BACKWARD layout in ArgSlotAddress helper - GCRefMapDecoder: add byte[] ctor for synthesized blobs + MemberNotNullWhen attrs - TransitionBlock.OffsetOfArgs uses [InstanceDataStart] (drop redundant datadescriptor field) - Add PInvokeCalliFrame data class + datadescriptor wiring for X86FrameHandler fallback - Stress tests: enforce KnownIssues == 0 on Windows x86/x64, remove x86 skip from GCRefStress Verified: x86 + x64 GCRefStress 11/11 pass, 0 known issues. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2c648d9 to
e335d6a
Compare
Comment on lines
58
to
63
| if (debuggee.SkipGCRefs) | ||
| throw new SkipTestException($"{debuggee.Name} is excluded from GCREFS pending follow-up work."); | ||
|
|
||
| // The GCREFS sub-check has only been validated on architectures where | ||
| // the cDAC GC root enumeration is at parity with the runtime. x86 has | ||
| // not been brought up yet (a separate effort); skip there until it is. | ||
| if (arch == Architecture.X86) | ||
| throw new SkipTestException("GCREFS stress is not yet validated on x86 (ARGITER stress runs there instead)"); | ||
|
|
||
| CdacStressResults results = await RunGCRefStressAsync(debuggee.Name); | ||
| AssertAllPassed(results, debuggee.Name); | ||
| } |
Comment on lines
58
to
63
| if (debuggee.SkipGCRefs) | ||
| throw new SkipTestException($"{debuggee.Name} is excluded from GCREFS pending follow-up work."); | ||
|
|
||
| // The GCREFS sub-check has only been validated on architectures where | ||
| // the cDAC GC root enumeration is at parity with the runtime. x86 has | ||
| // not been brought up yet (a separate effort); skip there until it is. | ||
| if (arch == Architecture.X86) | ||
| throw new SkipTestException("GCREFS stress is not yet validated on x86 (ARGITER stress runs there instead)"); | ||
|
|
||
| CdacStressResults results = await RunGCRefStressAsync(debuggee.Name); | ||
| AssertAllPassed(results, debuggee.Name); | ||
| } |
Comment on lines
+196
to
+202
| $"GCREFS stress test '{debuggeeName}' had {results.KnownIssues} known issue(s) " + | ||
| $"out of {results.TotalVerifications} verifications. " + | ||
| "Windows x86 / x64 do not accept any deferred frames in this PR's scope -- " + | ||
| "every transition Frame's caller-stack scan must succeed via the shared " + | ||
| "ICallingConvention.TryComputeArgGCRefMapBlob path. A non-zero count likely " + | ||
| "indicates the encoder declined a method it previously handled (regression " + | ||
| "in CallingConvention_1.ComputeArgGCRefMapBlobCore).\n" + |
Comment on lines
359
to
363
| for (int i = 0; i < callPndTabCnt; i++) | ||
| { | ||
| uint pndOffs = _target.GCDecodeUnsigned(ref offset); | ||
|
|
||
| uint stkOffs = val & ~byref_OFFSET_FLAG; | ||
| uint lowBit = val & byref_OFFSET_FLAG; | ||
| Console.WriteLine($"stkOffs: {stkOffs}, lowBit: {lowBit}"); | ||
|
|
||
| transition.PtrArgs.Add(new GcTransitionCall.PtrArg(pndOffs, 0)); | ||
| } |
Comment on lines
+328
to
+335
| bool isEbpRelative = Header.EbpFrame; | ||
| if (Header.DoubleAlign && | ||
| (uint)stkOffs >= _target.PointerSize * (Header.FrameSize + calleeSavedRegsCount)) | ||
| { | ||
| // Double-aligned frame: offsets above the frame proper are EBP-relative. | ||
| isEbpRelative = true; | ||
| stkOffs -= (int)(_target.PointerSize * (Header.FrameSize + calleeSavedRegsCount)); | ||
| } |
Comment on lines
178
to
182
| ExternalMethodFrame, | ||
| DynamicHelperFrame, | ||
| InterpreterFrame, | ||
| PInvokeCalliFrame, | ||
|
|
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This PR was authored with assistance from GitHub Copilot.
Summary
Brings the cDAC
IGCInfoDecoderx86 implementation (originally on the now-closed PR #129547) plus theGcScanner.PromoteCallerStackrewire (usingICallingConvention.TryComputeArgGCRefMapBlobfrom #129769) into a single change, and tightens the GCREFS stress assertion so any non-zeroKnownIssuescount on Windows x86 / x64 fails the test.After this change, x86 cDAC stack walking is at full parity with the runtime for the cdacstress GCREFS suite (BasicAlloc local run: 4798 / 4798 pass, 0 known, 0 fail).
Three logical groupings
1. x86
IGCInfoDecoderimplementation (28 commits, the bulk of the diff)Implements
EnumerateLiveSlotsandGetInterruptibleRangeson x86 by walking the legacyInfoHdrbyte-stream and the per-offsetTransitionsalready decoded inGCInfo.cs/GCArgTable.cs. Includes a long tail of correctness fixes uncovered by the cdacstress harness:0xFBhuge-encoding code-delta is cumulative; partial-EBP this-pointer-tag bytes do not record a call entry; ESP-frame untracked / VarPtr slots need apushedSizebias;ApplyPointerTransitionhonorsIsPtr=falsefor non-pointer pushes; etc.2.
GcScanner.PromoteCallerStackrewire (1 commit + 1 supporting commit)Replaces the
RecordDeferredFramestub with a real implementation that synthesizes a GCRefMap blob viaICallingConvention.TryComputeArgGCRefMapBloband runs the same token-iteration loop as the R2R-backedPromoteCallerStackUsingGCRefMap. Falls back toRecordDeferredFrameon unsupported targets (non-Windows or non-x86/x64) and on any failure to synthesize a blob. The supporting commit adds thePInvokeCalliFramedata class used by the x86HandleTransitionFrameoverride (PInvokeCalliFramehas noMethodDescso it falls back toVASigCookie.SizeOfArgs).Also adds:
GCRefMapDecoder(byte[])second constructor so synthesized blobs can be decoded with the same bit-stream reader asTargetPointer-resident blobs.X86FrameHandler.HandleTransitionFrameoverride -- decodes the leadingReadStackPop()of the synthesized blob to recovercbStackPopon x86.EnumerateGCRefMapTokenshelper, factored out so both the R2R-blob and synthesized-blob paths share the same token-iteration loop.3. Stress framework hardening
CdacStressTests.GCRefStress_AllVerificationsPass: removed theif (arch == Architecture.X86) throw new SkipTestExceptionblock.CdacStressTestBase.AssertAllPassed: on Windows x86 / x64, fail the test ifresults.KnownIssues > 0. Every transition Frame's caller-stack scan must succeed viaICallingConvention.TryComputeArgGCRefMapBlob.Local validation (windows-x86 Checked)
BasicAlloc smoke test:
Total verifications: 4,798
Passed: 4,798
Failed: 0
Known issues: 0
Frames examined: 64,406 (all matched)
cDAC unit tests: 2611 / 2611 pass.
Full GCRefStress suite will be validated by CI.
Scope notes
ARGITERstress remains scoped to Windows x86 / x64 as it is on main; nothing in this PR changes that gating.