Skip to content

Commit 975dedc

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 975dedc

File tree

10 files changed

+163
-29
lines changed

10 files changed

+163
-29
lines changed

src/coreclr/inc/eetwain.h

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

107107
LightUnwind = 0x0100, // Unwind just enough to get return addresses
108+
ReportFPBasedSlotsOnly
109+
= 0x0200,
108110
};
109111

110112
//*****************************************************************************

src/coreclr/inc/gcinfodecoder.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,11 @@ class GcInfoDecoder
679679
if(slotIndex < slotDecoder.GetNumRegisters())
680680
{
681681
UINT32 regNum = pSlot->Slot.RegisterNumber;
682-
if( reportScratchSlots || !IsScratchRegister( regNum, pRD ) )
682+
if( (reportScratchSlots || !IsScratchRegister( regNum, pRD ))
683+
#ifndef NATIVEAOT
684+
&& ((inputFlags & ReportFPBasedSlotsOnly) == 0)
685+
#endif
686+
)
683687
{
684688
ReportRegisterToGC(
685689
regNum,

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/eetwain.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,14 @@ bool EECodeManager::EnumGcRefs( PREGDISPLAY pContext,
13491349
GC_NOTRIGGER;
13501350
} CONTRACTL_END;
13511351

1352+
#ifdef FEATURE_EH_FUNCLETS
1353+
if (flags & ParentOfFuncletStackFrame)
1354+
{
1355+
STRESS_LOG0((LF_GCROOTS, LL_INFO100, "Not reporting this frame because it was already reported via another funclet.\n"));
1356+
return true;
1357+
}
1358+
#endif // FEATURE_EH_FUNCLETS
1359+
13521360
PTR_CBYTE methodStart = PTR_CBYTE(pCodeInfo->GetSavedMethodCode());
13531361
unsigned curOffs = pCodeInfo->GetRelOffset();
13541362
GCInfoToken gcInfoToken = pCodeInfo->GetGCInfoToken();

src/coreclr/vm/exinfo.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,9 @@ 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_lastFuncletReportedSlotsCount(0)
331+
m_lastReportedFunclet({0, 0, 0})
330332
{
331333
m_StackTraceInfo.AllocateStackTrace();
332334
pThread->GetExceptionState()->m_pCurrentTracker = this;

src/coreclr/vm/exinfo.h

Lines changed: 11 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,10 @@ struct ExInfo : public ExceptionTrackerBase
269276
REGDISPLAY m_regDisplay;
270277
// Initial explicit frame for stack walking
271278
Frame *m_pInitialFrame;
279+
// List of stack slots reported at the previous GC pass for a funclet
280+
// size_t m_lastFuncletReportedSlots[32];
281+
// int m_lastFuncletReportedSlotsCount;
282+
LastReportedFuncletInfo m_lastReportedFunclet;
272283

273284
#if defined(TARGET_UNIX)
274285
void TakeExceptionPointersOwnership(PAL_SEHException* ex);

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

Lines changed: 54 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
@@ -222,7 +225,52 @@ StackWalkAction GcStackCrawlCallBack(CrawlFrame* pCF, VOID* pData)
222225
fReportGCReferences = pCF->ShouldCrawlframeReportGCReferences();
223226
#endif // defined(FEATURE_EH_FUNCLETS)
224227

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

302354
// If we're executing a LCG dynamic method then we must promote the associated resolver to ensure it
303355
// 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: 38 additions & 22 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,7 @@ void StackFrameIterator::ResetCrawlFrame()
14601461
m_crawl.isFilterFuncletCached = false;
14611462
m_crawl.fShouldParentToFuncletSkipReportingGCReferences = false;
14621463
m_crawl.fShouldParentFrameUseUnwindTargetPCforGCReporting = false;
1464+
m_crawl.fShouldParentToFuncletReportSavedSlots = false;
14631465
#endif // FEATURE_EH_FUNCLETS
14641466

14651467
m_crawl.pThread = this->m_pThread;
@@ -1631,10 +1633,11 @@ StackWalkAction StackFrameIterator::Filter(void)
16311633
{
16321634
if (!m_movedPastFirstExInfo)
16331635
{
1634-
if ((pExInfo->m_passNumber == 2) && !pExInfo->m_csfEnclosingClause.IsNull() && m_sfFuncletParent.IsNull())
1636+
if ((pExInfo->m_passNumber == 2) && !pExInfo->m_csfEnclosingClause.IsNull() && m_sfFuncletParent.IsNull() && pExInfo->m_lastReportedFunclet.IP != 0)
16351637
{
16361638
// 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.
1639+
// finally funclet and reported that to GC in a previous GC run. But we have
1640+
// not seen any funclet on the call stack yet.
16381641
// Simulate that we have actualy seen a finally funclet during this pass and
16391642
// that it didn't report GC references to ensure that the references will be
16401643
// reported by the parent correctly.
@@ -1651,6 +1654,8 @@ StackWalkAction StackFrameIterator::Filter(void)
16511654
}
16521655
}
16531656

1657+
m_crawl.fShouldParentToFuncletReportSavedSlots = false;
1658+
16541659
// by default, there is no funclet for the current frame
16551660
// that reported GC references
16561661
m_crawl.fShouldParentToFuncletSkipReportingGCReferences = false;
@@ -1659,6 +1664,8 @@ StackWalkAction StackFrameIterator::Filter(void)
16591664
// CrawlFrame
16601665
m_crawl.fShouldCrawlframeReportGCReferences = true;
16611666

1667+
m_crawl.fShouldSaveReportedSlots = false;
1668+
16621669
// By default, assume that parent frame is going to report GC references from
16631670
// the actual location reported by the stack walk.
16641671
m_crawl.fShouldParentFrameUseUnwindTargetPCforGCReporting = false;
@@ -1855,7 +1862,7 @@ StackWalkAction StackFrameIterator::Filter(void)
18551862
// Initiate force reporting of references in the new managed exception handling code frames.
18561863
// These frames are still alive when we are in a finally funclet.
18571864
m_forceReportingWhileSkipping = ForceGCReportingStage::LookForManagedFrame;
1858-
STRESS_LOG0(LF_GCROOTS, LL_INFO100, "STACKWALK: Setting m_forceReportingWhileSkipping = ForceGCReportingStage::LookForManagedFrame\n");
1865+
STRESS_LOG0(LF_GCROOTS, LL_INFO100, "STACKWALK: Setting m_forceReportingWhileSkipping = ForceGCReportingStage::LookForManagedFrame while processing filter funclet\n");
18591866
}
18601867
}
18611868
}
@@ -1871,7 +1878,7 @@ StackWalkAction StackFrameIterator::Filter(void)
18711878
{
18721879
// Get a reference to the funclet's parent frame.
18731880
m_sfFuncletParent = ExceptionTracker::FindParentStackFrameForStackWalk(&m_crawl, true);
1874-
_ASSERTE(!m_fFuncletNotSeen);
1881+
//_ASSERTE(!m_fFuncletNotSeen);
18751882

18761883
if (m_sfFuncletParent.IsNull())
18771884
{
@@ -1899,7 +1906,15 @@ StackWalkAction StackFrameIterator::Filter(void)
18991906

19001907
if (g_isNewExceptionHandlingEnabled)
19011908
{
1902-
if (!ExecutionManager::IsManagedCode(GetIP(m_crawl.GetRegisterSet()->pCallerContext)))
1909+
// TODO: need to do this for the first funclet of the current exception handling only
1910+
if (!m_fFoundFirstFunclet && pExInfo > (void*)GetRegdisplaySP(m_crawl.GetRegisterSet()))
1911+
{
1912+
_ASSERTE(pExInfo != NULL);
1913+
m_crawl.fShouldSaveReportedSlots = true;
1914+
m_fFoundFirstFunclet = true;
1915+
}
1916+
1917+
if (!ExceptionTracker::HasFrameBeenUnwoundByAnyActiveException(&m_crawl) && !ExecutionManager::IsManagedCode(GetIP(m_crawl.GetRegisterSet()->pCallerContext)) )
19031918
{
19041919
// Initiate force reporting of references in the new managed exception handling code frames.
19051920
// These frames are still alive when we are in a finally funclet.
@@ -2117,29 +2132,30 @@ StackWalkAction StackFrameIterator::Filter(void)
21172132
m_crawl.ehClauseForCatch.HandlerEndPC);
21182133
}
21192134
}
2120-
else if (!m_crawl.IsFunclet())
2135+
else
21212136
{
2122-
// we've reached the parent and it's not handling an exception, it's also not
2123-
// a funclet so reset our state. note that we cannot reset the state when the
2124-
// parent is a funclet since the leaf funclet didn't report any references and
2125-
// we might have a catch handler below us that might contain GC roots.
2126-
m_fDidFuncletReportGCReferences = true;
2127-
STRESS_LOG0(LF_GCROOTS, LL_INFO100,
2128-
"STACKWALK: Reached parent of funclet which didn't report GC roots is not a funclet, resetting m_fDidFuncletReportGCReferences to true\n");
2137+
if (!m_crawl.IsFunclet())
2138+
{
2139+
if (m_fFuncletNotSeen)
2140+
{
2141+
// We need to report this parent frame
2142+
//shouldSkipReporting = false;
2143+
m_crawl.fShouldParentToFuncletReportSavedSlots = true;
2144+
m_fFuncletNotSeen = false;
2145+
}
2146+
// we've reached the parent and it's not handling an exception, it's also not
2147+
// a funclet so reset our state. note that we cannot reset the state when the
2148+
// parent is a funclet since the leaf funclet didn't report any references and
2149+
// we might have a catch handler below us that might contain GC roots.
2150+
m_fDidFuncletReportGCReferences = true;
2151+
STRESS_LOG0(LF_GCROOTS, LL_INFO100,
2152+
"STACKWALK: Reached parent of funclet which didn't report GC roots is not a funclet, resetting m_fDidFuncletReportGCReferences to true\n");
2153+
}
21292154
}
21302155

21312156
if (g_isNewExceptionHandlingEnabled)
21322157
{
21332158
_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-
}
21432159
}
21442160
else
21452161
{

src/coreclr/vm/stackwalk.h

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

420+
bool ShouldParentToFuncletReportSavedSlots()
421+
{
422+
LIMITED_METHOD_CONTRACT;
423+
return fShouldParentToFuncletReportSavedSlots;
424+
}
425+
426+
bool ShouldSaveReportedSlots()
427+
{
428+
LIMITED_METHOD_CONTRACT;
429+
return fShouldSaveReportedSlots;
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 fShouldSaveReportedSlots;
485+
bool fShouldParentToFuncletReportSavedSlots;
472486
EE_ILEXCEPTION_CLAUSE ehClauseForCatch;
473487
#endif //FEATURE_EH_FUNCLETS
474488
Thread* pThread;
@@ -701,7 +715,7 @@ class StackFrameIterator
701715

702716
if (!ResetOnlyIntermediaryState)
703717
{
704-
m_fFuncletNotSeen = false;
718+
//m_fFuncletNotSeen = false;
705719
m_sfFuncletParent = StackFrame();
706720
m_fProcessNonFilterFunclet = false;
707721
}
@@ -754,6 +768,9 @@ class StackFrameIterator
754768
bool m_movedPastFirstExInfo;
755769
// Indicates that no funclet was seen during the current stack walk yet
756770
bool m_fFuncletNotSeen;
771+
772+
bool m_fFoundFirstFunclet;
773+
757774
#if defined(RECORD_RESUMABLE_FRAME_SP)
758775
LPVOID m_pvResumableFrameTargetSP;
759776
#endif // RECORD_RESUMABLE_FRAME_SP

0 commit comments

Comments
 (0)