Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,12 @@ class AsmOffsets
public const int SIZEOF__EHEnum = 0x20;
public const int OFFSETOF__StackFrameIterator__m_pRegDisplay = 0x20;
public const int OFFSETOF__ExInfo__m_pPrevExInfo = 0;
public const int OFFSETOF__ExInfo__m_pExContext = 0xa8;
public const int OFFSETOF__ExInfo__m_exception = 0xb0;
public const int OFFSETOF__ExInfo__m_kind = 0xb8;
public const int OFFSETOF__ExInfo__m_passNumber = 0xb9;
public const int OFFSETOF__ExInfo__m_idxCurClause = 0xbc;
public const int OFFSETOF__ExInfo__m_frameIter = 0xc0;
public const int OFFSETOF__ExInfo__m_pExContext = 0xa0;
public const int OFFSETOF__ExInfo__m_exception = 0xa8;
public const int OFFSETOF__ExInfo__m_kind = 0xb0;
public const int OFFSETOF__ExInfo__m_passNumber = 0xb1;
public const int OFFSETOF__ExInfo__m_idxCurClause = 0xb4;
public const int OFFSETOF__ExInfo__m_frameIter = 0xb8;
public const int OFFSETOF__ExInfo__m_notifyDebuggerSP = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator;
public const int OFFSETOF__ExInfo__m_pCatchHandler = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x48;
public const int OFFSETOF__ExInfo__m_handlingFrameSP = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x50;
Expand All @@ -217,12 +217,12 @@ class AsmOffsets
public const int SIZEOF__EHEnum = 0x10;
public const int OFFSETOF__StackFrameIterator__m_pRegDisplay = 0x14;
public const int OFFSETOF__ExInfo__m_pPrevExInfo = 0;
public const int OFFSETOF__ExInfo__m_pExContext = 0x5c;
public const int OFFSETOF__ExInfo__m_exception = 0x60;
public const int OFFSETOF__ExInfo__m_kind = 0x64;
public const int OFFSETOF__ExInfo__m_passNumber = 0x65;
public const int OFFSETOF__ExInfo__m_idxCurClause = 0x68;
public const int OFFSETOF__ExInfo__m_frameIter = 0x6c;
public const int OFFSETOF__ExInfo__m_pExContext = 0x58;
public const int OFFSETOF__ExInfo__m_exception = 0x5c;
public const int OFFSETOF__ExInfo__m_kind = 0x60;
public const int OFFSETOF__ExInfo__m_passNumber = 0x61;
public const int OFFSETOF__ExInfo__m_idxCurClause = 0x64;
public const int OFFSETOF__ExInfo__m_frameIter = 0x68;
public const int OFFSETOF__ExInfo__m_notifyDebuggerSP = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator;
public const int OFFSETOF__ExInfo__m_pCatchHandler = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x2c;
public const int OFFSETOF__ExInfo__m_handlingFrameSP = OFFSETOF__ExInfo__m_frameIter + SIZEOF__StackFrameIterator + 0x30;
Expand Down
12 changes: 7 additions & 5 deletions src/coreclr/debug/daccess/dacdbiimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4817,9 +4817,9 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::HasUnhandledException(VMPTR_Threa
else
{
// most managed exceptions are just a throwable bound to a
// native exception. In that case this handle will be non-null
OBJECTHANDLE ohException = pThread->GetThrowableAsHandle();
if (ohException != (OBJECTHANDLE)NULL)
// native exception. In that case the exception will be non-null
OBJECTREF exception = pThread->GetExceptionState()->GetThrowable();
if (exception != NULL)
{
// during the UEF we set the unhandled bit, if it is set the exception
// was unhandled
Expand Down Expand Up @@ -4955,8 +4955,10 @@ HRESULT STDMETHODCALLTYPE DacDbiInterfaceImpl::GetCurrentException(VMPTR_Thread

Thread * pThread = vmThread.GetDacPtr();

// OBJECTHANDLEs are really just TADDRs.
OBJECTHANDLE ohException = pThread->GetThrowableAsHandle(); // ohException can be NULL
// Get the exception handle. The exception object is stored directly in ExInfo::m_exception,
// but debugger APIs require a handle. Use m_LastThrownObjectHandle which is kept in sync
// via SafeSetThrowables.
OBJECTHANDLE ohException = pThread->m_LastThrownObjectHandle;

if (ohException == (OBJECTHANDLE)NULL)
{
Expand Down
7 changes: 7 additions & 0 deletions src/coreclr/debug/daccess/dacimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3397,6 +3397,13 @@ class ClrDataExceptionState : public IXCLRDataExceptionState
Thread* thread,
ULONG32 flags,
ClrDataExStateType* exInfo,
// Target address of a slot containing the exception Object*.
// This may be a GC handle table slot (e.g. m_LastThrownObjectHandle)
// or the address of ExInfo::m_exception on the target stack.
// Both are valid: the ExInfo lives on the stack which is captured
// in dumps and stable while the target thread is suspended. The
// slot has the same lifetime as the ExInfo — both become invalid
// when ExInfo::PopExInfos calls ReleaseResources.
OBJECTHANDLE throwable,
ClrDataExStateType* prevExInfo);
virtual ~ClrDataExceptionState(void);
Expand Down
47 changes: 21 additions & 26 deletions src/coreclr/debug/daccess/request.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3308,7 +3308,7 @@ ClrDataAccess::GetNestedExceptionData(CLRDATA_ADDRESS exception, CLRDATA_ADDRESS
}
else
{
*exceptionObject = TO_CDADDR(*PTR_TADDR(pExData->m_hThrowable));
*exceptionObject = dac_cast<TADDR>(pExData->m_exception);
*nextNestedException = PTR_HOST_TO_TADDR(pExData->m_pPrevNestedInfo);
}

Expand Down Expand Up @@ -4064,35 +4064,30 @@ HRESULT ClrDataAccess::GetClrWatsonBucketsWorker(Thread * pThread, GenericModeBl
// By default, there are no buckets
PTR_VOID pBuckets = NULL;

// Get the handle to the throwble
OBJECTHANDLE ohThrowable = pThread->GetThrowableAsHandle();
if (ohThrowable != NULL)
// Get the current throwable
OBJECTREF oThrowable = pThread->GetExceptionState()->GetThrowable();
if (oThrowable != NULL)
{
// Get the object from handle and check if the throwable is preallocated or not
OBJECTREF oThrowable = ObjectFromHandle(ohThrowable);
if (oThrowable != NULL)
// Does the throwable have buckets?
U1ARRAYREF refWatsonBucketArray = ((EXCEPTIONREF)oThrowable)->GetWatsonBucketReference();
if (refWatsonBucketArray != NULL)
{
// Does the throwable have buckets?
U1ARRAYREF refWatsonBucketArray = ((EXCEPTIONREF)oThrowable)->GetWatsonBucketReference();
if (refWatsonBucketArray != NULL)
{
// Get the watson buckets from the throwable for non-preallocated
// exceptions
pBuckets = dac_cast<PTR_VOID>(refWatsonBucketArray->GetDataPtr());
}
else
// Get the watson buckets from the throwable for non-preallocated
// exceptions
pBuckets = dac_cast<PTR_VOID>(refWatsonBucketArray->GetDataPtr());
}
else
{
// This is a preallocated exception object - check if the UE Watson bucket tracker
// has any bucket details
pBuckets = pThread->GetExceptionState()->GetUEWatsonBucketTracker()->RetrieveWatsonBuckets();
if (pBuckets == NULL)
{
// This is a preallocated exception object - check if the UE Watson bucket tracker
// has any bucket details
pBuckets = pThread->GetExceptionState()->GetUEWatsonBucketTracker()->RetrieveWatsonBuckets();
if (pBuckets == NULL)
// Since the UE watson bucket tracker does not have them, look up the current
// exception tracker
if (pThread->GetExceptionState()->GetCurrentExceptionTracker() != NULL)
{
// Since the UE watson bucket tracker does not have them, look up the current
// exception tracker
if (pThread->GetExceptionState()->GetCurrentExceptionTracker() != NULL)
{
pBuckets = pThread->GetExceptionState()->GetCurrentExceptionTracker()->GetWatsonBucketTracker()->RetrieveWatsonBuckets();
}
pBuckets = pThread->GetExceptionState()->GetCurrentExceptionTracker()->GetWatsonBucketTracker()->RetrieveWatsonBuckets();
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/coreclr/debug/daccess/task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4556,13 +4556,17 @@ ClrDataExceptionState::GetPrevious(
{
if (m_prevExInfo)
{
// Pass the address of the ExInfo's m_exception field as the "handle".
// This is not a real GC handle — it is a target address of an OBJECTREF
// slot on the stack. It has the same lifetime as the ExInfo (both are
// invalidated by PopExInfos/ReleaseResources). See dacimpl.h comment.
*exState = new (nothrow)
ClrDataExceptionState(m_dac,
m_appDomain,
m_thread,
CLRDATA_EXCEPTION_DEFAULT,
m_prevExInfo,
m_prevExInfo->m_hThrowable,
(OBJECTHANDLE)dac_cast<TADDR>(&m_prevExInfo->m_exception),
m_prevExInfo->m_pPrevNestedInfo);
status = *exState ? S_OK : E_OUTOFMEMORY;
}
Expand Down Expand Up @@ -4920,13 +4924,15 @@ ClrDataExceptionState::NewFromThread(ClrDataAccess* dac,

exState = thread->GetExceptionState()->m_pCurrentTracker;

// Pass the address of the ExInfo's m_exception field as the "handle".
// See dacimpl.h comment on the throwable parameter.
exIf = new (nothrow)
ClrDataExceptionState(dac,
AppDomain::GetCurrentDomain(),
thread,
CLRDATA_EXCEPTION_DEFAULT,
exState,
exState->m_hThrowable,
(OBJECTHANDLE)dac_cast<TADDR>(&exState->m_exception),
exState->m_pPrevNestedInfo);
if (!exIf)
{
Expand Down
11 changes: 7 additions & 4 deletions src/coreclr/debug/ee/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7867,11 +7867,14 @@ BOOL Debugger::ShouldSendCatchHandlerFound(Thread* pThread)
else
{
BOOL forceSendCatchHandlerFound = FALSE;
OBJECTHANDLE objHandle = pThread->GetThrowableAsHandle();
OBJECTHANDLE retrievedHandle = m_pForceCatchHandlerFoundEventsTable->Lookup(objHandle); //destroy handle
if (retrievedHandle != NULL)
OBJECTHANDLE objHandle = pThread->m_LastThrownObjectHandle;
if (objHandle != NULL)
{
forceSendCatchHandlerFound = TRUE;
OBJECTHANDLE retrievedHandle = m_pForceCatchHandlerFoundEventsTable->Lookup(objHandle);
if (retrievedHandle != NULL)
{
forceSendCatchHandlerFound = TRUE;
}
}
return forceSendCatchHandlerFound;
}
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/datadescriptor/datadescriptor.inc
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ CDAC_TYPE_END(Exception)
CDAC_TYPE_BEGIN(ExceptionInfo)
CDAC_TYPE_INDETERMINATE(ExceptionInfo)
CDAC_TYPE_FIELD(ExceptionInfo, T_POINTER, PreviousNestedInfo, offsetof(ExInfo, m_pPrevNestedInfo))
CDAC_TYPE_FIELD(ExceptionInfo, TYPE(ObjectHandle), ThrownObjectHandle, offsetof(ExInfo, m_hThrowable))
CDAC_TYPE_FIELD(ExceptionInfo, T_POINTER, ThrownObject, offsetof(ExInfo, m_exception))
CDAC_TYPE_FIELD(ExceptionInfo, T_UINT32, ExceptionFlags, cdac_data<ExInfo>::ExceptionFlagsValue)
CDAC_TYPE_FIELD(ExceptionInfo, T_POINTER, StackLowBound, cdac_data<ExInfo>::StackLowBound)
CDAC_TYPE_FIELD(ExceptionInfo, T_POINTER, StackHighBound, cdac_data<ExInfo>::StackHighBound)
Expand Down
31 changes: 9 additions & 22 deletions src/coreclr/vm/eedbginterfaceimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -239,15 +239,16 @@ OBJECTHANDLE EEDbgInterfaceImpl::GetThreadException(Thread *pThread)
}
CONTRACTL_END;

OBJECTHANDLE oh = pThread->GetThrowableAsHandle();

if (oh != NULL)
// The exception object is stored directly in ExInfo::m_exception (no handle).
// Return m_LastThrownObjectHandle which is kept in sync via SafeSetThrowables
// before any debugger notification fires. Assert this invariant in debug builds.
#ifdef _DEBUG
ExInfo* pTracker = pThread->GetExceptionState()->GetCurrentExceptionTracker();
if (pTracker != NULL && pTracker->m_exception != NULL && pThread->m_LastThrownObjectHandle != NULL)
{
return oh;
_ASSERTE(ObjectFromHandle(pThread->m_LastThrownObjectHandle) == pTracker->m_exception);
}

// Return the last thrown object if there's no current throwable.
// This logic is similar to UpdateCurrentThrowable().
#endif
return pThread->m_LastThrownObjectHandle;
}

Expand All @@ -261,21 +262,7 @@ bool EEDbgInterfaceImpl::IsThreadExceptionNull(Thread *pThread)
}
CONTRACTL_END;

//
// We're assuming that the handle on the
// thread is a strong handle and we're goona check it for
// NULL. We're also assuming something about the
// implementation of the handle here, too.
//
OBJECTHANDLE h = pThread->GetThrowableAsHandle();
if (h == NULL)
{
return true;
}

void *pThrowable = *((void**)h);

return (pThrowable == NULL);
return pThread->IsThrowableNull() && pThread->IsLastThrownObjectNull();
}

void EEDbgInterfaceImpl::ClearThreadException(Thread *pThread)
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/excep.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1849,7 +1849,7 @@ BOOL IsInFirstFrameOfHandler(Thread *pThread, IJitManager *pJitManager, const ME
CONTRACTL_END;

// if don't have a throwable the aren't processing an exception
if (IsHandleNullUnchecked(pThread->GetThrowableAsHandle()))
if (pThread->IsThrowableNull())
return FALSE;

EH_CLAUSE_ENUMERATOR pEnumState;
Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/vm/exceptionhandling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2922,7 +2922,7 @@ ExInfo::StackRange::StackRange()
void ExInfo::EnumMemoryRegions(CLRDataEnumMemoryFlags flags)
{
// ExInfo is embedded so don't enum 'this'.
OBJECTHANDLE_EnumMemoryRegions(m_hThrowable);
OBJECTREF_EnumMemoryRegions(m_exception);
m_ptrs.ExceptionRecord.EnumMem();
m_ptrs.ContextRecord.EnumMem();
}
Expand Down Expand Up @@ -2973,7 +2973,7 @@ extern "C" void QCALLTYPE AppendExceptionStackFrame(QCall::ObjectHandleOnStack e
_ASSERTE(pMD == codeInfo.GetMethodDesc());
#endif // _DEBUG

StackTraceInfo::AppendElement(pExInfo->m_hThrowable, ip, sp, pMD, &pExInfo->m_frameIter.m_crawl);
StackTraceInfo::AppendElement((OBJECTHANDLE)&pExInfo->m_exception, ip, sp, pMD, &pExInfo->m_frameIter.m_crawl);
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

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

These calls pass the address of ExInfo::m_exception cast to OBJECTHANDLE. Since this is not a real GC handle-table handle (it's an OBJECTREF slot on the stack), it would be easy for future readers to misinterpret and accidentally treat it like a handle-table entry. Consider adding a brief comment or using a named helper/cast to make the "OBJECTREF slot as pseudo-handle" intent explicit at the call site.

Copilot uses AI. Check for mistakes.
}
}

Expand Down Expand Up @@ -3772,7 +3772,7 @@ CLR_BOOL SfiInitWorker(StackFrameIterator* pThis, CONTEXT* pStackwalkCtx, CLR_BO
if (pMD != NULL)
{
GCX_COOP();
StackTraceInfo::AppendElement(pExInfo->m_hThrowable, 0, GetRegdisplaySP(pExInfo->m_frameIter.m_crawl.GetRegisterSet()), pMD, &pExInfo->m_frameIter.m_crawl);
StackTraceInfo::AppendElement((OBJECTHANDLE)&pExInfo->m_exception, 0, GetRegdisplaySP(pExInfo->m_frameIter.m_crawl.GetRegisterSet()), pMD, &pExInfo->m_frameIter.m_crawl);

#if defined(DEBUGGING_SUPPORTED)
if (NotifyDebuggerOfStub(pThread, pFrame))
Expand Down Expand Up @@ -3954,7 +3954,7 @@ CLR_BOOL SfiNextWorker(StackFrameIterator* pThis, uint* uExCollideClauseIdx, CLR
void* callbackCxt = NULL;
Interop::ManagedToNativeExceptionCallback callback = Interop::GetPropagatingExceptionCallback(
&codeInfo,
pTopExInfo->m_hThrowable,
pTopExInfo->m_exception,
&callbackCxt);

if (callback != NULL)
Expand Down Expand Up @@ -4091,7 +4091,7 @@ CLR_BOOL SfiNextWorker(StackFrameIterator* pThis, uint* uExCollideClauseIdx, CLR
if (pMD != NULL)
{
GCX_COOP();
StackTraceInfo::AppendElement(pTopExInfo->m_hThrowable, 0, GetRegdisplaySP(pTopExInfo->m_frameIter.m_crawl.GetRegisterSet()), pMD, &pTopExInfo->m_frameIter.m_crawl);
StackTraceInfo::AppendElement((OBJECTHANDLE)&pTopExInfo->m_exception, 0, GetRegdisplaySP(pTopExInfo->m_frameIter.m_crawl.GetRegisterSet()), pMD, &pTopExInfo->m_frameIter.m_crawl);

#if defined(DEBUGGING_SUPPORTED)
if (NotifyDebuggerOfStub(pThread, pFrame))
Expand Down
10 changes: 1 addition & 9 deletions src/coreclr/vm/exinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

ExInfo::ExInfo(Thread *pThread, EXCEPTION_RECORD *pExceptionRecord, CONTEXT *pExceptionContext, ExKind exceptionKind) :
m_pPrevNestedInfo(pThread->GetExceptionState()->GetCurrentExceptionTracker()),
m_hThrowable{},
m_ptrs({pExceptionRecord, pExceptionContext}),
m_fDeliveredFirstChanceNotification(FALSE),
m_ExceptionCode((pExceptionRecord != PTR_NULL) ? pExceptionRecord->ExceptionCode : 0),
Expand Down Expand Up @@ -72,14 +71,7 @@ void ExInfo::TakeExceptionPointersOwnership(PAL_SEHException* ex)

void ExInfo::ReleaseResources()
{
if (m_hThrowable)
{
if (!CLRException::IsPreallocatedExceptionHandle(m_hThrowable))
{
DestroyHandle(m_hThrowable);
}
m_hThrowable = NULL;
}
m_exception = NULL;

#ifndef TARGET_UNIX
// Clear any held Watson Bucketing details
Expand Down
22 changes: 1 addition & 21 deletions src/coreclr/vm/exinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,6 @@ struct ExInfo

// Previous ExInfo in the chain of exceptions rethrown from their catch / finally handlers
PTR_ExInfo m_pPrevNestedInfo;
// thrown exception object handle
OBJECTHANDLE m_hThrowable;
// EXCEPTION_RECORD and CONTEXT_RECORD describing the exception and its location
DAC_EXCEPTION_POINTERS m_ptrs;
// Information for the funclet we are calling
Expand Down Expand Up @@ -218,12 +216,7 @@ struct ExInfo
}
CONTRACTL_END;

if (0 != m_hThrowable)
{
return ObjectFromHandle(m_hThrowable);
}

return NULL;
return m_exception;
}

inline BOOL DeliveredFirstChanceNotification()
Expand Down Expand Up @@ -257,19 +250,6 @@ struct ExInfo
return !m_ExceptionFlags.UnwindHasStarted();
}

#ifndef DACCESS_COMPILE
void DestroyExceptionHandle()
{
// Never, ever destroy a preallocated exception handle.
if ((m_hThrowable != NULL) && !CLRException::IsPreallocatedExceptionHandle(m_hThrowable))
{
DestroyHandle(m_hThrowable);
}

m_hThrowable = NULL;
}
#endif // !DACCESS_COMPILE

#ifdef DACCESS_COMPILE
void EnumMemoryRegions(CLRDataEnumMemoryFlags flags);
#endif
Expand Down
Loading
Loading