Skip to content

Commit

Permalink
Enable GC double reporting detection (#107986)
Browse files Browse the repository at this point in the history
* Enable GC double reporting detection

This change adds detection of double reporting of stack slots and
registers during GC stack walk. The detection is disabled by default and
can be enabled using the DOTNET_EnableGCHoleMonitoring env variable.

If a double reporting is found, it triggers an assert in both release and
debug builds of the runtime.

* Reflect PR feedback

* Allocate the hash table only when the monitoring is on

* Address PR feedback

* Reflect PR feedback
  • Loading branch information
janvorli authored Sep 25, 2024
1 parent 04b2934 commit 3227d4a
Show file tree
Hide file tree
Showing 7 changed files with 46 additions and 8 deletions.
8 changes: 0 additions & 8 deletions src/coreclr/gc/gcinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,6 @@ typedef void enum_alloc_context_func(gc_alloc_context*, void*);
// Callback passed to CreateBackgroundThread.
typedef uint32_t (__stdcall *GCBackgroundThreadFunction)(void* param);

// Struct often used as a parameter to callbacks.
typedef struct
{
promote_func* f;
ScanContext* sc;
CrawlFrame * cf;
} GCCONTEXT;

// SUSPEND_REASON is the reason why the GC wishes to suspend the EE,
// used as an argument to IGCToCLR::SuspendEE.
typedef enum
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/inc/clrconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ CONFIG_STRING_INFO(INTERNAL_SkipGCCoverage, W("SkipGcCoverage"), "Specify a list
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_StatsUpdatePeriod, W("StatsUpdatePeriod"), 60, "Specifies the interval, in seconds, at which to update the statistics")
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_GCRetainVM, W("GCRetainVM"), 0, "When set we put the segments that should be deleted on a standby list (instead of releasing them back to the OS) which will be considered to satisfy new segment requests (note that the same thing can be specified via API which is the supported way)")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_gcAllowVeryLargeObjects, W("gcAllowVeryLargeObjects"), 1, "Allow allocation of 2GB+ objects on GC heap")
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_CheckDoubleReporting, W("CheckDoubleReporting"), 0, "Enable checks to proactively watch for possible GC holes")
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_GCStress, W("GCStress"), 0, "Trigger GCs at regular intervals")
CONFIG_DWORD_INFO(INTERNAL_GcStressOnDirectCalls, W("GcStressOnDirectCalls"), 0, "Whether to trigger a GC on direct calls")
RETAIL_CONFIG_DWORD_INFO(UNSUPPORTED_HeapVerify, W("HeapVerify"), 0, "When set verifies the integrity of the managed heap on entry and exit of each GC")
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/vm/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,14 @@ void* GetClrModuleBase();
// use this when you want to memcpy something that contains GC refs
void memmoveGCRefs(void *dest, const void *src, size_t len);

// Struct often used as a parameter to callbacks.
typedef struct
{
promote_func* f;
ScanContext* sc;
CrawlFrame * cf;
SetSHash<Object**, PtrSetSHashTraits<Object**> > *pScannedSlots;
} GCCONTEXT;

#if defined(_DEBUG)

Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/vm/eeconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ HRESULT EEConfig::sync()
iGCConservative = (CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_gcConservative) != 0);
#endif // FEATURE_CONSERVATIVE_GC

fCheckDoubleReporting = (CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_CheckDoubleReporting, iGCStress ? 1 : 0) != 0);

#ifdef HOST_64BIT
iGCAllowVeryLargeObjects = (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_gcAllowVeryLargeObjects) != 0);
#endif
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/vm/eeconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ class EEConfig
#ifdef FEATURE_CONSERVATIVE_GC
bool GetGCConservative() const {LIMITED_METHOD_CONTRACT; return iGCConservative;}
#endif
bool GetCheckDoubleReporting() const {LIMITED_METHOD_CONTRACT; return fCheckDoubleReporting; }
#ifdef HOST_64BIT
bool GetGCAllowVeryLargeObjects() const {LIMITED_METHOD_CONTRACT; return iGCAllowVeryLargeObjects;}
#endif
Expand Down Expand Up @@ -572,6 +573,8 @@ class EEConfig
bool iGCAllowVeryLargeObjects;
#endif // HOST_64BIT

bool fCheckDoubleReporting;

bool fGCBreakOnOOM;

#ifdef _DEBUG
Expand Down
30 changes: 30 additions & 0 deletions src/coreclr/vm/gcenv.ee.common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,31 @@ inline bool SafeToReportGenericParamContext(CrawlFrame* pCF)
return true;
}

/*
* CheckDoubleReporting()
*
* This function is used to check for double reporting of the same stack slot or register.
* Double reporting is not allowed unless the reference is pinned, since it would
* result in incorrect updating of a reference in the slot in case the GC moves the
* object.
*/
void CheckDoubleReporting(GCCONTEXT *pCtx, Object **ppObj, uint32_t flags)
{
if (((flags & GC_CALL_PINNED) == 0) && pCtx->sc->promotion)
{
if (pCtx->pScannedSlots == NULL)
{
pCtx->pScannedSlots = new (nothrow) SetSHash<Object**, PtrSetSHashTraits<Object**> >();
if (pCtx->pScannedSlots == NULL)
{
return;
}
}
_ASSERTE_ALL_BUILDS(pCtx->pScannedSlots->Lookup(ppObj) == NULL);
pCtx->pScannedSlots->AddNoThrow(ppObj);
}
}

/*
* GcEnumObject()
*
Expand All @@ -149,6 +174,11 @@ void GcEnumObject(LPVOID pData, OBJECTREF *pObj, uint32_t flags)
Object ** ppObj = (Object **)pObj;
GCCONTEXT * pCtx = (GCCONTEXT *) pData;

if (g_pConfig->GetCheckDoubleReporting())
{
CheckDoubleReporting(pCtx, ppObj, flags);
}

// Since we may be asynchronously walking another thread's stack,
// check (frequently) for stack-buffer-overrun corruptions after
// any long operation
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/vm/gcenv.ee.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,9 @@ static void ScanStackRoots(Thread * pThread, promote_func* fn, ScanContext* sc)
#if defined(FEATURE_EH_FUNCLETS)
flagsStackWalk |= GC_FUNCLET_REFERENCE_REPORTING;
#endif // defined(FEATURE_EH_FUNCLETS)
gcctx.pScannedSlots = NULL;
pThread->StackWalkFrames( GcStackCrawlCallBack, &gcctx, flagsStackWalk);
delete gcctx.pScannedSlots;
}

GCFrame* pGCFrame = pThread->GetGCFrame();
Expand Down

0 comments on commit 3227d4a

Please sign in to comment.