[cDAC] Stack walk GC stress verification and fixes - #126408

Closed
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5
Closed

[cDAC] Stack walk GC stress verification and fixes#126408
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Apr 1, 2026

Copy link
Copy Markdown
Member

Summary

cDAC GC stress verification tool (DOTNET_CdacStress) that compares stack GC references between the cDAC and the runtime at allocation stress points. Includes stack walker fixes, GC reference scanning implementation, custom signature decoding, and calling convention argument iteration.

Note

This PR description was updated with AI assistance from Copilot.

Stack walker fixes

  • Fix SW_SKIPPED_FRAME: do not call UpdateContextFromFrame (matches native SFITER_SKIPPED_FRAME_FUNCTION which does not call UpdateRegDisplay)
  • Fix skipped-frame loop: check for more skipped frames before yielding managed method (prevents duplicate EnumGcRefs between consecutive skipped frames)
  • Restructure Filter() to drive Next() directly, matching native Filter()+NextRaw() integration (prevents funclet-to-parent walk cycles)
  • Remove SkipActiveICFOnce/SkipCurrentFrameInCheck — active ICF double-yield is natural and harmless
  • Remove IsAtFirstPassExceptionThrowSite — native does not suppress first-pass refs
  • Fix IsFirst not preserved for skipped frames (was causing IsActiveFrame=false for the topmost managed frame)

GC reference scanning

  • Implement PromoteCallerStack for stub frames (GCRefMap + MetaSig paths)
  • Implement SOSDacImpl.GetStackReferences using cDAC contract (was falling back to legacy DAC)
  • Read FilterContext for stack walk starting context
  • Three-way cDAC/DAC/RT comparison with InProcessDataTarget
  • DOTNET_CdacStress bit flags: ALLOC/INSTR/REFS/WALK/USE_DAC/UNIQUE

RuntimeSignatureDecoder

Custom signature decoder that handles ELEMENT_TYPE_INTERNAL (0x21) and ELEMENT_TYPE_CMOD_INTERNAL (0x22) which SRM's SignatureDecoder cannot parse. These occur in IL stubs, marshalling stubs, and unsafe accessor frames.

  • IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of SRM's ISignatureTypeProvider, adds GetInternalType and GetInternalModifiedType
  • ISignatureReader + SpanSignatureReader: endianness-aware reader abstraction
  • Correct ECMA-335 compliance: TypeDefOrRefOrSpecEncoded token decoding, sign-extension-by-width for compressed signed ints, bounds validation

ArgIterator (ported from crossgen2)

Proper calling convention analysis replacing the simplified 1-slot-per-param approach.

  • CallingConventionInfo: hybrid data descriptor layout values + ABI invariant constants for all architectures (x86, x64 Windows/Unix, ARM32, ARM64, LoongArch64, RISC-V64)
  • ArgIterator.GetNextOffset(): maps each argument to its actual register or stack offset
  • OffsetOfFloatArgumentRegisters added to TransitionBlock data descriptor
  • Handles multi-slot args, forced-byref params, return buffer placement, async continuation

CI infrastructure

  • cDAC stress tests run in Helix via cdac-stress-helix.proj
  • Extended runtime-diagnostics.yml CdacDumpTests buildArgs with +tools.cdacstresstests

Test results

Allocation-level stress (9 debuggees, ~46K verifications):

DebuggeeVerificationsPassFail
BasicAlloc4,9364,9360
DeepStack4,9644,9640
Generics4,9364,9360
MultiThread4,9964,9960
Comprehensive4,9944,9922*
ExceptionHandling4,9584,9580
StructScenarios4,9404,9400
DynamicMethods6,5206,5200
PInvoke4,9364,9360

Instruction-level stress (9 debuggees, ~226K verifications):
All 9 debuggees pass with zero failures across 226,452 verifications.

*Comprehensive's 2 failures are pre-existing legacy DAC issues (DAC returns 0 refs for background threads in kernel waits; cDAC and runtime agree).

@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

Adds a cDAC GC-stress verification harness and extends the cDAC stack-walk / stack-GC-ref pipeline so cDAC stack reference enumeration can be compared against runtime scanning at stress points.

Changes:

  • Implements/extends cDAC stack reference enumeration (including Frame-based scanning paths like PromoteCallerStack via GCRefMap / MetaSig) and wires SOS GetStackReferences to the cDAC contract.
  • Introduces a new GC stress integration test project with debuggee apps and orchestration targets.
  • Extends CoreCLR cDAC stress/GC stress plumbing and data descriptors to support the new stack-walk and frame-scanning capabilities.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 12 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.csUpdates mock type layout for execution manager-related data.
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.csUpdates mock type layouts (ExceptionInfo/Thread) for contract tests.
src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csprojExcludes new GCStressTests folder from the existing unit test project compilation.
src/native/managed/cdac/tests/GCStressTests/README.mdDocuments how to build/run the new GC stress tests.
src/native/managed/cdac/tests/GCStressTests/Microsoft.Diagnostics.DataContractReader.GCStressTests.csprojAdds a dedicated GC stress test project.
src/native/managed/cdac/tests/GCStressTests/GCStressTests.targetsMSBuild orchestration to discover/build debuggee projects.
src/native/managed/cdac/tests/GCStressTests/GCStressTestBase.csTest harness to run debuggees under corerun and parse verification logs.
src/native/managed/cdac/tests/GCStressTests/GCStressResults.csParses the native verification log into structured pass/fail/skip counts.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/Program.csAdds a P/Invoke-focused debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/PInvoke.csprojDebuggee project file for PInvoke scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/Program.csAdds a multi-threaded debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/MultiThread.csprojDebuggee project file for MultiThread scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Program.csAdds a generics/interface/delegate debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Generics.csprojDebuggee project file for Generics scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/Program.csAdds an exception-handling/funclet debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/ExceptionHandling.csprojDebuggee project file for ExceptionHandling scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Directory.Build.propsShared build props for debuggee projects (output layout, TFM, etc.).
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/Program.csAdds deep-recursion debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/DeepStack.csprojDebuggee project file for DeepStack scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Program.csAdds comprehensive “all scenarios” debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Comprehensive.csprojDebuggee project file for Comprehensive scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/Program.csAdds basic allocation/live-ref debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/BasicAlloc.csprojDebuggee project file for BasicAlloc scenario.
src/native/managed/cdac/tests/GCStressTests/BasicGCStressTests.csTheory-based test suite that runs the debuggees and asserts pass rate.
src/native/managed/cdac/tests/gcstress/known-issues.mdCaptures known mismatch classes and limitations.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.csImplements GetStackReferences using the cDAC contract rather than legacy DAC fallback.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.csExtends StubDispatchFrame data with GCRefMap pointer.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.csAdds ExternalMethodFrame contract data type (GCRefMap).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.csAdds DynamicHelperFrame contract data type (flags).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.csAdds clause-range fields used for catch-handler resumption offset selection.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.csRefactors stack-walk filtering and adds Frame-based GC root scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAdds optional relOffset override support for GC ref enumeration.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.csImplements GCRefMap decoding for transition-block scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/CorSigParser.csAdds minimal signature parsing to classify parameters for MetaSig-based scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.csAdds FindFirstInterruptiblePoint API to GCInfo decoders.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.csImplements FindFirstInterruptiblePoint using decoded interruptible ranges.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.csFixes code-start lookup for exception clause enumeration and adds minor flow adjustments.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.csAdds TransitionBlock-related global names used by frame scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.csAdds new DataType enum values for new frame contracts.
src/native/managed/cdac/cdac.slnxAdds the new GC stress test project to the cDAC solution.
src/coreclr/vm/gccover.cppAdds step-based skipping to reduce overhead when throttling verification.
src/coreclr/vm/frames.hExposes additional frame fields to cDAC via cdac_data<> descriptors.
src/coreclr/vm/datadescriptor/datadescriptor.incAdds new contract fields/globals for frames and TransitionBlock layout.
src/coreclr/vm/cdacstress.cppUpdates in-process cDAC/DAC verification logic, logging, and step behavior.
eng/Subsets.propsAdds an on-demand subset for running GC stress tests.
docs/design/datacontracts/StackWalk.mdDocuments the new frame fields and TransitionBlock globals in the StackWalk contract.
Comments suppressed due to low confidence (1)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:228

  • ThreadFields contains ProfilerFilterContext twice (lines 224 and 228). Duplicate field entries will skew offsets and make the mock descriptors inconsistent with the real data descriptor. Keep a single ProfilerFilterContext entry in the correct order.

Comment threadsrc/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/README.md Outdated

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

Comments suppressed due to low confidence (2)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:229

  • ThreadFields now includes DebuggerFilterContext/ProfilerFilterContext twice (duplicate entries at the end of the list). This can cause ambiguous/incorrect field offsets in the mock type layout. Keep each field only once.
    src/coreclr/vm/cdacstress.cpp:963
  • CompareRefSets uses a fixed-size matched[MAX_COLLECTED_REFS] buffer but no longer validates that countA/countB are <= MAX_COLLECTED_REFS. Since CollectStackRefs appends without a hard cap, this can lead to out-of-bounds access when countA or countB exceeds 4096. Reintroduce a guard or allocate the match state sized to the counts.
 return true;
bool matched[MAX_COLLECTED_REFS] = {};
for (int i = 0; i < countA; i++)

Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/gccover.cpp Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/GCStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 75ae7fe to 5419d15CompareApril 13, 2026 15:30
CopilotAI review requested due to automatic review settings April 13, 2026 15:47
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 5419d15 to 8ef9b22CompareApril 13, 2026 15:47

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

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated

@max-charlambmax-charlamb left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

feedback for local copilot

Comment threadsrc/native/managed/cdac/tests/StressTests/analysis/analyze-refs.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/StressTests.targets Outdated
Comment threadsrc/coreclr/vm/datadescriptor/datadescriptor.inc Outdated
CopilotAI review requested due to automatic review settings April 13, 2026 17:33

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

Comments suppressed due to low confidence (1)

src/coreclr/vm/cdacstress.cpp:625

  • CollectStackRefs appends to pRefs without any cap. Later comparisons allocate fixed-size arrays sized MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed/bUsed) and assume counts fit. If the DAC returns > MAX_COLLECTED_REFS refs, this will lead to out-of-bounds writes/reads. Add an explicit limit/overflow handling in CollectStackRefs (stop at MAX_COLLECTED_REFS and mark overflow / SKIP), or change the later comparison logic to handle arbitrary counts safely.
 SOSStackRefData refData;
unsigned int fetched = 0;
while (true)
{
hr = pEnum->Next(1, &refData, &fetched);
if (FAILED(hr) || fetched == 0)
break;
StackRef ref;
ref.Address = refData.Address;
ref.Object = refData.Object;
ref.Flags = refData.Flags;
ref.Source = refData.Source;
ref.SourceType = refData.SourceType;
ref.Register = refData.Register;
ref.Offset = refData.Offset;
ref.StackPointer = refData.StackPointer;
pRefs->Append(ref);
}

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/known-issues.md
Comment threaddocs/design/datacontracts/StackWalk.md
CopilotAI review requested due to automatic review settings April 13, 2026 19: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 52 out of 52 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/coreclr/vm/cdacstress.cpp
CopilotAI review requested due to automatic review settings April 13, 2026 20:24
Max Charlamband others added 5 commits April 22, 2026 15:23
- Fix platform-specific ctx.Rip usage: use GetIP(&ctx) instead
- Fix if( style: add space after if keyword in gccover.cpp
- Add RVA bounds validation in FindGCRefMap before uint cast
- Remove stale analysis file (eh-throwhelper-report.md)
- Update AssertHighPassRate comment to reflect current state
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Run GC stress verification tests in the runtime-diagnostics pipeline by
piggybacking on the existing CdacDumpTests Checked runtime build. The
stress tests run as a second Helix submission after the dump tests,
using the testhost shared framework as CORE_ROOT.
Runs on all cdacDumpPlatforms (windows_x64, linux_x64, etc.). On
non-Windows platforms, only instruction-level stress (via DoGcStress)
is supported; allocation-level stress (VerifyAtAllocPoint) is skipped
because it requires Windows-only RtlCaptureContext/RtlVirtualUnwind.
Infrastructure:
- cdac-stress-helix.proj: Helix SDK project that sends testhost as
correlation payload and stress test debuggees + test assembly as
work item payload. Sets CORE_ROOT env var for the test harness.
- prepare-cdac-stress-helix-steps.yml: Pipeline template that builds
debuggees, prepares Helix payload, and finds testhost directory.
- StressTests.targets: Added PrepareHelixPayload and BuildDebuggeesOnly
targets for CI payload preparation.
- CdacStressTestBase.cs: Added HELIX_WORKITEM_PAYLOAD support for
finding debuggees in Helix environment.
- runtime-diagnostics.yml: Extended CdacDumpTests buildArgs to include
tools.cdacstresstests, added stress test Helix submission steps.
- cdacstress.cpp: Guard VerifyAtAllocPoint with TARGET_WINDOWS for
RtlCaptureContext/RtlVirtualUnwind APIs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a custom signature decoder that handles runtime-internal type codes
(ELEMENT_TYPE_INTERNAL 0x21, ELEMENT_TYPE_CMOD_INTERNAL 0x22) which
SRM's SignatureDecoder cannot parse.
- IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of
SRM's ISignatureTypeProvider, adding GetInternalType for resolving
embedded TypeHandle pointers via the runtime type system.
- ISignatureReader + SpanSignatureReader: abstraction for reading
signature bytes from different sources (spans, target memory).
- RuntimeSignatureDecoder<TType, TGenericContext, TReader>: ref struct
decoder that handles all standard ECMA-335 types plus internal types.
- GcSignatureTypeProvider: implements IRuntimeSignatureTypeProvider to
classify types for GC scanning, resolving internal types via
RuntimeTypeSystem.GetSignatureCorElementType.
Key correctness details vs SRM's SignatureDecoder:
- CLASS/VALUETYPE tokens decoded as TypeDefOrRefOrSpecEncoded per
ECMA-335 II.23.2.8 (tag in low 2 bits, RID in upper bits).
- CMOD_INTERNAL correctly skips the required/optional flag byte before
the TypeHandle pointer, matching sigparser.h layout.
- ReadCompressedSignedInt uses ECMA sign-extension-by-width, not zigzag.
- ReadCompressedUInt rejects invalid 111xxxxx prefix.
- Unknown type codes throw BadImageFormatException instead of silently
returning Object (which would create false-positive GC refs).
- Method signature header kind is validated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port crossgen2's ArgIterator, TransitionBlock, and GC scanning logic
into the cDAC contracts for correct per-architecture argument placement.
- Add OffsetOfFloatArgumentRegisters to TransitionBlock data descriptor
- CallingConventionInfo: hybrid data descriptor + ABI invariant constants
for x86, x64 (Windows/Unix), ARM32, ARM64, LoongArch64, RISC-V64
- ArgTypeInfo: pre-computed type info replacing crossgen2's TypeHandle
- ArgIteratorData: parsed method signature holder
- ArgIterator: maps each argument to register/stack offsets via
GetNextOffset() with per-architecture register allocation
- Integrate into FrameIterator.PromoteCallerStackHelper replacing the
simplified 1-slot-per-param approach with proper offset computation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Quote debuggee DLL path in ProcessStartInfo.Arguments (dotnet#31)
- Fix timeout: use async stdout/stderr reads so WaitForExit works (dotnet#32)
- Update stale comment about ELEMENT_TYPE_INTERNAL limitation (dotnet#35)
- Move CallingConvention types to StackWalkHelpers.CallingConvention namespace (dotnet#37)
- Simplify x86 register eligibility check in ArgIterator (dotnet#38)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 22, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 1a92e2c to b85dbacCompareApril 22, 2026 19:25

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

Comment on lines +16 to +20
// FirstThreadLink is an embedded SLink struct. Read the SLink.Next pointer
// from the field's address to get the first thread link pointer.
Target.TypeInfo slinkType = target.GetTypeInfo(DataType.SLink);
TargetPointer slinkAddr = address + (ulong)type.Fields[nameof(FirstThreadLink)].Offset;
FirstThreadLink = target.ReadPointerField(slinkAddr, slinkType, "Next");

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

ThreadStore now calls target.GetTypeInfo(DataType.SLink) and reads field "Next", but the CoreCLR data descriptor in this PR doesn't define a SLink type/field (could not find any CDAC_TYPE_BEGIN/FIELD for SLink). This will cause GetTypeInfo(DataType.SLink) to fail at runtime. Either add an SLink descriptor (with a "Next" pointer) on the runtime side or avoid needing type info here (e.g., treat the embedded SLink as a single pointer at offset 0).

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 29
SLink,
ThreadLocalData,

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

DataType adds SLink, but there is no corresponding runtime data descriptor definition (no CDAC_TYPE_BEGIN/FIELD(SLink, ...) found). Any attempt to read this type via Target.GetTypeInfo(DataType.SLink) will fail. Either add the runtime descriptor entry for SLink or remove this enum value and read the embedded link without type metadata.

Suggested change
SLink,
ThreadLocalData,
ThreadLocalData=17,

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +69
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

These DataReceived event handlers append to strings via "+=" from background threads. That is not thread-safe and can lead to lost/garbled output under contention. Consider using a StringBuilder with locking (or ConcurrentQueue) for stderr/stdout aggregation.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +74
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

Same thread-safety issue as stderr: appending to stdout with += from OutputDataReceived is racy. Use a synchronized StringBuilder/collector to avoid missing output and to keep logs reliable when tests fail.

Copilot uses AI. Check for mistakes.
Comment on lines +732 to +751
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
private static ArgTypeInfo GcTypeKindToArgTypeInfo(GcTypeKind kind, int pointerSize)
{
return kind switch
{
GcTypeKind.None => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
GcTypeKind.Ref => ArgTypeInfo.ForPrimitive(CorElementType.Class, pointerSize),
GcTypeKind.Interior => ArgTypeInfo.ForPrimitive(CorElementType.Byref, pointerSize),
GcTypeKind.Other => new ArgTypeInfo
{
CorElementType = CorElementType.ValueType,
Size = pointerSize, // Conservative: assume pointer-sized for now
IsValueType = true,
},
_ => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
};
}

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

GcTypeKindToArgTypeInfo collapses all non-GC-ref types to CorElementType.I/pointer-sized and doesn't preserve float vs integer vs exact primitive sizes. ArgIterator offset calculation depends on the real signature shape (especially on Unix x64/ARM64 where float regs and argument sizing affect later argument placement), so this can produce incorrect offsets and cause missed/incorrect GC ref reporting for signatures with floats or non-pointer-sized primitives. Consider decoding into a richer type representation (e.g., CorElementType + size) and building ArgTypeInfo from that rather than from GcTypeKind.

Suggested change
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.None=> ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,// Conservative: assume pointer-sized for now
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
/// Converts a <see cref="GcTypeKind"/> to a conservative fallback <see cref="ArgTypeInfo"/>
/// when an exact signature cannot be decoded. Callers should prefer decoding the real
/// managed signature via <see cref="TryGetArgTypeInfosFromMethodSignature(ReadOnlySpan{byte}, int, out ArgTypeInfo[])"/>
/// so that floating-point and non-pointer-sized primitives retain their real layout.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
privatestaticboolTryGetArgTypeInfosFromMethodSignature(ReadOnlySpan<byte>signatureBytes,intpointerSize,outArgTypeInfo[]argTypes)
{
argTypes=Array.Empty<ArgTypeInfo>();
if(signatureBytes.IsEmpty)
returnfalse;
BlobReaderreader=new(signatureBytes);
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
ArgTypeInfo[]decodedArgTypes=newArgTypeInfo[parameterCount];
for(inti=0;i<parameterCount;i++)
{
if(!TryReadArgTypeInfo(refreader,pointerSize,outdecodedArgTypes[i]))
returnfalse;
}
argTypes=decodedArgTypes;
returntrue;
}
privatestaticboolTryReadArgTypeInfo(refBlobReaderreader,intpointerSize,outArgTypeInfoargTypeInfo)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Boolean:
caseCorElementType.I1:
caseCorElementType.U1:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,1);
returntrue;
caseCorElementType.Char:
caseCorElementType.I2:
caseCorElementType.U2:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,2);
returntrue;
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.R4:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,4);
returntrue;
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R8:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,8);
returntrue;
caseCorElementType.I:
caseCorElementType.U:
caseCorElementType.Ptr:
caseCorElementType.FnPtr:
if(elementTypeisCorElementType.Ptr or CorElementType.FnPtr)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize);
returntrue;
caseCorElementType.Byref:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize);
returntrue;
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.Class:
if(elementTypeisCorElementType.Class)
reader.ReadCompressedInteger();
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.SzArray:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.ValueType:
reader.ReadCompressedInteger();
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
}
default:
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=default;
returnfalse;
}
privatestaticboolTrySkipSignatureType(refBlobReaderreader)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Void:
caseCorElementType.Boolean:
caseCorElementType.Char:
caseCorElementType.I1:
caseCorElementType.U1:
caseCorElementType.I2:
caseCorElementType.U2:
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R4:
caseCorElementType.R8:
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.TypedByref:
caseCorElementType.I:
caseCorElementType.U:
returntrue;
caseCorElementType.Class:
caseCorElementType.ValueType:
caseCorElementType.Var:
caseCorElementType.MVar:
reader.ReadCompressedInteger();
returntrue;
caseCorElementType.Byref:
caseCorElementType.Ptr:
caseCorElementType.SzArray:
returnTrySkipSignatureType(refreader);
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
caseCorElementType.Array:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intrank=reader.ReadCompressedInteger();
intsizes=reader.ReadCompressedInteger();
for(inti=0;i<sizes;i++)
reader.ReadCompressedInteger();
intlowerBounds=reader.ReadCompressedInteger();
for(inti=0;i<lowerBounds;i++)
reader.ReadCompressedInteger();
returnrank>=0;
}
caseCorElementType.FnPtr:
{
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
for(inti=0;i<parameterCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
default:
returnfalse;
}
}
returnfalse;
}

Copilot uses AI. Check for mistakes.

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

Comment on lines +582 to 586
static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray<StackRef>* pRefs,
const char* label = nullptr)
{
if (pSosDac == nullptr)
return false;

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

CollectStackRefs currently appends every ref returned by ISOSStackRefEnum::Next with no upper bound. Later comparison helpers allocate fixed-size arrays sized by MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed[MAX_COLLECTED_REFS]), so if enumeration returns more than MAX_COLLECTED_REFS it can lead to out-of-bounds writes. Please cap collection to MAX_COLLECTED_REFS (and record an overflow/skip reason) or make the comparison logic handle arbitrarily large ref sets safely.

Copilot uses AI. Check for mistakes.
Comment on lines +1359 to +1362
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

When CollectRuntimeStackRefs overflows, the code logs a [SKIP] line but continues and still computes rtMatch/pass (and does not increment s_verifySkip). If CDACSTRESS_USE_DAC is not set, this can produce false failures based on a truncated runtime ref set. Consider treating runtime overflow as an actual skip (increment skip counter + return) when RT comparison is used for pass/fail, or otherwise avoid using rtMatch in the presence of overflow.

Suggested change
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
if (rtOverflow)
{
InterlockedIncrement(&s_verifySkip);
if (s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
}
return;

Copilot uses AI. Check for mistakes.
Comment on lines +823 to +824
ReportSlot(slotIndex, reportScratchSlots: true, reportFpBasedSlotsOnly, reportSlot);
}

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

In ReportUntrackedAndSucceed, ReportSlot is called with reportScratchSlots: true, which forces reporting scratch registers/stack slots even when CodeManagerFlags.ActiveStackFrame is not set (reportScratchSlots local is false). This changes GC root enumeration behavior and can introduce extra roots for non-leaf frames. It looks like this should pass the reportScratchSlots variable instead of always true.

Suggested change
ReportSlot(slotIndex,reportScratchSlots:true,reportFpBasedSlotsOnly,reportSlot);
}
ReportSlot(slotIndex,reportScratchSlots,reportFpBasedSlotsOnly,reportSlot);
}

Copilot uses AI. Check for mistakes.
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 49c8435 to de5cb46CompareApril 24, 2026 15:56
- Include RuntimeInfoOperatingSystem.Apple in Unix x64 ABI check
(macOS x64 uses SysV ABI, not Windows ABI)
- Thread MetadataReader from GetMethodSignatureBytes through
RuntimeSignatureDecoder to provider methods (instead of null!)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 24, 2026 16:07

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

Comment on lines +960 to 964
// Compare two ref sets using two-phase matching (for RT comparison where we
// don't have Source info). Returns true if all refs match.
static bool CompareRefSetsFlat(StackRef* refsA, int countA, StackRef* refsB, int countB)
{
if (countA != countB)

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

CompareRefSetsFlat uses a fixed-size matched[MAX_COLLECTED_REFS] buffer, but cDAC/DAC ref collection (CollectStackRefs) is unbounded. Without a guard/cap, a large ref set (>4096) can cause out-of-bounds writes during matching. Consider capping cDAC/DAC collection to MAX_COLLECTED_REFS (and treating overflow as a [SKIP]) or using dynamically sized bookkeeping here.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +76
// Read both stdout and stderr asynchronously to avoid deadlock
// when pipe buffers fill, and to allow WaitForExit timeout to work.
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};
process.BeginErrorReadLine();
process.BeginOutputReadLine();

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

The async stdout/stderr collection uses stdout += ... / stderr += ... inside DataReceived event handlers. These callbacks can run concurrently, and string concatenation is not thread-safe; it can also be costly for large output. Consider buffering with a thread-safe collector (e.g., ConcurrentQueue<string> or StringBuilder with a lock) and call process.WaitForExit() (or await stream completion) after WaitForExit(timeout) to ensure all async output has been drained before asserting/logging.

