Skip to content

Commit 0d71a2f

Browse files
committed
Fix stack walking for new EH
There is an edge case when the StackFrameIterator was incorrectly setting the fShouldParentFrameUseUnwindTargetPCforGCReporting, which resulted in a 100% reproducible GC hole in the baseservices\exceptions\unittests\ThrowInFinallyNestedInTry with GC stress C and tiered compilation off. The fix is to delete code that is already present in an "if" branch above the change and that should really be executed only when the funclet parent is the caller of the actual handler frame.
1 parent 90c3cbb commit 0d71a2f

File tree

8 files changed

+142
-20
lines changed

8 files changed

+142
-20
lines changed

src/coreclr/inc/eetwain.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ enum ICodeManagerFlags
105105
// any untracked slots
106106

107107
LightUnwind = 0x0100, // Unwind just enough to get return addresses
108+
ReportFPBasedSlotsOnly
109+
= 0x0200, // EnumGCRefs/EnumerateLiveSlots should only include
110+
// slots that are based on the frame pointer
108111
};
109112

110113
//*****************************************************************************

src/coreclr/vm/amd64/cgencpu.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,13 @@ inline void SetSSP(CONTEXT *context, DWORD64 ssp)
429429
}
430430
#endif // !DACCESS_COMPILE
431431

