diff --git a/eng/Subsets.props b/eng/Subsets.props
index ba8bc03906a1cb..bdb73e68c005cf 100644
--- a/eng/Subsets.props
+++ b/eng/Subsets.props
@@ -255,6 +255,7 @@
+
@@ -534,6 +535,10 @@
+
+
+
+
diff --git a/src/coreclr/vm/cdacstress.cpp b/src/coreclr/vm/cdacstress.cpp
index fbf019d01c7d58..eb48dacce3e8fd 100644
--- a/src/coreclr/vm/cdacstress.cpp
+++ b/src/coreclr/vm/cdacstress.cpp
@@ -72,7 +72,7 @@ static CrstStatic s_cdacLock; // Serializes cDAC access from concurr
// Unique-stack filtering: hash set of previously seen stack traces.
// Protected by s_cdacLock (already held during VerifyAtStressPoint).
-
+static const int UNIQUE_STACK_DEPTH = 8; // Number of return addresses to hash
static SHash>>* s_seenStacks = nullptr;
// Thread-local reentrancy guard — prevents infinite recursion when
@@ -146,12 +146,11 @@ static int ReadThreadContextCallback(uint32_t threadId, uint32_t contextFlags, u
// Minimal ICLRDataTarget implementation for loading the legacy DAC in-process.
// Routes ReadVirtual/GetThreadContext to the same callbacks as the cDAC.
//-----------------------------------------------------------------------------
-class InProcessDataTarget : public ICLRDataTarget, public ICLRRuntimeLocator
+class InProcessDataTarget : public ICLRDataTarget
{
volatile LONG m_refCount;
public:
InProcessDataTarget() : m_refCount(1) {}
- virtual ~InProcessDataTarget() = default;
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppObj) override
{
@@ -161,12 +160,6 @@ class InProcessDataTarget : public ICLRDataTarget, public ICLRRuntimeLocator
AddRef();
return S_OK;
}
- if (riid == __uuidof(ICLRRuntimeLocator))
- {
- *ppObj = static_cast(this);
- AddRef();
- return S_OK;
- }
*ppObj = nullptr;
return E_NOINTERFACE;
}
@@ -178,14 +171,6 @@ class InProcessDataTarget : public ICLRDataTarget, public ICLRRuntimeLocator
return c;
}
- // ICLRRuntimeLocator — provides the CLR base address directly so the DAC
- // does not fall back to GetImageBase (which needs GetModuleHandleW, unavailable on Linux).
- HRESULT STDMETHODCALLTYPE GetRuntimeBase(CLRDATA_ADDRESS* baseAddress) override
- {
- *baseAddress = (CLRDATA_ADDRESS)GetCurrentModuleBase();
- return S_OK;
- }
-
HRESULT STDMETHODCALLTYPE GetMachineType(ULONG32* machineType) override
{
#ifdef TARGET_AMD64
@@ -208,8 +193,10 @@ class InProcessDataTarget : public ICLRDataTarget, public ICLRRuntimeLocator
HRESULT STDMETHODCALLTYPE GetImageBase(LPCWSTR imagePath, CLRDATA_ADDRESS* baseAddress) override
{
- // Not needed — the DAC uses ICLRRuntimeLocator::GetRuntimeBase() instead.
- return E_NOTIMPL;
+ HMODULE hMod = ::GetModuleHandleW(imagePath);
+ if (hMod == NULL) return E_FAIL;
+ *baseAddress = (CLRDATA_ADDRESS)hMod;
+ return S_OK;
}
HRESULT STDMETHODCALLTYPE ReadVirtual(CLRDATA_ADDRESS address, BYTE* buffer, ULONG32 bytesRequested, ULONG32* bytesRead) override
@@ -282,8 +269,8 @@ bool CdacStress::Initialize()
}
else
{
- // Legacy: GCSTRESS_CDAC maps to allocation-point + reference verification
- s_cdacStressLevel = CDACSTRESS_ALLOC | CDACSTRESS_REFS;
+ // Legacy: GCSTRESS_CDAC maps to allocation-point verification
+ s_cdacStressLevel = CDACSTRESS_ALLOC;
}
// Load mscordaccore_universal from next to coreclr
@@ -449,15 +436,27 @@ bool CdacStress::Initialize()
pDacUnk->QueryInterface(__uuidof(IXCLRDataProcess), (void**)&s_dacProcess);
pDacUnk->Release();
}
+ else if (s_logFile != nullptr)
+ {
+ fprintf(s_logFile, "DAC: CLRDataCreateInstance failed hr=0x%08x pDacUnk=%p\n", hr, pDacUnk);
+ }
}
}
if (s_dacSosDac == nullptr)
{
+ if (s_logFile != nullptr)
+ fprintf(s_logFile, "DAC: Loaded mscordaccore.dll but QI for ISOSDacInterface failed\n");
LOG((LF_GCROOTS, LL_WARNING, "CDAC GC Stress: Legacy DAC loaded but QI for ISOSDacInterface failed\n"));
}
+ else if (s_logFile != nullptr)
+ {
+ fprintf(s_logFile, "DAC: Legacy DAC loaded successfully (s_dacSosDac=%p)\n", s_dacSosDac);
+ }
}
else
{
+ if (s_logFile != nullptr)
+ fprintf(s_logFile, "DAC: mscordaccore.dll not found at path\n");
LOG((LF_GCROOTS, LL_INFO10, "CDAC GC Stress: Legacy DAC not found (three-way comparison disabled)\n"));
}
}
@@ -535,11 +534,53 @@ void CdacStress::Shutdown()
LOG((LF_GCROOTS, LL_INFO10, "CDAC GC Stress: Shutdown complete\n"));
}
+//-----------------------------------------------------------------------------
+// Resolve a managed IP to a method name using ISOSDacInterface.
+// Falls back to "" or "" if resolution fails.
+// Uses the cDAC's ISOSDacInterface by default.
+//-----------------------------------------------------------------------------
+
+static void ResolveMethodName(CLRDATA_ADDRESS source, int sourceType, char* buf, int bufLen)
+{
+ if (bufLen <= 0)
+ return;
+
+ // Frame addresses (not managed IPs) — show as frame type
+ if (sourceType != 0) // SOS_StackSourceFrame
+ {
+ snprintf(buf, bufLen, "", (unsigned long long)source);
+ return;
+ }
+
+ // Try to resolve the IP using the cDAC's ISOSDacInterface
+ ISOSDacInterface* pSos = s_cdacSosDac;
+ if (pSos == nullptr)
+ pSos = s_dacSosDac; // Fallback to legacy DAC
+
+ if (pSos != nullptr)
+ {
+ CLRDATA_ADDRESS mdAddr = 0;
+ if (SUCCEEDED(pSos->GetMethodDescPtrFromIP(source, &mdAddr)) && mdAddr != 0)
+ {
+ WCHAR wname[256] = {};
+ unsigned int nameLen = 0;
+ if (SUCCEEDED(pSos->GetMethodDescName(mdAddr, ARRAY_SIZE(wname), wname, &nameLen)) && nameLen > 0)
+ {
+ WideCharToMultiByte(CP_UTF8, 0, wname, -1, buf, bufLen, NULL, NULL);
+ return;
+ }
+ }
+ }
+
+ snprintf(buf, bufLen, "", (unsigned long long)source);
+}
+
//-----------------------------------------------------------------------------
// Collect stack refs from the cDAC
//-----------------------------------------------------------------------------
-static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray* pRefs)
+static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray* pRefs,
+ const char* label = nullptr)
{
if (pSosDac == nullptr)
return false;
@@ -548,7 +589,20 @@ static bool CollectStackRefs(ISOSDacInterface* pSosDac, DWORD osThreadId, SArray
HRESULT hr = pSosDac->GetStackReferences(osThreadId, &pEnum);
if (FAILED(hr) || pEnum == nullptr)
+ {
+ // Log the first failure per source for diagnostics
+ static bool s_loggedCdacFailure = false;
+ static bool s_loggedDacFailure = false;
+ bool* pLogged = (label != nullptr && strcmp(label, "DAC") == 0) ? &s_loggedDacFailure : &s_loggedCdacFailure;
+ if (s_logFile != nullptr && !*pLogged)
+ {
+ fprintf(s_logFile, "%s: GetStackReferences failed hr=0x%08x pEnum=%p thread=0x%x\n",
+ label ? label : "???", hr, pEnum, osThreadId);
+ fflush(s_logFile);
+ *pLogged = true;
+ }
return false;
+ }
SOSStackRefData refData;
unsigned int fetched = 0;
@@ -835,7 +889,6 @@ static void CompareStackWalks(Thread* pThread, PCONTEXT regs)
bool mismatch = false;
while (frameIdx < 200) // safety limit
{
- // Compare GetContext
BYTE cdacCtx[4096] = {};
BYTE dacCtx[4096] = {};
ULONG32 cdacCtxSize = 0, dacCtxSize = 0;
@@ -854,18 +907,8 @@ static void CompareStackWalks(Thread* pThread, PCONTEXT regs)
if (hr1 != S_OK)
break; // both finished
- if (cdacCtxSize != dacCtxSize)
- {
- if (s_logFile)
- fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: Context size differs cDAC=%u DAC=%u\n",
- frameIdx, cdacCtxSize, dacCtxSize);
- mismatch = true;
- }
- else if (cdacCtxSize >= sizeof(CONTEXT))
+ if (cdacCtxSize >= sizeof(CONTEXT) && dacCtxSize >= sizeof(CONTEXT))
{
- // Compare IP and SP — these are what matter for stack walk parity.
- // Other CONTEXT fields (floating-point, debug registers, xstate) may
- // differ between cDAC and DAC without affecting the walk.
PCODE cdacIP = GetIP((CONTEXT*)cdacCtx);
PCODE dacIP = GetIP((CONTEXT*)dacCtx);
TADDR cdacSP = GetSP((CONTEXT*)cdacCtx);
@@ -882,28 +925,6 @@ static void CompareStackWalks(Thread* pThread, PCONTEXT regs)
}
}
- // Compare Request(FRAME_DATA)
- ULONG64 cdacFrameAddr = 0, dacFrameAddr = 0;
- hr1 = cdacWalk->Request(0xf0000000, 0, nullptr, sizeof(cdacFrameAddr), (BYTE*)&cdacFrameAddr);
- hr2 = dacWalk->Request(0xf0000000, 0, nullptr, sizeof(dacFrameAddr), (BYTE*)&dacFrameAddr);
-
- if (hr1 == S_OK && hr2 == S_OK && cdacFrameAddr != dacFrameAddr)
- {
- if (s_logFile)
- {
- PCODE cdacIP = 0, dacIP = 0;
- if (cdacCtxSize >= sizeof(CONTEXT))
- cdacIP = GetIP((CONTEXT*)cdacCtx);
- if (dacCtxSize >= sizeof(CONTEXT))
- dacIP = GetIP((CONTEXT*)dacCtx);
- fprintf(s_logFile, " [WALK_MISMATCH] Frame %d: FrameAddr cDAC=0x%llx DAC=0x%llx (cDAC_IP=0x%llx DAC_IP=0x%llx)\n",
- frameIdx, (unsigned long long)cdacFrameAddr, (unsigned long long)dacFrameAddr,
- (unsigned long long)cdacIP, (unsigned long long)dacIP);
- }
- mismatch = true;
- }
-
- // Advance both
hr1 = cdacWalk->Next();
hr2 = dacWalk->Next();
@@ -929,29 +950,29 @@ static void CompareStackWalks(Thread* pThread, PCONTEXT regs)
}
//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-// Compare two ref sets using two-phase matching.
-// Phase 1: Match stack refs (Address != 0) by exact (Address, Object, Flags).
-// Phase 2: Match register refs (Address == 0) by (Object, Flags) only.
-// Returns true if all refs in setA have a match in setB and counts are equal.
+// Per-frame ref comparison.
+//
+// Groups refs by Source (IP or Frame address), aligns the two sets by Source,
+// and reports per-frame differences with resolved method names.
+// Returns true if all refs match.
//-----------------------------------------------------------------------------
-static bool CompareRefSets(StackRef* refsA, int countA, StackRef* refsB, int countB)
+// 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)
return false;
if (countA == 0)
return true;
- if (countA > MAX_COLLECTED_REFS)
- return false;
bool matched[MAX_COLLECTED_REFS] = {};
+ // Phase 1: Match stack refs (Address != 0) by exact (Address, Object, Flags).
for (int i = 0; i < countA; i++)
{
if (refsA[i].Address == 0)
continue;
- bool found = false;
for (int j = 0; j < countB; j++)
{
if (matched[j]) continue;
@@ -960,18 +981,16 @@ static bool CompareRefSets(StackRef* refsA, int countA, StackRef* refsB, int cou
refsA[i].Flags == refsB[j].Flags)
{
matched[j] = true;
- found = true;
break;
}
}
- if (!found) return false;
}
+ // Phase 2: Match register refs (Address == 0) by (Object, Flags) only.
for (int i = 0; i < countA; i++)
{
if (refsA[i].Address != 0)
continue;
- bool found = false;
for (int j = 0; j < countB; j++)
{
if (matched[j]) continue;
@@ -979,16 +998,223 @@ static bool CompareRefSets(StackRef* refsA, int countA, StackRef* refsB, int cou
refsA[i].Flags == refsB[j].Flags)
{
matched[j] = true;
- found = true;
break;
}
}
- if (!found) return false;
}
+ // Check that every ref in B was matched
+ for (int j = 0; j < countB; j++)
+ {
+ if (!matched[j])
+ return false;
+ }
return true;
}
+// Represents a group of refs from the same Source (managed frame or explicit Frame).
+struct FrameRefGroup
+{
+ CLRDATA_ADDRESS Source;
+ int SourceType; // 0 = IP, 1 = Frame
+ int StartIdx; // Index into the original ref array
+ int Count; // Number of refs in this group
+};
+
+// Build a sorted list of unique Sources with their ref index ranges.
+// The refs array is sorted by Source as a side effect.
+static int __cdecl CompareBySource(const void* a, const void* b)
+{
+ const StackRef* ra = static_cast(a);
+ const StackRef* rb = static_cast(b);
+ if (ra->Source != rb->Source)
+ return (ra->Source < rb->Source) ? -1 : 1;
+ return 0;
+}
+
+static int GroupRefsByFrame(StackRef* refs, int count, FrameRefGroup* groups, int maxGroups)
+{
+ if (count == 0)
+ return 0;
+
+ qsort(refs, count, sizeof(StackRef), CompareBySource);
+
+ int groupCount = 0;
+ CLRDATA_ADDRESS currentSource = refs[0].Source;
+ int startIdx = 0;
+
+ for (int i = 1; i <= count; i++)
+ {
+ if (i == count || refs[i].Source != currentSource)
+ {
+ if (groupCount < maxGroups)
+ {
+ groups[groupCount].Source = currentSource;
+ groups[groupCount].SourceType = refs[startIdx].SourceType;
+ groups[groupCount].StartIdx = startIdx;
+ groups[groupCount].Count = i - startIdx;
+ groupCount++;
+ }
+ if (i < count)
+ {
+ currentSource = refs[i].Source;
+ startIdx = i;
+ }
+ }
+ }
+ return groupCount;
+}
+
+// Compare refs within a single frame. Returns the number of unmatched refs in each set.
+static void CompareFrameRefs(StackRef* refsA, int countA, StackRef* refsB, int countB,
+ int* unmatchedA, int* unmatchedB,
+ bool* aUsed, bool* bUsed)
+{
+ // Phase 1: exact (Address, Object, Flags) for Address != 0
+ for (int i = 0; i < countA; i++)
+ {
+ if (refsA[i].Address == 0)
+ continue;
+ for (int j = 0; j < countB; j++)
+ {
+ if (bUsed[j]) continue;
+ if (refsA[i].Address == refsB[j].Address &&
+ refsA[i].Object == refsB[j].Object &&
+ refsA[i].Flags == refsB[j].Flags)
+ {
+ aUsed[i] = bUsed[j] = true;
+ break;
+ }
+ }
+ }
+
+ // Phase 2: (Object, Flags) for Address=0 or unmatched refs
+ for (int i = 0; i < countA; i++)
+ {
+ if (aUsed[i]) continue;
+ for (int j = 0; j < countB; j++)
+ {
+ if (bUsed[j]) continue;
+ if (refsA[i].Object == refsB[j].Object &&
+ refsA[i].Flags == refsB[j].Flags)
+ {
+ aUsed[i] = bUsed[j] = true;
+ break;
+ }
+ }
+ }
+
+ *unmatchedA = 0;
+ *unmatchedB = 0;
+ for (int i = 0; i < countA; i++)
+ if (!aUsed[i]) (*unmatchedA)++;
+ for (int j = 0; j < countB; j++)
+ if (!bUsed[j]) (*unmatchedB)++;
+}
+
+// Per-frame comparison: groups refs by Source, compares per-frame, logs structured diff.
+// Returns true if all refs match between the two sets.
+static bool ComparePerFrame(StackRef* refsA, int countA, const char* labelA,
+ StackRef* refsB, int countB, const char* labelB)
+{
+ static const int MAX_GROUPS = 256;
+ FrameRefGroup groupsA[MAX_GROUPS], groupsB[MAX_GROUPS];
+ int numGroupsA = GroupRefsByFrame(refsA, countA, groupsA, MAX_GROUPS);
+ int numGroupsB = GroupRefsByFrame(refsB, countB, groupsB, MAX_GROUPS);
+
+ bool allMatch = true;
+ int idxA = 0, idxB = 0;
+
+ if (s_logFile)
+ fprintf(s_logFile, " [COMPARE %s-vs-%s]\n", labelA, labelB);
+
+ while (idxA < numGroupsA || idxB < numGroupsB)
+ {
+ if (idxA < numGroupsA && idxB < numGroupsB && groupsA[idxA].Source == groupsB[idxB].Source)
+ {
+ // Both have this frame — compare refs within
+ int cA = groupsA[idxA].Count;
+ int cB = groupsB[idxB].Count;
+ bool aUsed[MAX_COLLECTED_REFS] = {};
+ bool bUsed[MAX_COLLECTED_REFS] = {};
+ int unmatchedA = 0, unmatchedB = 0;
+
+ CompareFrameRefs(&refsA[groupsA[idxA].StartIdx], cA,
+ &refsB[groupsB[idxB].StartIdx], cB,
+ &unmatchedA, &unmatchedB, aUsed, bUsed);
+
+ if (unmatchedA > 0 || unmatchedB > 0)
+ {
+ allMatch = false;
+ if (s_logFile)
+ {
+ char methodName[256];
+ ResolveMethodName(groupsA[idxA].Source, groupsA[idxA].SourceType, methodName, sizeof(methodName));
+
+ // Log SP from first ref in each group for unwinder comparison
+ auto spA = refsA[groupsA[idxA].StartIdx].StackPointer;
+ auto spB = refsB[groupsB[idxB].StartIdx].StackPointer;
+ fprintf(s_logFile, " [FRAME_DIFF] Source=0x%llx (%s): %s=%d %s=%d SP_%s=0x%llx SP_%s=0x%llx%s\n",
+ (unsigned long long)groupsA[idxA].Source, methodName, labelA, cA, labelB, cB,
+ labelA, (unsigned long long)spA, labelB, (unsigned long long)spB,
+ spA != spB ? " <-- SP MISMATCH" : "");
+
+ // Dump ALL refs from both sides for detailed comparison
+ for (int i = 0; i < cA; i++)
+ {
+ auto& r = refsA[groupsA[idxA].StartIdx + i];
+ fprintf(s_logFile, " [%s_%s] Addr=0x%llx Obj=0x%llx Flags=0x%x Reg=%d Off=%d\n",
+ labelA, aUsed[i] ? "MATCHED" : "ONLY",
+ (unsigned long long)r.Address, (unsigned long long)r.Object, r.Flags,
+ r.Register, r.Offset);
+ }
+ for (int j = 0; j < cB; j++)
+ {
+ auto& r = refsB[groupsB[idxB].StartIdx + j];
+ fprintf(s_logFile, " [%s_%s] Addr=0x%llx Obj=0x%llx Flags=0x%x Reg=%d Off=%d\n",
+ labelB, bUsed[j] ? "MATCHED" : "ONLY",
+ (unsigned long long)r.Address, (unsigned long long)r.Object, r.Flags,
+ r.Register, r.Offset);
+ }
+ }
+ }
+ idxA++;
+ idxB++;
+ }
+ else if (idxB >= numGroupsB || (idxA < numGroupsA && groupsA[idxA].Source < groupsB[idxB].Source))
+ {
+ // Frame only in A
+ allMatch = false;
+ if (s_logFile)
+ {
+ char methodName[256];
+ ResolveMethodName(groupsA[idxA].Source, groupsA[idxA].SourceType, methodName, sizeof(methodName));
+ fprintf(s_logFile, " [FRAME_%s_ONLY] Source=0x%llx (%s): %s=%d\n",
+ labelA, (unsigned long long)groupsA[idxA].Source, methodName, labelA, groupsA[idxA].Count);
+ }
+ idxA++;
+ }
+ else
+ {
+ // Frame only in B
+ allMatch = false;
+ if (s_logFile)
+ {
+ char methodName[256];
+ ResolveMethodName(groupsB[idxB].Source, groupsB[idxB].SourceType, methodName, sizeof(methodName));
+ fprintf(s_logFile, " [FRAME_%s_ONLY] Source=0x%llx (%s): %s=%d\n",
+ labelB, (unsigned long long)groupsB[idxB].Source, methodName, labelB, groupsB[idxB].Count);
+ }
+ idxB++;
+ }
+ }
+
+ if (allMatch && s_logFile)
+ fprintf(s_logFile, " [MATCH] All %d refs matched\n", countA);
+
+ return allMatch;
+}
+
//-----------------------------------------------------------------------------
// Filter interior stack pointers and deduplicate a ref set in place.
//-----------------------------------------------------------------------------
@@ -1024,12 +1250,33 @@ void CdacStress::VerifyAtAllocPoint()
if (t_inVerification)
return;
+ if (ShouldSkipStressPoint())
+ return;
+
Thread* pThread = GetThreadNULLOk();
if (pThread == nullptr || !pThread->PreemptiveGCDisabled())
return;
+ // Capture the current context and unwind past VerifyAtAllocPoint
+ // so the walk starts from the caller, not from inside this function.
CONTEXT ctx;
RtlCaptureContext(&ctx);
+#ifndef TARGET_UNIX
+ {
+ ULONG64 imageBase = 0;
+ PRUNTIME_FUNCTION pFunctionEntry = RtlLookupFunctionEntry(GetIP(&ctx), &imageBase, nullptr);
+ if (pFunctionEntry != nullptr)
+ {
+ void* handlerData = nullptr;
+ ULONG64 establisherFrame = 0;
+ RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, GetIP(&ctx),
+ pFunctionEntry, &ctx, &handlerData, &establisherFrame, nullptr);
+ }
+ }
+#else
+ PAL_VirtualUnwind(&ctx);
+#endif
+
VerifyAtStressPoint(pThread, &ctx);
}
@@ -1067,21 +1314,15 @@ void CdacStress::VerifyAtStressPoint(Thread* pThread, PCONTEXT regs)
// Flush the cDAC's ProcessedData cache so it re-reads from the live process.
if (s_cdacProcess != nullptr)
- {
s_cdacProcess->Flush();
- }
// Flush the legacy DAC cache too.
if (s_dacProcess != nullptr)
- {
s_dacProcess->Flush();
- }
// Compare IXCLRDataStackWalk frame-by-frame between cDAC and legacy DAC.
if (s_cdacStressLevel & CDACSTRESS_WALK)
- {
CompareStackWalks(pThread, regs);
- }
// Compare GC stack references.
if (!(s_cdacStressLevel & CDACSTRESS_REFS))
@@ -1092,54 +1333,45 @@ void CdacStress::VerifyAtStressPoint(Thread* pThread, PCONTEXT regs)
}
// Step 1: Collect raw refs from cDAC (always) and DAC (if USE_DAC).
+ // Save and restore the thread's Frame chain around the cDAC call because
+ // the cDAC runs as NativeAOT managed code on the same thread and may
+ // push its own Frames during execution.
DWORD osThreadId = pThread->GetOSThreadId();
+ Frame* pSavedFrame = pThread->GetFrame();
SArray cdacRefs;
- bool haveCdac = CollectStackRefs(s_cdacSosDac, osThreadId, &cdacRefs);
+ bool haveCdac = CollectStackRefs(s_cdacSosDac, osThreadId, &cdacRefs, "cDAC");
+
+ // Restore the Frame chain to what it was before the cDAC walk
+ pThread->SetFrame(pSavedFrame);
SArray dacRefs;
bool haveDac = false;
if (s_cdacStressLevel & CDACSTRESS_USE_DAC)
- {
- haveDac = (s_dacSosDac != nullptr) && CollectStackRefs(s_dacSosDac, osThreadId, &dacRefs);
- }
+ haveDac = (s_dacSosDac != nullptr) && CollectStackRefs(s_dacSosDac, osThreadId, &dacRefs, "DAC");
s_currentContext = nullptr;
s_currentThreadId = 0;
StackRef runtimeRefsBuf[MAX_COLLECTED_REFS];
int runtimeCount = 0;
- bool haveRuntime = CollectRuntimeStackRefs(pThread, regs, runtimeRefsBuf, &runtimeCount);
+ bool rtOverflow = !CollectRuntimeStackRefs(pThread, regs, runtimeRefsBuf, &runtimeCount);
+ 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 (!haveCdac || !haveRuntime)
+ if (!haveCdac)
{
InterlockedIncrement(&s_verifySkip);
if (s_logFile != nullptr)
- {
- if (!haveCdac)
- fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - cDAC GetStackReferences failed\n",
- osThreadId, (void*)GetIP(regs));
- else
- fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - runtime CollectRuntimeStackRefs overflowed\n",
- osThreadId, (void*)GetIP(regs));
- }
+ fprintf(s_logFile, "[SKIP] Thread=0x%x IP=0x%p - cDAC GetStackReferences failed\n",
+ osThreadId, (void*)GetIP(regs));
return;
}
- // Step 2: Compare cDAC vs DAC raw (before any filtering).
- int rawCdacCount = (int)cdacRefs.GetCount();
- int rawDacCount = haveDac ? (int)dacRefs.GetCount() : -1;
- bool dacMatch = true;
- if (haveDac)
- {
- StackRef* cdacBuf = cdacRefs.OpenRawBuffer();
- StackRef* dacBuf = dacRefs.OpenRawBuffer();
- dacMatch = CompareRefSets(cdacBuf, rawCdacCount, dacBuf, rawDacCount);
- cdacRefs.CloseRawBuffer();
- dacRefs.CloseRawBuffer();
- }
-
- // Step 3: Filter cDAC refs and compare vs RT (always).
+ // Step 2: Compute stack limit for filtering.
Frame* pTopFrame = pThread->GetFrame();
Object** topStack = (Object**)pTopFrame;
if (InlinedCallFrame::FrameHasActiveCall(pTopFrame))
@@ -1149,56 +1381,168 @@ void CdacStress::VerifyAtStressPoint(Thread* pThread, PCONTEXT regs)
}
uintptr_t stackLimit = (uintptr_t)topStack;
+ // Step 3: Apply unified filtering to both cDAC and DAC refs.
+ int rawCdacCount = (int)cdacRefs.GetCount();
+ int rawDacCount = haveDac ? (int)dacRefs.GetCount() : -1;
+
int filteredCdacCount = rawCdacCount;
if (filteredCdacCount > 0)
{
- StackRef* cdacBuf = cdacRefs.OpenRawBuffer();
- filteredCdacCount = FilterAndDedup(cdacBuf, filteredCdacCount, pThread, stackLimit);
+ StackRef* buf = cdacRefs.OpenRawBuffer();
+ filteredCdacCount = FilterAndDedup(buf, filteredCdacCount, pThread, stackLimit);
cdacRefs.CloseRawBuffer();
}
+
+ int filteredDacCount = rawDacCount;
+ if (haveDac && filteredDacCount > 0)
+ {
+ StackRef* buf = dacRefs.OpenRawBuffer();
+ filteredDacCount = FilterAndDedup(buf, filteredDacCount, pThread, stackLimit);
+ dacRefs.CloseRawBuffer();
+ }
+
runtimeCount = DeduplicateRefs(runtimeRefsBuf, runtimeCount);
- StackRef* cdacBuf = cdacRefs.OpenRawBuffer();
- bool rtMatch = CompareRefSets(cdacBuf, filteredCdacCount, runtimeRefsBuf, runtimeCount);
- cdacRefs.CloseRawBuffer();
+ StackRef* cdacBuf = nullptr;
- // Step 4: Pass requires cDAC vs RT match.
- // DAC mismatch is logged separately but doesn't affect pass/fail.
- bool pass = rtMatch;
+ // Step 4: Compare cDAC vs DAC (determines pass/fail when USE_DAC is set).
+ bool dacMatch = true;
+ if (haveDac)
+ {
+ cdacBuf = cdacRefs.OpenRawBuffer();
+ StackRef* dacBuf = dacRefs.OpenRawBuffer();
+ dacMatch = CompareRefSetsFlat(cdacBuf, filteredCdacCount, dacBuf, filteredDacCount);
+ cdacRefs.CloseRawBuffer();
+ dacRefs.CloseRawBuffer();
+ }
+
+ // Step 5: Compare cDAC vs RT (informational).
+ cdacBuf = cdacRefs.OpenRawBuffer();
+ bool rtMatch = CompareRefSetsFlat(cdacBuf, filteredCdacCount, runtimeRefsBuf, runtimeCount);
+ cdacRefs.CloseRawBuffer();
+ bool pass = haveDac ? dacMatch : rtMatch;
if (pass)
InterlockedIncrement(&s_verifyPass);
else
InterlockedIncrement(&s_verifyFail);
- // Step 5: Log results.
+ // Step 6: Log structured results.
if (s_logFile != nullptr)
{
- const char* label = pass ? "PASS" : "FAIL";
- if (pass && !dacMatch)
- label = "DAC_MISMATCH";
- fprintf(s_logFile, "[%s] Thread=0x%x IP=0x%p cDAC=%d DAC=%d RT=%d\n",
- label, osThreadId, (void*)GetIP(regs),
- rawCdacCount, rawDacCount, runtimeCount);
-
- if (!pass || !dacMatch)
+ if (pass && rtMatch)
{
- for (int i = 0; i < rawCdacCount; i++)
- fprintf(s_logFile, " cDAC [%d]: Address=0x%llx Object=0x%llx Flags=0x%x Source=0x%llx SourceType=%d SP=0x%llx\n",
- i, (unsigned long long)cdacRefs[i].Address, (unsigned long long)cdacRefs[i].Object,
- cdacRefs[i].Flags, (unsigned long long)cdacRefs[i].Source, cdacRefs[i].SourceType,
- (unsigned long long)cdacRefs[i].StackPointer);
- if (haveDac)
+ // Clean pass — one-liner
+ fprintf(s_logFile, "[PASS] Thread=0x%x IP=0x%p cDAC=%d DAC=%d RT=%d\n",
+ osThreadId, (void*)GetIP(regs), filteredCdacCount, filteredDacCount, runtimeCount);
+ }
+ else
+ {
+ // Failure or RT mismatch — structured per-frame output
+ const char* label = pass ? "PASS" : "FAIL";
+ fprintf(s_logFile, "[%s] Thread=0x%x IP=0x%p cDAC=%d DAC=%d RT=%d\n",
+ label, osThreadId, (void*)GetIP(regs), filteredCdacCount, filteredDacCount, runtimeCount);
+
+ if (!dacMatch && haveDac)
{
- for (int i = 0; i < rawDacCount; i++)
- fprintf(s_logFile, " DAC [%d]: Address=0x%llx Object=0x%llx Flags=0x%x Source=0x%llx\n",
- i, (unsigned long long)dacRefs[i].Address, (unsigned long long)dacRefs[i].Object,
- dacRefs[i].Flags, (unsigned long long)dacRefs[i].Source);
+ cdacBuf = cdacRefs.OpenRawBuffer();
+ StackRef* dacBuf = dacRefs.OpenRawBuffer();
+ ComparePerFrame(cdacBuf, filteredCdacCount, "cDAC",
+ dacBuf, filteredDacCount, "DAC");
+ cdacRefs.CloseRawBuffer();
+ dacRefs.CloseRawBuffer();
+ }
+
+ if (!rtMatch)
+ {
+ fprintf(s_logFile, " [RT_DIFF] cDAC=%d RT=%d (cDAC matches DAC but differs from RT)\n",
+ filteredCdacCount, runtimeCount);
+ }
+
+ // Log a stack trace derived from unique Source IPs in the refs
+ if (!pass)
+ {
+ fprintf(s_logFile, " [STACK_TRACE] (cDAC=%d DAC=%d RT=%d)\n",
+ filteredCdacCount, filteredDacCount, runtimeCount);
+
+ // Collect unique Sources from cDAC refs (sorted by SP descending = top of stack first)
+ struct FrameEntry { CLRDATA_ADDRESS Source; int SourceType; CLRDATA_ADDRESS SP; };
+ FrameEntry frames[256];
+ int frameCount = 0;
+
+ cdacBuf = cdacRefs.OpenRawBuffer();
+ for (int i = 0; i < filteredCdacCount && frameCount < 256; i++)
+ {
+ bool dup = false;
+ for (int j = 0; j < frameCount; j++)
+ {
+ if (frames[j].Source == cdacBuf[i].Source)
+ {
+ dup = true;
+ break;
+ }
+ }
+ if (!dup)
+ {
+ frames[frameCount].Source = cdacBuf[i].Source;
+ frames[frameCount].SourceType = cdacBuf[i].SourceType;
+ frames[frameCount].SP = cdacBuf[i].StackPointer;
+ frameCount++;
+ }
+ }
+ cdacRefs.CloseRawBuffer();
+
+ // Also include DAC-only sources that the cDAC missed
+ if (haveDac)
+ {
+ StackRef* dacBuf = dacRefs.OpenRawBuffer();
+ for (int i = 0; i < filteredDacCount && frameCount < 256; i++)
+ {
+ bool dup = false;
+ for (int j = 0; j < frameCount; j++)
+ {
+ if (frames[j].Source == dacBuf[i].Source)
+ {
+ dup = true;
+ break;
+ }
+ }
+ if (!dup)
+ {
+ frames[frameCount].Source = dacBuf[i].Source;
+ frames[frameCount].SourceType = dacBuf[i].SourceType;
+ frames[frameCount].SP = 0; // DAC doesn't provide SP
+ frameCount++;
+ }
+ }
+ dacRefs.CloseRawBuffer();
+ }
+
+ for (int i = 0; i < frameCount; i++)
+ {
+ char methodName[256];
+ ResolveMethodName(frames[i].Source, frames[i].SourceType, methodName, sizeof(methodName));
+
+ // Count refs from this source in cDAC and DAC
+ int cdacCount = 0, dacCount = 0;
+ cdacBuf = cdacRefs.OpenRawBuffer();
+ for (int j = 0; j < filteredCdacCount; j++)
+ if (cdacBuf[j].Source == frames[i].Source) cdacCount++;
+ cdacRefs.CloseRawBuffer();
+
+ if (haveDac)
+ {
+ StackRef* dacBuf2 = dacRefs.OpenRawBuffer();
+ for (int j = 0; j < filteredDacCount; j++)
+ if (dacBuf2[j].Source == frames[i].Source) dacCount++;
+ dacRefs.CloseRawBuffer();
+ }
+
+ const char* marker = (cdacCount != dacCount) ? " <-- MISMATCH" : "";
+ fprintf(s_logFile, " #%d %s (cDAC=%d DAC=%d)%s\n",
+ i, methodName, cdacCount, dacCount, marker);
+ }
}
- for (int i = 0; i < runtimeCount; i++)
- fprintf(s_logFile, " RT [%d]: Address=0x%llx Object=0x%llx Flags=0x%x\n",
- i, (unsigned long long)runtimeRefsBuf[i].Address, (unsigned long long)runtimeRefsBuf[i].Object,
- runtimeRefsBuf[i].Flags);
fflush(s_logFile);
}
diff --git a/src/coreclr/vm/gccover.cpp b/src/coreclr/vm/gccover.cpp
index 64f22359891a57..7069ddb818f7d6 100644
--- a/src/coreclr/vm/gccover.cpp
+++ b/src/coreclr/vm/gccover.cpp
@@ -853,6 +853,24 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion)
enableWhenDone = true;
}
+ // When DOTNET_CdacStressStep > 1, skip most stress points (both cDAC verification
+ // and StressHeap) to reduce overhead.
+ if (CdacStress::IsInitialized() && CdacStress::ShouldSkipStressPoint())
+ {
+ if (pThread->HasPendingGCStressInstructionUpdate())
+ UpdateGCStressInstructionWithoutGC();
+
+ FlushInstructionCache(GetCurrentProcess(), (LPCVOID)instrPtr, 4);
+
+ if (enableWhenDone)
+ {
+ BOOL b = GC_ON_TRANSITIONS(FALSE);
+ pThread->EnablePreemptiveGC();
+ GC_ON_TRANSITIONS(b);
+ }
+ return;
+ }
+
//
// If we redirect for gc stress, we don't need this frame on the stack,
// the redirection will push a resumable frame.
@@ -1177,6 +1195,18 @@ void DoGcStress (PCONTEXT regs, NativeCodeVersion nativeCodeVersion)
// code and it will just raise a STATUS_ACCESS_VIOLATION.
pThread->PostGCStressInstructionUpdate((BYTE*)instrPtr, &gcCover->savedCode[offset]);
+ // When DOTNET_CdacStressStep > 1, skip most stress points (both cDAC verification
+ // and StressHeap) to reduce overhead. We still restore the instruction since the
+ // breakpoint must be removed regardless.
+ if (CdacStress::IsInitialized() && CdacStress::ShouldSkipStressPoint())
+ {
+ if (pThread->HasPendingGCStressInstructionUpdate())
+ UpdateGCStressInstructionWithoutGC();
+
+ FlushInstructionCache(GetCurrentProcess(), (LPCVOID)instrPtr, 4);
+ return;
+ }
+
// we should be in coop mode.
_ASSERTE(pThread->PreemptiveGCDisabled());