Copilot uses AI. Check for mistakes.
max-charlamb added a commit that referenced this pull request May 1, 2026
## Summary
Part 1 of 5 stacked PRs splitting
[#126408](#126408) into reviewable
pieces.
### What this PR contains
**Stack Walk GC Reference Scanning:**
- `PromoteCallerStack` / `PromoteCallerStackUsingGCRefMap` for
transition frames
- `GCRefMapDecoder` + `FindGCRefMap` with ReadyToRun import section
resolution
- `GcSignatureTypeProvider` for GC type classification
- `SOSDacImpl.GetStackReferences` fully implemented using cDAC contracts
- `GCInfoDecoder.EnumerateLiveSlots` promoted to `IGCInfo` contract
(returns `IReadOnlyList<LiveSlot>`)
- `GcSlotEnumerationOptions` replaces native `CodeManagerFlags` with
descriptive boolean properties
**Stack Walker Fixes:**
- `IsFirst` preserved for skipped frames (matches native
SFITER_SKIPPED_FRAME_FUNCTION)
- `IsInterrupted` state tracking for exception frames
(FaultingExceptionFrame, SoftwareExceptionFrame)
- `GetReturnAddress` gating in SW_FRAME (only UpdateRegDisplay if return
address non-null)
- Catch handler offset override via `GetInterruptibleRanges` for EH
resumption
**Contract API Additions:**
- `IGCInfo`: `EnumerateLiveSlots`, `GetStackBaseRegister`,
`GetInterruptibleRanges`
- `IExecutionManager`: `FindReadyToRunModule`
- `IRuntimeTypeSystem`: `RequiresInstArg`, `IsAsyncMethod`
- `IStackWalk`: `WalkStackReferences`
**Data Descriptor Changes:**
- Removed `ZapModule` and `GCRefMap` cached pointers (always resolve via
`FindReadyToRunModule`)
- Added `Indirection` for StubDispatchFrame, ExternalMethodFrame
- Added `DynamicHelperFrame.DynamicHelperFrameFlags`
- Added TransitionBlock fields (`OffsetOfArgs`,
`ArgumentRegistersOffset`, `FirstGCRefMapSlot`)
- Added ReadyToRunInfo fields (`ImportSections`, `NumImportSections`)
- Added ExceptionInfo catch clause fields
(`ClauseForCatchHandlerStartPC`, `ClauseForCatchHandlerEndPC`)
**Documentation:**
- GCInfo.md: Comprehensive implementation docs (header/body decoding,
slot table, EnumerateLiveSlots algorithm, type definitions for
`LiveSlot`, `InterruptibleRange`, `GcSlotEnumerationOptions`)
- StackWalk.md: GC scanning algorithm, GCRefMap resolution flow, return
address per frame type, `WalkStackReferences` API
- ExecutionManager.md: `FindReadyToRunModule` API and implementation
- RuntimeTypeSystem.md: `RequiresInstArg`, `IsAsyncMethod` APIs
### Stack overview
| PR | Content | Status |
|----|---------|--------|
| **This PR** | Stack walk fixes + GC scanning | Open |
| PR 2 | RuntimeSignatureDecoder (ELEMENT_TYPE_INTERNAL) | Pending |
| PR 3 | ArgIterator port from crossgen2 | Pending |
| PR 4 | Native stress framework (cdacstress.cpp) | Pending |
| PR 5 | Managed stress tests + CI pipeline | Pending |
### Testing
- 1727/1751 unit tests pass (24 pre-existing ThreadTests failures on
main)
- Dump tests (StackWalkDumpTests, StackReferenceDumpTests) validate
end-to-end
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

Closing in favor of stacked PR approach

steveisok pushed a commit that referenced this pull request May 11, 2026
…5) (#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[#126408](#126408). Builds on
[#127395](#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jakobbotsch pushed a commit to jakobbotsch/runtime that referenced this pull request May 12, 2026
…5) (dotnet#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[dotnet#126408](dotnet#126408). Builds on
[dotnet#127395](dotnet#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 1, 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.

2 participants

@max-charlamb
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

[cDAC] Stack walk GC stress verification and fixes - #126408

Closed
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5
Closed

[cDAC] Stack walk GC stress verification and fixes#126408
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Apr 1, 2026

Copy link
Copy Markdown
Member

Summary

cDAC GC stress verification tool (DOTNET_CdacStress) that compares stack GC references between the cDAC and the runtime at allocation stress points. Includes stack walker fixes, GC reference scanning implementation, custom signature decoding, and calling convention argument iteration.

Note

This PR description was updated with AI assistance from Copilot.

Stack walker fixes

  • Fix SW_SKIPPED_FRAME: do not call UpdateContextFromFrame (matches native SFITER_SKIPPED_FRAME_FUNCTION which does not call UpdateRegDisplay)
  • Fix skipped-frame loop: check for more skipped frames before yielding managed method (prevents duplicate EnumGcRefs between consecutive skipped frames)
  • Restructure Filter() to drive Next() directly, matching native Filter()+NextRaw() integration (prevents funclet-to-parent walk cycles)
  • Remove SkipActiveICFOnce/SkipCurrentFrameInCheck — active ICF double-yield is natural and harmless
  • Remove IsAtFirstPassExceptionThrowSite — native does not suppress first-pass refs
  • Fix IsFirst not preserved for skipped frames (was causing IsActiveFrame=false for the topmost managed frame)

GC reference scanning

  • Implement PromoteCallerStack for stub frames (GCRefMap + MetaSig paths)
  • Implement SOSDacImpl.GetStackReferences using cDAC contract (was falling back to legacy DAC)
  • Read FilterContext for stack walk starting context
  • Three-way cDAC/DAC/RT comparison with InProcessDataTarget
  • DOTNET_CdacStress bit flags: ALLOC/INSTR/REFS/WALK/USE_DAC/UNIQUE

RuntimeSignatureDecoder

Custom signature decoder that handles ELEMENT_TYPE_INTERNAL (0x21) and ELEMENT_TYPE_CMOD_INTERNAL (0x22) which SRM's SignatureDecoder cannot parse. These occur in IL stubs, marshalling stubs, and unsafe accessor frames.

  • IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of SRM's ISignatureTypeProvider, adds GetInternalType and GetInternalModifiedType
  • ISignatureReader + SpanSignatureReader: endianness-aware reader abstraction
  • Correct ECMA-335 compliance: TypeDefOrRefOrSpecEncoded token decoding, sign-extension-by-width for compressed signed ints, bounds validation

ArgIterator (ported from crossgen2)

Proper calling convention analysis replacing the simplified 1-slot-per-param approach.

  • CallingConventionInfo: hybrid data descriptor layout values + ABI invariant constants for all architectures (x86, x64 Windows/Unix, ARM32, ARM64, LoongArch64, RISC-V64)
  • ArgIterator.GetNextOffset(): maps each argument to its actual register or stack offset
  • OffsetOfFloatArgumentRegisters added to TransitionBlock data descriptor
  • Handles multi-slot args, forced-byref params, return buffer placement, async continuation

CI infrastructure

  • cDAC stress tests run in Helix via cdac-stress-helix.proj
  • Extended runtime-diagnostics.yml CdacDumpTests buildArgs with +tools.cdacstresstests

Test results

Allocation-level stress (9 debuggees, ~46K verifications):

DebuggeeVerificationsPassFail
BasicAlloc4,9364,9360
DeepStack4,9644,9640
Generics4,9364,9360
MultiThread4,9964,9960
Comprehensive4,9944,9922*
ExceptionHandling4,9584,9580
StructScenarios4,9404,9400
DynamicMethods6,5206,5200
PInvoke4,9364,9360

Instruction-level stress (9 debuggees, ~226K verifications):
All 9 debuggees pass with zero failures across 226,452 verifications.

*Comprehensive's 2 failures are pre-existing legacy DAC issues (DAC returns 0 refs for background threads in kernel waits; cDAC and runtime agree).

@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

Adds a cDAC GC-stress verification harness and extends the cDAC stack-walk / stack-GC-ref pipeline so cDAC stack reference enumeration can be compared against runtime scanning at stress points.

Changes:

  • Implements/extends cDAC stack reference enumeration (including Frame-based scanning paths like PromoteCallerStack via GCRefMap / MetaSig) and wires SOS GetStackReferences to the cDAC contract.
  • Introduces a new GC stress integration test project with debuggee apps and orchestration targets.
  • Extends CoreCLR cDAC stress/GC stress plumbing and data descriptors to support the new stack-walk and frame-scanning capabilities.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 12 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.csUpdates mock type layout for execution manager-related data.
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.csUpdates mock type layouts (ExceptionInfo/Thread) for contract tests.
src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csprojExcludes new GCStressTests folder from the existing unit test project compilation.
src/native/managed/cdac/tests/GCStressTests/README.mdDocuments how to build/run the new GC stress tests.
src/native/managed/cdac/tests/GCStressTests/Microsoft.Diagnostics.DataContractReader.GCStressTests.csprojAdds a dedicated GC stress test project.
src/native/managed/cdac/tests/GCStressTests/GCStressTests.targetsMSBuild orchestration to discover/build debuggee projects.
src/native/managed/cdac/tests/GCStressTests/GCStressTestBase.csTest harness to run debuggees under corerun and parse verification logs.
src/native/managed/cdac/tests/GCStressTests/GCStressResults.csParses the native verification log into structured pass/fail/skip counts.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/Program.csAdds a P/Invoke-focused debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/PInvoke.csprojDebuggee project file for PInvoke scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/Program.csAdds a multi-threaded debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/MultiThread.csprojDebuggee project file for MultiThread scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Program.csAdds a generics/interface/delegate debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Generics.csprojDebuggee project file for Generics scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/Program.csAdds an exception-handling/funclet debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/ExceptionHandling.csprojDebuggee project file for ExceptionHandling scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Directory.Build.propsShared build props for debuggee projects (output layout, TFM, etc.).
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/Program.csAdds deep-recursion debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/DeepStack.csprojDebuggee project file for DeepStack scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Program.csAdds comprehensive “all scenarios” debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Comprehensive.csprojDebuggee project file for Comprehensive scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/Program.csAdds basic allocation/live-ref debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/BasicAlloc.csprojDebuggee project file for BasicAlloc scenario.
src/native/managed/cdac/tests/GCStressTests/BasicGCStressTests.csTheory-based test suite that runs the debuggees and asserts pass rate.
src/native/managed/cdac/tests/gcstress/known-issues.mdCaptures known mismatch classes and limitations.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.csImplements GetStackReferences using the cDAC contract rather than legacy DAC fallback.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.csExtends StubDispatchFrame data with GCRefMap pointer.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.csAdds ExternalMethodFrame contract data type (GCRefMap).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.csAdds DynamicHelperFrame contract data type (flags).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.csAdds clause-range fields used for catch-handler resumption offset selection.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.csRefactors stack-walk filtering and adds Frame-based GC root scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAdds optional relOffset override support for GC ref enumeration.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.csImplements GCRefMap decoding for transition-block scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/CorSigParser.csAdds minimal signature parsing to classify parameters for MetaSig-based scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.csAdds FindFirstInterruptiblePoint API to GCInfo decoders.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.csImplements FindFirstInterruptiblePoint using decoded interruptible ranges.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.csFixes code-start lookup for exception clause enumeration and adds minor flow adjustments.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.csAdds TransitionBlock-related global names used by frame scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.csAdds new DataType enum values for new frame contracts.
src/native/managed/cdac/cdac.slnxAdds the new GC stress test project to the cDAC solution.
src/coreclr/vm/gccover.cppAdds step-based skipping to reduce overhead when throttling verification.
src/coreclr/vm/frames.hExposes additional frame fields to cDAC via cdac_data<> descriptors.
src/coreclr/vm/datadescriptor/datadescriptor.incAdds new contract fields/globals for frames and TransitionBlock layout.
src/coreclr/vm/cdacstress.cppUpdates in-process cDAC/DAC verification logic, logging, and step behavior.
eng/Subsets.propsAdds an on-demand subset for running GC stress tests.
docs/design/datacontracts/StackWalk.mdDocuments the new frame fields and TransitionBlock globals in the StackWalk contract.
Comments suppressed due to low confidence (1)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:228

  • ThreadFields contains ProfilerFilterContext twice (lines 224 and 228). Duplicate field entries will skew offsets and make the mock descriptors inconsistent with the real data descriptor. Keep a single ProfilerFilterContext entry in the correct order.

Comment threadsrc/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/README.md Outdated

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

Comments suppressed due to low confidence (2)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:229

  • ThreadFields now includes DebuggerFilterContext/ProfilerFilterContext twice (duplicate entries at the end of the list). This can cause ambiguous/incorrect field offsets in the mock type layout. Keep each field only once.
    src/coreclr/vm/cdacstress.cpp:963
  • CompareRefSets uses a fixed-size matched[MAX_COLLECTED_REFS] buffer but no longer validates that countA/countB are <= MAX_COLLECTED_REFS. Since CollectStackRefs appends without a hard cap, this can lead to out-of-bounds access when countA or countB exceeds 4096. Reintroduce a guard or allocate the match state sized to the counts.
 return true;
bool matched[MAX_COLLECTED_REFS] = {};
for (int i = 0; i < countA; i++)

Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/gccover.cpp Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/GCStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 75ae7fe to 5419d15CompareApril 13, 2026 15:30
CopilotAI review requested due to automatic review settings April 13, 2026 15:47
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 5419d15 to 8ef9b22CompareApril 13, 2026 15:47

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

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated

@max-charlambmax-charlamb left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

feedback for local copilot

Comment threadsrc/native/managed/cdac/tests/StressTests/analysis/analyze-refs.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/StressTests.targets Outdated
Comment threadsrc/coreclr/vm/datadescriptor/datadescriptor.inc Outdated
CopilotAI review requested due to automatic review settings April 13, 2026 17:33

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

Comments suppressed due to low confidence (1)

src/coreclr/vm/cdacstress.cpp:625

  • CollectStackRefs appends to pRefs without any cap. Later comparisons allocate fixed-size arrays sized MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed/bUsed) and assume counts fit. If the DAC returns > MAX_COLLECTED_REFS refs, this will lead to out-of-bounds writes/reads. Add an explicit limit/overflow handling in CollectStackRefs (stop at MAX_COLLECTED_REFS and mark overflow / SKIP), or change the later comparison logic to handle arbitrary counts safely.
 SOSStackRefData refData;
unsigned int fetched = 0;
while (true)
{
hr = pEnum->Next(1, &refData, &fetched);
if (FAILED(hr) || fetched == 0)
break;
StackRef ref;
ref.Address = refData.Address;
ref.Object = refData.Object;
ref.Flags = refData.Flags;
ref.Source = refData.Source;
ref.SourceType = refData.SourceType;
ref.Register = refData.Register;
ref.Offset = refData.Offset;
ref.StackPointer = refData.StackPointer;
pRefs->Append(ref);
}

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/known-issues.md
Comment threaddocs/design/datacontracts/StackWalk.md
CopilotAI review requested due to automatic review settings April 13, 2026 19: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 52 out of 52 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/coreclr/vm/cdacstress.cpp
CopilotAI review requested due to automatic review settings April 13, 2026 20:24
Max Charlamband others added 5 commits April 22, 2026 15:23
- Fix platform-specific ctx.Rip usage: use GetIP(&ctx) instead
- Fix if( style: add space after if keyword in gccover.cpp
- Add RVA bounds validation in FindGCRefMap before uint cast
- Remove stale analysis file (eh-throwhelper-report.md)
- Update AssertHighPassRate comment to reflect current state
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Run GC stress verification tests in the runtime-diagnostics pipeline by
piggybacking on the existing CdacDumpTests Checked runtime build. The
stress tests run as a second Helix submission after the dump tests,
using the testhost shared framework as CORE_ROOT.
Runs on all cdacDumpPlatforms (windows_x64, linux_x64, etc.). On
non-Windows platforms, only instruction-level stress (via DoGcStress)
is supported; allocation-level stress (VerifyAtAllocPoint) is skipped
because it requires Windows-only RtlCaptureContext/RtlVirtualUnwind.
Infrastructure:
- cdac-stress-helix.proj: Helix SDK project that sends testhost as
correlation payload and stress test debuggees + test assembly as
work item payload. Sets CORE_ROOT env var for the test harness.
- prepare-cdac-stress-helix-steps.yml: Pipeline template that builds
debuggees, prepares Helix payload, and finds testhost directory.
- StressTests.targets: Added PrepareHelixPayload and BuildDebuggeesOnly
targets for CI payload preparation.
- CdacStressTestBase.cs: Added HELIX_WORKITEM_PAYLOAD support for
finding debuggees in Helix environment.
- runtime-diagnostics.yml: Extended CdacDumpTests buildArgs to include
tools.cdacstresstests, added stress test Helix submission steps.
- cdacstress.cpp: Guard VerifyAtAllocPoint with TARGET_WINDOWS for
RtlCaptureContext/RtlVirtualUnwind APIs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a custom signature decoder that handles runtime-internal type codes
(ELEMENT_TYPE_INTERNAL 0x21, ELEMENT_TYPE_CMOD_INTERNAL 0x22) which
SRM's SignatureDecoder cannot parse.
- IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of
SRM's ISignatureTypeProvider, adding GetInternalType for resolving
embedded TypeHandle pointers via the runtime type system.
- ISignatureReader + SpanSignatureReader: abstraction for reading
signature bytes from different sources (spans, target memory).
- RuntimeSignatureDecoder<TType, TGenericContext, TReader>: ref struct
decoder that handles all standard ECMA-335 types plus internal types.
- GcSignatureTypeProvider: implements IRuntimeSignatureTypeProvider to
classify types for GC scanning, resolving internal types via
RuntimeTypeSystem.GetSignatureCorElementType.
Key correctness details vs SRM's SignatureDecoder:
- CLASS/VALUETYPE tokens decoded as TypeDefOrRefOrSpecEncoded per
ECMA-335 II.23.2.8 (tag in low 2 bits, RID in upper bits).
- CMOD_INTERNAL correctly skips the required/optional flag byte before
the TypeHandle pointer, matching sigparser.h layout.
- ReadCompressedSignedInt uses ECMA sign-extension-by-width, not zigzag.
- ReadCompressedUInt rejects invalid 111xxxxx prefix.
- Unknown type codes throw BadImageFormatException instead of silently
returning Object (which would create false-positive GC refs).
- Method signature header kind is validated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port crossgen2's ArgIterator, TransitionBlock, and GC scanning logic
into the cDAC contracts for correct per-architecture argument placement.
- Add OffsetOfFloatArgumentRegisters to TransitionBlock data descriptor
- CallingConventionInfo: hybrid data descriptor + ABI invariant constants
for x86, x64 (Windows/Unix), ARM32, ARM64, LoongArch64, RISC-V64
- ArgTypeInfo: pre-computed type info replacing crossgen2's TypeHandle
- ArgIteratorData: parsed method signature holder
- ArgIterator: maps each argument to register/stack offsets via
GetNextOffset() with per-architecture register allocation
- Integrate into FrameIterator.PromoteCallerStackHelper replacing the
simplified 1-slot-per-param approach with proper offset computation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Quote debuggee DLL path in ProcessStartInfo.Arguments (dotnet#31)
- Fix timeout: use async stdout/stderr reads so WaitForExit works (dotnet#32)
- Update stale comment about ELEMENT_TYPE_INTERNAL limitation (dotnet#35)
- Move CallingConvention types to StackWalkHelpers.CallingConvention namespace (dotnet#37)
- Simplify x86 register eligibility check in ArgIterator (dotnet#38)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 22, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 1a92e2c to b85dbacCompareApril 22, 2026 19:25

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

Comment on lines +16 to +20
// FirstThreadLink is an embedded SLink struct. Read the SLink.Next pointer
// from the field's address to get the first thread link pointer.
Target.TypeInfo slinkType = target.GetTypeInfo(DataType.SLink);
TargetPointer slinkAddr = address + (ulong)type.Fields[nameof(FirstThreadLink)].Offset;
FirstThreadLink = target.ReadPointerField(slinkAddr, slinkType, "Next");

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

ThreadStore now calls target.GetTypeInfo(DataType.SLink) and reads field "Next", but the CoreCLR data descriptor in this PR doesn't define a SLink type/field (could not find any CDAC_TYPE_BEGIN/FIELD for SLink). This will cause GetTypeInfo(DataType.SLink) to fail at runtime. Either add an SLink descriptor (with a "Next" pointer) on the runtime side or avoid needing type info here (e.g., treat the embedded SLink as a single pointer at offset 0).

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 29
SLink,
ThreadLocalData,

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

DataType adds SLink, but there is no corresponding runtime data descriptor definition (no CDAC_TYPE_BEGIN/FIELD(SLink, ...) found). Any attempt to read this type via Target.GetTypeInfo(DataType.SLink) will fail. Either add the runtime descriptor entry for SLink or remove this enum value and read the embedded link without type metadata.

Suggested change
SLink,
ThreadLocalData,
ThreadLocalData=17,

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +69
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

These DataReceived event handlers append to strings via "+=" from background threads. That is not thread-safe and can lead to lost/garbled output under contention. Consider using a StringBuilder with locking (or ConcurrentQueue) for stderr/stdout aggregation.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +74
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

Same thread-safety issue as stderr: appending to stdout with += from OutputDataReceived is racy. Use a synchronized StringBuilder/collector to avoid missing output and to keep logs reliable when tests fail.

Copilot uses AI. Check for mistakes.
Comment on lines +732 to +751
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
private static ArgTypeInfo GcTypeKindToArgTypeInfo(GcTypeKind kind, int pointerSize)
{
return kind switch
{
GcTypeKind.None => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
GcTypeKind.Ref => ArgTypeInfo.ForPrimitive(CorElementType.Class, pointerSize),
GcTypeKind.Interior => ArgTypeInfo.ForPrimitive(CorElementType.Byref, pointerSize),
GcTypeKind.Other => new ArgTypeInfo
{
CorElementType = CorElementType.ValueType,
Size = pointerSize, // Conservative: assume pointer-sized for now
IsValueType = true,
},
_ => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
};
}

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

GcTypeKindToArgTypeInfo collapses all non-GC-ref types to CorElementType.I/pointer-sized and doesn't preserve float vs integer vs exact primitive sizes. ArgIterator offset calculation depends on the real signature shape (especially on Unix x64/ARM64 where float regs and argument sizing affect later argument placement), so this can produce incorrect offsets and cause missed/incorrect GC ref reporting for signatures with floats or non-pointer-sized primitives. Consider decoding into a richer type representation (e.g., CorElementType + size) and building ArgTypeInfo from that rather than from GcTypeKind.

Suggested change
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.None=> ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,// Conservative: assume pointer-sized for now
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
/// Converts a <see cref="GcTypeKind"/> to a conservative fallback <see cref="ArgTypeInfo"/>
/// when an exact signature cannot be decoded. Callers should prefer decoding the real
/// managed signature via <see cref="TryGetArgTypeInfosFromMethodSignature(ReadOnlySpan{byte}, int, out ArgTypeInfo[])"/>
/// so that floating-point and non-pointer-sized primitives retain their real layout.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
privatestaticboolTryGetArgTypeInfosFromMethodSignature(ReadOnlySpan<byte>signatureBytes,intpointerSize,outArgTypeInfo[]argTypes)
{
argTypes=Array.Empty<ArgTypeInfo>();
if(signatureBytes.IsEmpty)
returnfalse;
BlobReaderreader=new(signatureBytes);
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
ArgTypeInfo[]decodedArgTypes=newArgTypeInfo[parameterCount];
for(inti=0;i<parameterCount;i++)
{
if(!TryReadArgTypeInfo(refreader,pointerSize,outdecodedArgTypes[i]))
returnfalse;
}
argTypes=decodedArgTypes;
returntrue;
}
privatestaticboolTryReadArgTypeInfo(refBlobReaderreader,intpointerSize,outArgTypeInfoargTypeInfo)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Boolean:
caseCorElementType.I1:
caseCorElementType.U1:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,1);
returntrue;
caseCorElementType.Char:
caseCorElementType.I2:
caseCorElementType.U2:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,2);
returntrue;
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.R4:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,4);
returntrue;
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R8:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,8);
returntrue;
caseCorElementType.I:
caseCorElementType.U:
caseCorElementType.Ptr:
caseCorElementType.FnPtr:
if(elementTypeisCorElementType.Ptr or CorElementType.FnPtr)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize);
returntrue;
caseCorElementType.Byref:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize);
returntrue;
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.Class:
if(elementTypeisCorElementType.Class)
reader.ReadCompressedInteger();
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.SzArray:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.ValueType:
reader.ReadCompressedInteger();
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
}
default:
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=default;
returnfalse;
}
privatestaticboolTrySkipSignatureType(refBlobReaderreader)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Void:
caseCorElementType.Boolean:
caseCorElementType.Char:
caseCorElementType.I1:
caseCorElementType.U1:
caseCorElementType.I2:
caseCorElementType.U2:
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R4:
caseCorElementType.R8:
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.TypedByref:
caseCorElementType.I:
caseCorElementType.U:
returntrue;
caseCorElementType.Class:
caseCorElementType.ValueType:
caseCorElementType.Var:
caseCorElementType.MVar:
reader.ReadCompressedInteger();
returntrue;
caseCorElementType.Byref:
caseCorElementType.Ptr:
caseCorElementType.SzArray:
returnTrySkipSignatureType(refreader);
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
caseCorElementType.Array:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intrank=reader.ReadCompressedInteger();
intsizes=reader.ReadCompressedInteger();
for(inti=0;i<sizes;i++)
reader.ReadCompressedInteger();
intlowerBounds=reader.ReadCompressedInteger();
for(inti=0;i<lowerBounds;i++)
reader.ReadCompressedInteger();
returnrank>=0;
}
caseCorElementType.FnPtr:
{
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
for(inti=0;i<parameterCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
default:
returnfalse;
}
}
returnfalse;
}

Copilot uses AI. Check for mistakes.

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

Comment on lines +582 to 586
static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray<StackRef>* pRefs,
const char* label = nullptr)
{
if (pSosDac == nullptr)
return false;

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

CollectStackRefs currently appends every ref returned by ISOSStackRefEnum::Next with no upper bound. Later comparison helpers allocate fixed-size arrays sized by MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed[MAX_COLLECTED_REFS]), so if enumeration returns more than MAX_COLLECTED_REFS it can lead to out-of-bounds writes. Please cap collection to MAX_COLLECTED_REFS (and record an overflow/skip reason) or make the comparison logic handle arbitrarily large ref sets safely.

Copilot uses AI. Check for mistakes.
Comment on lines +1359 to +1362
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

When CollectRuntimeStackRefs overflows, the code logs a [SKIP] line but continues and still computes rtMatch/pass (and does not increment s_verifySkip). If CDACSTRESS_USE_DAC is not set, this can produce false failures based on a truncated runtime ref set. Consider treating runtime overflow as an actual skip (increment skip counter + return) when RT comparison is used for pass/fail, or otherwise avoid using rtMatch in the presence of overflow.

Suggested change
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
if (rtOverflow)
{
InterlockedIncrement(&s_verifySkip);
if (s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
}
return;

Copilot uses AI. Check for mistakes.
Comment on lines +823 to +824
ReportSlot(slotIndex, reportScratchSlots: true, reportFpBasedSlotsOnly, reportSlot);
}

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

In ReportUntrackedAndSucceed, ReportSlot is called with reportScratchSlots: true, which forces reporting scratch registers/stack slots even when CodeManagerFlags.ActiveStackFrame is not set (reportScratchSlots local is false). This changes GC root enumeration behavior and can introduce extra roots for non-leaf frames. It looks like this should pass the reportScratchSlots variable instead of always true.

Suggested change
ReportSlot(slotIndex,reportScratchSlots:true,reportFpBasedSlotsOnly,reportSlot);
}
ReportSlot(slotIndex,reportScratchSlots,reportFpBasedSlotsOnly,reportSlot);
}

Copilot uses AI. Check for mistakes.
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 49c8435 to de5cb46CompareApril 24, 2026 15:56
- Include RuntimeInfoOperatingSystem.Apple in Unix x64 ABI check
(macOS x64 uses SysV ABI, not Windows ABI)
- Thread MetadataReader from GetMethodSignatureBytes through
RuntimeSignatureDecoder to provider methods (instead of null!)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 24, 2026 16:07

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

Comment on lines +960 to 964
// Compare two ref sets using two-phase matching (for RT comparison where we
// don't have Source info). Returns true if all refs match.
static bool CompareRefSetsFlat(StackRef* refsA, int countA, StackRef* refsB, int countB)
{
if (countA != countB)

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

CompareRefSetsFlat uses a fixed-size matched[MAX_COLLECTED_REFS] buffer, but cDAC/DAC ref collection (CollectStackRefs) is unbounded. Without a guard/cap, a large ref set (>4096) can cause out-of-bounds writes during matching. Consider capping cDAC/DAC collection to MAX_COLLECTED_REFS (and treating overflow as a [SKIP]) or using dynamically sized bookkeeping here.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +76
// Read both stdout and stderr asynchronously to avoid deadlock
// when pipe buffers fill, and to allow WaitForExit timeout to work.
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};
process.BeginErrorReadLine();
process.BeginOutputReadLine();

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

The async stdout/stderr collection uses stdout += ... / stderr += ... inside DataReceived event handlers. These callbacks can run concurrently, and string concatenation is not thread-safe; it can also be costly for large output. Consider buffering with a thread-safe collector (e.g., ConcurrentQueue<string> or StringBuilder with a lock) and call process.WaitForExit() (or await stream completion) after WaitForExit(timeout) to ensure all async output has been drained before asserting/logging.