432-
#define SetFP(context, ebp)
432+
inline void SetFP(CONTEXT *context, TADDR rbp)
433+
{
434+
LIMITED_METHOD_DAC_CONTRACT;
435+
436+
context->Rbp = (DWORD64)rbp;
437+
}
438+
433439
inline TADDR GetFP(const CONTEXT * context)
434440
{
435441
LIMITED_METHOD_CONTRACT;

src/coreclr/vm/exinfo.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,8 @@ ExInfo::ExInfo(Thread *pThread, EXCEPTION_RECORD *pExceptionRecord, CONTEXT *pEx
326326
m_propagateExceptionContext(NULL),
327327
#endif // HOST_UNIX
328328
m_CurrentClause({}),
329-
m_pMDToReportFunctionLeave(NULL)
329+
m_pMDToReportFunctionLeave(NULL),
330+
m_lastReportedFunclet({0, 0, 0})
330331
{
331332
m_StackTraceInfo.AllocateStackTrace();
332333
pThread->GetExceptionState()->m_pCurrentTracker = this;

src/coreclr/vm/exinfo.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,13 @@ enum class ExKind : uint8_t
196196

197197
struct PAL_SEHException;
198198

199+
struct LastReportedFuncletInfo
200+
{
201+
PCODE IP;
202+
TADDR FP;
203+
uint32_t Flags;
204+
};
205+
199206
struct ExInfo : public ExceptionTrackerBase
200207
{
201208
ExInfo(Thread *pThread, EXCEPTION_RECORD *pExceptionRecord, CONTEXT *pExceptionContext, ExKind exceptionKind);
@@ -269,6 +276,8 @@ struct ExInfo : public ExceptionTrackerBase
269276
REGDISPLAY m_regDisplay;
270277
// Initial explicit frame for stack walking
271278
Frame *m_pInitialFrame;
279+
// Info on the last reported funclet used to report references in the parent frame
280+
LastReportedFuncletInfo m_lastReportedFunclet;
272281

273282
#if defined(TARGET_UNIX)
274283
void TakeExceptionPointersOwnership(PAL_SEHException* ex);

src/coreclr/vm/gcenv.ee.common.cpp

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "common.h"
55
#include "gcenv.h"
6+
#include <exinfo.h>
67

78
#if defined(FEATURE_EH_FUNCLETS)
89

@@ -148,6 +149,8 @@ void GcEnumObject(LPVOID pData, OBJECTREF *pObj, uint32_t flags)
148149
Object ** ppObj = (Object **)pObj;
149150
GCCONTEXT * pCtx = (GCCONTEXT *) pData;
150151

152+
STRESS_LOG3(LF_GCROOTS, LL_INFO1000, "GcEnumObject at slot %p, object %p, pinned=%d\n", pObj, *ppObj, (flags & GC_CALL_PINNED) ? 1 : 0);
153+
151154
// Since we may be asynchronously walking another thread's stack,
152155
// check (frequently) for stack-buffer-overrun corruptions after
153156
// any long operation
@@ -220,8 +223,54 @@ StackWalkAction GcStackCrawlCallBack(CrawlFrame* pCF, VOID* pData)
220223
// We may have unwound this crawlFrame and thus, shouldn't report the invalid
221224
// references it may contain.
222225
fReportGCReferences = pCF->ShouldCrawlframeReportGCReferences();
223-
#endif // defined(FEATURE_EH_FUNCLETS)
224226

227+
Thread *pThread = pCF->GetThread();
228+
ExInfo *pExInfo = (ExInfo *)pThread->GetExceptionState()->GetCurrentExceptionTracker();
229+
230+
if (pCF->ShouldSaveFuncletInfo())
231+
{
232+
STRESS_LOG3(LF_GCROOTS, LL_INFO1000, "Saving info on funclet at SP: %p, PC: %p, FP: %p\n",
233+
GetRegdisplaySP(pCF->GetRegisterSet()), GetControlPC(pCF->GetRegisterSet()), GetFP(pCF->GetRegisterSet()->pCurrentContext));
234+
235+
_ASSERTE(pExInfo);
236+
REGDISPLAY *pRD = pCF->GetRegisterSet();
237+
pExInfo->m_lastReportedFunclet.IP = GetControlPC(pRD);
238+
pExInfo->m_lastReportedFunclet.FP = GetFP(pRD->pCurrentContext);
239+
pExInfo->m_lastReportedFunclet.Flags = pCF->GetCodeManagerFlags();
240+
}
241+
242+
if (pCF->ShouldParentToFuncletReportSavedFuncletSlots())
243+
{
244+
STRESS_LOG4(LF_GCROOTS, LL_INFO1000, "Reporting slots in funclet parent frame method at SP: %p, PC: %p using original FP: %p, PC: %p\n",
245+
GetRegdisplaySP(pCF->GetRegisterSet()), GetControlPC(pCF->GetRegisterSet()), pExInfo->m_lastReportedFunclet.FP, pExInfo->m_lastReportedFunclet.IP);
246+
247+
_ASSERTE(!pCF->ShouldParentToFuncletUseUnwindTargetLocationForGCReporting());
248+
_ASSERTE(pExInfo);
249+
250+
ICodeManager * pCM = pCF->GetCodeManager();
251+
_ASSERTE(pCM != NULL);
252+
253+
CONTEXT context = {};
254+
REGDISPLAY partialRD;
255+
SetIP(&context, pExInfo->m_lastReportedFunclet.IP);
256+
SetFP(&context, pExInfo->m_lastReportedFunclet.FP);
257+
SetSP(&context, 0);
258+
259+
context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;
260+
FillRegDisplay(&partialRD, &context);
261+
262+
EECodeInfo codeInfo(pExInfo->m_lastReportedFunclet.IP);
263+
_ASSERTE(codeInfo.IsValid());
264+
265+
pCM->EnumGcRefs(&partialRD,
266+
&codeInfo,
267+
pExInfo->m_lastReportedFunclet.Flags | ReportFPBasedSlotsOnly,
268+
GcEnumObject,
269+
pData,
270+
NO_OVERRIDE_OFFSET);
271+
}
272+
else
273+
#endif // defined(FEATURE_EH_FUNCLETS)
225274
if (fReportGCReferences)
226275
{
227276
if (pCF->IsFrameless())
@@ -297,7 +346,11 @@ StackWalkAction GcStackCrawlCallBack(CrawlFrame* pCF, VOID* pData)
297346
pFrame->GcScanRoots( gcctx->f, gcctx->sc);
298347
}
299348
}
300-
349+
else
350+
{
351+
STRESS_LOG2(LF_GCROOTS, LL_INFO1000, "Skipping GC scanning in frame method at SP: %p, PC: %p\n",
352+
GetRegdisplaySP(pCF->GetRegisterSet()), GetControlPC(pCF->GetRegisterSet()));
353+
}
301354

302355
// If we're executing a LCG dynamic method then we must promote the associated resolver to ensure it
303356
// doesn't get collected and yank the method code out from under us).

src/coreclr/vm/gcinfodecoder.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,9 @@ bool GcInfoDecoder::EnumerateLiveSlots(
680680
// previously visited a child funclet
681681
if (WantsReportOnlyLeaf() && (inputFlags & ParentOfFuncletStackFrame))
682682
{
683-
LOG((LF_GCROOTS, LL_INFO100000, "Not reporting this frame because it was already reported via another funclet.\n"));
683+
#ifndef NATIVEAOT
684+
STRESS_LOG0(LF_GCROOTS, LL_INFO100, "Not reporting this frame because it was already reported via another funclet.\n");
685+
#endif
684686
return true;
685687
}
686688

@@ -1510,6 +1512,13 @@ void GcInfoDecoder::ReportRegisterToGC( // AMD64
15101512
{
15111513
GCINFODECODER_CONTRACT;
15121514

1515+
#ifndef NATIVEAOT
1516+
if (flags & ReportFPBasedSlotsOnly)
1517+
{
1518+
return;
1519+
}
1520+
#endif
1521+
15131522
_ASSERTE(regNum >= 0 && regNum <= 16);
15141523
_ASSERTE(regNum != 4); // rsp
15151524

@@ -2205,6 +2214,13 @@ void GcInfoDecoder::ReportStackSlotToGC(
22052214
OBJECTREF* pObjRef = GetStackSlot(spOffset, spBase, pRD);
22062215
_ASSERTE(IS_ALIGNED(pObjRef, sizeof(OBJECTREF*)));
22072216

2217+
#ifndef NATIVEAOT
2218+
if ((flags & ReportFPBasedSlotsOnly) && (GC_FRAMEREG_REL != spBase))
2219+
{
2220+
return;
2221+
}
2222+
#endif
2223+
22082224
#ifdef _DEBUG
22092225
LOG((LF_GCROOTS, LL_INFO1000, /* Part One */
22102226
"Reporting %s" FMT_STK,

src/coreclr/vm/stackwalk.cpp

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,7 @@ void StackFrameIterator::CommonCtor(Thread * pThread, PTR_Frame pFrame, ULONG32
10981098
m_forceReportingWhileSkipping = ForceGCReportingStage::Off;
10991099
m_movedPastFirstExInfo = false;
11001100
m_fFuncletNotSeen = false;
1101+
m_fFoundFirstFunclet = false;
11011102
#if defined(RECORD_RESUMABLE_FRAME_SP)
11021103
m_pvResumableFrameTargetSP = NULL;
11031104
#endif
@@ -1460,6 +1461,8 @@ void StackFrameIterator::ResetCrawlFrame()
14601461
m_crawl.isFilterFuncletCached = false;
14611462
m_crawl.fShouldParentToFuncletSkipReportingGCReferences = false;
14621463
m_crawl.fShouldParentFrameUseUnwindTargetPCforGCReporting = false;
1464+
m_crawl.fShouldSaveFuncletInfo = false;
1465+
m_crawl.fShouldParentToFuncletReportSavedFuncletSlots = false;
14631466
#endif // FEATURE_EH_FUNCLETS
14641467

14651468
m_crawl.pThread = this->m_pThread;
@@ -1631,10 +1634,11 @@ StackWalkAction StackFrameIterator::Filter(void)
16311634
{
16321635
if (!m_movedPastFirstExInfo)
16331636
{
1634-
if ((pExInfo->m_passNumber == 2) && !pExInfo->m_csfEnclosingClause.IsNull() && m_sfFuncletParent.IsNull())
1637+
if ((pExInfo->m_passNumber == 2) && !pExInfo->m_csfEnclosingClause.IsNull() && m_sfFuncletParent.IsNull() && pExInfo->m_lastReportedFunclet.IP != 0)
16351638
{
16361639
// We are in the 2nd pass and we have already called an exceptionally called
1637-
// a finally funclet, but we have not seen any funclet on the call stack yet.
1640+
// finally funclet and reported that to GC in a previous GC run. But we have
1641+
// not seen any funclet on the call stack yet.
16381642
// Simulate that we have actualy seen a finally funclet during this pass and
16391643
// that it didn't report GC references to ensure that the references will be
16401644
// reported by the parent correctly.
@@ -1651,6 +1655,8 @@ StackWalkAction StackFrameIterator::Filter(void)
16511655
}
16521656
}
16531657

1658+
m_crawl.fShouldParentToFuncletReportSavedFuncletSlots = false;
1659+
16541660
// by default, there is no funclet for the current frame
16551661
// that reported GC references
16561662
m_crawl.fShouldParentToFuncletSkipReportingGCReferences = false;
@@ -1659,6 +1665,8 @@ StackWalkAction StackFrameIterator::Filter(void)
16591665
// CrawlFrame
16601666
m_crawl.fShouldCrawlframeReportGCReferences = true;
16611667

1668+
m_crawl.fShouldSaveFuncletInfo = false;
1669+
16621670
// By default, assume that parent frame is going to report GC references from
16631671
// the actual location reported by the stack walk.
16641672
m_crawl.fShouldParentFrameUseUnwindTargetPCforGCReporting = false;
@@ -1855,7 +1863,7 @@ StackWalkAction StackFrameIterator::Filter(void)
18551863
// Initiate force reporting of references in the new managed exception handling code frames.
18561864
// These frames are still alive when we are in a finally funclet.
18571865
m_forceReportingWhileSkipping = ForceGCReportingStage::LookForManagedFrame;
1858-
STRESS_LOG0(LF_GCROOTS, LL_INFO100, "STACKWALK: Setting m_forceReportingWhileSkipping = ForceGCReportingStage::LookForManagedFrame\n");
1866+
STRESS_LOG0(LF_GCROOTS, LL_INFO100, "STACKWALK: Setting m_forceReportingWhileSkipping = ForceGCReportingStage::LookForManagedFrame while processing filter funclet\n");
18591867
}
18601868
}
18611869
}
@@ -1873,10 +1881,11 @@ StackWalkAction StackFrameIterator::Filter(void)
18731881
m_sfFuncletParent = ExceptionTracker::FindParentStackFrameForStackWalk(&m_crawl, true);
18741882
_ASSERTE(!m_fFuncletNotSeen);
18751883

1884+
bool fFrameWasUnwound = ExceptionTracker::HasFrameBeenUnwoundByAnyActiveException(&m_crawl);
18761885
if (m_sfFuncletParent.IsNull())
18771886
{
18781887
// This can only happen if the funclet (and its parent) have been unwound.
1879-
_ASSERTE(ExceptionTracker::HasFrameBeenUnwoundByAnyActiveException(&m_crawl));
1888+
_ASSERTE(fFrameWasUnwound);
18801889
}
18811890
else
18821891
{
@@ -1899,7 +1908,17 @@ StackWalkAction StackFrameIterator::Filter(void)
18991908

19001909
if (g_isNewExceptionHandlingEnabled)
19011910
{
1902-
if (!ExecutionManager::IsManagedCode(GetIP(m_crawl.GetRegisterSet()->pCallerContext)))
1911+
if (!m_fFoundFirstFunclet && pExInfo > (void*)GetRegdisplaySP(m_crawl.GetRegisterSet()))
1912+
{
1913+
// For the first funclet we encounter below the topmost ExInfo, we instruct the GC scanning of the frame
1914+
// to save information on the funclet so that we can use it to report references in the parent frame if
1915+
// no such funclet is found in future GC scans for the same exception.
1916+
_ASSERTE(pExInfo != NULL);
1917+
m_crawl.fShouldSaveFuncletInfo = true;
1918+
m_fFoundFirstFunclet = true;
1919+
}
1920+
1921+
if (!fFrameWasUnwound && !ExecutionManager::IsManagedCode(GetIP(m_crawl.GetRegisterSet()->pCallerContext)))
19031922
{
19041923
// Initiate force reporting of references in the new managed exception handling code frames.
19051924
// These frames are still alive when we are in a finally funclet.
@@ -2119,6 +2138,14 @@ StackWalkAction StackFrameIterator::Filter(void)
21192138
}
21202139
else if (!m_crawl.IsFunclet())
21212140
{
2141+
if (m_fFuncletNotSeen)
2142+
{
2143+
// We have reached a real parent of a funclet that would be on the stack if GC didn't
2144+
// kick in between the calls to funclets in the second pass. We instruct GC to report
2145+
// roots using the info of the saved funclet we've seen during a previous GC.
2146+
m_crawl.fShouldParentToFuncletReportSavedFuncletSlots = true;
2147+
m_fFuncletNotSeen = false;
2148+
}
21222149
// we've reached the parent and it's not handling an exception, it's also not
21232150
// a funclet so reset our state. note that we cannot reset the state when the
21242151
// parent is a funclet since the leaf funclet didn't report any references and
@@ -2131,15 +2158,6 @@ StackWalkAction StackFrameIterator::Filter(void)
21312158
if (g_isNewExceptionHandlingEnabled)
21322159
{
21332160
_ASSERTE(!ExceptionTracker::HasFrameBeenUnwoundByAnyActiveException(&m_crawl));
2134-
if (m_fFuncletNotSeen && m_crawl.IsFunclet())
2135-
{
2136-
_ASSERTE(!m_fProcessIntermediaryNonFilterFunclet);
2137-
_ASSERTE(m_crawl.fShouldCrawlframeReportGCReferences);
2138-
m_fDidFuncletReportGCReferences = true;
2139-
shouldSkipReporting = false;
2140-
m_crawl.fShouldParentFrameUseUnwindTargetPCforGCReporting = true;
2141-
m_crawl.ehClauseForCatch = pExInfo->m_ClauseForCatch;
2142-
}
21432161
}
21442162
else
21452163
{

src/coreclr/vm/stackwalk.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,18 @@ class CrawlFrame
417417
return fShouldParentFrameUseUnwindTargetPCforGCReporting;
418418
}
419419

420+
bool ShouldParentToFuncletReportSavedFuncletSlots()
421+
{
422+
LIMITED_METHOD_CONTRACT;
423+
return fShouldParentToFuncletReportSavedFuncletSlots;
424+
}
425+
426+
bool ShouldSaveFuncletInfo()
427+
{
428+
LIMITED_METHOD_CONTRACT;
429+
return fShouldSaveFuncletInfo;
430+
}
431+
420432
const EE_ILEXCEPTION_CLAUSE& GetEHClauseForCatch()
421433
{
422434
return ehClauseForCatch;
@@ -469,6 +481,8 @@ class CrawlFrame
469481
bool fShouldParentToFuncletSkipReportingGCReferences;
470482
bool fShouldCrawlframeReportGCReferences;
471483
bool fShouldParentFrameUseUnwindTargetPCforGCReporting;
484+
bool fShouldSaveFuncletInfo;
485+
bool fShouldParentToFuncletReportSavedFuncletSlots;
472486
EE_ILEXCEPTION_CLAUSE ehClauseForCatch;
473487
#endif //FEATURE_EH_FUNCLETS
474488
Thread* pThread;
@@ -701,7 +715,6 @@ class StackFrameIterator
701715

702716
if (!ResetOnlyIntermediaryState)
703717
{
704-
m_fFuncletNotSeen = false;
705718
m_sfFuncletParent = StackFrame();
706719
m_fProcessNonFilterFunclet = false;
707720
}
@@ -754,6 +767,9 @@ class StackFrameIterator
754767
bool m_movedPastFirstExInfo;
755768
// Indicates that no funclet was seen during the current stack walk yet
756769
bool m_fFuncletNotSeen;
770+
// Indicates that the stack walk has moved past a funclet
771+
bool m_fFoundFirstFunclet;
772+
757773
#if defined(RECORD_RESUMABLE_FRAME_SP)
758774
LPVOID m_pvResumableFrameTargetSP;
759775
#endif // RECORD_RESUMABLE_FRAME_SP

0 commit comments

Comments
 (0)