Copilot uses AI. Check for mistakes.
max-charlamb added a commit that referenced this pull request May 1, 2026
## Summary
Part 1 of 5 stacked PRs splitting
[#126408](#126408) into reviewable
pieces.
### What this PR contains
**Stack Walk GC Reference Scanning:**
- `PromoteCallerStack` / `PromoteCallerStackUsingGCRefMap` for
transition frames
- `GCRefMapDecoder` + `FindGCRefMap` with ReadyToRun import section
resolution
- `GcSignatureTypeProvider` for GC type classification
- `SOSDacImpl.GetStackReferences` fully implemented using cDAC contracts
- `GCInfoDecoder.EnumerateLiveSlots` promoted to `IGCInfo` contract
(returns `IReadOnlyList<LiveSlot>`)
- `GcSlotEnumerationOptions` replaces native `CodeManagerFlags` with
descriptive boolean properties
**Stack Walker Fixes:**
- `IsFirst` preserved for skipped frames (matches native
SFITER_SKIPPED_FRAME_FUNCTION)
- `IsInterrupted` state tracking for exception frames
(FaultingExceptionFrame, SoftwareExceptionFrame)
- `GetReturnAddress` gating in SW_FRAME (only UpdateRegDisplay if return
address non-null)
- Catch handler offset override via `GetInterruptibleRanges` for EH
resumption
**Contract API Additions:**
- `IGCInfo`: `EnumerateLiveSlots`, `GetStackBaseRegister`,
`GetInterruptibleRanges`
- `IExecutionManager`: `FindReadyToRunModule`
- `IRuntimeTypeSystem`: `RequiresInstArg`, `IsAsyncMethod`
- `IStackWalk`: `WalkStackReferences`
**Data Descriptor Changes:**
- Removed `ZapModule` and `GCRefMap` cached pointers (always resolve via
`FindReadyToRunModule`)
- Added `Indirection` for StubDispatchFrame, ExternalMethodFrame
- Added `DynamicHelperFrame.DynamicHelperFrameFlags`
- Added TransitionBlock fields (`OffsetOfArgs`,
`ArgumentRegistersOffset`, `FirstGCRefMapSlot`)
- Added ReadyToRunInfo fields (`ImportSections`, `NumImportSections`)
- Added ExceptionInfo catch clause fields
(`ClauseForCatchHandlerStartPC`, `ClauseForCatchHandlerEndPC`)
**Documentation:**
- GCInfo.md: Comprehensive implementation docs (header/body decoding,
slot table, EnumerateLiveSlots algorithm, type definitions for
`LiveSlot`, `InterruptibleRange`, `GcSlotEnumerationOptions`)
- StackWalk.md: GC scanning algorithm, GCRefMap resolution flow, return
address per frame type, `WalkStackReferences` API
- ExecutionManager.md: `FindReadyToRunModule` API and implementation
- RuntimeTypeSystem.md: `RequiresInstArg`, `IsAsyncMethod` APIs
### Stack overview
| PR | Content | Status |
|----|---------|--------|
| **This PR** | Stack walk fixes + GC scanning | Open |
| PR 2 | RuntimeSignatureDecoder (ELEMENT_TYPE_INTERNAL) | Pending |
| PR 3 | ArgIterator port from crossgen2 | Pending |
| PR 4 | Native stress framework (cdacstress.cpp) | Pending |
| PR 5 | Managed stress tests + CI pipeline | Pending |
### Testing
- 1727/1751 unit tests pass (24 pre-existing ThreadTests failures on
main)
- Dump tests (StackWalkDumpTests, StackReferenceDumpTests) validate
end-to-end
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

Closing in favor of stacked PR approach

steveisok pushed a commit that referenced this pull request May 11, 2026
…5) (#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[#126408](#126408). Builds on
[#127395](#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jakobbotsch pushed a commit to jakobbotsch/runtime that referenced this pull request May 12, 2026
…5) (dotnet#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[dotnet#126408](dotnet#126408). Builds on
[dotnet#127395](dotnet#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 1, 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.

2 participants

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

[cDAC] Stack walk GC stress verification and fixes - #126408

Closed
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5
Closed

[cDAC] Stack walk GC stress verification and fixes#126408
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Apr 1, 2026

Copy link
Copy Markdown
Member

Summary

cDAC GC stress verification tool (DOTNET_CdacStress) that compares stack GC references between the cDAC and the runtime at allocation stress points. Includes stack walker fixes, GC reference scanning implementation, custom signature decoding, and calling convention argument iteration.

Note

This PR description was updated with AI assistance from Copilot.

Stack walker fixes

  • Fix SW_SKIPPED_FRAME: do not call UpdateContextFromFrame (matches native SFITER_SKIPPED_FRAME_FUNCTION which does not call UpdateRegDisplay)
  • Fix skipped-frame loop: check for more skipped frames before yielding managed method (prevents duplicate EnumGcRefs between consecutive skipped frames)
  • Restructure Filter() to drive Next() directly, matching native Filter()+NextRaw() integration (prevents funclet-to-parent walk cycles)
  • Remove SkipActiveICFOnce/SkipCurrentFrameInCheck — active ICF double-yield is natural and harmless
  • Remove IsAtFirstPassExceptionThrowSite — native does not suppress first-pass refs
  • Fix IsFirst not preserved for skipped frames (was causing IsActiveFrame=false for the topmost managed frame)

GC reference scanning

  • Implement PromoteCallerStack for stub frames (GCRefMap + MetaSig paths)
  • Implement SOSDacImpl.GetStackReferences using cDAC contract (was falling back to legacy DAC)
  • Read FilterContext for stack walk starting context
  • Three-way cDAC/DAC/RT comparison with InProcessDataTarget
  • DOTNET_CdacStress bit flags: ALLOC/INSTR/REFS/WALK/USE_DAC/UNIQUE

RuntimeSignatureDecoder

Custom signature decoder that handles ELEMENT_TYPE_INTERNAL (0x21) and ELEMENT_TYPE_CMOD_INTERNAL (0x22) which SRM's SignatureDecoder cannot parse. These occur in IL stubs, marshalling stubs, and unsafe accessor frames.

  • IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of SRM's ISignatureTypeProvider, adds GetInternalType and GetInternalModifiedType
  • ISignatureReader + SpanSignatureReader: endianness-aware reader abstraction
  • Correct ECMA-335 compliance: TypeDefOrRefOrSpecEncoded token decoding, sign-extension-by-width for compressed signed ints, bounds validation

ArgIterator (ported from crossgen2)

Proper calling convention analysis replacing the simplified 1-slot-per-param approach.

  • CallingConventionInfo: hybrid data descriptor layout values + ABI invariant constants for all architectures (x86, x64 Windows/Unix, ARM32, ARM64, LoongArch64, RISC-V64)
  • ArgIterator.GetNextOffset(): maps each argument to its actual register or stack offset
  • OffsetOfFloatArgumentRegisters added to TransitionBlock data descriptor
  • Handles multi-slot args, forced-byref params, return buffer placement, async continuation

CI infrastructure

  • cDAC stress tests run in Helix via cdac-stress-helix.proj
  • Extended runtime-diagnostics.yml CdacDumpTests buildArgs with +tools.cdacstresstests

Test results

Allocation-level stress (9 debuggees, ~46K verifications):

DebuggeeVerificationsPassFail
BasicAlloc4,9364,9360
DeepStack4,9644,9640
Generics4,9364,9360
MultiThread4,9964,9960
Comprehensive4,9944,9922*
ExceptionHandling4,9584,9580
StructScenarios4,9404,9400
DynamicMethods6,5206,5200
PInvoke4,9364,9360

Instruction-level stress (9 debuggees, ~226K verifications):
All 9 debuggees pass with zero failures across 226,452 verifications.

*Comprehensive's 2 failures are pre-existing legacy DAC issues (DAC returns 0 refs for background threads in kernel waits; cDAC and runtime agree).

@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

Adds a cDAC GC-stress verification harness and extends the cDAC stack-walk / stack-GC-ref pipeline so cDAC stack reference enumeration can be compared against runtime scanning at stress points.

Changes:

  • Implements/extends cDAC stack reference enumeration (including Frame-based scanning paths like PromoteCallerStack via GCRefMap / MetaSig) and wires SOS GetStackReferences to the cDAC contract.
  • Introduces a new GC stress integration test project with debuggee apps and orchestration targets.
  • Extends CoreCLR cDAC stress/GC stress plumbing and data descriptors to support the new stack-walk and frame-scanning capabilities.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 12 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.csUpdates mock type layout for execution manager-related data.
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.csUpdates mock type layouts (ExceptionInfo/Thread) for contract tests.
src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csprojExcludes new GCStressTests folder from the existing unit test project compilation.
src/native/managed/cdac/tests/GCStressTests/README.mdDocuments how to build/run the new GC stress tests.
src/native/managed/cdac/tests/GCStressTests/Microsoft.Diagnostics.DataContractReader.GCStressTests.csprojAdds a dedicated GC stress test project.
src/native/managed/cdac/tests/GCStressTests/GCStressTests.targetsMSBuild orchestration to discover/build debuggee projects.
src/native/managed/cdac/tests/GCStressTests/GCStressTestBase.csTest harness to run debuggees under corerun and parse verification logs.
src/native/managed/cdac/tests/GCStressTests/GCStressResults.csParses the native verification log into structured pass/fail/skip counts.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/Program.csAdds a P/Invoke-focused debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/PInvoke.csprojDebuggee project file for PInvoke scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/Program.csAdds a multi-threaded debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/MultiThread.csprojDebuggee project file for MultiThread scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Program.csAdds a generics/interface/delegate debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Generics.csprojDebuggee project file for Generics scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/Program.csAdds an exception-handling/funclet debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/ExceptionHandling.csprojDebuggee project file for ExceptionHandling scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Directory.Build.propsShared build props for debuggee projects (output layout, TFM, etc.).
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/Program.csAdds deep-recursion debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/DeepStack.csprojDebuggee project file for DeepStack scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Program.csAdds comprehensive “all scenarios” debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Comprehensive.csprojDebuggee project file for Comprehensive scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/Program.csAdds basic allocation/live-ref debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/BasicAlloc.csprojDebuggee project file for BasicAlloc scenario.
src/native/managed/cdac/tests/GCStressTests/BasicGCStressTests.csTheory-based test suite that runs the debuggees and asserts pass rate.
src/native/managed/cdac/tests/gcstress/known-issues.mdCaptures known mismatch classes and limitations.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.csImplements GetStackReferences using the cDAC contract rather than legacy DAC fallback.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.csExtends StubDispatchFrame data with GCRefMap pointer.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.csAdds ExternalMethodFrame contract data type (GCRefMap).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.csAdds DynamicHelperFrame contract data type (flags).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.csAdds clause-range fields used for catch-handler resumption offset selection.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.csRefactors stack-walk filtering and adds Frame-based GC root scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAdds optional relOffset override support for GC ref enumeration.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.csImplements GCRefMap decoding for transition-block scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/CorSigParser.csAdds minimal signature parsing to classify parameters for MetaSig-based scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.csAdds FindFirstInterruptiblePoint API to GCInfo decoders.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.csImplements FindFirstInterruptiblePoint using decoded interruptible ranges.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.csFixes code-start lookup for exception clause enumeration and adds minor flow adjustments.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.csAdds TransitionBlock-related global names used by frame scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.csAdds new DataType enum values for new frame contracts.
src/native/managed/cdac/cdac.slnxAdds the new GC stress test project to the cDAC solution.
src/coreclr/vm/gccover.cppAdds step-based skipping to reduce overhead when throttling verification.
src/coreclr/vm/frames.hExposes additional frame fields to cDAC via cdac_data<> descriptors.
src/coreclr/vm/datadescriptor/datadescriptor.incAdds new contract fields/globals for frames and TransitionBlock layout.
src/coreclr/vm/cdacstress.cppUpdates in-process cDAC/DAC verification logic, logging, and step behavior.
eng/Subsets.propsAdds an on-demand subset for running GC stress tests.
docs/design/datacontracts/StackWalk.mdDocuments the new frame fields and TransitionBlock globals in the StackWalk contract.
Comments suppressed due to low confidence (1)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:228

  • ThreadFields contains ProfilerFilterContext twice (lines 224 and 228). Duplicate field entries will skew offsets and make the mock descriptors inconsistent with the real data descriptor. Keep a single ProfilerFilterContext entry in the correct order.

Comment threadsrc/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/README.md Outdated

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

Comments suppressed due to low confidence (2)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:229

  • ThreadFields now includes DebuggerFilterContext/ProfilerFilterContext twice (duplicate entries at the end of the list). This can cause ambiguous/incorrect field offsets in the mock type layout. Keep each field only once.
    src/coreclr/vm/cdacstress.cpp:963
  • CompareRefSets uses a fixed-size matched[MAX_COLLECTED_REFS] buffer but no longer validates that countA/countB are <= MAX_COLLECTED_REFS. Since CollectStackRefs appends without a hard cap, this can lead to out-of-bounds access when countA or countB exceeds 4096. Reintroduce a guard or allocate the match state sized to the counts.
 return true;
bool matched[MAX_COLLECTED_REFS] = {};
for (int i = 0; i < countA; i++)

Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/gccover.cpp Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/GCStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 75ae7fe to 5419d15CompareApril 13, 2026 15:30
CopilotAI review requested due to automatic review settings April 13, 2026 15:47
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 5419d15 to 8ef9b22CompareApril 13, 2026 15:47

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

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated

@max-charlambmax-charlamb left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

feedback for local copilot

Comment threadsrc/native/managed/cdac/tests/StressTests/analysis/analyze-refs.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/StressTests.targets Outdated
Comment threadsrc/coreclr/vm/datadescriptor/datadescriptor.inc Outdated
CopilotAI review requested due to automatic review settings April 13, 2026 17:33

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

Comments suppressed due to low confidence (1)

src/coreclr/vm/cdacstress.cpp:625

  • CollectStackRefs appends to pRefs without any cap. Later comparisons allocate fixed-size arrays sized MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed/bUsed) and assume counts fit. If the DAC returns > MAX_COLLECTED_REFS refs, this will lead to out-of-bounds writes/reads. Add an explicit limit/overflow handling in CollectStackRefs (stop at MAX_COLLECTED_REFS and mark overflow / SKIP), or change the later comparison logic to handle arbitrary counts safely.
 SOSStackRefData refData;
unsigned int fetched = 0;
while (true)
{
hr = pEnum->Next(1, &refData, &fetched);
if (FAILED(hr) || fetched == 0)
break;
StackRef ref;
ref.Address = refData.Address;
ref.Object = refData.Object;
ref.Flags = refData.Flags;
ref.Source = refData.Source;
ref.SourceType = refData.SourceType;
ref.Register = refData.Register;
ref.Offset = refData.Offset;
ref.StackPointer = refData.StackPointer;
pRefs->Append(ref);
}

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/known-issues.md
Comment threaddocs/design/datacontracts/StackWalk.md
CopilotAI review requested due to automatic review settings April 13, 2026 19: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 52 out of 52 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/coreclr/vm/cdacstress.cpp
CopilotAI review requested due to automatic review settings April 13, 2026 20:24
Max Charlamband others added 5 commits April 22, 2026 15:23
- Fix platform-specific ctx.Rip usage: use GetIP(&ctx) instead
- Fix if( style: add space after if keyword in gccover.cpp
- Add RVA bounds validation in FindGCRefMap before uint cast
- Remove stale analysis file (eh-throwhelper-report.md)
- Update AssertHighPassRate comment to reflect current state
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Run GC stress verification tests in the runtime-diagnostics pipeline by
piggybacking on the existing CdacDumpTests Checked runtime build. The
stress tests run as a second Helix submission after the dump tests,
using the testhost shared framework as CORE_ROOT.
Runs on all cdacDumpPlatforms (windows_x64, linux_x64, etc.). On
non-Windows platforms, only instruction-level stress (via DoGcStress)
is supported; allocation-level stress (VerifyAtAllocPoint) is skipped
because it requires Windows-only RtlCaptureContext/RtlVirtualUnwind.
Infrastructure:
- cdac-stress-helix.proj: Helix SDK project that sends testhost as
correlation payload and stress test debuggees + test assembly as
work item payload. Sets CORE_ROOT env var for the test harness.
- prepare-cdac-stress-helix-steps.yml: Pipeline template that builds
debuggees, prepares Helix payload, and finds testhost directory.
- StressTests.targets: Added PrepareHelixPayload and BuildDebuggeesOnly
targets for CI payload preparation.
- CdacStressTestBase.cs: Added HELIX_WORKITEM_PAYLOAD support for
finding debuggees in Helix environment.
- runtime-diagnostics.yml: Extended CdacDumpTests buildArgs to include
tools.cdacstresstests, added stress test Helix submission steps.
- cdacstress.cpp: Guard VerifyAtAllocPoint with TARGET_WINDOWS for
RtlCaptureContext/RtlVirtualUnwind APIs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a custom signature decoder that handles runtime-internal type codes
(ELEMENT_TYPE_INTERNAL 0x21, ELEMENT_TYPE_CMOD_INTERNAL 0x22) which
SRM's SignatureDecoder cannot parse.
- IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of
SRM's ISignatureTypeProvider, adding GetInternalType for resolving
embedded TypeHandle pointers via the runtime type system.
- ISignatureReader + SpanSignatureReader: abstraction for reading
signature bytes from different sources (spans, target memory).
- RuntimeSignatureDecoder<TType, TGenericContext, TReader>: ref struct
decoder that handles all standard ECMA-335 types plus internal types.
- GcSignatureTypeProvider: implements IRuntimeSignatureTypeProvider to
classify types for GC scanning, resolving internal types via
RuntimeTypeSystem.GetSignatureCorElementType.
Key correctness details vs SRM's SignatureDecoder:
- CLASS/VALUETYPE tokens decoded as TypeDefOrRefOrSpecEncoded per
ECMA-335 II.23.2.8 (tag in low 2 bits, RID in upper bits).
- CMOD_INTERNAL correctly skips the required/optional flag byte before
the TypeHandle pointer, matching sigparser.h layout.
- ReadCompressedSignedInt uses ECMA sign-extension-by-width, not zigzag.
- ReadCompressedUInt rejects invalid 111xxxxx prefix.
- Unknown type codes throw BadImageFormatException instead of silently
returning Object (which would create false-positive GC refs).
- Method signature header kind is validated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port crossgen2's ArgIterator, TransitionBlock, and GC scanning logic
into the cDAC contracts for correct per-architecture argument placement.
- Add OffsetOfFloatArgumentRegisters to TransitionBlock data descriptor
- CallingConventionInfo: hybrid data descriptor + ABI invariant constants
for x86, x64 (Windows/Unix), ARM32, ARM64, LoongArch64, RISC-V64
- ArgTypeInfo: pre-computed type info replacing crossgen2's TypeHandle
- ArgIteratorData: parsed method signature holder
- ArgIterator: maps each argument to register/stack offsets via
GetNextOffset() with per-architecture register allocation
- Integrate into FrameIterator.PromoteCallerStackHelper replacing the
simplified 1-slot-per-param approach with proper offset computation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Quote debuggee DLL path in ProcessStartInfo.Arguments (dotnet#31)
- Fix timeout: use async stdout/stderr reads so WaitForExit works (dotnet#32)
- Update stale comment about ELEMENT_TYPE_INTERNAL limitation (dotnet#35)
- Move CallingConvention types to StackWalkHelpers.CallingConvention namespace (dotnet#37)
- Simplify x86 register eligibility check in ArgIterator (dotnet#38)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 22, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 1a92e2c to b85dbacCompareApril 22, 2026 19:25

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

Comment on lines +16 to +20
// FirstThreadLink is an embedded SLink struct. Read the SLink.Next pointer
// from the field's address to get the first thread link pointer.
Target.TypeInfo slinkType = target.GetTypeInfo(DataType.SLink);
TargetPointer slinkAddr = address + (ulong)type.Fields[nameof(FirstThreadLink)].Offset;
FirstThreadLink = target.ReadPointerField(slinkAddr, slinkType, "Next");

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

ThreadStore now calls target.GetTypeInfo(DataType.SLink) and reads field "Next", but the CoreCLR data descriptor in this PR doesn't define a SLink type/field (could not find any CDAC_TYPE_BEGIN/FIELD for SLink). This will cause GetTypeInfo(DataType.SLink) to fail at runtime. Either add an SLink descriptor (with a "Next" pointer) on the runtime side or avoid needing type info here (e.g., treat the embedded SLink as a single pointer at offset 0).

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 29
SLink,
ThreadLocalData,

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

DataType adds SLink, but there is no corresponding runtime data descriptor definition (no CDAC_TYPE_BEGIN/FIELD(SLink, ...) found). Any attempt to read this type via Target.GetTypeInfo(DataType.SLink) will fail. Either add the runtime descriptor entry for SLink or remove this enum value and read the embedded link without type metadata.

Suggested change
SLink,
ThreadLocalData,
ThreadLocalData=17,

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +69
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

These DataReceived event handlers append to strings via "+=" from background threads. That is not thread-safe and can lead to lost/garbled output under contention. Consider using a StringBuilder with locking (or ConcurrentQueue) for stderr/stdout aggregation.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +74
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

Same thread-safety issue as stderr: appending to stdout with += from OutputDataReceived is racy. Use a synchronized StringBuilder/collector to avoid missing output and to keep logs reliable when tests fail.

Copilot uses AI. Check for mistakes.
Comment on lines +732 to +751
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
private static ArgTypeInfo GcTypeKindToArgTypeInfo(GcTypeKind kind, int pointerSize)
{
return kind switch
{
GcTypeKind.None => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
GcTypeKind.Ref => ArgTypeInfo.ForPrimitive(CorElementType.Class, pointerSize),
GcTypeKind.Interior => ArgTypeInfo.ForPrimitive(CorElementType.Byref, pointerSize),
GcTypeKind.Other => new ArgTypeInfo
{
CorElementType = CorElementType.ValueType,
Size = pointerSize, // Conservative: assume pointer-sized for now
IsValueType = true,
},
_ => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
};
}

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

GcTypeKindToArgTypeInfo collapses all non-GC-ref types to CorElementType.I/pointer-sized and doesn't preserve float vs integer vs exact primitive sizes. ArgIterator offset calculation depends on the real signature shape (especially on Unix x64/ARM64 where float regs and argument sizing affect later argument placement), so this can produce incorrect offsets and cause missed/incorrect GC ref reporting for signatures with floats or non-pointer-sized primitives. Consider decoding into a richer type representation (e.g., CorElementType + size) and building ArgTypeInfo from that rather than from GcTypeKind.

Suggested change
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.None=> ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,// Conservative: assume pointer-sized for now
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
/// Converts a <see cref="GcTypeKind"/> to a conservative fallback <see cref="ArgTypeInfo"/>
/// when an exact signature cannot be decoded. Callers should prefer decoding the real
/// managed signature via <see cref="TryGetArgTypeInfosFromMethodSignature(ReadOnlySpan{byte}, int, out ArgTypeInfo[])"/>
/// so that floating-point and non-pointer-sized primitives retain their real layout.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
privatestaticboolTryGetArgTypeInfosFromMethodSignature(ReadOnlySpan<byte>signatureBytes,intpointerSize,outArgTypeInfo[]argTypes)
{
argTypes=Array.Empty<ArgTypeInfo>();
if(signatureBytes.IsEmpty)
returnfalse;
BlobReaderreader=new(signatureBytes);
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
ArgTypeInfo[]decodedArgTypes=newArgTypeInfo[parameterCount];
for(inti=0;i<parameterCount;i++)
{
if(!TryReadArgTypeInfo(refreader,pointerSize,outdecodedArgTypes[i]))
returnfalse;
}
argTypes=decodedArgTypes;
returntrue;
}
privatestaticboolTryReadArgTypeInfo(refBlobReaderreader,intpointerSize,outArgTypeInfoargTypeInfo)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Boolean:
caseCorElementType.I1:
caseCorElementType.U1:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,1);
returntrue;
caseCorElementType.Char:
caseCorElementType.I2:
caseCorElementType.U2:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,2);
returntrue;
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.R4:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,4);
returntrue;
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R8:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,8);
returntrue;
caseCorElementType.I:
caseCorElementType.U:
caseCorElementType.Ptr:
caseCorElementType.FnPtr:
if(elementTypeisCorElementType.Ptr or CorElementType.FnPtr)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize);
returntrue;
caseCorElementType.Byref:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize);
returntrue;
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.Class:
if(elementTypeisCorElementType.Class)
reader.ReadCompressedInteger();
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.SzArray:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.ValueType:
reader.ReadCompressedInteger();
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
}
default:
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=default;
returnfalse;
}
privatestaticboolTrySkipSignatureType(refBlobReaderreader)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Void:
caseCorElementType.Boolean:
caseCorElementType.Char:
caseCorElementType.I1:
caseCorElementType.U1:
caseCorElementType.I2:
caseCorElementType.U2:
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R4:
caseCorElementType.R8:
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.TypedByref:
caseCorElementType.I:
caseCorElementType.U:
returntrue;
caseCorElementType.Class:
caseCorElementType.ValueType:
caseCorElementType.Var:
caseCorElementType.MVar:
reader.ReadCompressedInteger();
returntrue;
caseCorElementType.Byref:
caseCorElementType.Ptr:
caseCorElementType.SzArray:
returnTrySkipSignatureType(refreader);
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
caseCorElementType.Array:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intrank=reader.ReadCompressedInteger();
intsizes=reader.ReadCompressedInteger();
for(inti=0;i<sizes;i++)
reader.ReadCompressedInteger();
intlowerBounds=reader.ReadCompressedInteger();
for(inti=0;i<lowerBounds;i++)
reader.ReadCompressedInteger();
returnrank>=0;
}
caseCorElementType.FnPtr:
{
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
for(inti=0;i<parameterCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
default:
returnfalse;
}
}
returnfalse;
}

Copilot uses AI. Check for mistakes.

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

Comment on lines +582 to 586
static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray<StackRef>* pRefs,
const char* label = nullptr)
{
if (pSosDac == nullptr)
return false;

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

CollectStackRefs currently appends every ref returned by ISOSStackRefEnum::Next with no upper bound. Later comparison helpers allocate fixed-size arrays sized by MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed[MAX_COLLECTED_REFS]), so if enumeration returns more than MAX_COLLECTED_REFS it can lead to out-of-bounds writes. Please cap collection to MAX_COLLECTED_REFS (and record an overflow/skip reason) or make the comparison logic handle arbitrarily large ref sets safely.

Copilot uses AI. Check for mistakes.
Comment on lines +1359 to +1362
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

When CollectRuntimeStackRefs overflows, the code logs a [SKIP] line but continues and still computes rtMatch/pass (and does not increment s_verifySkip). If CDACSTRESS_USE_DAC is not set, this can produce false failures based on a truncated runtime ref set. Consider treating runtime overflow as an actual skip (increment skip counter + return) when RT comparison is used for pass/fail, or otherwise avoid using rtMatch in the presence of overflow.

Suggested change
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
if (rtOverflow)
{
InterlockedIncrement(&s_verifySkip);
if (s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
}
return;

Copilot uses AI. Check for mistakes.
Comment on lines +823 to +824
ReportSlot(slotIndex, reportScratchSlots: true, reportFpBasedSlotsOnly, reportSlot);
}

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

In ReportUntrackedAndSucceed, ReportSlot is called with reportScratchSlots: true, which forces reporting scratch registers/stack slots even when CodeManagerFlags.ActiveStackFrame is not set (reportScratchSlots local is false). This changes GC root enumeration behavior and can introduce extra roots for non-leaf frames. It looks like this should pass the reportScratchSlots variable instead of always true.

Suggested change
ReportSlot(slotIndex,reportScratchSlots:true,reportFpBasedSlotsOnly,reportSlot);
}
ReportSlot(slotIndex,reportScratchSlots,reportFpBasedSlotsOnly,reportSlot);
}

Copilot uses AI. Check for mistakes.
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 49c8435 to de5cb46CompareApril 24, 2026 15:56
- Include RuntimeInfoOperatingSystem.Apple in Unix x64 ABI check
(macOS x64 uses SysV ABI, not Windows ABI)
- Thread MetadataReader from GetMethodSignatureBytes through
RuntimeSignatureDecoder to provider methods (instead of null!)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 24, 2026 16:07

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

Comment on lines +960 to 964
// Compare two ref sets using two-phase matching (for RT comparison where we
// don't have Source info). Returns true if all refs match.
static bool CompareRefSetsFlat(StackRef* refsA, int countA, StackRef* refsB, int countB)
{
if (countA != countB)

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

CompareRefSetsFlat uses a fixed-size matched[MAX_COLLECTED_REFS] buffer, but cDAC/DAC ref collection (CollectStackRefs) is unbounded. Without a guard/cap, a large ref set (>4096) can cause out-of-bounds writes during matching. Consider capping cDAC/DAC collection to MAX_COLLECTED_REFS (and treating overflow as a [SKIP]) or using dynamically sized bookkeeping here.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +76
// Read both stdout and stderr asynchronously to avoid deadlock
// when pipe buffers fill, and to allow WaitForExit timeout to work.
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};
process.BeginErrorReadLine();
process.BeginOutputReadLine();

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

The async stdout/stderr collection uses stdout += ... / stderr += ... inside DataReceived event handlers. These callbacks can run concurrently, and string concatenation is not thread-safe; it can also be costly for large output. Consider buffering with a thread-safe collector (e.g., ConcurrentQueue<string> or StringBuilder with a lock) and call process.WaitForExit() (or await stream completion) after WaitForExit(timeout) to ensure all async output has been drained before asserting/logging.

Copilot uses AI. Check for mistakes.
max-charlamb added a commit that referenced this pull request May 1, 2026
## Summary
Part 1 of 5 stacked PRs splitting
[#126408](#126408) into reviewable
pieces.
### What this PR contains
**Stack Walk GC Reference Scanning:**
- `PromoteCallerStack` / `PromoteCallerStackUsingGCRefMap` for
transition frames
- `GCRefMapDecoder` + `FindGCRefMap` with ReadyToRun import section
resolution
- `GcSignatureTypeProvider` for GC type classification
- `SOSDacImpl.GetStackReferences` fully implemented using cDAC contracts
- `GCInfoDecoder.EnumerateLiveSlots` promoted to `IGCInfo` contract
(returns `IReadOnlyList<LiveSlot>`)
- `GcSlotEnumerationOptions` replaces native `CodeManagerFlags` with
descriptive boolean properties
**Stack Walker Fixes:**
- `IsFirst` preserved for skipped frames (matches native
SFITER_SKIPPED_FRAME_FUNCTION)
- `IsInterrupted` state tracking for exception frames
(FaultingExceptionFrame, SoftwareExceptionFrame)
- `GetReturnAddress` gating in SW_FRAME (only UpdateRegDisplay if return
address non-null)
- Catch handler offset override via `GetInterruptibleRanges` for EH
resumption
**Contract API Additions:**
- `IGCInfo`: `EnumerateLiveSlots`, `GetStackBaseRegister`,
`GetInterruptibleRanges`
- `IExecutionManager`: `FindReadyToRunModule`
- `IRuntimeTypeSystem`: `RequiresInstArg`, `IsAsyncMethod`
- `IStackWalk`: `WalkStackReferences`
**Data Descriptor Changes:**
- Removed `ZapModule` and `GCRefMap` cached pointers (always resolve via
`FindReadyToRunModule`)
- Added `Indirection` for StubDispatchFrame, ExternalMethodFrame
- Added `DynamicHelperFrame.DynamicHelperFrameFlags`
- Added TransitionBlock fields (`OffsetOfArgs`,
`ArgumentRegistersOffset`, `FirstGCRefMapSlot`)
- Added ReadyToRunInfo fields (`ImportSections`, `NumImportSections`)
- Added ExceptionInfo catch clause fields
(`ClauseForCatchHandlerStartPC`, `ClauseForCatchHandlerEndPC`)
**Documentation:**
- GCInfo.md: Comprehensive implementation docs (header/body decoding,
slot table, EnumerateLiveSlots algorithm, type definitions for
`LiveSlot`, `InterruptibleRange`, `GcSlotEnumerationOptions`)
- StackWalk.md: GC scanning algorithm, GCRefMap resolution flow, return
address per frame type, `WalkStackReferences` API
- ExecutionManager.md: `FindReadyToRunModule` API and implementation
- RuntimeTypeSystem.md: `RequiresInstArg`, `IsAsyncMethod` APIs
### Stack overview
| PR | Content | Status |
|----|---------|--------|
| **This PR** | Stack walk fixes + GC scanning | Open |
| PR 2 | RuntimeSignatureDecoder (ELEMENT_TYPE_INTERNAL) | Pending |
| PR 3 | ArgIterator port from crossgen2 | Pending |
| PR 4 | Native stress framework (cdacstress.cpp) | Pending |
| PR 5 | Managed stress tests + CI pipeline | Pending |
### Testing
- 1727/1751 unit tests pass (24 pre-existing ThreadTests failures on
main)
- Dump tests (StackWalkDumpTests, StackReferenceDumpTests) validate
end-to-end
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

Closing in favor of stacked PR approach

steveisok pushed a commit that referenced this pull request May 11, 2026
…5) (#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[#126408](#126408). Builds on
[#127395](#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jakobbotsch pushed a commit to jakobbotsch/runtime that referenced this pull request May 12, 2026
…5) (dotnet#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[dotnet#126408](dotnet#126408). Builds on
[dotnet#127395](dotnet#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 1, 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.

2 participants

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

[cDAC] Stack walk GC stress verification and fixes - #126408

Closed
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5
Closed

[cDAC] Stack walk GC stress verification and fixes#126408
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Apr 1, 2026

Copy link
Copy Markdown
Member

Summary

cDAC GC stress verification tool (DOTNET_CdacStress) that compares stack GC references between the cDAC and the runtime at allocation stress points. Includes stack walker fixes, GC reference scanning implementation, custom signature decoding, and calling convention argument iteration.

Note

This PR description was updated with AI assistance from Copilot.

Stack walker fixes

  • Fix SW_SKIPPED_FRAME: do not call UpdateContextFromFrame (matches native SFITER_SKIPPED_FRAME_FUNCTION which does not call UpdateRegDisplay)
  • Fix skipped-frame loop: check for more skipped frames before yielding managed method (prevents duplicate EnumGcRefs between consecutive skipped frames)
  • Restructure Filter() to drive Next() directly, matching native Filter()+NextRaw() integration (prevents funclet-to-parent walk cycles)
  • Remove SkipActiveICFOnce/SkipCurrentFrameInCheck — active ICF double-yield is natural and harmless
  • Remove IsAtFirstPassExceptionThrowSite — native does not suppress first-pass refs
  • Fix IsFirst not preserved for skipped frames (was causing IsActiveFrame=false for the topmost managed frame)

GC reference scanning

  • Implement PromoteCallerStack for stub frames (GCRefMap + MetaSig paths)
  • Implement SOSDacImpl.GetStackReferences using cDAC contract (was falling back to legacy DAC)
  • Read FilterContext for stack walk starting context
  • Three-way cDAC/DAC/RT comparison with InProcessDataTarget
  • DOTNET_CdacStress bit flags: ALLOC/INSTR/REFS/WALK/USE_DAC/UNIQUE

RuntimeSignatureDecoder

Custom signature decoder that handles ELEMENT_TYPE_INTERNAL (0x21) and ELEMENT_TYPE_CMOD_INTERNAL (0x22) which SRM's SignatureDecoder cannot parse. These occur in IL stubs, marshalling stubs, and unsafe accessor frames.

  • IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of SRM's ISignatureTypeProvider, adds GetInternalType and GetInternalModifiedType
  • ISignatureReader + SpanSignatureReader: endianness-aware reader abstraction
  • Correct ECMA-335 compliance: TypeDefOrRefOrSpecEncoded token decoding, sign-extension-by-width for compressed signed ints, bounds validation

ArgIterator (ported from crossgen2)

Proper calling convention analysis replacing the simplified 1-slot-per-param approach.

  • CallingConventionInfo: hybrid data descriptor layout values + ABI invariant constants for all architectures (x86, x64 Windows/Unix, ARM32, ARM64, LoongArch64, RISC-V64)
  • ArgIterator.GetNextOffset(): maps each argument to its actual register or stack offset
  • OffsetOfFloatArgumentRegisters added to TransitionBlock data descriptor
  • Handles multi-slot args, forced-byref params, return buffer placement, async continuation

CI infrastructure

  • cDAC stress tests run in Helix via cdac-stress-helix.proj
  • Extended runtime-diagnostics.yml CdacDumpTests buildArgs with +tools.cdacstresstests

Test results

Allocation-level stress (9 debuggees, ~46K verifications):

DebuggeeVerificationsPassFail
BasicAlloc4,9364,9360
DeepStack4,9644,9640
Generics4,9364,9360
MultiThread4,9964,9960
Comprehensive4,9944,9922*
ExceptionHandling4,9584,9580
StructScenarios4,9404,9400
DynamicMethods6,5206,5200
PInvoke4,9364,9360

Instruction-level stress (9 debuggees, ~226K verifications):
All 9 debuggees pass with zero failures across 226,452 verifications.

*Comprehensive's 2 failures are pre-existing legacy DAC issues (DAC returns 0 refs for background threads in kernel waits; cDAC and runtime agree).

@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

Adds a cDAC GC-stress verification harness and extends the cDAC stack-walk / stack-GC-ref pipeline so cDAC stack reference enumeration can be compared against runtime scanning at stress points.

Changes:

  • Implements/extends cDAC stack reference enumeration (including Frame-based scanning paths like PromoteCallerStack via GCRefMap / MetaSig) and wires SOS GetStackReferences to the cDAC contract.
  • Introduces a new GC stress integration test project with debuggee apps and orchestration targets.
  • Extends CoreCLR cDAC stress/GC stress plumbing and data descriptors to support the new stack-walk and frame-scanning capabilities.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 12 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.csUpdates mock type layout for execution manager-related data.
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.csUpdates mock type layouts (ExceptionInfo/Thread) for contract tests.
src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csprojExcludes new GCStressTests folder from the existing unit test project compilation.
src/native/managed/cdac/tests/GCStressTests/README.mdDocuments how to build/run the new GC stress tests.
src/native/managed/cdac/tests/GCStressTests/Microsoft.Diagnostics.DataContractReader.GCStressTests.csprojAdds a dedicated GC stress test project.
src/native/managed/cdac/tests/GCStressTests/GCStressTests.targetsMSBuild orchestration to discover/build debuggee projects.
src/native/managed/cdac/tests/GCStressTests/GCStressTestBase.csTest harness to run debuggees under corerun and parse verification logs.
src/native/managed/cdac/tests/GCStressTests/GCStressResults.csParses the native verification log into structured pass/fail/skip counts.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/Program.csAdds a P/Invoke-focused debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/PInvoke.csprojDebuggee project file for PInvoke scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/Program.csAdds a multi-threaded debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/MultiThread.csprojDebuggee project file for MultiThread scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Program.csAdds a generics/interface/delegate debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Generics.csprojDebuggee project file for Generics scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/Program.csAdds an exception-handling/funclet debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/ExceptionHandling.csprojDebuggee project file for ExceptionHandling scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Directory.Build.propsShared build props for debuggee projects (output layout, TFM, etc.).
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/Program.csAdds deep-recursion debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/DeepStack.csprojDebuggee project file for DeepStack scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Program.csAdds comprehensive “all scenarios” debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Comprehensive.csprojDebuggee project file for Comprehensive scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/Program.csAdds basic allocation/live-ref debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/BasicAlloc.csprojDebuggee project file for BasicAlloc scenario.
src/native/managed/cdac/tests/GCStressTests/BasicGCStressTests.csTheory-based test suite that runs the debuggees and asserts pass rate.
src/native/managed/cdac/tests/gcstress/known-issues.mdCaptures known mismatch classes and limitations.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.csImplements GetStackReferences using the cDAC contract rather than legacy DAC fallback.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.csExtends StubDispatchFrame data with GCRefMap pointer.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.csAdds ExternalMethodFrame contract data type (GCRefMap).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.csAdds DynamicHelperFrame contract data type (flags).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.csAdds clause-range fields used for catch-handler resumption offset selection.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.csRefactors stack-walk filtering and adds Frame-based GC root scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAdds optional relOffset override support for GC ref enumeration.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.csImplements GCRefMap decoding for transition-block scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/CorSigParser.csAdds minimal signature parsing to classify parameters for MetaSig-based scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.csAdds FindFirstInterruptiblePoint API to GCInfo decoders.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.csImplements FindFirstInterruptiblePoint using decoded interruptible ranges.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.csFixes code-start lookup for exception clause enumeration and adds minor flow adjustments.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.csAdds TransitionBlock-related global names used by frame scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.csAdds new DataType enum values for new frame contracts.
src/native/managed/cdac/cdac.slnxAdds the new GC stress test project to the cDAC solution.
src/coreclr/vm/gccover.cppAdds step-based skipping to reduce overhead when throttling verification.
src/coreclr/vm/frames.hExposes additional frame fields to cDAC via cdac_data<> descriptors.
src/coreclr/vm/datadescriptor/datadescriptor.incAdds new contract fields/globals for frames and TransitionBlock layout.
src/coreclr/vm/cdacstress.cppUpdates in-process cDAC/DAC verification logic, logging, and step behavior.
eng/Subsets.propsAdds an on-demand subset for running GC stress tests.
docs/design/datacontracts/StackWalk.mdDocuments the new frame fields and TransitionBlock globals in the StackWalk contract.
Comments suppressed due to low confidence (1)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:228

  • ThreadFields contains ProfilerFilterContext twice (lines 224 and 228). Duplicate field entries will skew offsets and make the mock descriptors inconsistent with the real data descriptor. Keep a single ProfilerFilterContext entry in the correct order.

Comment threadsrc/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/README.md Outdated

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

Comments suppressed due to low confidence (2)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:229

  • ThreadFields now includes DebuggerFilterContext/ProfilerFilterContext twice (duplicate entries at the end of the list). This can cause ambiguous/incorrect field offsets in the mock type layout. Keep each field only once.
    src/coreclr/vm/cdacstress.cpp:963
  • CompareRefSets uses a fixed-size matched[MAX_COLLECTED_REFS] buffer but no longer validates that countA/countB are <= MAX_COLLECTED_REFS. Since CollectStackRefs appends without a hard cap, this can lead to out-of-bounds access when countA or countB exceeds 4096. Reintroduce a guard or allocate the match state sized to the counts.
 return true;
bool matched[MAX_COLLECTED_REFS] = {};
for (int i = 0; i < countA; i++)

Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/gccover.cpp Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/GCStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 75ae7fe to 5419d15CompareApril 13, 2026 15:30
CopilotAI review requested due to automatic review settings April 13, 2026 15:47
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 5419d15 to 8ef9b22CompareApril 13, 2026 15:47

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

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated

@max-charlambmax-charlamb left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

feedback for local copilot

Comment threadsrc/native/managed/cdac/tests/StressTests/analysis/analyze-refs.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/StressTests.targets Outdated
Comment threadsrc/coreclr/vm/datadescriptor/datadescriptor.inc Outdated
CopilotAI review requested due to automatic review settings April 13, 2026 17:33

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

Comments suppressed due to low confidence (1)

src/coreclr/vm/cdacstress.cpp:625

  • CollectStackRefs appends to pRefs without any cap. Later comparisons allocate fixed-size arrays sized MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed/bUsed) and assume counts fit. If the DAC returns > MAX_COLLECTED_REFS refs, this will lead to out-of-bounds writes/reads. Add an explicit limit/overflow handling in CollectStackRefs (stop at MAX_COLLECTED_REFS and mark overflow / SKIP), or change the later comparison logic to handle arbitrary counts safely.
 SOSStackRefData refData;
unsigned int fetched = 0;
while (true)
{
hr = pEnum->Next(1, &refData, &fetched);
if (FAILED(hr) || fetched == 0)
break;
StackRef ref;
ref.Address = refData.Address;
ref.Object = refData.Object;
ref.Flags = refData.Flags;
ref.Source = refData.Source;
ref.SourceType = refData.SourceType;
ref.Register = refData.Register;
ref.Offset = refData.Offset;
ref.StackPointer = refData.StackPointer;
pRefs->Append(ref);
}

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/known-issues.md
Comment threaddocs/design/datacontracts/StackWalk.md
CopilotAI review requested due to automatic review settings April 13, 2026 19: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 52 out of 52 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/coreclr/vm/cdacstress.cpp
CopilotAI review requested due to automatic review settings April 13, 2026 20:24
Max Charlamband others added 5 commits April 22, 2026 15:23
- Fix platform-specific ctx.Rip usage: use GetIP(&ctx) instead
- Fix if( style: add space after if keyword in gccover.cpp
- Add RVA bounds validation in FindGCRefMap before uint cast
- Remove stale analysis file (eh-throwhelper-report.md)
- Update AssertHighPassRate comment to reflect current state
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Run GC stress verification tests in the runtime-diagnostics pipeline by
piggybacking on the existing CdacDumpTests Checked runtime build. The
stress tests run as a second Helix submission after the dump tests,
using the testhost shared framework as CORE_ROOT.
Runs on all cdacDumpPlatforms (windows_x64, linux_x64, etc.). On
non-Windows platforms, only instruction-level stress (via DoGcStress)
is supported; allocation-level stress (VerifyAtAllocPoint) is skipped
because it requires Windows-only RtlCaptureContext/RtlVirtualUnwind.
Infrastructure:
- cdac-stress-helix.proj: Helix SDK project that sends testhost as
correlation payload and stress test debuggees + test assembly as
work item payload. Sets CORE_ROOT env var for the test harness.
- prepare-cdac-stress-helix-steps.yml: Pipeline template that builds
debuggees, prepares Helix payload, and finds testhost directory.
- StressTests.targets: Added PrepareHelixPayload and BuildDebuggeesOnly
targets for CI payload preparation.
- CdacStressTestBase.cs: Added HELIX_WORKITEM_PAYLOAD support for
finding debuggees in Helix environment.
- runtime-diagnostics.yml: Extended CdacDumpTests buildArgs to include
tools.cdacstresstests, added stress test Helix submission steps.
- cdacstress.cpp: Guard VerifyAtAllocPoint with TARGET_WINDOWS for
RtlCaptureContext/RtlVirtualUnwind APIs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a custom signature decoder that handles runtime-internal type codes
(ELEMENT_TYPE_INTERNAL 0x21, ELEMENT_TYPE_CMOD_INTERNAL 0x22) which
SRM's SignatureDecoder cannot parse.
- IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of
SRM's ISignatureTypeProvider, adding GetInternalType for resolving
embedded TypeHandle pointers via the runtime type system.
- ISignatureReader + SpanSignatureReader: abstraction for reading
signature bytes from different sources (spans, target memory).
- RuntimeSignatureDecoder<TType, TGenericContext, TReader>: ref struct
decoder that handles all standard ECMA-335 types plus internal types.
- GcSignatureTypeProvider: implements IRuntimeSignatureTypeProvider to
classify types for GC scanning, resolving internal types via
RuntimeTypeSystem.GetSignatureCorElementType.
Key correctness details vs SRM's SignatureDecoder:
- CLASS/VALUETYPE tokens decoded as TypeDefOrRefOrSpecEncoded per
ECMA-335 II.23.2.8 (tag in low 2 bits, RID in upper bits).
- CMOD_INTERNAL correctly skips the required/optional flag byte before
the TypeHandle pointer, matching sigparser.h layout.
- ReadCompressedSignedInt uses ECMA sign-extension-by-width, not zigzag.
- ReadCompressedUInt rejects invalid 111xxxxx prefix.
- Unknown type codes throw BadImageFormatException instead of silently
returning Object (which would create false-positive GC refs).
- Method signature header kind is validated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port crossgen2's ArgIterator, TransitionBlock, and GC scanning logic
into the cDAC contracts for correct per-architecture argument placement.
- Add OffsetOfFloatArgumentRegisters to TransitionBlock data descriptor
- CallingConventionInfo: hybrid data descriptor + ABI invariant constants
for x86, x64 (Windows/Unix), ARM32, ARM64, LoongArch64, RISC-V64
- ArgTypeInfo: pre-computed type info replacing crossgen2's TypeHandle
- ArgIteratorData: parsed method signature holder
- ArgIterator: maps each argument to register/stack offsets via
GetNextOffset() with per-architecture register allocation
- Integrate into FrameIterator.PromoteCallerStackHelper replacing the
simplified 1-slot-per-param approach with proper offset computation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Quote debuggee DLL path in ProcessStartInfo.Arguments (dotnet#31)
- Fix timeout: use async stdout/stderr reads so WaitForExit works (dotnet#32)
- Update stale comment about ELEMENT_TYPE_INTERNAL limitation (dotnet#35)
- Move CallingConvention types to StackWalkHelpers.CallingConvention namespace (dotnet#37)
- Simplify x86 register eligibility check in ArgIterator (dotnet#38)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 22, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 1a92e2c to b85dbacCompareApril 22, 2026 19:25

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

Comment on lines +16 to +20
// FirstThreadLink is an embedded SLink struct. Read the SLink.Next pointer
// from the field's address to get the first thread link pointer.
Target.TypeInfo slinkType = target.GetTypeInfo(DataType.SLink);
TargetPointer slinkAddr = address + (ulong)type.Fields[nameof(FirstThreadLink)].Offset;
FirstThreadLink = target.ReadPointerField(slinkAddr, slinkType, "Next");

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

ThreadStore now calls target.GetTypeInfo(DataType.SLink) and reads field "Next", but the CoreCLR data descriptor in this PR doesn't define a SLink type/field (could not find any CDAC_TYPE_BEGIN/FIELD for SLink). This will cause GetTypeInfo(DataType.SLink) to fail at runtime. Either add an SLink descriptor (with a "Next" pointer) on the runtime side or avoid needing type info here (e.g., treat the embedded SLink as a single pointer at offset 0).

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 29
SLink,
ThreadLocalData,

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

DataType adds SLink, but there is no corresponding runtime data descriptor definition (no CDAC_TYPE_BEGIN/FIELD(SLink, ...) found). Any attempt to read this type via Target.GetTypeInfo(DataType.SLink) will fail. Either add the runtime descriptor entry for SLink or remove this enum value and read the embedded link without type metadata.

Suggested change
SLink,
ThreadLocalData,
ThreadLocalData=17,

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +69
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

These DataReceived event handlers append to strings via "+=" from background threads. That is not thread-safe and can lead to lost/garbled output under contention. Consider using a StringBuilder with locking (or ConcurrentQueue) for stderr/stdout aggregation.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +74
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

Same thread-safety issue as stderr: appending to stdout with += from OutputDataReceived is racy. Use a synchronized StringBuilder/collector to avoid missing output and to keep logs reliable when tests fail.

Copilot uses AI. Check for mistakes.
Comment on lines +732 to +751
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
private static ArgTypeInfo GcTypeKindToArgTypeInfo(GcTypeKind kind, int pointerSize)
{
return kind switch
{
GcTypeKind.None => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
GcTypeKind.Ref => ArgTypeInfo.ForPrimitive(CorElementType.Class, pointerSize),
GcTypeKind.Interior => ArgTypeInfo.ForPrimitive(CorElementType.Byref, pointerSize),
GcTypeKind.Other => new ArgTypeInfo
{
CorElementType = CorElementType.ValueType,
Size = pointerSize, // Conservative: assume pointer-sized for now
IsValueType = true,
},
_ => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
};
}

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

GcTypeKindToArgTypeInfo collapses all non-GC-ref types to CorElementType.I/pointer-sized and doesn't preserve float vs integer vs exact primitive sizes. ArgIterator offset calculation depends on the real signature shape (especially on Unix x64/ARM64 where float regs and argument sizing affect later argument placement), so this can produce incorrect offsets and cause missed/incorrect GC ref reporting for signatures with floats or non-pointer-sized primitives. Consider decoding into a richer type representation (e.g., CorElementType + size) and building ArgTypeInfo from that rather than from GcTypeKind.

Suggested change
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.None=> ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,// Conservative: assume pointer-sized for now
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
/// Converts a <see cref="GcTypeKind"/> to a conservative fallback <see cref="ArgTypeInfo"/>
/// when an exact signature cannot be decoded. Callers should prefer decoding the real
/// managed signature via <see cref="TryGetArgTypeInfosFromMethodSignature(ReadOnlySpan{byte}, int, out ArgTypeInfo[])"/>
/// so that floating-point and non-pointer-sized primitives retain their real layout.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
privatestaticboolTryGetArgTypeInfosFromMethodSignature(ReadOnlySpan<byte>signatureBytes,intpointerSize,outArgTypeInfo[]argTypes)
{
argTypes=Array.Empty<ArgTypeInfo>();
if(signatureBytes.IsEmpty)
returnfalse;
BlobReaderreader=new(signatureBytes);
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
ArgTypeInfo[]decodedArgTypes=newArgTypeInfo[parameterCount];
for(inti=0;i<parameterCount;i++)
{
if(!TryReadArgTypeInfo(refreader,pointerSize,outdecodedArgTypes[i]))
returnfalse;
}
argTypes=decodedArgTypes;
returntrue;
}
privatestaticboolTryReadArgTypeInfo(refBlobReaderreader,intpointerSize,outArgTypeInfoargTypeInfo)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Boolean:
caseCorElementType.I1:
caseCorElementType.U1:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,1);
returntrue;
caseCorElementType.Char:
caseCorElementType.I2:
caseCorElementType.U2:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,2);
returntrue;
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.R4:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,4);
returntrue;
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R8:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,8);
returntrue;
caseCorElementType.I:
caseCorElementType.U:
caseCorElementType.Ptr:
caseCorElementType.FnPtr:
if(elementTypeisCorElementType.Ptr or CorElementType.FnPtr)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize);
returntrue;
caseCorElementType.Byref:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize);
returntrue;
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.Class:
if(elementTypeisCorElementType.Class)
reader.ReadCompressedInteger();
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.SzArray:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.ValueType:
reader.ReadCompressedInteger();
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
}
default:
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=default;
returnfalse;
}
privatestaticboolTrySkipSignatureType(refBlobReaderreader)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Void:
caseCorElementType.Boolean:
caseCorElementType.Char:
caseCorElementType.I1:
caseCorElementType.U1:
caseCorElementType.I2:
caseCorElementType.U2:
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R4:
caseCorElementType.R8:
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.TypedByref:
caseCorElementType.I:
caseCorElementType.U:
returntrue;
caseCorElementType.Class:
caseCorElementType.ValueType:
caseCorElementType.Var:
caseCorElementType.MVar:
reader.ReadCompressedInteger();
returntrue;
caseCorElementType.Byref:
caseCorElementType.Ptr:
caseCorElementType.SzArray:
returnTrySkipSignatureType(refreader);
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
caseCorElementType.Array:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intrank=reader.ReadCompressedInteger();
intsizes=reader.ReadCompressedInteger();
for(inti=0;i<sizes;i++)
reader.ReadCompressedInteger();
intlowerBounds=reader.ReadCompressedInteger();
for(inti=0;i<lowerBounds;i++)
reader.ReadCompressedInteger();
returnrank>=0;
}
caseCorElementType.FnPtr:
{
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
for(inti=0;i<parameterCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
default:
returnfalse;
}
}
returnfalse;
}

Copilot uses AI. Check for mistakes.

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

Comment on lines +582 to 586
static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray<StackRef>* pRefs,
const char* label = nullptr)
{
if (pSosDac == nullptr)
return false;

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

CollectStackRefs currently appends every ref returned by ISOSStackRefEnum::Next with no upper bound. Later comparison helpers allocate fixed-size arrays sized by MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed[MAX_COLLECTED_REFS]), so if enumeration returns more than MAX_COLLECTED_REFS it can lead to out-of-bounds writes. Please cap collection to MAX_COLLECTED_REFS (and record an overflow/skip reason) or make the comparison logic handle arbitrarily large ref sets safely.

Copilot uses AI. Check for mistakes.
Comment on lines +1359 to +1362
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

When CollectRuntimeStackRefs overflows, the code logs a [SKIP] line but continues and still computes rtMatch/pass (and does not increment s_verifySkip). If CDACSTRESS_USE_DAC is not set, this can produce false failures based on a truncated runtime ref set. Consider treating runtime overflow as an actual skip (increment skip counter + return) when RT comparison is used for pass/fail, or otherwise avoid using rtMatch in the presence of overflow.

Suggested change
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
if (rtOverflow)
{
InterlockedIncrement(&s_verifySkip);
if (s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
}
return;

Copilot uses AI. Check for mistakes.
Comment on lines +823 to +824
ReportSlot(slotIndex, reportScratchSlots: true, reportFpBasedSlotsOnly, reportSlot);
}

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

In ReportUntrackedAndSucceed, ReportSlot is called with reportScratchSlots: true, which forces reporting scratch registers/stack slots even when CodeManagerFlags.ActiveStackFrame is not set (reportScratchSlots local is false). This changes GC root enumeration behavior and can introduce extra roots for non-leaf frames. It looks like this should pass the reportScratchSlots variable instead of always true.

Suggested change
ReportSlot(slotIndex,reportScratchSlots:true,reportFpBasedSlotsOnly,reportSlot);
}
ReportSlot(slotIndex,reportScratchSlots,reportFpBasedSlotsOnly,reportSlot);
}

Copilot uses AI. Check for mistakes.
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 49c8435 to de5cb46CompareApril 24, 2026 15:56
- Include RuntimeInfoOperatingSystem.Apple in Unix x64 ABI check
(macOS x64 uses SysV ABI, not Windows ABI)
- Thread MetadataReader from GetMethodSignatureBytes through
RuntimeSignatureDecoder to provider methods (instead of null!)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 24, 2026 16:07

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

Comment on lines +960 to 964
// Compare two ref sets using two-phase matching (for RT comparison where we
// don't have Source info). Returns true if all refs match.
static bool CompareRefSetsFlat(StackRef* refsA, int countA, StackRef* refsB, int countB)
{
if (countA != countB)

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

CompareRefSetsFlat uses a fixed-size matched[MAX_COLLECTED_REFS] buffer, but cDAC/DAC ref collection (CollectStackRefs) is unbounded. Without a guard/cap, a large ref set (>4096) can cause out-of-bounds writes during matching. Consider capping cDAC/DAC collection to MAX_COLLECTED_REFS (and treating overflow as a [SKIP]) or using dynamically sized bookkeeping here.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +76
// Read both stdout and stderr asynchronously to avoid deadlock
// when pipe buffers fill, and to allow WaitForExit timeout to work.
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};
process.BeginErrorReadLine();
process.BeginOutputReadLine();

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

The async stdout/stderr collection uses stdout += ... / stderr += ... inside DataReceived event handlers. These callbacks can run concurrently, and string concatenation is not thread-safe; it can also be costly for large output. Consider buffering with a thread-safe collector (e.g., ConcurrentQueue<string> or StringBuilder with a lock) and call process.WaitForExit() (or await stream completion) after WaitForExit(timeout) to ensure all async output has been drained before asserting/logging.

Copilot uses AI. Check for mistakes.
max-charlamb added a commit that referenced this pull request May 1, 2026
## Summary
Part 1 of 5 stacked PRs splitting
[#126408](#126408) into reviewable
pieces.
### What this PR contains
**Stack Walk GC Reference Scanning:**
- `PromoteCallerStack` / `PromoteCallerStackUsingGCRefMap` for
transition frames
- `GCRefMapDecoder` + `FindGCRefMap` with ReadyToRun import section
resolution
- `GcSignatureTypeProvider` for GC type classification
- `SOSDacImpl.GetStackReferences` fully implemented using cDAC contracts
- `GCInfoDecoder.EnumerateLiveSlots` promoted to `IGCInfo` contract
(returns `IReadOnlyList<LiveSlot>`)
- `GcSlotEnumerationOptions` replaces native `CodeManagerFlags` with
descriptive boolean properties
**Stack Walker Fixes:**
- `IsFirst` preserved for skipped frames (matches native
SFITER_SKIPPED_FRAME_FUNCTION)
- `IsInterrupted` state tracking for exception frames
(FaultingExceptionFrame, SoftwareExceptionFrame)
- `GetReturnAddress` gating in SW_FRAME (only UpdateRegDisplay if return
address non-null)
- Catch handler offset override via `GetInterruptibleRanges` for EH
resumption
**Contract API Additions:**
- `IGCInfo`: `EnumerateLiveSlots`, `GetStackBaseRegister`,
`GetInterruptibleRanges`
- `IExecutionManager`: `FindReadyToRunModule`
- `IRuntimeTypeSystem`: `RequiresInstArg`, `IsAsyncMethod`
- `IStackWalk`: `WalkStackReferences`
**Data Descriptor Changes:**
- Removed `ZapModule` and `GCRefMap` cached pointers (always resolve via
`FindReadyToRunModule`)
- Added `Indirection` for StubDispatchFrame, ExternalMethodFrame
- Added `DynamicHelperFrame.DynamicHelperFrameFlags`
- Added TransitionBlock fields (`OffsetOfArgs`,
`ArgumentRegistersOffset`, `FirstGCRefMapSlot`)
- Added ReadyToRunInfo fields (`ImportSections`, `NumImportSections`)
- Added ExceptionInfo catch clause fields
(`ClauseForCatchHandlerStartPC`, `ClauseForCatchHandlerEndPC`)
**Documentation:**
- GCInfo.md: Comprehensive implementation docs (header/body decoding,
slot table, EnumerateLiveSlots algorithm, type definitions for
`LiveSlot`, `InterruptibleRange`, `GcSlotEnumerationOptions`)
- StackWalk.md: GC scanning algorithm, GCRefMap resolution flow, return
address per frame type, `WalkStackReferences` API
- ExecutionManager.md: `FindReadyToRunModule` API and implementation
- RuntimeTypeSystem.md: `RequiresInstArg`, `IsAsyncMethod` APIs
### Stack overview
| PR | Content | Status |
|----|---------|--------|
| **This PR** | Stack walk fixes + GC scanning | Open |
| PR 2 | RuntimeSignatureDecoder (ELEMENT_TYPE_INTERNAL) | Pending |
| PR 3 | ArgIterator port from crossgen2 | Pending |
| PR 4 | Native stress framework (cdacstress.cpp) | Pending |
| PR 5 | Managed stress tests + CI pipeline | Pending |
### Testing
- 1727/1751 unit tests pass (24 pre-existing ThreadTests failures on
main)
- Dump tests (StackWalkDumpTests, StackReferenceDumpTests) validate
end-to-end
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

Closing in favor of stacked PR approach

steveisok pushed a commit that referenced this pull request May 11, 2026
…5) (#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[#126408](#126408). Builds on
[#127395](#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jakobbotsch pushed a commit to jakobbotsch/runtime that referenced this pull request May 12, 2026
…5) (dotnet#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[dotnet#126408](dotnet#126408). Builds on
[dotnet#127395](dotnet#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 1, 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.

2 participants

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

[cDAC] Stack walk GC stress verification and fixes - #126408

Closed
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5
Closed

[cDAC] Stack walk GC stress verification and fixes#126408
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Apr 1, 2026

Copy link
Copy Markdown
Member

Summary

cDAC GC stress verification tool (DOTNET_CdacStress) that compares stack GC references between the cDAC and the runtime at allocation stress points. Includes stack walker fixes, GC reference scanning implementation, custom signature decoding, and calling convention argument iteration.

Note

This PR description was updated with AI assistance from Copilot.

Stack walker fixes

  • Fix SW_SKIPPED_FRAME: do not call UpdateContextFromFrame (matches native SFITER_SKIPPED_FRAME_FUNCTION which does not call UpdateRegDisplay)
  • Fix skipped-frame loop: check for more skipped frames before yielding managed method (prevents duplicate EnumGcRefs between consecutive skipped frames)
  • Restructure Filter() to drive Next() directly, matching native Filter()+NextRaw() integration (prevents funclet-to-parent walk cycles)
  • Remove SkipActiveICFOnce/SkipCurrentFrameInCheck — active ICF double-yield is natural and harmless
  • Remove IsAtFirstPassExceptionThrowSite — native does not suppress first-pass refs
  • Fix IsFirst not preserved for skipped frames (was causing IsActiveFrame=false for the topmost managed frame)

GC reference scanning

  • Implement PromoteCallerStack for stub frames (GCRefMap + MetaSig paths)
  • Implement SOSDacImpl.GetStackReferences using cDAC contract (was falling back to legacy DAC)
  • Read FilterContext for stack walk starting context
  • Three-way cDAC/DAC/RT comparison with InProcessDataTarget
  • DOTNET_CdacStress bit flags: ALLOC/INSTR/REFS/WALK/USE_DAC/UNIQUE

RuntimeSignatureDecoder

Custom signature decoder that handles ELEMENT_TYPE_INTERNAL (0x21) and ELEMENT_TYPE_CMOD_INTERNAL (0x22) which SRM's SignatureDecoder cannot parse. These occur in IL stubs, marshalling stubs, and unsafe accessor frames.

  • IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of SRM's ISignatureTypeProvider, adds GetInternalType and GetInternalModifiedType
  • ISignatureReader + SpanSignatureReader: endianness-aware reader abstraction
  • Correct ECMA-335 compliance: TypeDefOrRefOrSpecEncoded token decoding, sign-extension-by-width for compressed signed ints, bounds validation

ArgIterator (ported from crossgen2)

Proper calling convention analysis replacing the simplified 1-slot-per-param approach.

  • CallingConventionInfo: hybrid data descriptor layout values + ABI invariant constants for all architectures (x86, x64 Windows/Unix, ARM32, ARM64, LoongArch64, RISC-V64)
  • ArgIterator.GetNextOffset(): maps each argument to its actual register or stack offset
  • OffsetOfFloatArgumentRegisters added to TransitionBlock data descriptor
  • Handles multi-slot args, forced-byref params, return buffer placement, async continuation

CI infrastructure

  • cDAC stress tests run in Helix via cdac-stress-helix.proj
  • Extended runtime-diagnostics.yml CdacDumpTests buildArgs with +tools.cdacstresstests

Test results

Allocation-level stress (9 debuggees, ~46K verifications):

DebuggeeVerificationsPassFail
BasicAlloc4,9364,9360
DeepStack4,9644,9640
Generics4,9364,9360
MultiThread4,9964,9960
Comprehensive4,9944,9922*
ExceptionHandling4,9584,9580
StructScenarios4,9404,9400
DynamicMethods6,5206,5200
PInvoke4,9364,9360

Instruction-level stress (9 debuggees, ~226K verifications):
All 9 debuggees pass with zero failures across 226,452 verifications.

*Comprehensive's 2 failures are pre-existing legacy DAC issues (DAC returns 0 refs for background threads in kernel waits; cDAC and runtime agree).

@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

Adds a cDAC GC-stress verification harness and extends the cDAC stack-walk / stack-GC-ref pipeline so cDAC stack reference enumeration can be compared against runtime scanning at stress points.

Changes:

  • Implements/extends cDAC stack reference enumeration (including Frame-based scanning paths like PromoteCallerStack via GCRefMap / MetaSig) and wires SOS GetStackReferences to the cDAC contract.
  • Introduces a new GC stress integration test project with debuggee apps and orchestration targets.
  • Extends CoreCLR cDAC stress/GC stress plumbing and data descriptors to support the new stack-walk and frame-scanning capabilities.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 12 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.csUpdates mock type layout for execution manager-related data.
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.csUpdates mock type layouts (ExceptionInfo/Thread) for contract tests.
src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csprojExcludes new GCStressTests folder from the existing unit test project compilation.
src/native/managed/cdac/tests/GCStressTests/README.mdDocuments how to build/run the new GC stress tests.
src/native/managed/cdac/tests/GCStressTests/Microsoft.Diagnostics.DataContractReader.GCStressTests.csprojAdds a dedicated GC stress test project.
src/native/managed/cdac/tests/GCStressTests/GCStressTests.targetsMSBuild orchestration to discover/build debuggee projects.
src/native/managed/cdac/tests/GCStressTests/GCStressTestBase.csTest harness to run debuggees under corerun and parse verification logs.
src/native/managed/cdac/tests/GCStressTests/GCStressResults.csParses the native verification log into structured pass/fail/skip counts.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/Program.csAdds a P/Invoke-focused debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/PInvoke.csprojDebuggee project file for PInvoke scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/Program.csAdds a multi-threaded debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/MultiThread.csprojDebuggee project file for MultiThread scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Program.csAdds a generics/interface/delegate debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Generics.csprojDebuggee project file for Generics scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/Program.csAdds an exception-handling/funclet debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/ExceptionHandling.csprojDebuggee project file for ExceptionHandling scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Directory.Build.propsShared build props for debuggee projects (output layout, TFM, etc.).
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/Program.csAdds deep-recursion debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/DeepStack.csprojDebuggee project file for DeepStack scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Program.csAdds comprehensive “all scenarios” debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Comprehensive.csprojDebuggee project file for Comprehensive scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/Program.csAdds basic allocation/live-ref debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/BasicAlloc.csprojDebuggee project file for BasicAlloc scenario.
src/native/managed/cdac/tests/GCStressTests/BasicGCStressTests.csTheory-based test suite that runs the debuggees and asserts pass rate.
src/native/managed/cdac/tests/gcstress/known-issues.mdCaptures known mismatch classes and limitations.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.csImplements GetStackReferences using the cDAC contract rather than legacy DAC fallback.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.csExtends StubDispatchFrame data with GCRefMap pointer.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.csAdds ExternalMethodFrame contract data type (GCRefMap).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.csAdds DynamicHelperFrame contract data type (flags).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.csAdds clause-range fields used for catch-handler resumption offset selection.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.csRefactors stack-walk filtering and adds Frame-based GC root scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAdds optional relOffset override support for GC ref enumeration.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.csImplements GCRefMap decoding for transition-block scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/CorSigParser.csAdds minimal signature parsing to classify parameters for MetaSig-based scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.csAdds FindFirstInterruptiblePoint API to GCInfo decoders.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.csImplements FindFirstInterruptiblePoint using decoded interruptible ranges.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.csFixes code-start lookup for exception clause enumeration and adds minor flow adjustments.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.csAdds TransitionBlock-related global names used by frame scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.csAdds new DataType enum values for new frame contracts.
src/native/managed/cdac/cdac.slnxAdds the new GC stress test project to the cDAC solution.
src/coreclr/vm/gccover.cppAdds step-based skipping to reduce overhead when throttling verification.
src/coreclr/vm/frames.hExposes additional frame fields to cDAC via cdac_data<> descriptors.
src/coreclr/vm/datadescriptor/datadescriptor.incAdds new contract fields/globals for frames and TransitionBlock layout.
src/coreclr/vm/cdacstress.cppUpdates in-process cDAC/DAC verification logic, logging, and step behavior.
eng/Subsets.propsAdds an on-demand subset for running GC stress tests.
docs/design/datacontracts/StackWalk.mdDocuments the new frame fields and TransitionBlock globals in the StackWalk contract.
Comments suppressed due to low confidence (1)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:228

  • ThreadFields contains ProfilerFilterContext twice (lines 224 and 228). Duplicate field entries will skew offsets and make the mock descriptors inconsistent with the real data descriptor. Keep a single ProfilerFilterContext entry in the correct order.

Comment threadsrc/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/README.md Outdated

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

Comments suppressed due to low confidence (2)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:229

  • ThreadFields now includes DebuggerFilterContext/ProfilerFilterContext twice (duplicate entries at the end of the list). This can cause ambiguous/incorrect field offsets in the mock type layout. Keep each field only once.
    src/coreclr/vm/cdacstress.cpp:963
  • CompareRefSets uses a fixed-size matched[MAX_COLLECTED_REFS] buffer but no longer validates that countA/countB are <= MAX_COLLECTED_REFS. Since CollectStackRefs appends without a hard cap, this can lead to out-of-bounds access when countA or countB exceeds 4096. Reintroduce a guard or allocate the match state sized to the counts.
 return true;
bool matched[MAX_COLLECTED_REFS] = {};
for (int i = 0; i < countA; i++)

Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/gccover.cpp Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/GCStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 75ae7fe to 5419d15CompareApril 13, 2026 15:30
CopilotAI review requested due to automatic review settings April 13, 2026 15:47
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 5419d15 to 8ef9b22CompareApril 13, 2026 15:47

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

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated

@max-charlambmax-charlamb left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

feedback for local copilot

Comment threadsrc/native/managed/cdac/tests/StressTests/analysis/analyze-refs.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/StressTests.targets Outdated
Comment threadsrc/coreclr/vm/datadescriptor/datadescriptor.inc Outdated
CopilotAI review requested due to automatic review settings April 13, 2026 17:33

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

Comments suppressed due to low confidence (1)

src/coreclr/vm/cdacstress.cpp:625

  • CollectStackRefs appends to pRefs without any cap. Later comparisons allocate fixed-size arrays sized MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed/bUsed) and assume counts fit. If the DAC returns > MAX_COLLECTED_REFS refs, this will lead to out-of-bounds writes/reads. Add an explicit limit/overflow handling in CollectStackRefs (stop at MAX_COLLECTED_REFS and mark overflow / SKIP), or change the later comparison logic to handle arbitrary counts safely.
 SOSStackRefData refData;
unsigned int fetched = 0;
while (true)
{
hr = pEnum->Next(1, &refData, &fetched);
if (FAILED(hr) || fetched == 0)
break;
StackRef ref;
ref.Address = refData.Address;
ref.Object = refData.Object;
ref.Flags = refData.Flags;
ref.Source = refData.Source;
ref.SourceType = refData.SourceType;
ref.Register = refData.Register;
ref.Offset = refData.Offset;
ref.StackPointer = refData.StackPointer;
pRefs->Append(ref);
}

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/known-issues.md
Comment threaddocs/design/datacontracts/StackWalk.md
CopilotAI review requested due to automatic review settings April 13, 2026 19: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 52 out of 52 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/coreclr/vm/cdacstress.cpp
CopilotAI review requested due to automatic review settings April 13, 2026 20:24
Max Charlamband others added 5 commits April 22, 2026 15:23
- Fix platform-specific ctx.Rip usage: use GetIP(&ctx) instead
- Fix if( style: add space after if keyword in gccover.cpp
- Add RVA bounds validation in FindGCRefMap before uint cast
- Remove stale analysis file (eh-throwhelper-report.md)
- Update AssertHighPassRate comment to reflect current state
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Run GC stress verification tests in the runtime-diagnostics pipeline by
piggybacking on the existing CdacDumpTests Checked runtime build. The
stress tests run as a second Helix submission after the dump tests,
using the testhost shared framework as CORE_ROOT.
Runs on all cdacDumpPlatforms (windows_x64, linux_x64, etc.). On
non-Windows platforms, only instruction-level stress (via DoGcStress)
is supported; allocation-level stress (VerifyAtAllocPoint) is skipped
because it requires Windows-only RtlCaptureContext/RtlVirtualUnwind.
Infrastructure:
- cdac-stress-helix.proj: Helix SDK project that sends testhost as
correlation payload and stress test debuggees + test assembly as
work item payload. Sets CORE_ROOT env var for the test harness.
- prepare-cdac-stress-helix-steps.yml: Pipeline template that builds
debuggees, prepares Helix payload, and finds testhost directory.
- StressTests.targets: Added PrepareHelixPayload and BuildDebuggeesOnly
targets for CI payload preparation.
- CdacStressTestBase.cs: Added HELIX_WORKITEM_PAYLOAD support for
finding debuggees in Helix environment.
- runtime-diagnostics.yml: Extended CdacDumpTests buildArgs to include
tools.cdacstresstests, added stress test Helix submission steps.
- cdacstress.cpp: Guard VerifyAtAllocPoint with TARGET_WINDOWS for
RtlCaptureContext/RtlVirtualUnwind APIs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a custom signature decoder that handles runtime-internal type codes
(ELEMENT_TYPE_INTERNAL 0x21, ELEMENT_TYPE_CMOD_INTERNAL 0x22) which
SRM's SignatureDecoder cannot parse.
- IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of
SRM's ISignatureTypeProvider, adding GetInternalType for resolving
embedded TypeHandle pointers via the runtime type system.
- ISignatureReader + SpanSignatureReader: abstraction for reading
signature bytes from different sources (spans, target memory).
- RuntimeSignatureDecoder<TType, TGenericContext, TReader>: ref struct
decoder that handles all standard ECMA-335 types plus internal types.
- GcSignatureTypeProvider: implements IRuntimeSignatureTypeProvider to
classify types for GC scanning, resolving internal types via
RuntimeTypeSystem.GetSignatureCorElementType.
Key correctness details vs SRM's SignatureDecoder:
- CLASS/VALUETYPE tokens decoded as TypeDefOrRefOrSpecEncoded per
ECMA-335 II.23.2.8 (tag in low 2 bits, RID in upper bits).
- CMOD_INTERNAL correctly skips the required/optional flag byte before
the TypeHandle pointer, matching sigparser.h layout.
- ReadCompressedSignedInt uses ECMA sign-extension-by-width, not zigzag.
- ReadCompressedUInt rejects invalid 111xxxxx prefix.
- Unknown type codes throw BadImageFormatException instead of silently
returning Object (which would create false-positive GC refs).
- Method signature header kind is validated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port crossgen2's ArgIterator, TransitionBlock, and GC scanning logic
into the cDAC contracts for correct per-architecture argument placement.
- Add OffsetOfFloatArgumentRegisters to TransitionBlock data descriptor
- CallingConventionInfo: hybrid data descriptor + ABI invariant constants
for x86, x64 (Windows/Unix), ARM32, ARM64, LoongArch64, RISC-V64
- ArgTypeInfo: pre-computed type info replacing crossgen2's TypeHandle
- ArgIteratorData: parsed method signature holder
- ArgIterator: maps each argument to register/stack offsets via
GetNextOffset() with per-architecture register allocation
- Integrate into FrameIterator.PromoteCallerStackHelper replacing the
simplified 1-slot-per-param approach with proper offset computation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Quote debuggee DLL path in ProcessStartInfo.Arguments (dotnet#31)
- Fix timeout: use async stdout/stderr reads so WaitForExit works (dotnet#32)
- Update stale comment about ELEMENT_TYPE_INTERNAL limitation (dotnet#35)
- Move CallingConvention types to StackWalkHelpers.CallingConvention namespace (dotnet#37)
- Simplify x86 register eligibility check in ArgIterator (dotnet#38)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 22, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 1a92e2c to b85dbacCompareApril 22, 2026 19:25

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

Comment on lines +16 to +20
// FirstThreadLink is an embedded SLink struct. Read the SLink.Next pointer
// from the field's address to get the first thread link pointer.
Target.TypeInfo slinkType = target.GetTypeInfo(DataType.SLink);
TargetPointer slinkAddr = address + (ulong)type.Fields[nameof(FirstThreadLink)].Offset;
FirstThreadLink = target.ReadPointerField(slinkAddr, slinkType, "Next");

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

ThreadStore now calls target.GetTypeInfo(DataType.SLink) and reads field "Next", but the CoreCLR data descriptor in this PR doesn't define a SLink type/field (could not find any CDAC_TYPE_BEGIN/FIELD for SLink). This will cause GetTypeInfo(DataType.SLink) to fail at runtime. Either add an SLink descriptor (with a "Next" pointer) on the runtime side or avoid needing type info here (e.g., treat the embedded SLink as a single pointer at offset 0).

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 29
SLink,
ThreadLocalData,

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

DataType adds SLink, but there is no corresponding runtime data descriptor definition (no CDAC_TYPE_BEGIN/FIELD(SLink, ...) found). Any attempt to read this type via Target.GetTypeInfo(DataType.SLink) will fail. Either add the runtime descriptor entry for SLink or remove this enum value and read the embedded link without type metadata.

Suggested change
SLink,
ThreadLocalData,
ThreadLocalData=17,

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +69
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

These DataReceived event handlers append to strings via "+=" from background threads. That is not thread-safe and can lead to lost/garbled output under contention. Consider using a StringBuilder with locking (or ConcurrentQueue) for stderr/stdout aggregation.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +74
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

Same thread-safety issue as stderr: appending to stdout with += from OutputDataReceived is racy. Use a synchronized StringBuilder/collector to avoid missing output and to keep logs reliable when tests fail.

Copilot uses AI. Check for mistakes.
Comment on lines +732 to +751
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
private static ArgTypeInfo GcTypeKindToArgTypeInfo(GcTypeKind kind, int pointerSize)
{
return kind switch
{
GcTypeKind.None => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
GcTypeKind.Ref => ArgTypeInfo.ForPrimitive(CorElementType.Class, pointerSize),
GcTypeKind.Interior => ArgTypeInfo.ForPrimitive(CorElementType.Byref, pointerSize),
GcTypeKind.Other => new ArgTypeInfo
{
CorElementType = CorElementType.ValueType,
Size = pointerSize, // Conservative: assume pointer-sized for now
IsValueType = true,
},
_ => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
};
}

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

GcTypeKindToArgTypeInfo collapses all non-GC-ref types to CorElementType.I/pointer-sized and doesn't preserve float vs integer vs exact primitive sizes. ArgIterator offset calculation depends on the real signature shape (especially on Unix x64/ARM64 where float regs and argument sizing affect later argument placement), so this can produce incorrect offsets and cause missed/incorrect GC ref reporting for signatures with floats or non-pointer-sized primitives. Consider decoding into a richer type representation (e.g., CorElementType + size) and building ArgTypeInfo from that rather than from GcTypeKind.

Suggested change
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.None=> ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,// Conservative: assume pointer-sized for now
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
/// Converts a <see cref="GcTypeKind"/> to a conservative fallback <see cref="ArgTypeInfo"/>
/// when an exact signature cannot be decoded. Callers should prefer decoding the real
/// managed signature via <see cref="TryGetArgTypeInfosFromMethodSignature(ReadOnlySpan{byte}, int, out ArgTypeInfo[])"/>
/// so that floating-point and non-pointer-sized primitives retain their real layout.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
privatestaticboolTryGetArgTypeInfosFromMethodSignature(ReadOnlySpan<byte>signatureBytes,intpointerSize,outArgTypeInfo[]argTypes)
{
argTypes=Array.Empty<ArgTypeInfo>();
if(signatureBytes.IsEmpty)
returnfalse;
BlobReaderreader=new(signatureBytes);
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
ArgTypeInfo[]decodedArgTypes=newArgTypeInfo[parameterCount];
for(inti=0;i<parameterCount;i++)
{
if(!TryReadArgTypeInfo(refreader,pointerSize,outdecodedArgTypes[i]))
returnfalse;
}
argTypes=decodedArgTypes;
returntrue;
}
privatestaticboolTryReadArgTypeInfo(refBlobReaderreader,intpointerSize,outArgTypeInfoargTypeInfo)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Boolean:
caseCorElementType.I1:
caseCorElementType.U1:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,1);
returntrue;
caseCorElementType.Char:
caseCorElementType.I2:
caseCorElementType.U2:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,2);
returntrue;
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.R4:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,4);
returntrue;
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R8:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,8);
returntrue;
caseCorElementType.I:
caseCorElementType.U:
caseCorElementType.Ptr:
caseCorElementType.FnPtr:
if(elementTypeisCorElementType.Ptr or CorElementType.FnPtr)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize);
returntrue;
caseCorElementType.Byref:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize);
returntrue;
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.Class:
if(elementTypeisCorElementType.Class)
reader.ReadCompressedInteger();
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.SzArray:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.ValueType:
reader.ReadCompressedInteger();
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
}
default:
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=default;
returnfalse;
}
privatestaticboolTrySkipSignatureType(refBlobReaderreader)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Void:
caseCorElementType.Boolean:
caseCorElementType.Char:
caseCorElementType.I1:
caseCorElementType.U1:
caseCorElementType.I2:
caseCorElementType.U2:
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R4:
caseCorElementType.R8:
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.TypedByref:
caseCorElementType.I:
caseCorElementType.U:
returntrue;
caseCorElementType.Class:
caseCorElementType.ValueType:
caseCorElementType.Var:
caseCorElementType.MVar:
reader.ReadCompressedInteger();
returntrue;
caseCorElementType.Byref:
caseCorElementType.Ptr:
caseCorElementType.SzArray:
returnTrySkipSignatureType(refreader);
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
caseCorElementType.Array:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intrank=reader.ReadCompressedInteger();
intsizes=reader.ReadCompressedInteger();
for(inti=0;i<sizes;i++)
reader.ReadCompressedInteger();
intlowerBounds=reader.ReadCompressedInteger();
for(inti=0;i<lowerBounds;i++)
reader.ReadCompressedInteger();
returnrank>=0;
}
caseCorElementType.FnPtr:
{
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
for(inti=0;i<parameterCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
default:
returnfalse;
}
}
returnfalse;
}

Copilot uses AI. Check for mistakes.

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

Comment on lines +582 to 586
static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray<StackRef>* pRefs,
const char* label = nullptr)
{
if (pSosDac == nullptr)
return false;

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

CollectStackRefs currently appends every ref returned by ISOSStackRefEnum::Next with no upper bound. Later comparison helpers allocate fixed-size arrays sized by MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed[MAX_COLLECTED_REFS]), so if enumeration returns more than MAX_COLLECTED_REFS it can lead to out-of-bounds writes. Please cap collection to MAX_COLLECTED_REFS (and record an overflow/skip reason) or make the comparison logic handle arbitrarily large ref sets safely.

Copilot uses AI. Check for mistakes.
Comment on lines +1359 to +1362
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

When CollectRuntimeStackRefs overflows, the code logs a [SKIP] line but continues and still computes rtMatch/pass (and does not increment s_verifySkip). If CDACSTRESS_USE_DAC is not set, this can produce false failures based on a truncated runtime ref set. Consider treating runtime overflow as an actual skip (increment skip counter + return) when RT comparison is used for pass/fail, or otherwise avoid using rtMatch in the presence of overflow.

Suggested change
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
if (rtOverflow)
{
InterlockedIncrement(&s_verifySkip);
if (s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
}
return;

Copilot uses AI. Check for mistakes.
Comment on lines +823 to +824
ReportSlot(slotIndex, reportScratchSlots: true, reportFpBasedSlotsOnly, reportSlot);
}

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

In ReportUntrackedAndSucceed, ReportSlot is called with reportScratchSlots: true, which forces reporting scratch registers/stack slots even when CodeManagerFlags.ActiveStackFrame is not set (reportScratchSlots local is false). This changes GC root enumeration behavior and can introduce extra roots for non-leaf frames. It looks like this should pass the reportScratchSlots variable instead of always true.

Suggested change
ReportSlot(slotIndex,reportScratchSlots:true,reportFpBasedSlotsOnly,reportSlot);
}
ReportSlot(slotIndex,reportScratchSlots,reportFpBasedSlotsOnly,reportSlot);
}

Copilot uses AI. Check for mistakes.
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 49c8435 to de5cb46CompareApril 24, 2026 15:56
- Include RuntimeInfoOperatingSystem.Apple in Unix x64 ABI check
(macOS x64 uses SysV ABI, not Windows ABI)
- Thread MetadataReader from GetMethodSignatureBytes through
RuntimeSignatureDecoder to provider methods (instead of null!)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 24, 2026 16:07

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

Comment on lines +960 to 964
// Compare two ref sets using two-phase matching (for RT comparison where we
// don't have Source info). Returns true if all refs match.
static bool CompareRefSetsFlat(StackRef* refsA, int countA, StackRef* refsB, int countB)
{
if (countA != countB)

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

CompareRefSetsFlat uses a fixed-size matched[MAX_COLLECTED_REFS] buffer, but cDAC/DAC ref collection (CollectStackRefs) is unbounded. Without a guard/cap, a large ref set (>4096) can cause out-of-bounds writes during matching. Consider capping cDAC/DAC collection to MAX_COLLECTED_REFS (and treating overflow as a [SKIP]) or using dynamically sized bookkeeping here.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +76
// Read both stdout and stderr asynchronously to avoid deadlock
// when pipe buffers fill, and to allow WaitForExit timeout to work.
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};
process.BeginErrorReadLine();
process.BeginOutputReadLine();

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

The async stdout/stderr collection uses stdout += ... / stderr += ... inside DataReceived event handlers. These callbacks can run concurrently, and string concatenation is not thread-safe; it can also be costly for large output. Consider buffering with a thread-safe collector (e.g., ConcurrentQueue<string> or StringBuilder with a lock) and call process.WaitForExit() (or await stream completion) after WaitForExit(timeout) to ensure all async output has been drained before asserting/logging.

Copilot uses AI. Check for mistakes.
max-charlamb added a commit that referenced this pull request May 1, 2026
## Summary
Part 1 of 5 stacked PRs splitting
[#126408](#126408) into reviewable
pieces.
### What this PR contains
**Stack Walk GC Reference Scanning:**
- `PromoteCallerStack` / `PromoteCallerStackUsingGCRefMap` for
transition frames
- `GCRefMapDecoder` + `FindGCRefMap` with ReadyToRun import section
resolution
- `GcSignatureTypeProvider` for GC type classification
- `SOSDacImpl.GetStackReferences` fully implemented using cDAC contracts
- `GCInfoDecoder.EnumerateLiveSlots` promoted to `IGCInfo` contract
(returns `IReadOnlyList<LiveSlot>`)
- `GcSlotEnumerationOptions` replaces native `CodeManagerFlags` with
descriptive boolean properties
**Stack Walker Fixes:**
- `IsFirst` preserved for skipped frames (matches native
SFITER_SKIPPED_FRAME_FUNCTION)
- `IsInterrupted` state tracking for exception frames
(FaultingExceptionFrame, SoftwareExceptionFrame)
- `GetReturnAddress` gating in SW_FRAME (only UpdateRegDisplay if return
address non-null)
- Catch handler offset override via `GetInterruptibleRanges` for EH
resumption
**Contract API Additions:**
- `IGCInfo`: `EnumerateLiveSlots`, `GetStackBaseRegister`,
`GetInterruptibleRanges`
- `IExecutionManager`: `FindReadyToRunModule`
- `IRuntimeTypeSystem`: `RequiresInstArg`, `IsAsyncMethod`
- `IStackWalk`: `WalkStackReferences`
**Data Descriptor Changes:**
- Removed `ZapModule` and `GCRefMap` cached pointers (always resolve via
`FindReadyToRunModule`)
- Added `Indirection` for StubDispatchFrame, ExternalMethodFrame
- Added `DynamicHelperFrame.DynamicHelperFrameFlags`
- Added TransitionBlock fields (`OffsetOfArgs`,
`ArgumentRegistersOffset`, `FirstGCRefMapSlot`)
- Added ReadyToRunInfo fields (`ImportSections`, `NumImportSections`)
- Added ExceptionInfo catch clause fields
(`ClauseForCatchHandlerStartPC`, `ClauseForCatchHandlerEndPC`)
**Documentation:**
- GCInfo.md: Comprehensive implementation docs (header/body decoding,
slot table, EnumerateLiveSlots algorithm, type definitions for
`LiveSlot`, `InterruptibleRange`, `GcSlotEnumerationOptions`)
- StackWalk.md: GC scanning algorithm, GCRefMap resolution flow, return
address per frame type, `WalkStackReferences` API
- ExecutionManager.md: `FindReadyToRunModule` API and implementation
- RuntimeTypeSystem.md: `RequiresInstArg`, `IsAsyncMethod` APIs
### Stack overview
| PR | Content | Status |
|----|---------|--------|
| **This PR** | Stack walk fixes + GC scanning | Open |
| PR 2 | RuntimeSignatureDecoder (ELEMENT_TYPE_INTERNAL) | Pending |
| PR 3 | ArgIterator port from crossgen2 | Pending |
| PR 4 | Native stress framework (cdacstress.cpp) | Pending |
| PR 5 | Managed stress tests + CI pipeline | Pending |
### Testing
- 1727/1751 unit tests pass (24 pre-existing ThreadTests failures on
main)
- Dump tests (StackWalkDumpTests, StackReferenceDumpTests) validate
end-to-end
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

Closing in favor of stacked PR approach

steveisok pushed a commit that referenced this pull request May 11, 2026
…5) (#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[#126408](#126408). Builds on
[#127395](#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jakobbotsch pushed a commit to jakobbotsch/runtime that referenced this pull request May 12, 2026
…5) (dotnet#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[dotnet#126408](dotnet#126408). Builds on
[dotnet#127395](dotnet#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 1, 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.

2 participants

@max-charlamb
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[cDAC] Stack walk GC stress verification and fixes - #126408

Closed
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5
Closed

[cDAC] Stack walk GC stress verification and fixes#126408
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Apr 1, 2026

Copy link
Copy Markdown
Member

Summary

cDAC GC stress verification tool (DOTNET_CdacStress) that compares stack GC references between the cDAC and the runtime at allocation stress points. Includes stack walker fixes, GC reference scanning implementation, custom signature decoding, and calling convention argument iteration.

Note

This PR description was updated with AI assistance from Copilot.

Stack walker fixes

  • Fix SW_SKIPPED_FRAME: do not call UpdateContextFromFrame (matches native SFITER_SKIPPED_FRAME_FUNCTION which does not call UpdateRegDisplay)
  • Fix skipped-frame loop: check for more skipped frames before yielding managed method (prevents duplicate EnumGcRefs between consecutive skipped frames)
  • Restructure Filter() to drive Next() directly, matching native Filter()+NextRaw() integration (prevents funclet-to-parent walk cycles)
  • Remove SkipActiveICFOnce/SkipCurrentFrameInCheck — active ICF double-yield is natural and harmless
  • Remove IsAtFirstPassExceptionThrowSite — native does not suppress first-pass refs
  • Fix IsFirst not preserved for skipped frames (was causing IsActiveFrame=false for the topmost managed frame)

GC reference scanning

  • Implement PromoteCallerStack for stub frames (GCRefMap + MetaSig paths)
  • Implement SOSDacImpl.GetStackReferences using cDAC contract (was falling back to legacy DAC)
  • Read FilterContext for stack walk starting context
  • Three-way cDAC/DAC/RT comparison with InProcessDataTarget
  • DOTNET_CdacStress bit flags: ALLOC/INSTR/REFS/WALK/USE_DAC/UNIQUE

RuntimeSignatureDecoder

Custom signature decoder that handles ELEMENT_TYPE_INTERNAL (0x21) and ELEMENT_TYPE_CMOD_INTERNAL (0x22) which SRM's SignatureDecoder cannot parse. These occur in IL stubs, marshalling stubs, and unsafe accessor frames.

  • IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of SRM's ISignatureTypeProvider, adds GetInternalType and GetInternalModifiedType
  • ISignatureReader + SpanSignatureReader: endianness-aware reader abstraction
  • Correct ECMA-335 compliance: TypeDefOrRefOrSpecEncoded token decoding, sign-extension-by-width for compressed signed ints, bounds validation

ArgIterator (ported from crossgen2)

Proper calling convention analysis replacing the simplified 1-slot-per-param approach.

  • CallingConventionInfo: hybrid data descriptor layout values + ABI invariant constants for all architectures (x86, x64 Windows/Unix, ARM32, ARM64, LoongArch64, RISC-V64)
  • ArgIterator.GetNextOffset(): maps each argument to its actual register or stack offset
  • OffsetOfFloatArgumentRegisters added to TransitionBlock data descriptor
  • Handles multi-slot args, forced-byref params, return buffer placement, async continuation

CI infrastructure

  • cDAC stress tests run in Helix via cdac-stress-helix.proj
  • Extended runtime-diagnostics.yml CdacDumpTests buildArgs with +tools.cdacstresstests

Test results

Allocation-level stress (9 debuggees, ~46K verifications):

DebuggeeVerificationsPassFail
BasicAlloc4,9364,9360
DeepStack4,9644,9640
Generics4,9364,9360
MultiThread4,9964,9960
Comprehensive4,9944,9922*
ExceptionHandling4,9584,9580
StructScenarios4,9404,9400
DynamicMethods6,5206,5200
PInvoke4,9364,9360

Instruction-level stress (9 debuggees, ~226K verifications):
All 9 debuggees pass with zero failures across 226,452 verifications.

*Comprehensive's 2 failures are pre-existing legacy DAC issues (DAC returns 0 refs for background threads in kernel waits; cDAC and runtime agree).

@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

Adds a cDAC GC-stress verification harness and extends the cDAC stack-walk / stack-GC-ref pipeline so cDAC stack reference enumeration can be compared against runtime scanning at stress points.

Changes:

  • Implements/extends cDAC stack reference enumeration (including Frame-based scanning paths like PromoteCallerStack via GCRefMap / MetaSig) and wires SOS GetStackReferences to the cDAC contract.
  • Introduces a new GC stress integration test project with debuggee apps and orchestration targets.
  • Extends CoreCLR cDAC stress/GC stress plumbing and data descriptors to support the new stack-walk and frame-scanning capabilities.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 12 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.csUpdates mock type layout for execution manager-related data.
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.csUpdates mock type layouts (ExceptionInfo/Thread) for contract tests.
src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csprojExcludes new GCStressTests folder from the existing unit test project compilation.
src/native/managed/cdac/tests/GCStressTests/README.mdDocuments how to build/run the new GC stress tests.
src/native/managed/cdac/tests/GCStressTests/Microsoft.Diagnostics.DataContractReader.GCStressTests.csprojAdds a dedicated GC stress test project.
src/native/managed/cdac/tests/GCStressTests/GCStressTests.targetsMSBuild orchestration to discover/build debuggee projects.
src/native/managed/cdac/tests/GCStressTests/GCStressTestBase.csTest harness to run debuggees under corerun and parse verification logs.
src/native/managed/cdac/tests/GCStressTests/GCStressResults.csParses the native verification log into structured pass/fail/skip counts.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/Program.csAdds a P/Invoke-focused debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/PInvoke.csprojDebuggee project file for PInvoke scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/Program.csAdds a multi-threaded debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/MultiThread.csprojDebuggee project file for MultiThread scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Program.csAdds a generics/interface/delegate debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Generics.csprojDebuggee project file for Generics scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/Program.csAdds an exception-handling/funclet debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/ExceptionHandling.csprojDebuggee project file for ExceptionHandling scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Directory.Build.propsShared build props for debuggee projects (output layout, TFM, etc.).
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/Program.csAdds deep-recursion debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/DeepStack.csprojDebuggee project file for DeepStack scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Program.csAdds comprehensive “all scenarios” debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Comprehensive.csprojDebuggee project file for Comprehensive scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/Program.csAdds basic allocation/live-ref debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/BasicAlloc.csprojDebuggee project file for BasicAlloc scenario.
src/native/managed/cdac/tests/GCStressTests/BasicGCStressTests.csTheory-based test suite that runs the debuggees and asserts pass rate.
src/native/managed/cdac/tests/gcstress/known-issues.mdCaptures known mismatch classes and limitations.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.csImplements GetStackReferences using the cDAC contract rather than legacy DAC fallback.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.csExtends StubDispatchFrame data with GCRefMap pointer.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.csAdds ExternalMethodFrame contract data type (GCRefMap).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.csAdds DynamicHelperFrame contract data type (flags).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.csAdds clause-range fields used for catch-handler resumption offset selection.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.csRefactors stack-walk filtering and adds Frame-based GC root scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAdds optional relOffset override support for GC ref enumeration.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.csImplements GCRefMap decoding for transition-block scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/CorSigParser.csAdds minimal signature parsing to classify parameters for MetaSig-based scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.csAdds FindFirstInterruptiblePoint API to GCInfo decoders.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.csImplements FindFirstInterruptiblePoint using decoded interruptible ranges.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.csFixes code-start lookup for exception clause enumeration and adds minor flow adjustments.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.csAdds TransitionBlock-related global names used by frame scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.csAdds new DataType enum values for new frame contracts.
src/native/managed/cdac/cdac.slnxAdds the new GC stress test project to the cDAC solution.
src/coreclr/vm/gccover.cppAdds step-based skipping to reduce overhead when throttling verification.
src/coreclr/vm/frames.hExposes additional frame fields to cDAC via cdac_data<> descriptors.
src/coreclr/vm/datadescriptor/datadescriptor.incAdds new contract fields/globals for frames and TransitionBlock layout.
src/coreclr/vm/cdacstress.cppUpdates in-process cDAC/DAC verification logic, logging, and step behavior.
eng/Subsets.propsAdds an on-demand subset for running GC stress tests.
docs/design/datacontracts/StackWalk.mdDocuments the new frame fields and TransitionBlock globals in the StackWalk contract.
Comments suppressed due to low confidence (1)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:228

  • ThreadFields contains ProfilerFilterContext twice (lines 224 and 228). Duplicate field entries will skew offsets and make the mock descriptors inconsistent with the real data descriptor. Keep a single ProfilerFilterContext entry in the correct order.

Comment threadsrc/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/README.md Outdated

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

Comments suppressed due to low confidence (2)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:229

  • ThreadFields now includes DebuggerFilterContext/ProfilerFilterContext twice (duplicate entries at the end of the list). This can cause ambiguous/incorrect field offsets in the mock type layout. Keep each field only once.
    src/coreclr/vm/cdacstress.cpp:963
  • CompareRefSets uses a fixed-size matched[MAX_COLLECTED_REFS] buffer but no longer validates that countA/countB are <= MAX_COLLECTED_REFS. Since CollectStackRefs appends without a hard cap, this can lead to out-of-bounds access when countA or countB exceeds 4096. Reintroduce a guard or allocate the match state sized to the counts.
 return true;
bool matched[MAX_COLLECTED_REFS] = {};
for (int i = 0; i < countA; i++)

Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/gccover.cpp Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/GCStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 75ae7fe to 5419d15CompareApril 13, 2026 15:30
CopilotAI review requested due to automatic review settings April 13, 2026 15:47
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 5419d15 to 8ef9b22CompareApril 13, 2026 15:47

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

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated

@max-charlambmax-charlamb left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

feedback for local copilot

Comment threadsrc/native/managed/cdac/tests/StressTests/analysis/analyze-refs.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/StressTests.targets Outdated
Comment threadsrc/coreclr/vm/datadescriptor/datadescriptor.inc Outdated
CopilotAI review requested due to automatic review settings April 13, 2026 17:33

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

Comments suppressed due to low confidence (1)

src/coreclr/vm/cdacstress.cpp:625

  • CollectStackRefs appends to pRefs without any cap. Later comparisons allocate fixed-size arrays sized MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed/bUsed) and assume counts fit. If the DAC returns > MAX_COLLECTED_REFS refs, this will lead to out-of-bounds writes/reads. Add an explicit limit/overflow handling in CollectStackRefs (stop at MAX_COLLECTED_REFS and mark overflow / SKIP), or change the later comparison logic to handle arbitrary counts safely.
 SOSStackRefData refData;
unsigned int fetched = 0;
while (true)
{
hr = pEnum->Next(1, &refData, &fetched);
if (FAILED(hr) || fetched == 0)
break;
StackRef ref;
ref.Address = refData.Address;
ref.Object = refData.Object;
ref.Flags = refData.Flags;
ref.Source = refData.Source;
ref.SourceType = refData.SourceType;
ref.Register = refData.Register;
ref.Offset = refData.Offset;
ref.StackPointer = refData.StackPointer;
pRefs->Append(ref);
}

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/known-issues.md
Comment threaddocs/design/datacontracts/StackWalk.md
CopilotAI review requested due to automatic review settings April 13, 2026 19: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 52 out of 52 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/coreclr/vm/cdacstress.cpp
CopilotAI review requested due to automatic review settings April 13, 2026 20:24
Max Charlamband others added 5 commits April 22, 2026 15:23
- Fix platform-specific ctx.Rip usage: use GetIP(&ctx) instead
- Fix if( style: add space after if keyword in gccover.cpp
- Add RVA bounds validation in FindGCRefMap before uint cast
- Remove stale analysis file (eh-throwhelper-report.md)
- Update AssertHighPassRate comment to reflect current state
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Run GC stress verification tests in the runtime-diagnostics pipeline by
piggybacking on the existing CdacDumpTests Checked runtime build. The
stress tests run as a second Helix submission after the dump tests,
using the testhost shared framework as CORE_ROOT.
Runs on all cdacDumpPlatforms (windows_x64, linux_x64, etc.). On
non-Windows platforms, only instruction-level stress (via DoGcStress)
is supported; allocation-level stress (VerifyAtAllocPoint) is skipped
because it requires Windows-only RtlCaptureContext/RtlVirtualUnwind.
Infrastructure:
- cdac-stress-helix.proj: Helix SDK project that sends testhost as
correlation payload and stress test debuggees + test assembly as
work item payload. Sets CORE_ROOT env var for the test harness.
- prepare-cdac-stress-helix-steps.yml: Pipeline template that builds
debuggees, prepares Helix payload, and finds testhost directory.
- StressTests.targets: Added PrepareHelixPayload and BuildDebuggeesOnly
targets for CI payload preparation.
- CdacStressTestBase.cs: Added HELIX_WORKITEM_PAYLOAD support for
finding debuggees in Helix environment.
- runtime-diagnostics.yml: Extended CdacDumpTests buildArgs to include
tools.cdacstresstests, added stress test Helix submission steps.
- cdacstress.cpp: Guard VerifyAtAllocPoint with TARGET_WINDOWS for
RtlCaptureContext/RtlVirtualUnwind APIs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a custom signature decoder that handles runtime-internal type codes
(ELEMENT_TYPE_INTERNAL 0x21, ELEMENT_TYPE_CMOD_INTERNAL 0x22) which
SRM's SignatureDecoder cannot parse.
- IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of
SRM's ISignatureTypeProvider, adding GetInternalType for resolving
embedded TypeHandle pointers via the runtime type system.
- ISignatureReader + SpanSignatureReader: abstraction for reading
signature bytes from different sources (spans, target memory).
- RuntimeSignatureDecoder<TType, TGenericContext, TReader>: ref struct
decoder that handles all standard ECMA-335 types plus internal types.
- GcSignatureTypeProvider: implements IRuntimeSignatureTypeProvider to
classify types for GC scanning, resolving internal types via
RuntimeTypeSystem.GetSignatureCorElementType.
Key correctness details vs SRM's SignatureDecoder:
- CLASS/VALUETYPE tokens decoded as TypeDefOrRefOrSpecEncoded per
ECMA-335 II.23.2.8 (tag in low 2 bits, RID in upper bits).
- CMOD_INTERNAL correctly skips the required/optional flag byte before
the TypeHandle pointer, matching sigparser.h layout.
- ReadCompressedSignedInt uses ECMA sign-extension-by-width, not zigzag.
- ReadCompressedUInt rejects invalid 111xxxxx prefix.
- Unknown type codes throw BadImageFormatException instead of silently
returning Object (which would create false-positive GC refs).
- Method signature header kind is validated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port crossgen2's ArgIterator, TransitionBlock, and GC scanning logic
into the cDAC contracts for correct per-architecture argument placement.
- Add OffsetOfFloatArgumentRegisters to TransitionBlock data descriptor
- CallingConventionInfo: hybrid data descriptor + ABI invariant constants
for x86, x64 (Windows/Unix), ARM32, ARM64, LoongArch64, RISC-V64
- ArgTypeInfo: pre-computed type info replacing crossgen2's TypeHandle
- ArgIteratorData: parsed method signature holder
- ArgIterator: maps each argument to register/stack offsets via
GetNextOffset() with per-architecture register allocation
- Integrate into FrameIterator.PromoteCallerStackHelper replacing the
simplified 1-slot-per-param approach with proper offset computation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Quote debuggee DLL path in ProcessStartInfo.Arguments (dotnet#31)
- Fix timeout: use async stdout/stderr reads so WaitForExit works (dotnet#32)
- Update stale comment about ELEMENT_TYPE_INTERNAL limitation (dotnet#35)
- Move CallingConvention types to StackWalkHelpers.CallingConvention namespace (dotnet#37)
- Simplify x86 register eligibility check in ArgIterator (dotnet#38)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 22, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 1a92e2c to b85dbacCompareApril 22, 2026 19:25

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

Comment on lines +16 to +20
// FirstThreadLink is an embedded SLink struct. Read the SLink.Next pointer
// from the field's address to get the first thread link pointer.
Target.TypeInfo slinkType = target.GetTypeInfo(DataType.SLink);
TargetPointer slinkAddr = address + (ulong)type.Fields[nameof(FirstThreadLink)].Offset;
FirstThreadLink = target.ReadPointerField(slinkAddr, slinkType, "Next");

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

ThreadStore now calls target.GetTypeInfo(DataType.SLink) and reads field "Next", but the CoreCLR data descriptor in this PR doesn't define a SLink type/field (could not find any CDAC_TYPE_BEGIN/FIELD for SLink). This will cause GetTypeInfo(DataType.SLink) to fail at runtime. Either add an SLink descriptor (with a "Next" pointer) on the runtime side or avoid needing type info here (e.g., treat the embedded SLink as a single pointer at offset 0).

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 29
SLink,
ThreadLocalData,

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

DataType adds SLink, but there is no corresponding runtime data descriptor definition (no CDAC_TYPE_BEGIN/FIELD(SLink, ...) found). Any attempt to read this type via Target.GetTypeInfo(DataType.SLink) will fail. Either add the runtime descriptor entry for SLink or remove this enum value and read the embedded link without type metadata.

Suggested change
SLink,
ThreadLocalData,
ThreadLocalData=17,

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +69
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

These DataReceived event handlers append to strings via "+=" from background threads. That is not thread-safe and can lead to lost/garbled output under contention. Consider using a StringBuilder with locking (or ConcurrentQueue) for stderr/stdout aggregation.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +74
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

Same thread-safety issue as stderr: appending to stdout with += from OutputDataReceived is racy. Use a synchronized StringBuilder/collector to avoid missing output and to keep logs reliable when tests fail.

Copilot uses AI. Check for mistakes.
Comment on lines +732 to +751
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
private static ArgTypeInfo GcTypeKindToArgTypeInfo(GcTypeKind kind, int pointerSize)
{
return kind switch
{
GcTypeKind.None => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
GcTypeKind.Ref => ArgTypeInfo.ForPrimitive(CorElementType.Class, pointerSize),
GcTypeKind.Interior => ArgTypeInfo.ForPrimitive(CorElementType.Byref, pointerSize),
GcTypeKind.Other => new ArgTypeInfo
{
CorElementType = CorElementType.ValueType,
Size = pointerSize, // Conservative: assume pointer-sized for now
IsValueType = true,
},
_ => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
};
}

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

GcTypeKindToArgTypeInfo collapses all non-GC-ref types to CorElementType.I/pointer-sized and doesn't preserve float vs integer vs exact primitive sizes. ArgIterator offset calculation depends on the real signature shape (especially on Unix x64/ARM64 where float regs and argument sizing affect later argument placement), so this can produce incorrect offsets and cause missed/incorrect GC ref reporting for signatures with floats or non-pointer-sized primitives. Consider decoding into a richer type representation (e.g., CorElementType + size) and building ArgTypeInfo from that rather than from GcTypeKind.

Suggested change
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.None=> ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,// Conservative: assume pointer-sized for now
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
/// Converts a <see cref="GcTypeKind"/> to a conservative fallback <see cref="ArgTypeInfo"/>
/// when an exact signature cannot be decoded. Callers should prefer decoding the real
/// managed signature via <see cref="TryGetArgTypeInfosFromMethodSignature(ReadOnlySpan{byte}, int, out ArgTypeInfo[])"/>
/// so that floating-point and non-pointer-sized primitives retain their real layout.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
privatestaticboolTryGetArgTypeInfosFromMethodSignature(ReadOnlySpan<byte>signatureBytes,intpointerSize,outArgTypeInfo[]argTypes)
{
argTypes=Array.Empty<ArgTypeInfo>();
if(signatureBytes.IsEmpty)
returnfalse;
BlobReaderreader=new(signatureBytes);
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
ArgTypeInfo[]decodedArgTypes=newArgTypeInfo[parameterCount];
for(inti=0;i<parameterCount;i++)
{
if(!TryReadArgTypeInfo(refreader,pointerSize,outdecodedArgTypes[i]))
returnfalse;
}
argTypes=decodedArgTypes;
returntrue;
}
privatestaticboolTryReadArgTypeInfo(refBlobReaderreader,intpointerSize,outArgTypeInfoargTypeInfo)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Boolean:
caseCorElementType.I1:
caseCorElementType.U1:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,1);
returntrue;
caseCorElementType.Char:
caseCorElementType.I2:
caseCorElementType.U2:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,2);
returntrue;
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.R4:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,4);
returntrue;
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R8:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,8);
returntrue;
caseCorElementType.I:
caseCorElementType.U:
caseCorElementType.Ptr:
caseCorElementType.FnPtr:
if(elementTypeisCorElementType.Ptr or CorElementType.FnPtr)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize);
returntrue;
caseCorElementType.Byref:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize);
returntrue;
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.Class:
if(elementTypeisCorElementType.Class)
reader.ReadCompressedInteger();
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.SzArray:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.ValueType:
reader.ReadCompressedInteger();
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
}
default:
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=default;
returnfalse;
}
privatestaticboolTrySkipSignatureType(refBlobReaderreader)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Void:
caseCorElementType.Boolean:
caseCorElementType.Char:
caseCorElementType.I1:
caseCorElementType.U1:
caseCorElementType.I2:
caseCorElementType.U2:
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R4:
caseCorElementType.R8:
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.TypedByref:
caseCorElementType.I:
caseCorElementType.U:
returntrue;
caseCorElementType.Class:
caseCorElementType.ValueType:
caseCorElementType.Var:
caseCorElementType.MVar:
reader.ReadCompressedInteger();
returntrue;
caseCorElementType.Byref:
caseCorElementType.Ptr:
caseCorElementType.SzArray:
returnTrySkipSignatureType(refreader);
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
caseCorElementType.Array:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intrank=reader.ReadCompressedInteger();
intsizes=reader.ReadCompressedInteger();
for(inti=0;i<sizes;i++)
reader.ReadCompressedInteger();
intlowerBounds=reader.ReadCompressedInteger();
for(inti=0;i<lowerBounds;i++)
reader.ReadCompressedInteger();
returnrank>=0;
}
caseCorElementType.FnPtr:
{
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
for(inti=0;i<parameterCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
default:
returnfalse;
}
}
returnfalse;
}

Copilot uses AI. Check for mistakes.

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

Comment on lines +582 to 586
static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray<StackRef>* pRefs,
const char* label = nullptr)
{
if (pSosDac == nullptr)
return false;

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

CollectStackRefs currently appends every ref returned by ISOSStackRefEnum::Next with no upper bound. Later comparison helpers allocate fixed-size arrays sized by MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed[MAX_COLLECTED_REFS]), so if enumeration returns more than MAX_COLLECTED_REFS it can lead to out-of-bounds writes. Please cap collection to MAX_COLLECTED_REFS (and record an overflow/skip reason) or make the comparison logic handle arbitrarily large ref sets safely.

Copilot uses AI. Check for mistakes.
Comment on lines +1359 to +1362
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

When CollectRuntimeStackRefs overflows, the code logs a [SKIP] line but continues and still computes rtMatch/pass (and does not increment s_verifySkip). If CDACSTRESS_USE_DAC is not set, this can produce false failures based on a truncated runtime ref set. Consider treating runtime overflow as an actual skip (increment skip counter + return) when RT comparison is used for pass/fail, or otherwise avoid using rtMatch in the presence of overflow.

Suggested change
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
if (rtOverflow)
{
InterlockedIncrement(&s_verifySkip);
if (s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
}
return;

Copilot uses AI. Check for mistakes.
Comment on lines +823 to +824
ReportSlot(slotIndex, reportScratchSlots: true, reportFpBasedSlotsOnly, reportSlot);
}

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

In ReportUntrackedAndSucceed, ReportSlot is called with reportScratchSlots: true, which forces reporting scratch registers/stack slots even when CodeManagerFlags.ActiveStackFrame is not set (reportScratchSlots local is false). This changes GC root enumeration behavior and can introduce extra roots for non-leaf frames. It looks like this should pass the reportScratchSlots variable instead of always true.

Suggested change
ReportSlot(slotIndex,reportScratchSlots:true,reportFpBasedSlotsOnly,reportSlot);
}
ReportSlot(slotIndex,reportScratchSlots,reportFpBasedSlotsOnly,reportSlot);
}

Copilot uses AI. Check for mistakes.
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 49c8435 to de5cb46CompareApril 24, 2026 15:56
- Include RuntimeInfoOperatingSystem.Apple in Unix x64 ABI check
(macOS x64 uses SysV ABI, not Windows ABI)
- Thread MetadataReader from GetMethodSignatureBytes through
RuntimeSignatureDecoder to provider methods (instead of null!)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 24, 2026 16:07

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

Comment on lines +960 to 964
// Compare two ref sets using two-phase matching (for RT comparison where we
// don't have Source info). Returns true if all refs match.
static bool CompareRefSetsFlat(StackRef* refsA, int countA, StackRef* refsB, int countB)
{
if (countA != countB)

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

CompareRefSetsFlat uses a fixed-size matched[MAX_COLLECTED_REFS] buffer, but cDAC/DAC ref collection (CollectStackRefs) is unbounded. Without a guard/cap, a large ref set (>4096) can cause out-of-bounds writes during matching. Consider capping cDAC/DAC collection to MAX_COLLECTED_REFS (and treating overflow as a [SKIP]) or using dynamically sized bookkeeping here.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +76
// Read both stdout and stderr asynchronously to avoid deadlock
// when pipe buffers fill, and to allow WaitForExit timeout to work.
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};
process.BeginErrorReadLine();
process.BeginOutputReadLine();

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

The async stdout/stderr collection uses stdout += ... / stderr += ... inside DataReceived event handlers. These callbacks can run concurrently, and string concatenation is not thread-safe; it can also be costly for large output. Consider buffering with a thread-safe collector (e.g., ConcurrentQueue<string> or StringBuilder with a lock) and call process.WaitForExit() (or await stream completion) after WaitForExit(timeout) to ensure all async output has been drained before asserting/logging.

Copilot uses AI. Check for mistakes.
max-charlamb added a commit that referenced this pull request May 1, 2026
## Summary
Part 1 of 5 stacked PRs splitting
[#126408](#126408) into reviewable
pieces.
### What this PR contains
**Stack Walk GC Reference Scanning:**
- `PromoteCallerStack` / `PromoteCallerStackUsingGCRefMap` for
transition frames
- `GCRefMapDecoder` + `FindGCRefMap` with ReadyToRun import section
resolution
- `GcSignatureTypeProvider` for GC type classification
- `SOSDacImpl.GetStackReferences` fully implemented using cDAC contracts
- `GCInfoDecoder.EnumerateLiveSlots` promoted to `IGCInfo` contract
(returns `IReadOnlyList<LiveSlot>`)
- `GcSlotEnumerationOptions` replaces native `CodeManagerFlags` with
descriptive boolean properties
**Stack Walker Fixes:**
- `IsFirst` preserved for skipped frames (matches native
SFITER_SKIPPED_FRAME_FUNCTION)
- `IsInterrupted` state tracking for exception frames
(FaultingExceptionFrame, SoftwareExceptionFrame)
- `GetReturnAddress` gating in SW_FRAME (only UpdateRegDisplay if return
address non-null)
- Catch handler offset override via `GetInterruptibleRanges` for EH
resumption
**Contract API Additions:**
- `IGCInfo`: `EnumerateLiveSlots`, `GetStackBaseRegister`,
`GetInterruptibleRanges`
- `IExecutionManager`: `FindReadyToRunModule`
- `IRuntimeTypeSystem`: `RequiresInstArg`, `IsAsyncMethod`
- `IStackWalk`: `WalkStackReferences`
**Data Descriptor Changes:**
- Removed `ZapModule` and `GCRefMap` cached pointers (always resolve via
`FindReadyToRunModule`)
- Added `Indirection` for StubDispatchFrame, ExternalMethodFrame
- Added `DynamicHelperFrame.DynamicHelperFrameFlags`
- Added TransitionBlock fields (`OffsetOfArgs`,
`ArgumentRegistersOffset`, `FirstGCRefMapSlot`)
- Added ReadyToRunInfo fields (`ImportSections`, `NumImportSections`)
- Added ExceptionInfo catch clause fields
(`ClauseForCatchHandlerStartPC`, `ClauseForCatchHandlerEndPC`)
**Documentation:**
- GCInfo.md: Comprehensive implementation docs (header/body decoding,
slot table, EnumerateLiveSlots algorithm, type definitions for
`LiveSlot`, `InterruptibleRange`, `GcSlotEnumerationOptions`)
- StackWalk.md: GC scanning algorithm, GCRefMap resolution flow, return
address per frame type, `WalkStackReferences` API
- ExecutionManager.md: `FindReadyToRunModule` API and implementation
- RuntimeTypeSystem.md: `RequiresInstArg`, `IsAsyncMethod` APIs
### Stack overview
| PR | Content | Status |
|----|---------|--------|
| **This PR** | Stack walk fixes + GC scanning | Open |
| PR 2 | RuntimeSignatureDecoder (ELEMENT_TYPE_INTERNAL) | Pending |
| PR 3 | ArgIterator port from crossgen2 | Pending |
| PR 4 | Native stress framework (cdacstress.cpp) | Pending |
| PR 5 | Managed stress tests + CI pipeline | Pending |
### Testing
- 1727/1751 unit tests pass (24 pre-existing ThreadTests failures on
main)
- Dump tests (StackWalkDumpTests, StackReferenceDumpTests) validate
end-to-end
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

Closing in favor of stacked PR approach

steveisok pushed a commit that referenced this pull request May 11, 2026
…5) (#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[#126408](#126408). Builds on
[#127395](#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jakobbotsch pushed a commit to jakobbotsch/runtime that referenced this pull request May 12, 2026
…5) (dotnet#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[dotnet#126408](dotnet#126408). Builds on
[dotnet#127395](dotnet#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 1, 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.

2 participants

@max-charlamb
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

[cDAC] Stack walk GC stress verification and fixes - #126408

Closed
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5
Closed

[cDAC] Stack walk GC stress verification and fixes#126408
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Apr 1, 2026

Copy link
Copy Markdown
Member

Summary

cDAC GC stress verification tool (DOTNET_CdacStress) that compares stack GC references between the cDAC and the runtime at allocation stress points. Includes stack walker fixes, GC reference scanning implementation, custom signature decoding, and calling convention argument iteration.

Note

This PR description was updated with AI assistance from Copilot.

Stack walker fixes

  • Fix SW_SKIPPED_FRAME: do not call UpdateContextFromFrame (matches native SFITER_SKIPPED_FRAME_FUNCTION which does not call UpdateRegDisplay)
  • Fix skipped-frame loop: check for more skipped frames before yielding managed method (prevents duplicate EnumGcRefs between consecutive skipped frames)
  • Restructure Filter() to drive Next() directly, matching native Filter()+NextRaw() integration (prevents funclet-to-parent walk cycles)
  • Remove SkipActiveICFOnce/SkipCurrentFrameInCheck — active ICF double-yield is natural and harmless
  • Remove IsAtFirstPassExceptionThrowSite — native does not suppress first-pass refs
  • Fix IsFirst not preserved for skipped frames (was causing IsActiveFrame=false for the topmost managed frame)

GC reference scanning

  • Implement PromoteCallerStack for stub frames (GCRefMap + MetaSig paths)
  • Implement SOSDacImpl.GetStackReferences using cDAC contract (was falling back to legacy DAC)
  • Read FilterContext for stack walk starting context
  • Three-way cDAC/DAC/RT comparison with InProcessDataTarget
  • DOTNET_CdacStress bit flags: ALLOC/INSTR/REFS/WALK/USE_DAC/UNIQUE

RuntimeSignatureDecoder

Custom signature decoder that handles ELEMENT_TYPE_INTERNAL (0x21) and ELEMENT_TYPE_CMOD_INTERNAL (0x22) which SRM's SignatureDecoder cannot parse. These occur in IL stubs, marshalling stubs, and unsafe accessor frames.

  • IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of SRM's ISignatureTypeProvider, adds GetInternalType and GetInternalModifiedType
  • ISignatureReader + SpanSignatureReader: endianness-aware reader abstraction
  • Correct ECMA-335 compliance: TypeDefOrRefOrSpecEncoded token decoding, sign-extension-by-width for compressed signed ints, bounds validation

ArgIterator (ported from crossgen2)

Proper calling convention analysis replacing the simplified 1-slot-per-param approach.

  • CallingConventionInfo: hybrid data descriptor layout values + ABI invariant constants for all architectures (x86, x64 Windows/Unix, ARM32, ARM64, LoongArch64, RISC-V64)
  • ArgIterator.GetNextOffset(): maps each argument to its actual register or stack offset
  • OffsetOfFloatArgumentRegisters added to TransitionBlock data descriptor
  • Handles multi-slot args, forced-byref params, return buffer placement, async continuation

CI infrastructure

  • cDAC stress tests run in Helix via cdac-stress-helix.proj
  • Extended runtime-diagnostics.yml CdacDumpTests buildArgs with +tools.cdacstresstests

Test results

Allocation-level stress (9 debuggees, ~46K verifications):

DebuggeeVerificationsPassFail
BasicAlloc4,9364,9360
DeepStack4,9644,9640
Generics4,9364,9360
MultiThread4,9964,9960
Comprehensive4,9944,9922*
ExceptionHandling4,9584,9580
StructScenarios4,9404,9400
DynamicMethods6,5206,5200
PInvoke4,9364,9360

Instruction-level stress (9 debuggees, ~226K verifications):
All 9 debuggees pass with zero failures across 226,452 verifications.

*Comprehensive's 2 failures are pre-existing legacy DAC issues (DAC returns 0 refs for background threads in kernel waits; cDAC and runtime agree).

@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

Adds a cDAC GC-stress verification harness and extends the cDAC stack-walk / stack-GC-ref pipeline so cDAC stack reference enumeration can be compared against runtime scanning at stress points.

Changes:

  • Implements/extends cDAC stack reference enumeration (including Frame-based scanning paths like PromoteCallerStack via GCRefMap / MetaSig) and wires SOS GetStackReferences to the cDAC contract.
  • Introduces a new GC stress integration test project with debuggee apps and orchestration targets.
  • Extends CoreCLR cDAC stress/GC stress plumbing and data descriptors to support the new stack-walk and frame-scanning capabilities.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 12 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.csUpdates mock type layout for execution manager-related data.
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.csUpdates mock type layouts (ExceptionInfo/Thread) for contract tests.
src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csprojExcludes new GCStressTests folder from the existing unit test project compilation.
src/native/managed/cdac/tests/GCStressTests/README.mdDocuments how to build/run the new GC stress tests.
src/native/managed/cdac/tests/GCStressTests/Microsoft.Diagnostics.DataContractReader.GCStressTests.csprojAdds a dedicated GC stress test project.
src/native/managed/cdac/tests/GCStressTests/GCStressTests.targetsMSBuild orchestration to discover/build debuggee projects.
src/native/managed/cdac/tests/GCStressTests/GCStressTestBase.csTest harness to run debuggees under corerun and parse verification logs.
src/native/managed/cdac/tests/GCStressTests/GCStressResults.csParses the native verification log into structured pass/fail/skip counts.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/Program.csAdds a P/Invoke-focused debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/PInvoke.csprojDebuggee project file for PInvoke scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/Program.csAdds a multi-threaded debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/MultiThread.csprojDebuggee project file for MultiThread scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Program.csAdds a generics/interface/delegate debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Generics.csprojDebuggee project file for Generics scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/Program.csAdds an exception-handling/funclet debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/ExceptionHandling.csprojDebuggee project file for ExceptionHandling scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Directory.Build.propsShared build props for debuggee projects (output layout, TFM, etc.).
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/Program.csAdds deep-recursion debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/DeepStack.csprojDebuggee project file for DeepStack scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Program.csAdds comprehensive “all scenarios” debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Comprehensive.csprojDebuggee project file for Comprehensive scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/Program.csAdds basic allocation/live-ref debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/BasicAlloc.csprojDebuggee project file for BasicAlloc scenario.
src/native/managed/cdac/tests/GCStressTests/BasicGCStressTests.csTheory-based test suite that runs the debuggees and asserts pass rate.
src/native/managed/cdac/tests/gcstress/known-issues.mdCaptures known mismatch classes and limitations.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.csImplements GetStackReferences using the cDAC contract rather than legacy DAC fallback.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.csExtends StubDispatchFrame data with GCRefMap pointer.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.csAdds ExternalMethodFrame contract data type (GCRefMap).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.csAdds DynamicHelperFrame contract data type (flags).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.csAdds clause-range fields used for catch-handler resumption offset selection.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.csRefactors stack-walk filtering and adds Frame-based GC root scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAdds optional relOffset override support for GC ref enumeration.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.csImplements GCRefMap decoding for transition-block scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/CorSigParser.csAdds minimal signature parsing to classify parameters for MetaSig-based scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.csAdds FindFirstInterruptiblePoint API to GCInfo decoders.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.csImplements FindFirstInterruptiblePoint using decoded interruptible ranges.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.csFixes code-start lookup for exception clause enumeration and adds minor flow adjustments.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.csAdds TransitionBlock-related global names used by frame scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.csAdds new DataType enum values for new frame contracts.
src/native/managed/cdac/cdac.slnxAdds the new GC stress test project to the cDAC solution.
src/coreclr/vm/gccover.cppAdds step-based skipping to reduce overhead when throttling verification.
src/coreclr/vm/frames.hExposes additional frame fields to cDAC via cdac_data<> descriptors.
src/coreclr/vm/datadescriptor/datadescriptor.incAdds new contract fields/globals for frames and TransitionBlock layout.
src/coreclr/vm/cdacstress.cppUpdates in-process cDAC/DAC verification logic, logging, and step behavior.
eng/Subsets.propsAdds an on-demand subset for running GC stress tests.
docs/design/datacontracts/StackWalk.mdDocuments the new frame fields and TransitionBlock globals in the StackWalk contract.
Comments suppressed due to low confidence (1)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:228

  • ThreadFields contains ProfilerFilterContext twice (lines 224 and 228). Duplicate field entries will skew offsets and make the mock descriptors inconsistent with the real data descriptor. Keep a single ProfilerFilterContext entry in the correct order.

Comment threadsrc/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/README.md Outdated

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

Comments suppressed due to low confidence (2)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:229

  • ThreadFields now includes DebuggerFilterContext/ProfilerFilterContext twice (duplicate entries at the end of the list). This can cause ambiguous/incorrect field offsets in the mock type layout. Keep each field only once.
    src/coreclr/vm/cdacstress.cpp:963
  • CompareRefSets uses a fixed-size matched[MAX_COLLECTED_REFS] buffer but no longer validates that countA/countB are <= MAX_COLLECTED_REFS. Since CollectStackRefs appends without a hard cap, this can lead to out-of-bounds access when countA or countB exceeds 4096. Reintroduce a guard or allocate the match state sized to the counts.
 return true;
bool matched[MAX_COLLECTED_REFS] = {};
for (int i = 0; i < countA; i++)

Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/gccover.cpp Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/GCStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 75ae7fe to 5419d15CompareApril 13, 2026 15:30
CopilotAI review requested due to automatic review settings April 13, 2026 15:47
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 5419d15 to 8ef9b22CompareApril 13, 2026 15:47

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

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated

@max-charlambmax-charlamb left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

feedback for local copilot

Comment threadsrc/native/managed/cdac/tests/StressTests/analysis/analyze-refs.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/StressTests.targets Outdated
Comment threadsrc/coreclr/vm/datadescriptor/datadescriptor.inc Outdated
CopilotAI review requested due to automatic review settings April 13, 2026 17:33

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

Comments suppressed due to low confidence (1)

src/coreclr/vm/cdacstress.cpp:625

  • CollectStackRefs appends to pRefs without any cap. Later comparisons allocate fixed-size arrays sized MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed/bUsed) and assume counts fit. If the DAC returns > MAX_COLLECTED_REFS refs, this will lead to out-of-bounds writes/reads. Add an explicit limit/overflow handling in CollectStackRefs (stop at MAX_COLLECTED_REFS and mark overflow / SKIP), or change the later comparison logic to handle arbitrary counts safely.
 SOSStackRefData refData;
unsigned int fetched = 0;
while (true)
{
hr = pEnum->Next(1, &refData, &fetched);
if (FAILED(hr) || fetched == 0)
break;
StackRef ref;
ref.Address = refData.Address;
ref.Object = refData.Object;
ref.Flags = refData.Flags;
ref.Source = refData.Source;
ref.SourceType = refData.SourceType;
ref.Register = refData.Register;
ref.Offset = refData.Offset;
ref.StackPointer = refData.StackPointer;
pRefs->Append(ref);
}

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/known-issues.md
Comment threaddocs/design/datacontracts/StackWalk.md
CopilotAI review requested due to automatic review settings April 13, 2026 19: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 52 out of 52 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/coreclr/vm/cdacstress.cpp
CopilotAI review requested due to automatic review settings April 13, 2026 20:24
Max Charlamband others added 5 commits April 22, 2026 15:23
- Fix platform-specific ctx.Rip usage: use GetIP(&ctx) instead
- Fix if( style: add space after if keyword in gccover.cpp
- Add RVA bounds validation in FindGCRefMap before uint cast
- Remove stale analysis file (eh-throwhelper-report.md)
- Update AssertHighPassRate comment to reflect current state
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Run GC stress verification tests in the runtime-diagnostics pipeline by
piggybacking on the existing CdacDumpTests Checked runtime build. The
stress tests run as a second Helix submission after the dump tests,
using the testhost shared framework as CORE_ROOT.
Runs on all cdacDumpPlatforms (windows_x64, linux_x64, etc.). On
non-Windows platforms, only instruction-level stress (via DoGcStress)
is supported; allocation-level stress (VerifyAtAllocPoint) is skipped
because it requires Windows-only RtlCaptureContext/RtlVirtualUnwind.
Infrastructure:
- cdac-stress-helix.proj: Helix SDK project that sends testhost as
correlation payload and stress test debuggees + test assembly as
work item payload. Sets CORE_ROOT env var for the test harness.
- prepare-cdac-stress-helix-steps.yml: Pipeline template that builds
debuggees, prepares Helix payload, and finds testhost directory.
- StressTests.targets: Added PrepareHelixPayload and BuildDebuggeesOnly
targets for CI payload preparation.
- CdacStressTestBase.cs: Added HELIX_WORKITEM_PAYLOAD support for
finding debuggees in Helix environment.
- runtime-diagnostics.yml: Extended CdacDumpTests buildArgs to include
tools.cdacstresstests, added stress test Helix submission steps.
- cdacstress.cpp: Guard VerifyAtAllocPoint with TARGET_WINDOWS for
RtlCaptureContext/RtlVirtualUnwind APIs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a custom signature decoder that handles runtime-internal type codes
(ELEMENT_TYPE_INTERNAL 0x21, ELEMENT_TYPE_CMOD_INTERNAL 0x22) which
SRM's SignatureDecoder cannot parse.
- IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of
SRM's ISignatureTypeProvider, adding GetInternalType for resolving
embedded TypeHandle pointers via the runtime type system.
- ISignatureReader + SpanSignatureReader: abstraction for reading
signature bytes from different sources (spans, target memory).
- RuntimeSignatureDecoder<TType, TGenericContext, TReader>: ref struct
decoder that handles all standard ECMA-335 types plus internal types.
- GcSignatureTypeProvider: implements IRuntimeSignatureTypeProvider to
classify types for GC scanning, resolving internal types via
RuntimeTypeSystem.GetSignatureCorElementType.
Key correctness details vs SRM's SignatureDecoder:
- CLASS/VALUETYPE tokens decoded as TypeDefOrRefOrSpecEncoded per
ECMA-335 II.23.2.8 (tag in low 2 bits, RID in upper bits).
- CMOD_INTERNAL correctly skips the required/optional flag byte before
the TypeHandle pointer, matching sigparser.h layout.
- ReadCompressedSignedInt uses ECMA sign-extension-by-width, not zigzag.
- ReadCompressedUInt rejects invalid 111xxxxx prefix.
- Unknown type codes throw BadImageFormatException instead of silently
returning Object (which would create false-positive GC refs).
- Method signature header kind is validated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port crossgen2's ArgIterator, TransitionBlock, and GC scanning logic
into the cDAC contracts for correct per-architecture argument placement.
- Add OffsetOfFloatArgumentRegisters to TransitionBlock data descriptor
- CallingConventionInfo: hybrid data descriptor + ABI invariant constants
for x86, x64 (Windows/Unix), ARM32, ARM64, LoongArch64, RISC-V64
- ArgTypeInfo: pre-computed type info replacing crossgen2's TypeHandle
- ArgIteratorData: parsed method signature holder
- ArgIterator: maps each argument to register/stack offsets via
GetNextOffset() with per-architecture register allocation
- Integrate into FrameIterator.PromoteCallerStackHelper replacing the
simplified 1-slot-per-param approach with proper offset computation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Quote debuggee DLL path in ProcessStartInfo.Arguments (dotnet#31)
- Fix timeout: use async stdout/stderr reads so WaitForExit works (dotnet#32)
- Update stale comment about ELEMENT_TYPE_INTERNAL limitation (dotnet#35)
- Move CallingConvention types to StackWalkHelpers.CallingConvention namespace (dotnet#37)
- Simplify x86 register eligibility check in ArgIterator (dotnet#38)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 22, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 1a92e2c to b85dbacCompareApril 22, 2026 19:25

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

Comment on lines +16 to +20
// FirstThreadLink is an embedded SLink struct. Read the SLink.Next pointer
// from the field's address to get the first thread link pointer.
Target.TypeInfo slinkType = target.GetTypeInfo(DataType.SLink);
TargetPointer slinkAddr = address + (ulong)type.Fields[nameof(FirstThreadLink)].Offset;
FirstThreadLink = target.ReadPointerField(slinkAddr, slinkType, "Next");

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

ThreadStore now calls target.GetTypeInfo(DataType.SLink) and reads field "Next", but the CoreCLR data descriptor in this PR doesn't define a SLink type/field (could not find any CDAC_TYPE_BEGIN/FIELD for SLink). This will cause GetTypeInfo(DataType.SLink) to fail at runtime. Either add an SLink descriptor (with a "Next" pointer) on the runtime side or avoid needing type info here (e.g., treat the embedded SLink as a single pointer at offset 0).

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 29
SLink,
ThreadLocalData,

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

DataType adds SLink, but there is no corresponding runtime data descriptor definition (no CDAC_TYPE_BEGIN/FIELD(SLink, ...) found). Any attempt to read this type via Target.GetTypeInfo(DataType.SLink) will fail. Either add the runtime descriptor entry for SLink or remove this enum value and read the embedded link without type metadata.

Suggested change
SLink,
ThreadLocalData,
ThreadLocalData=17,

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +69
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

These DataReceived event handlers append to strings via "+=" from background threads. That is not thread-safe and can lead to lost/garbled output under contention. Consider using a StringBuilder with locking (or ConcurrentQueue) for stderr/stdout aggregation.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +74
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

Same thread-safety issue as stderr: appending to stdout with += from OutputDataReceived is racy. Use a synchronized StringBuilder/collector to avoid missing output and to keep logs reliable when tests fail.

Copilot uses AI. Check for mistakes.
Comment on lines +732 to +751
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
private static ArgTypeInfo GcTypeKindToArgTypeInfo(GcTypeKind kind, int pointerSize)
{
return kind switch
{
GcTypeKind.None => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
GcTypeKind.Ref => ArgTypeInfo.ForPrimitive(CorElementType.Class, pointerSize),
GcTypeKind.Interior => ArgTypeInfo.ForPrimitive(CorElementType.Byref, pointerSize),
GcTypeKind.Other => new ArgTypeInfo
{
CorElementType = CorElementType.ValueType,
Size = pointerSize, // Conservative: assume pointer-sized for now
IsValueType = true,
},
_ => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
};
}

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

GcTypeKindToArgTypeInfo collapses all non-GC-ref types to CorElementType.I/pointer-sized and doesn't preserve float vs integer vs exact primitive sizes. ArgIterator offset calculation depends on the real signature shape (especially on Unix x64/ARM64 where float regs and argument sizing affect later argument placement), so this can produce incorrect offsets and cause missed/incorrect GC ref reporting for signatures with floats or non-pointer-sized primitives. Consider decoding into a richer type representation (e.g., CorElementType + size) and building ArgTypeInfo from that rather than from GcTypeKind.

Suggested change
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.None=> ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,// Conservative: assume pointer-sized for now
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
/// Converts a <see cref="GcTypeKind"/> to a conservative fallback <see cref="ArgTypeInfo"/>
/// when an exact signature cannot be decoded. Callers should prefer decoding the real
/// managed signature via <see cref="TryGetArgTypeInfosFromMethodSignature(ReadOnlySpan{byte}, int, out ArgTypeInfo[])"/>
/// so that floating-point and non-pointer-sized primitives retain their real layout.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
privatestaticboolTryGetArgTypeInfosFromMethodSignature(ReadOnlySpan<byte>signatureBytes,intpointerSize,outArgTypeInfo[]argTypes)
{
argTypes=Array.Empty<ArgTypeInfo>();
if(signatureBytes.IsEmpty)
returnfalse;
BlobReaderreader=new(signatureBytes);
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
ArgTypeInfo[]decodedArgTypes=newArgTypeInfo[parameterCount];
for(inti=0;i<parameterCount;i++)
{
if(!TryReadArgTypeInfo(refreader,pointerSize,outdecodedArgTypes[i]))
returnfalse;
}
argTypes=decodedArgTypes;
returntrue;
}
privatestaticboolTryReadArgTypeInfo(refBlobReaderreader,intpointerSize,outArgTypeInfoargTypeInfo)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Boolean:
caseCorElementType.I1:
caseCorElementType.U1:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,1);
returntrue;
caseCorElementType.Char:
caseCorElementType.I2:
caseCorElementType.U2:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,2);
returntrue;
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.R4:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,4);
returntrue;
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R8:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,8);
returntrue;
caseCorElementType.I:
caseCorElementType.U:
caseCorElementType.Ptr:
caseCorElementType.FnPtr:
if(elementTypeisCorElementType.Ptr or CorElementType.FnPtr)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize);
returntrue;
caseCorElementType.Byref:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize);
returntrue;
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.Class:
if(elementTypeisCorElementType.Class)
reader.ReadCompressedInteger();
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.SzArray:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.ValueType:
reader.ReadCompressedInteger();
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
}
default:
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=default;
returnfalse;
}
privatestaticboolTrySkipSignatureType(refBlobReaderreader)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Void:
caseCorElementType.Boolean:
caseCorElementType.Char:
caseCorElementType.I1:
caseCorElementType.U1:
caseCorElementType.I2:
caseCorElementType.U2:
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R4:
caseCorElementType.R8:
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.TypedByref:
caseCorElementType.I:
caseCorElementType.U:
returntrue;
caseCorElementType.Class:
caseCorElementType.ValueType:
caseCorElementType.Var:
caseCorElementType.MVar:
reader.ReadCompressedInteger();
returntrue;
caseCorElementType.Byref:
caseCorElementType.Ptr:
caseCorElementType.SzArray:
returnTrySkipSignatureType(refreader);
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
caseCorElementType.Array:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intrank=reader.ReadCompressedInteger();
intsizes=reader.ReadCompressedInteger();
for(inti=0;i<sizes;i++)
reader.ReadCompressedInteger();
intlowerBounds=reader.ReadCompressedInteger();
for(inti=0;i<lowerBounds;i++)
reader.ReadCompressedInteger();
returnrank>=0;
}
caseCorElementType.FnPtr:
{
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
for(inti=0;i<parameterCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
default:
returnfalse;
}
}
returnfalse;
}

Copilot uses AI. Check for mistakes.

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

Comment on lines +582 to 586
static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray<StackRef>* pRefs,
const char* label = nullptr)
{
if (pSosDac == nullptr)
return false;

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

CollectStackRefs currently appends every ref returned by ISOSStackRefEnum::Next with no upper bound. Later comparison helpers allocate fixed-size arrays sized by MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed[MAX_COLLECTED_REFS]), so if enumeration returns more than MAX_COLLECTED_REFS it can lead to out-of-bounds writes. Please cap collection to MAX_COLLECTED_REFS (and record an overflow/skip reason) or make the comparison logic handle arbitrarily large ref sets safely.

Copilot uses AI. Check for mistakes.
Comment on lines +1359 to +1362
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

When CollectRuntimeStackRefs overflows, the code logs a [SKIP] line but continues and still computes rtMatch/pass (and does not increment s_verifySkip). If CDACSTRESS_USE_DAC is not set, this can produce false failures based on a truncated runtime ref set. Consider treating runtime overflow as an actual skip (increment skip counter + return) when RT comparison is used for pass/fail, or otherwise avoid using rtMatch in the presence of overflow.

Suggested change
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
if (rtOverflow)
{
InterlockedIncrement(&s_verifySkip);
if (s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
}
return;

Copilot uses AI. Check for mistakes.
Comment on lines +823 to +824
ReportSlot(slotIndex, reportScratchSlots: true, reportFpBasedSlotsOnly, reportSlot);
}

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

In ReportUntrackedAndSucceed, ReportSlot is called with reportScratchSlots: true, which forces reporting scratch registers/stack slots even when CodeManagerFlags.ActiveStackFrame is not set (reportScratchSlots local is false). This changes GC root enumeration behavior and can introduce extra roots for non-leaf frames. It looks like this should pass the reportScratchSlots variable instead of always true.

Suggested change
ReportSlot(slotIndex,reportScratchSlots:true,reportFpBasedSlotsOnly,reportSlot);
}
ReportSlot(slotIndex,reportScratchSlots,reportFpBasedSlotsOnly,reportSlot);
}

Copilot uses AI. Check for mistakes.
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 49c8435 to de5cb46CompareApril 24, 2026 15:56
- Include RuntimeInfoOperatingSystem.Apple in Unix x64 ABI check
(macOS x64 uses SysV ABI, not Windows ABI)
- Thread MetadataReader from GetMethodSignatureBytes through
RuntimeSignatureDecoder to provider methods (instead of null!)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 24, 2026 16:07

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

Comment on lines +960 to 964
// Compare two ref sets using two-phase matching (for RT comparison where we
// don't have Source info). Returns true if all refs match.
static bool CompareRefSetsFlat(StackRef* refsA, int countA, StackRef* refsB, int countB)
{
if (countA != countB)

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

CompareRefSetsFlat uses a fixed-size matched[MAX_COLLECTED_REFS] buffer, but cDAC/DAC ref collection (CollectStackRefs) is unbounded. Without a guard/cap, a large ref set (>4096) can cause out-of-bounds writes during matching. Consider capping cDAC/DAC collection to MAX_COLLECTED_REFS (and treating overflow as a [SKIP]) or using dynamically sized bookkeeping here.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +76
// Read both stdout and stderr asynchronously to avoid deadlock
// when pipe buffers fill, and to allow WaitForExit timeout to work.
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};
process.BeginErrorReadLine();
process.BeginOutputReadLine();

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

The async stdout/stderr collection uses stdout += ... / stderr += ... inside DataReceived event handlers. These callbacks can run concurrently, and string concatenation is not thread-safe; it can also be costly for large output. Consider buffering with a thread-safe collector (e.g., ConcurrentQueue<string> or StringBuilder with a lock) and call process.WaitForExit() (or await stream completion) after WaitForExit(timeout) to ensure all async output has been drained before asserting/logging.

Copilot uses AI. Check for mistakes.
max-charlamb added a commit that referenced this pull request May 1, 2026
## Summary
Part 1 of 5 stacked PRs splitting
[#126408](#126408) into reviewable
pieces.
### What this PR contains
**Stack Walk GC Reference Scanning:**
- `PromoteCallerStack` / `PromoteCallerStackUsingGCRefMap` for
transition frames
- `GCRefMapDecoder` + `FindGCRefMap` with ReadyToRun import section
resolution
- `GcSignatureTypeProvider` for GC type classification
- `SOSDacImpl.GetStackReferences` fully implemented using cDAC contracts
- `GCInfoDecoder.EnumerateLiveSlots` promoted to `IGCInfo` contract
(returns `IReadOnlyList<LiveSlot>`)
- `GcSlotEnumerationOptions` replaces native `CodeManagerFlags` with
descriptive boolean properties
**Stack Walker Fixes:**
- `IsFirst` preserved for skipped frames (matches native
SFITER_SKIPPED_FRAME_FUNCTION)
- `IsInterrupted` state tracking for exception frames
(FaultingExceptionFrame, SoftwareExceptionFrame)
- `GetReturnAddress` gating in SW_FRAME (only UpdateRegDisplay if return
address non-null)
- Catch handler offset override via `GetInterruptibleRanges` for EH
resumption
**Contract API Additions:**
- `IGCInfo`: `EnumerateLiveSlots`, `GetStackBaseRegister`,
`GetInterruptibleRanges`
- `IExecutionManager`: `FindReadyToRunModule`
- `IRuntimeTypeSystem`: `RequiresInstArg`, `IsAsyncMethod`
- `IStackWalk`: `WalkStackReferences`
**Data Descriptor Changes:**
- Removed `ZapModule` and `GCRefMap` cached pointers (always resolve via
`FindReadyToRunModule`)
- Added `Indirection` for StubDispatchFrame, ExternalMethodFrame
- Added `DynamicHelperFrame.DynamicHelperFrameFlags`
- Added TransitionBlock fields (`OffsetOfArgs`,
`ArgumentRegistersOffset`, `FirstGCRefMapSlot`)
- Added ReadyToRunInfo fields (`ImportSections`, `NumImportSections`)
- Added ExceptionInfo catch clause fields
(`ClauseForCatchHandlerStartPC`, `ClauseForCatchHandlerEndPC`)
**Documentation:**
- GCInfo.md: Comprehensive implementation docs (header/body decoding,
slot table, EnumerateLiveSlots algorithm, type definitions for
`LiveSlot`, `InterruptibleRange`, `GcSlotEnumerationOptions`)
- StackWalk.md: GC scanning algorithm, GCRefMap resolution flow, return
address per frame type, `WalkStackReferences` API
- ExecutionManager.md: `FindReadyToRunModule` API and implementation
- RuntimeTypeSystem.md: `RequiresInstArg`, `IsAsyncMethod` APIs
### Stack overview
| PR | Content | Status |
|----|---------|--------|
| **This PR** | Stack walk fixes + GC scanning | Open |
| PR 2 | RuntimeSignatureDecoder (ELEMENT_TYPE_INTERNAL) | Pending |
| PR 3 | ArgIterator port from crossgen2 | Pending |
| PR 4 | Native stress framework (cdacstress.cpp) | Pending |
| PR 5 | Managed stress tests + CI pipeline | Pending |
### Testing
- 1727/1751 unit tests pass (24 pre-existing ThreadTests failures on
main)
- Dump tests (StackWalkDumpTests, StackReferenceDumpTests) validate
end-to-end
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

Closing in favor of stacked PR approach

steveisok pushed a commit that referenced this pull request May 11, 2026
…5) (#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[#126408](#126408). Builds on
[#127395](#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jakobbotsch pushed a commit to jakobbotsch/runtime that referenced this pull request May 12, 2026
…5) (dotnet#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[dotnet#126408](dotnet#126408). Builds on
[dotnet#127395](dotnet#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 1, 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.

2 participants

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

[cDAC] Stack walk GC stress verification and fixes - #126408

Closed
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5
Closed

[cDAC] Stack walk GC stress verification and fixes#126408
max-charlamb wants to merge 23 commits into
dotnet:mainfrom
max-charlamb:cdac-stackreferences-5

Conversation

@max-charlamb

@max-charlambmax-charlamb commented Apr 1, 2026

Copy link
Copy Markdown
Member

Summary

cDAC GC stress verification tool (DOTNET_CdacStress) that compares stack GC references between the cDAC and the runtime at allocation stress points. Includes stack walker fixes, GC reference scanning implementation, custom signature decoding, and calling convention argument iteration.

Note

This PR description was updated with AI assistance from Copilot.

Stack walker fixes

  • Fix SW_SKIPPED_FRAME: do not call UpdateContextFromFrame (matches native SFITER_SKIPPED_FRAME_FUNCTION which does not call UpdateRegDisplay)
  • Fix skipped-frame loop: check for more skipped frames before yielding managed method (prevents duplicate EnumGcRefs between consecutive skipped frames)
  • Restructure Filter() to drive Next() directly, matching native Filter()+NextRaw() integration (prevents funclet-to-parent walk cycles)
  • Remove SkipActiveICFOnce/SkipCurrentFrameInCheck — active ICF double-yield is natural and harmless
  • Remove IsAtFirstPassExceptionThrowSite — native does not suppress first-pass refs
  • Fix IsFirst not preserved for skipped frames (was causing IsActiveFrame=false for the topmost managed frame)

GC reference scanning

  • Implement PromoteCallerStack for stub frames (GCRefMap + MetaSig paths)
  • Implement SOSDacImpl.GetStackReferences using cDAC contract (was falling back to legacy DAC)
  • Read FilterContext for stack walk starting context
  • Three-way cDAC/DAC/RT comparison with InProcessDataTarget
  • DOTNET_CdacStress bit flags: ALLOC/INSTR/REFS/WALK/USE_DAC/UNIQUE

RuntimeSignatureDecoder

Custom signature decoder that handles ELEMENT_TYPE_INTERNAL (0x21) and ELEMENT_TYPE_CMOD_INTERNAL (0x22) which SRM's SignatureDecoder cannot parse. These occur in IL stubs, marshalling stubs, and unsafe accessor frames.

  • IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of SRM's ISignatureTypeProvider, adds GetInternalType and GetInternalModifiedType
  • ISignatureReader + SpanSignatureReader: endianness-aware reader abstraction
  • Correct ECMA-335 compliance: TypeDefOrRefOrSpecEncoded token decoding, sign-extension-by-width for compressed signed ints, bounds validation

ArgIterator (ported from crossgen2)

Proper calling convention analysis replacing the simplified 1-slot-per-param approach.

  • CallingConventionInfo: hybrid data descriptor layout values + ABI invariant constants for all architectures (x86, x64 Windows/Unix, ARM32, ARM64, LoongArch64, RISC-V64)
  • ArgIterator.GetNextOffset(): maps each argument to its actual register or stack offset
  • OffsetOfFloatArgumentRegisters added to TransitionBlock data descriptor
  • Handles multi-slot args, forced-byref params, return buffer placement, async continuation

CI infrastructure

  • cDAC stress tests run in Helix via cdac-stress-helix.proj
  • Extended runtime-diagnostics.yml CdacDumpTests buildArgs with +tools.cdacstresstests

Test results

Allocation-level stress (9 debuggees, ~46K verifications):

DebuggeeVerificationsPassFail
BasicAlloc4,9364,9360
DeepStack4,9644,9640
Generics4,9364,9360
MultiThread4,9964,9960
Comprehensive4,9944,9922*
ExceptionHandling4,9584,9580
StructScenarios4,9404,9400
DynamicMethods6,5206,5200
PInvoke4,9364,9360

Instruction-level stress (9 debuggees, ~226K verifications):
All 9 debuggees pass with zero failures across 226,452 verifications.

*Comprehensive's 2 failures are pre-existing legacy DAC issues (DAC returns 0 refs for background threads in kernel waits; cDAC and runtime agree).

@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

Adds a cDAC GC-stress verification harness and extends the cDAC stack-walk / stack-GC-ref pipeline so cDAC stack reference enumeration can be compared against runtime scanning at stress points.

Changes:

  • Implements/extends cDAC stack reference enumeration (including Frame-based scanning paths like PromoteCallerStack via GCRefMap / MetaSig) and wires SOS GetStackReferences to the cDAC contract.
  • Introduces a new GC stress integration test project with debuggee apps and orchestration targets.
  • Extends CoreCLR cDAC stress/GC stress plumbing and data descriptors to support the new stack-walk and frame-scanning capabilities.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 12 comments.

Show a summary per file
FileDescription
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.ExecutionManager.csUpdates mock type layout for execution manager-related data.
src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.csUpdates mock type layouts (ExceptionInfo/Thread) for contract tests.
src/native/managed/cdac/tests/Microsoft.Diagnostics.DataContractReader.Tests.csprojExcludes new GCStressTests folder from the existing unit test project compilation.
src/native/managed/cdac/tests/GCStressTests/README.mdDocuments how to build/run the new GC stress tests.
src/native/managed/cdac/tests/GCStressTests/Microsoft.Diagnostics.DataContractReader.GCStressTests.csprojAdds a dedicated GC stress test project.
src/native/managed/cdac/tests/GCStressTests/GCStressTests.targetsMSBuild orchestration to discover/build debuggee projects.
src/native/managed/cdac/tests/GCStressTests/GCStressTestBase.csTest harness to run debuggees under corerun and parse verification logs.
src/native/managed/cdac/tests/GCStressTests/GCStressResults.csParses the native verification log into structured pass/fail/skip counts.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/Program.csAdds a P/Invoke-focused debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/PInvoke/PInvoke.csprojDebuggee project file for PInvoke scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/Program.csAdds a multi-threaded debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/MultiThread/MultiThread.csprojDebuggee project file for MultiThread scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Program.csAdds a generics/interface/delegate debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Generics/Generics.csprojDebuggee project file for Generics scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/Program.csAdds an exception-handling/funclet debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/ExceptionHandling/ExceptionHandling.csprojDebuggee project file for ExceptionHandling scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Directory.Build.propsShared build props for debuggee projects (output layout, TFM, etc.).
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/Program.csAdds deep-recursion debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/DeepStack/DeepStack.csprojDebuggee project file for DeepStack scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Program.csAdds comprehensive “all scenarios” debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/Comprehensive/Comprehensive.csprojDebuggee project file for Comprehensive scenario.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/Program.csAdds basic allocation/live-ref debuggee workload.
src/native/managed/cdac/tests/GCStressTests/Debuggees/BasicAlloc/BasicAlloc.csprojDebuggee project file for BasicAlloc scenario.
src/native/managed/cdac/tests/GCStressTests/BasicGCStressTests.csTheory-based test suite that runs the debuggees and asserts pass rate.
src/native/managed/cdac/tests/gcstress/known-issues.mdCaptures known mismatch classes and limitations.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/SOSDacImpl.csImplements GetStackReferences using the cDAC contract rather than legacy DAC fallback.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/StubDispatchFrame.csExtends StubDispatchFrame data with GCRefMap pointer.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/ExternalMethodFrame.csAdds ExternalMethodFrame contract data type (GCRefMap).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/Frames/DynamicHelperFrame.csAdds DynamicHelperFrame contract data type (flags).
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Data/ExceptionInfo.csAdds clause-range fields used for catch-handler resumption offset selection.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.csRefactors stack-walk filtering and adds Frame-based GC root scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanner.csAdds optional relOffset override support for GC ref enumeration.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GCRefMapDecoder.csImplements GCRefMap decoding for transition-block scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/CorSigParser.csAdds minimal signature parsing to classify parameters for MetaSig-based scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/IGCInfoDecoder.csAdds FindFirstInterruptiblePoint API to GCInfo decoders.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.csImplements FindFirstInterruptiblePoint using decoded interruptible ranges.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/ExecutionManager/ExecutionManagerCore.EEJitManager.csFixes code-start lookup for exception clause enumeration and adds minor flow adjustments.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Constants.csAdds TransitionBlock-related global names used by frame scanning.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/DataType.csAdds new DataType enum values for new frame contracts.
src/native/managed/cdac/cdac.slnxAdds the new GC stress test project to the cDAC solution.
src/coreclr/vm/gccover.cppAdds step-based skipping to reduce overhead when throttling verification.
src/coreclr/vm/frames.hExposes additional frame fields to cDAC via cdac_data<> descriptors.
src/coreclr/vm/datadescriptor/datadescriptor.incAdds new contract fields/globals for frames and TransitionBlock layout.
src/coreclr/vm/cdacstress.cppUpdates in-process cDAC/DAC verification logic, logging, and step behavior.
eng/Subsets.propsAdds an on-demand subset for running GC stress tests.
docs/design/datacontracts/StackWalk.mdDocuments the new frame fields and TransitionBlock globals in the StackWalk contract.
Comments suppressed due to low confidence (1)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:228

  • ThreadFields contains ProfilerFilterContext twice (lines 224 and 228). Duplicate field entries will skew offsets and make the mock descriptors inconsistent with the real data descriptor. Keep a single ProfilerFilterContext entry in the correct order.

Comment threadsrc/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/README.md Outdated

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

Comments suppressed due to low confidence (2)

src/native/managed/cdac/tests/MockDescriptors/MockDescriptors.cs:229

  • ThreadFields now includes DebuggerFilterContext/ProfilerFilterContext twice (duplicate entries at the end of the list). This can cause ambiguous/incorrect field offsets in the mock type layout. Keep each field only once.
    src/coreclr/vm/cdacstress.cpp:963
  • CompareRefSets uses a fixed-size matched[MAX_COLLECTED_REFS] buffer but no longer validates that countA/countB are <= MAX_COLLECTED_REFS. Since CollectStackRefs appends without a hard cap, this can lead to out-of-bounds access when countA or countB exceeds 4096. Reintroduce a guard or allocate the match state sized to the counts.
 return true;
bool matched[MAX_COLLECTED_REFS] = {};
for (int i = 0; i < countA; i++)

Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp Outdated
Comment threadsrc/coreclr/vm/gccover.cpp Outdated
Comment threadsrc/native/managed/cdac/tests/GCStressTests/GCStressTestBase.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 75ae7fe to 5419d15CompareApril 13, 2026 15:30
CopilotAI review requested due to automatic review settings April 13, 2026 15:47
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 5419d15 to 8ef9b22CompareApril 13, 2026 15:47

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

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated

@max-charlambmax-charlamb left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

feedback for local copilot

Comment threadsrc/native/managed/cdac/tests/StressTests/analysis/analyze-refs.cs Outdated
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/StressTests.targets Outdated
Comment threadsrc/coreclr/vm/datadescriptor/datadescriptor.inc Outdated
CopilotAI review requested due to automatic review settings April 13, 2026 17:33

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

Comments suppressed due to low confidence (1)

src/coreclr/vm/cdacstress.cpp:625

  • CollectStackRefs appends to pRefs without any cap. Later comparisons allocate fixed-size arrays sized MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed/bUsed) and assume counts fit. If the DAC returns > MAX_COLLECTED_REFS refs, this will lead to out-of-bounds writes/reads. Add an explicit limit/overflow handling in CollectStackRefs (stop at MAX_COLLECTED_REFS and mark overflow / SKIP), or change the later comparison logic to handle arbitrary counts safely.
 SOSStackRefData refData;
unsigned int fetched = 0;
while (true)
{
hr = pEnum->Next(1, &refData, &fetched);
if (FAILED(hr) || fetched == 0)
break;
StackRef ref;
ref.Address = refData.Address;
ref.Object = refData.Object;
ref.Flags = refData.Flags;
ref.Source = refData.Source;
ref.SourceType = refData.SourceType;
ref.Register = refData.Register;
ref.Offset = refData.Offset;
ref.StackPointer = refData.StackPointer;
pRefs->Append(ref);
}

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/CdacStressTestBase.cs Outdated
Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/native/managed/cdac/tests/StressTests/README.md
Comment threadsrc/native/managed/cdac/tests/StressTests/known-issues.md
Comment threaddocs/design/datacontracts/StackWalk.md
CopilotAI review requested due to automatic review settings April 13, 2026 19: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 52 out of 52 changed files in this pull request and generated 3 comments.

Comment threadsrc/coreclr/vm/cdacstress.cpp
Comment threadsrc/coreclr/vm/cdacstress.cpp
CopilotAI review requested due to automatic review settings April 13, 2026 20:24
Max Charlamband others added 5 commits April 22, 2026 15:23
- Fix platform-specific ctx.Rip usage: use GetIP(&ctx) instead
- Fix if( style: add space after if keyword in gccover.cpp
- Add RVA bounds validation in FindGCRefMap before uint cast
- Remove stale analysis file (eh-throwhelper-report.md)
- Update AssertHighPassRate comment to reflect current state
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Run GC stress verification tests in the runtime-diagnostics pipeline by
piggybacking on the existing CdacDumpTests Checked runtime build. The
stress tests run as a second Helix submission after the dump tests,
using the testhost shared framework as CORE_ROOT.
Runs on all cdacDumpPlatforms (windows_x64, linux_x64, etc.). On
non-Windows platforms, only instruction-level stress (via DoGcStress)
is supported; allocation-level stress (VerifyAtAllocPoint) is skipped
because it requires Windows-only RtlCaptureContext/RtlVirtualUnwind.
Infrastructure:
- cdac-stress-helix.proj: Helix SDK project that sends testhost as
correlation payload and stress test debuggees + test assembly as
work item payload. Sets CORE_ROOT env var for the test harness.
- prepare-cdac-stress-helix-steps.yml: Pipeline template that builds
debuggees, prepares Helix payload, and finds testhost directory.
- StressTests.targets: Added PrepareHelixPayload and BuildDebuggeesOnly
targets for CI payload preparation.
- CdacStressTestBase.cs: Added HELIX_WORKITEM_PAYLOAD support for
finding debuggees in Helix environment.
- runtime-diagnostics.yml: Extended CdacDumpTests buildArgs to include
tools.cdacstresstests, added stress test Helix submission steps.
- cdacstress.cpp: Guard VerifyAtAllocPoint with TARGET_WINDOWS for
RtlCaptureContext/RtlVirtualUnwind APIs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a custom signature decoder that handles runtime-internal type codes
(ELEMENT_TYPE_INTERNAL 0x21, ELEMENT_TYPE_CMOD_INTERNAL 0x22) which
SRM's SignatureDecoder cannot parse.
- IRuntimeSignatureTypeProvider<TType, TGenericContext>: superset of
SRM's ISignatureTypeProvider, adding GetInternalType for resolving
embedded TypeHandle pointers via the runtime type system.
- ISignatureReader + SpanSignatureReader: abstraction for reading
signature bytes from different sources (spans, target memory).
- RuntimeSignatureDecoder<TType, TGenericContext, TReader>: ref struct
decoder that handles all standard ECMA-335 types plus internal types.
- GcSignatureTypeProvider: implements IRuntimeSignatureTypeProvider to
classify types for GC scanning, resolving internal types via
RuntimeTypeSystem.GetSignatureCorElementType.
Key correctness details vs SRM's SignatureDecoder:
- CLASS/VALUETYPE tokens decoded as TypeDefOrRefOrSpecEncoded per
ECMA-335 II.23.2.8 (tag in low 2 bits, RID in upper bits).
- CMOD_INTERNAL correctly skips the required/optional flag byte before
the TypeHandle pointer, matching sigparser.h layout.
- ReadCompressedSignedInt uses ECMA sign-extension-by-width, not zigzag.
- ReadCompressedUInt rejects invalid 111xxxxx prefix.
- Unknown type codes throw BadImageFormatException instead of silently
returning Object (which would create false-positive GC refs).
- Method signature header kind is validated.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port crossgen2's ArgIterator, TransitionBlock, and GC scanning logic
into the cDAC contracts for correct per-architecture argument placement.
- Add OffsetOfFloatArgumentRegisters to TransitionBlock data descriptor
- CallingConventionInfo: hybrid data descriptor + ABI invariant constants
for x86, x64 (Windows/Unix), ARM32, ARM64, LoongArch64, RISC-V64
- ArgTypeInfo: pre-computed type info replacing crossgen2's TypeHandle
- ArgIteratorData: parsed method signature holder
- ArgIterator: maps each argument to register/stack offsets via
GetNextOffset() with per-architecture register allocation
- Integrate into FrameIterator.PromoteCallerStackHelper replacing the
simplified 1-slot-per-param approach with proper offset computation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Quote debuggee DLL path in ProcessStartInfo.Arguments (dotnet#31)
- Fix timeout: use async stdout/stderr reads so WaitForExit works (dotnet#32)
- Update stale comment about ELEMENT_TYPE_INTERNAL limitation (dotnet#35)
- Move CallingConvention types to StackWalkHelpers.CallingConvention namespace (dotnet#37)
- Simplify x86 register eligibility check in ArgIterator (dotnet#38)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 22, 2026 19:25
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 1a92e2c to b85dbacCompareApril 22, 2026 19:25

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

Comment on lines +16 to +20
// FirstThreadLink is an embedded SLink struct. Read the SLink.Next pointer
// from the field's address to get the first thread link pointer.
Target.TypeInfo slinkType = target.GetTypeInfo(DataType.SLink);
TargetPointer slinkAddr = address + (ulong)type.Fields[nameof(FirstThreadLink)].Offset;
FirstThreadLink = target.ReadPointerField(slinkAddr, slinkType, "Next");

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

ThreadStore now calls target.GetTypeInfo(DataType.SLink) and reads field "Next", but the CoreCLR data descriptor in this PR doesn't define a SLink type/field (could not find any CDAC_TYPE_BEGIN/FIELD for SLink). This will cause GetTypeInfo(DataType.SLink) to fail at runtime. Either add an SLink descriptor (with a "Next" pointer) on the runtime side or avoid needing type info here (e.g., treat the embedded SLink as a single pointer at offset 0).

Copilot uses AI. Check for mistakes.
Comment on lines 28 to 29
SLink,
ThreadLocalData,

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

DataType adds SLink, but there is no corresponding runtime data descriptor definition (no CDAC_TYPE_BEGIN/FIELD(SLink, ...) found). Any attempt to read this type via Target.GetTypeInfo(DataType.SLink) will fail. Either add the runtime descriptor entry for SLink or remove this enum value and read the embedded link without type metadata.

Suggested change
SLink,
ThreadLocalData,
ThreadLocalData=17,

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +69
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

These DataReceived event handlers append to strings via "+=" from background threads. That is not thread-safe and can lead to lost/garbled output under contention. Consider using a StringBuilder with locking (or ConcurrentQueue) for stderr/stdout aggregation.

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +74
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

Same thread-safety issue as stderr: appending to stdout with += from OutputDataReceived is racy. Use a synchronized StringBuilder/collector to avoid missing output and to keep logs reliable when tests fail.

Copilot uses AI. Check for mistakes.
Comment on lines +732 to +751
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
private static ArgTypeInfo GcTypeKindToArgTypeInfo(GcTypeKind kind, int pointerSize)
{
return kind switch
{
GcTypeKind.None => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
GcTypeKind.Ref => ArgTypeInfo.ForPrimitive(CorElementType.Class, pointerSize),
GcTypeKind.Interior => ArgTypeInfo.ForPrimitive(CorElementType.Byref, pointerSize),
GcTypeKind.Other => new ArgTypeInfo
{
CorElementType = CorElementType.ValueType,
Size = pointerSize, // Conservative: assume pointer-sized for now
IsValueType = true,
},
_ => ArgTypeInfo.ForPrimitive(CorElementType.I, pointerSize),
};
}

CopilotAIApr 22, 2026

Copy link

Choose a reason for hiding this comment

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

GcTypeKindToArgTypeInfo collapses all non-GC-ref types to CorElementType.I/pointer-sized and doesn't preserve float vs integer vs exact primitive sizes. ArgIterator offset calculation depends on the real signature shape (especially on Unix x64/ARM64 where float regs and argument sizing affect later argument placement), so this can produce incorrect offsets and cause missed/incorrect GC ref reporting for signatures with floats or non-pointer-sized primitives. Consider decoding into a richer type representation (e.g., CorElementType + size) and building ArgTypeInfo from that rather than from GcTypeKind.

Suggested change
/// Converts a <see cref="GcTypeKind"/> to a minimal <see cref="ArgTypeInfo"/> for
/// ArgIterator consumption. This is a bridge until we have a full type-aware provider.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.None=> ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,// Conservative: assume pointer-sized for now
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
/// Converts a <see cref="GcTypeKind"/> to a conservative fallback <see cref="ArgTypeInfo"/>
/// when an exact signature cannot be decoded. Callers should prefer decoding the real
/// managed signature via <see cref="TryGetArgTypeInfosFromMethodSignature(ReadOnlySpan{byte}, int, out ArgTypeInfo[])"/>
/// so that floating-point and non-pointer-sized primitives retain their real layout.
/// </summary>
privatestaticArgTypeInfoGcTypeKindToArgTypeInfo(GcTypeKindkind,intpointerSize)
{
returnkindswitch
{
GcTypeKind.Ref=> ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize),
GcTypeKind.Interior=> ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize),
GcTypeKind.Other=>new ArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
},
_ =>ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize),
};
}
privatestaticboolTryGetArgTypeInfosFromMethodSignature(ReadOnlySpan<byte>signatureBytes,intpointerSize,outArgTypeInfo[]argTypes)
{
argTypes=Array.Empty<ArgTypeInfo>();
if(signatureBytes.IsEmpty)
returnfalse;
BlobReaderreader=new(signatureBytes);
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
ArgTypeInfo[]decodedArgTypes=newArgTypeInfo[parameterCount];
for(inti=0;i<parameterCount;i++)
{
if(!TryReadArgTypeInfo(refreader,pointerSize,outdecodedArgTypes[i]))
returnfalse;
}
argTypes=decodedArgTypes;
returntrue;
}
privatestaticboolTryReadArgTypeInfo(refBlobReaderreader,intpointerSize,outArgTypeInfoargTypeInfo)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Boolean:
caseCorElementType.I1:
caseCorElementType.U1:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,1);
returntrue;
caseCorElementType.Char:
caseCorElementType.I2:
caseCorElementType.U2:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,2);
returntrue;
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.R4:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,4);
returntrue;
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R8:
argTypeInfo=ArgTypeInfo.ForPrimitive(elementType,8);
returntrue;
caseCorElementType.I:
caseCorElementType.U:
caseCorElementType.Ptr:
caseCorElementType.FnPtr:
if(elementTypeisCorElementType.Ptr or CorElementType.FnPtr)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.I,pointerSize);
returntrue;
caseCorElementType.Byref:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Byref,pointerSize);
returntrue;
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.Class:
if(elementTypeisCorElementType.Class)
reader.ReadCompressedInteger();
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.SzArray:
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
argTypeInfo=ArgTypeInfo.ForPrimitive(CorElementType.Class,pointerSize);
returntrue;
caseCorElementType.ValueType:
reader.ReadCompressedInteger();
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
{
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=newArgTypeInfo
{
CorElementType=CorElementType.ValueType,
Size=pointerSize,
IsValueType=true,
};
returntrue;
}
default:
argTypeInfo=default;
returnfalse;
}
}
argTypeInfo=default;
returnfalse;
}
privatestaticboolTrySkipSignatureType(refBlobReaderreader)
{
while(reader.RemainingBytes>0)
{
CorElementTypeelementType=(CorElementType)reader.ReadByte();
switch(elementType)
{
caseCorElementType.CmodOpt:
caseCorElementType.CmodReqd:
reader.ReadCompressedInteger();
continue;
caseCorElementType.Pinned:
continue;
caseCorElementType.Void:
caseCorElementType.Boolean:
caseCorElementType.Char:
caseCorElementType.I1:
caseCorElementType.U1:
caseCorElementType.I2:
caseCorElementType.U2:
caseCorElementType.I4:
caseCorElementType.U4:
caseCorElementType.I8:
caseCorElementType.U8:
caseCorElementType.R4:
caseCorElementType.R8:
caseCorElementType.String:
caseCorElementType.Object:
caseCorElementType.TypedByref:
caseCorElementType.I:
caseCorElementType.U:
returntrue;
caseCorElementType.Class:
caseCorElementType.ValueType:
caseCorElementType.Var:
caseCorElementType.MVar:
reader.ReadCompressedInteger();
returntrue;
caseCorElementType.Byref:
caseCorElementType.Ptr:
caseCorElementType.SzArray:
returnTrySkipSignatureType(refreader);
caseCorElementType.GenericInst:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intgenericArgCount=reader.ReadCompressedInteger();
for(inti=0;i<genericArgCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
caseCorElementType.Array:
{
if(!TrySkipSignatureType(refreader))
returnfalse;
intrank=reader.ReadCompressedInteger();
intsizes=reader.ReadCompressedInteger();
for(inti=0;i<sizes;i++)
reader.ReadCompressedInteger();
intlowerBounds=reader.ReadCompressedInteger();
for(inti=0;i<lowerBounds;i++)
reader.ReadCompressedInteger();
returnrank>=0;
}
caseCorElementType.FnPtr:
{
SignatureHeaderheader=reader.ReadSignatureHeader();
if(header.IsGeneric)
reader.ReadCompressedInteger();
intparameterCount=reader.ReadCompressedInteger();
if(parameterCount<0)
returnfalse;
if(!TrySkipSignatureType(refreader))
returnfalse;
for(inti=0;i<parameterCount;i++)
{
if(!TrySkipSignatureType(refreader))
returnfalse;
}
returntrue;
}
default:
returnfalse;
}
}
returnfalse;
}

Copilot uses AI. Check for mistakes.

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

Comment on lines +582 to 586
static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray<StackRef>* pRefs,
const char* label = nullptr)
{
if (pSosDac == nullptr)
return false;

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

CollectStackRefs currently appends every ref returned by ISOSStackRefEnum::Next with no upper bound. Later comparison helpers allocate fixed-size arrays sized by MAX_COLLECTED_REFS (e.g., matched[MAX_COLLECTED_REFS], aUsed[MAX_COLLECTED_REFS]), so if enumeration returns more than MAX_COLLECTED_REFS it can lead to out-of-bounds writes. Please cap collection to MAX_COLLECTED_REFS (and record an overflow/skip reason) or make the comparison logic handle arbitrarily large ref sets safely.

Copilot uses AI. Check for mistakes.
Comment on lines +1359 to +1362
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

When CollectRuntimeStackRefs overflows, the code logs a [SKIP] line but continues and still computes rtMatch/pass (and does not increment s_verifySkip). If CDACSTRESS_USE_DAC is not set, this can produce false failures based on a truncated runtime ref set. Consider treating runtime overflow as an actual skip (increment skip counter + return) when RT comparison is used for pass/fail, or otherwise avoid using rtMatch in the presence of overflow.

Suggested change
if (rtOverflow && s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
if (rtOverflow)
{
InterlockedIncrement(&s_verifySkip);
if (s_logFile != nullptr)
{
fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - RT overflow (>%d refs)\n",
osThreadId, (void*)GetIP(regs), MAX_COLLECTED_REFS);
}
return;

Copilot uses AI. Check for mistakes.
Comment on lines +823 to +824
ReportSlot(slotIndex, reportScratchSlots: true, reportFpBasedSlotsOnly, reportSlot);
}

CopilotAIApr 23, 2026

Copy link

Choose a reason for hiding this comment

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

In ReportUntrackedAndSucceed, ReportSlot is called with reportScratchSlots: true, which forces reporting scratch registers/stack slots even when CodeManagerFlags.ActiveStackFrame is not set (reportScratchSlots local is false). This changes GC root enumeration behavior and can introduce extra roots for non-leaf frames. It looks like this should pass the reportScratchSlots variable instead of always true.

Suggested change
ReportSlot(slotIndex,reportScratchSlots:true,reportFpBasedSlotsOnly,reportSlot);
}
ReportSlot(slotIndex,reportScratchSlots,reportFpBasedSlotsOnly,reportSlot);
}

Copilot uses AI. Check for mistakes.
- Fix README test filter syntax: use FullyQualifiedName~BasicAlloc (#1)
- Remove goto statements from GCInfoDecoder.EnumerateLiveSlots: extract
ReportUntrackedAndSucceed local function (#2)
- Move CheckForSkippedFrames from Next() to UpdateState (#6)
- Add XUnitConsoleRunner package reference for Helix payload (#9)
- Support TypeSpec (tag=2) in DecodeTypeDefOrRefOrSpec matching native
CorSigUncompressToken behavior (#10)
- Fix IsAppleArm64ABI: set to false until Apple platform detection is
available (filed dotnet#127282) (#11)
- Fix Unix x64 float register stride: use FloatRegisterSize instead of
hardcoded 8 (#12)
- Replace FrameIterator.OffsetFromGCRefMapPos with CallingConventionInfo
version that handles x86 reversed register layout (dotnet#13)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb
max-charlambforce-pushed the cdac-stackreferences-5 branch from 49c8435 to de5cb46CompareApril 24, 2026 15:56
- Include RuntimeInfoOperatingSystem.Apple in Unix x64 ABI check
(macOS x64 uses SysV ABI, not Windows ABI)
- Thread MetadataReader from GetMethodSignatureBytes through
RuntimeSignatureDecoder to provider methods (instead of null!)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 24, 2026 16:07

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

Comment on lines +960 to 964
// Compare two ref sets using two-phase matching (for RT comparison where we
// don't have Source info). Returns true if all refs match.
static bool CompareRefSetsFlat(StackRef* refsA, int countA, StackRef* refsB, int countB)
{
if (countA != countB)

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

CompareRefSetsFlat uses a fixed-size matched[MAX_COLLECTED_REFS] buffer, but cDAC/DAC ref collection (CollectStackRefs) is unbounded. Without a guard/cap, a large ref set (>4096) can cause out-of-bounds writes during matching. Consider capping cDAC/DAC collection to MAX_COLLECTED_REFS (and treating overflow as a [SKIP]) or using dynamically sized bookkeeping here.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +76
// Read both stdout and stderr asynchronously to avoid deadlock
// when pipe buffers fill, and to allow WaitForExit timeout to work.
string stderr = "";
string stdout = "";
process.ErrorDataReceived += (_, e) =>
{
if (e.Data is not null)
stderr += e.Data + Environment.NewLine;
};
process.OutputDataReceived += (_, e) =>
{
if (e.Data is not null)
stdout += e.Data + Environment.NewLine;
};
process.BeginErrorReadLine();
process.BeginOutputReadLine();

CopilotAIApr 24, 2026

Copy link

Choose a reason for hiding this comment

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

The async stdout/stderr collection uses stdout += ... / stderr += ... inside DataReceived event handlers. These callbacks can run concurrently, and string concatenation is not thread-safe; it can also be costly for large output. Consider buffering with a thread-safe collector (e.g., ConcurrentQueue<string> or StringBuilder with a lock) and call process.WaitForExit() (or await stream completion) after WaitForExit(timeout) to ensure all async output has been drained before asserting/logging.

Copilot uses AI. Check for mistakes.
max-charlamb added a commit that referenced this pull request May 1, 2026
## Summary
Part 1 of 5 stacked PRs splitting
[#126408](#126408) into reviewable
pieces.
### What this PR contains
**Stack Walk GC Reference Scanning:**
- `PromoteCallerStack` / `PromoteCallerStackUsingGCRefMap` for
transition frames
- `GCRefMapDecoder` + `FindGCRefMap` with ReadyToRun import section
resolution
- `GcSignatureTypeProvider` for GC type classification
- `SOSDacImpl.GetStackReferences` fully implemented using cDAC contracts
- `GCInfoDecoder.EnumerateLiveSlots` promoted to `IGCInfo` contract
(returns `IReadOnlyList<LiveSlot>`)
- `GcSlotEnumerationOptions` replaces native `CodeManagerFlags` with
descriptive boolean properties
**Stack Walker Fixes:**
- `IsFirst` preserved for skipped frames (matches native
SFITER_SKIPPED_FRAME_FUNCTION)
- `IsInterrupted` state tracking for exception frames
(FaultingExceptionFrame, SoftwareExceptionFrame)
- `GetReturnAddress` gating in SW_FRAME (only UpdateRegDisplay if return
address non-null)
- Catch handler offset override via `GetInterruptibleRanges` for EH
resumption
**Contract API Additions:**
- `IGCInfo`: `EnumerateLiveSlots`, `GetStackBaseRegister`,
`GetInterruptibleRanges`
- `IExecutionManager`: `FindReadyToRunModule`
- `IRuntimeTypeSystem`: `RequiresInstArg`, `IsAsyncMethod`
- `IStackWalk`: `WalkStackReferences`
**Data Descriptor Changes:**
- Removed `ZapModule` and `GCRefMap` cached pointers (always resolve via
`FindReadyToRunModule`)
- Added `Indirection` for StubDispatchFrame, ExternalMethodFrame
- Added `DynamicHelperFrame.DynamicHelperFrameFlags`
- Added TransitionBlock fields (`OffsetOfArgs`,
`ArgumentRegistersOffset`, `FirstGCRefMapSlot`)
- Added ReadyToRunInfo fields (`ImportSections`, `NumImportSections`)
- Added ExceptionInfo catch clause fields
(`ClauseForCatchHandlerStartPC`, `ClauseForCatchHandlerEndPC`)
**Documentation:**
- GCInfo.md: Comprehensive implementation docs (header/body decoding,
slot table, EnumerateLiveSlots algorithm, type definitions for
`LiveSlot`, `InterruptibleRange`, `GcSlotEnumerationOptions`)
- StackWalk.md: GC scanning algorithm, GCRefMap resolution flow, return
address per frame type, `WalkStackReferences` API
- ExecutionManager.md: `FindReadyToRunModule` API and implementation
- RuntimeTypeSystem.md: `RequiresInstArg`, `IsAsyncMethod` APIs
### Stack overview
| PR | Content | Status |
|----|---------|--------|
| **This PR** | Stack walk fixes + GC scanning | Open |
| PR 2 | RuntimeSignatureDecoder (ELEMENT_TYPE_INTERNAL) | Pending |
| PR 3 | ArgIterator port from crossgen2 | Pending |
| PR 4 | Native stress framework (cdacstress.cpp) | Pending |
| PR 5 | Managed stress tests + CI pipeline | Pending |
### Testing
- 1727/1751 unit tests pass (24 pre-existing ThreadTests failures on
main)
- Dump tests (StackWalkDumpTests, StackReferenceDumpTests) validate
end-to-end
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@max-charlamb

Copy link
Copy Markdown
MemberAuthor

Closing in favor of stacked PR approach

steveisok pushed a commit that referenced this pull request May 11, 2026
…5) (#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[#126408](#126408). Builds on
[#127395](#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
jakobbotsch pushed a commit to jakobbotsch/runtime that referenced this pull request May 12, 2026
…5) (dotnet#127636)
## Summary
Part 2 of 5 stacked PRs splitting
[dotnet#126408](dotnet#126408). Builds on
[dotnet#127395](dotnet#127395) (merged).
This PR adds a cDAC ECMA-335 signature decoder that closely mirrors
`System.Reflection.Metadata.SignatureDecoder` and supports the two
runtime-only extensions used in CoreCLR-internal signatures:
`ELEMENT_TYPE_INTERNAL` (`0x21`) and `ELEMENT_TYPE_CMOD_INTERNAL`
(`0x22`). The decoder is then used by both the Signature contract (for
field signatures) and the StackWalk contract (for signature-based GC
reference scanning of transition frames).
### What this PR contains
**`RuntimeSignatureDecoder` (SRM-aligned polyfill):**
- `RuntimeSignatureDecoder<TType, TGenericContext>` -- readonly struct
mirroring SRM's `SignatureDecoder<TType, TGenericContext>`
(`DecodeType`, `DecodeFieldSignature`, `DecodeMethodSignature`,
`DecodeLocalSignature`, all taking `ref BlobReader`).
- `IRuntimeSignatureTypeProvider<TType, TGenericContext>` -- superset of
SRM's `ISignatureTypeProvider` adding `GetInternalType(TargetPointer)`
and `GetInternalModifiedType(TargetPointer, TType, bool)` for the two
runtime-only encodings.
- `SignatureTypeProvider` (the existing field-signature provider)
implements `IRuntimeSignatureTypeProvider` so internal types in field
signatures resolve via `RuntimeTypeSystem.GetTypeHandle`.
**Signature-based GC reference scanning in StackWalk:**
- `GcSignatureTypeProvider` (internal, in `StackWalkHelpers`) classifies
each method-signature parameter as `Ref`, `Interior`, `Other` (value
type or larger-than-slot), or `None`.
- A new `GcSignatureContext(TypeHandle classContext, MethodDescHandle
methodContext)` record struct is plumbed through
`RuntimeSignatureDecoder<GcTypeKind, GcSignatureContext>` so
`ELEMENT_TYPE_VAR` / `ELEMENT_TYPE_MVAR` placeholders resolve to the
method's actual class / method instantiation -- matching native
`SigTypeContext`-driven `PeekElemTypeNormalized` behavior.
- `GcScanner.PromoteCallerStack` constructs the provider per call and
walks the `TransitionBlock` using a reserved-slot count derived from
`IsInstance` / return-buffer / `RequiresInstArg` / `IsAsyncMethod` /
ARM64 `x8`, reporting each parameter slot as a GC reference, interior
pointer, or skip.
- Signature acquisition mirrors native `MethodDesc::GetSig`: prefers
`IsStoredSigMethodDesc` (dynamic, EEImpl, array methods) before falling
back to the metadata token, so caller-stack roots of dynamic and
array-method transition frames are handled. Stored sigs are pinned with
an inline `fixed` block and read via a `BlobReader`, matching the
existing `SigFormat.cs` pattern.
- This is the cDAC equivalent of native
`TransitionFrame::PromoteCallerStack` and is used for
`PrestubMethodFrame`, `CallCountingHelperFrame`, and the
`StubDispatchFrame` / `ExternalMethodFrame` fallback when no GCRefMap is
available.
**Signature contract surface stays minimal:**
- `ISignatureDecoder` continues to expose only
`DecodeFieldSignature(BlobHandle, ModuleHandle, TypeHandle)`.
- GC-specific decoding lives entirely inside the StackWalk contract; the
Signature contract has no GC dependencies.
**cDAC documentation:**
- `docs/design/datacontracts/SignatureDecoder.md` -- describes
`RuntimeSignatureDecoder`, `IRuntimeSignatureTypeProvider`, and the
`ELEMENT_TYPE_INTERNAL` / `CMOD_INTERNAL` extensions; refreshed
`DecodeFieldSignature` code sample.
- `docs/design/datacontracts/StackWalk.md` -- new "Signature-Based
Scanning" section under GC stack reference scanning, covering
`GcSignatureTypeProvider` (with module scoping, `GcSignatureContext`,
and enum normalization), the `PromoteCallerStack` algorithm, the
reserved-slot table, and limitations vs. native.
### Testing
- Build: clean, 0 warnings / 0 errors.
- 1921 / 1921 cDAC unit tests pass.
- Behavioral verification against the legacy DAC happens via the
GC-stress verification harness introduced later in the PR series.
> [!NOTE]
> This PR description was created with AI assistance from Copilot.
---------
Co-authored-by: Max Charlamb <maxcharlamb@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 1, 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.

2 participants

@max-charlamb