Skip to content
Merged
3 changes: 2 additions & 1 deletion src/coreclr/debug/ee/debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7573,7 +7573,8 @@ HRESULT Debugger::SendException(Thread *pThread,
}
CONTRACTL_END;

LOG((LF_CORDB, LL_INFO10000, "D::SendException\n"));
LOG((LF_CORDB, LL_INFO10000, "D::SendException pThread=0x%p fFirstChance=%s currentIP=0x%p currentSP= 0x%p fContinuable=%s fAttaching=%s fForceNonInterceptable=%s\n",
pThread, fFirstChance ? "true" : "false", (void*)currentIP, (void*)currentSP, fContinuable ? "true" : "false", fAttaching ? "true" : "false", fForceNonInterceptable ? "true" : "false"));

if (CORDBUnrecoverableError(this))
{
Expand Down
6 changes: 3 additions & 3 deletions src/coreclr/debug/ee/frameinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1265,7 +1265,7 @@ FramePointer GetFramePointerForDebugger(DebuggerFrameData* pData, CrawlFrame* pC

FramePointer fpResult;

#if defined(FEATURE_EH_FUNCLETS)
#if !defined(TARGET_X86)
if (pData->info.frame == NULL)
{
// This is a managed method frame.
Expand All @@ -1277,7 +1277,7 @@ FramePointer GetFramePointerForDebugger(DebuggerFrameData* pData, CrawlFrame* pC
fpResult = FramePointer::MakeFramePointer((LPVOID)(pData->info.frame));
}

#else // !FEATURE_EH_FUNCLETS
#else // !TARGET_X86
if ((pCF == NULL || !pCF->IsFrameless()) && pData->info.frame != NULL)
Copy link
Member

Choose a reason for hiding this comment

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

Just out of curiosity for me - why is this branch only taken on non-x86? I'd expect this to be a funclet path.

Copy link
Member Author

Choose a reason for hiding this comment

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

The x86 unwinder continues to have a subtly different set of interactions with the rest of the codebase compared to the other architectures. Its... much closer than it was before, but the frame pointer concepts are somewhat different, and when I wrote this it appeared that it is related to the nature of X86 codegen where the sp moves during code execution in a frame, and how the unwinder updates somewhat different structures as it runs.

{
//
Expand All @@ -1299,7 +1299,7 @@ FramePointer GetFramePointerForDebugger(DebuggerFrameData* pData, CrawlFrame* pC
fpResult = FramePointer::MakeFramePointer((LPVOID)GetRegdisplayStackMark(&(pData->regDisplay)));
}

#endif // !FEATURE_EH_FUNCLETS
#endif // !TARGET_X86

LOG((LF_CORDB, LL_INFO100000, "GFPFD: Frame pointer is 0x%p\n", fpResult.GetSPValue()));

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/inc/clrnt.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ RtlpGetFunctionEndAddress (
_In_ TADDR ImageBase
)
{
PUNWIND_INFO pUnwindInfo = (PUNWIND_INFO)(ImageBase + FunctionEntry->UnwindData);
DPTR(UNWIND_INFO) pUnwindInfo = dac_cast<DPTR(UNWIND_INFO)>(ImageBase + FunctionEntry->UnwindData);
Copy link
Member

Choose a reason for hiding this comment

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

ouch :)


return FunctionEntry->BeginAddress + pUnwindInfo->FunctionLength;
}
Expand Down
7 changes: 6 additions & 1 deletion src/coreclr/inc/eetwain.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,14 +259,15 @@ virtual void * GetGSCookieAddr(PREGDISPLAY pContext,
virtual bool IsInPrologOrEpilog(DWORD relPCOffset,
GCInfoToken gcInfoToken,
size_t* prologSize) = 0;

#ifndef FEATURE_EH_FUNCLETS
/*
Returns true if the given IP is in the synchronized region of the method (valid for synchronized methods only)
*/
virtual bool IsInSynchronizedRegion(
DWORD relOffset,
GCInfoToken gcInfoToken,
unsigned flags) = 0;
#endif // FEATURE_EH_FUNCLETS
#endif // !USE_GC_INFO_DECODER

/*
Expand Down Expand Up @@ -513,6 +514,7 @@ bool IsInPrologOrEpilog(
GCInfoToken gcInfoToken,
size_t* prologSize);

#ifndef FEATURE_EH_FUNCLETS
/*
Returns true if the given IP is in the synchronized region of the method (valid for synchronized functions only)
*/
Expand All @@ -521,6 +523,7 @@ bool IsInSynchronizedRegion(
DWORD relOffset,
GCInfoToken gcInfoToken,
unsigned flags);
#endif // FEATURE_EH_FUNCLETS
#endif // !USE_GC_INFO_DECODER

/*
Expand Down Expand Up @@ -710,6 +713,7 @@ bool IsInPrologOrEpilog(
return false;
}

#ifndef FEATURE_EH_FUNCLETS
virtual
bool IsInSynchronizedRegion(
DWORD relOffset,
Expand All @@ -720,6 +724,7 @@ bool IsInSynchronizedRegion(
_ASSERTE(FALSE);
return false;
}
#endif // FEATURE_EH_FUNCLETS
#endif // !USE_GC_INFO_DECODER

virtual
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/inc/regdisp.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@ inline TADDR GetRegdisplayFP(REGDISPLAY *display) {
inline LPVOID GetRegdisplayFPAddress(REGDISPLAY *display) {
LIMITED_METHOD_CONTRACT;

#ifdef FEATURE_EH_FUNCLETS
return &display->pCurrentContext->Ebp;
#else
return (LPVOID)display->GetEbpLocation();
#endif
}

inline TADDR GetRegdisplayPCTAddr(REGDISPLAY *display)
Expand Down
7 changes: 7 additions & 0 deletions src/coreclr/jit/codegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11101,6 +11101,13 @@ void CodeGen::genFuncletProlog(BasicBlock* block)
#ifdef UNIX_X86_ABI
// Add a padding for 16-byte alignment
inst_RV_IV(INS_sub, REG_SPBASE, 12, EA_PTRSIZE);
#else
if (!compiler->IsTargetAbi(CORINFO_NATIVEAOT_ABI))
{
// Funclet prologs need to have at least 1 byte or the IL->Native mapping data will not
// include the first IL instruction in the funclet.
instGen(INS_nop);
}
#endif
}

Expand Down
9 changes: 9 additions & 0 deletions src/coreclr/jit/gcencode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1578,6 +1578,15 @@ size_t GCInfo::gcInfoBlockHdrSave(
assert(header->epilogCount <= 1);
}
#endif
if (compiler->UsesFunclets() && compiler->info.compFlags & CORINFO_FLG_SYNCH && compiler->opts.compDbgEnC)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (compiler->UsesFunclets() && compiler->info.compFlags & CORINFO_FLG_SYNCH && compiler->opts.compDbgEnC)
if (compiler->UsesFunclets() && compiler->info.compFlags & CORINFO_FLG_SYNCH)

We need this information even for non-EnC to get position of generic parameters.

Copy link
Member

Choose a reason for hiding this comment

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

For reference, this is exposed with the Pri1 tests with DOTNET_GCStress=0x3 in JIT\Generics\Fields\getclassfrommethodparam\getclassfrommethodparam.dll.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've addressed this issue as well as an issue where the method is both synchronized and has a stack allocation in it.

{
// While the sync start offset and end offset are not used by the stackwalker/EH system
// in funclets mode, we do need to know if the code is synchronized if we are generating
// and edit and continue method, so that we can properly manage the stack during a Remap
// operation. Instead of inventing a new encoding, just encode some non-0 offsets into these fields.
header->syncStartOffset = 1;
Copy link

Copilot AI May 15, 2025

Choose a reason for hiding this comment

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

Consider adding a comment to explain the rationale behind using the literal offsets (1 and 2) for syncStartOffset and syncEndOffset to improve clarity and future maintainability.

Copilot uses AI. Check for mistakes.
header->syncEndOffset = 2;
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to set syncEndOffset to 1 as well? That way the region becomes effectively empty and invalid. It would make it easier to detect this abuse of the encoding in diagnostic tools for both FEATURE_EH_FUNCLETS and legacy builds.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a decent point. I don't think we have any diagnostic tools that care, but data that is patently invalid would be easier to check for. As I recall, I'd need to relax an assert or two, but that's no big deal.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done. Looks like it was just the one assert which was a problem.

}

header->revPInvokeOffset = INVALID_REV_PINVOKE_OFFSET;
if (compiler->opts.IsReversePInvoke())
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/lclvars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4085,13 +4085,15 @@ unsigned Compiler::lvaGetMaxSpillTempSize()
* | security object |
* |-----------------------|
* | ParamTypeArg |
// If funclet support is disabled
* |-----------------------|
* | Last-executed-filter |
* |-----------------------|
* | |
* ~ Shadow SPs ~
* | |
* |-----------------------|
// Endif funclet support is disabled
* | |
* ~ Variables ~
* | |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.InteropServices;
#if !NATIVEAOT
using System.Runtime.ExceptionServices;
Expand Down Expand Up @@ -60,6 +61,7 @@ internal unsafe struct StackFrameIterator
#pragma warning restore CA1822
#endif // NATIVEAOT

[StackTraceHidden]
internal bool Init(EH.PAL_LIMITED_CONTEXT* pStackwalkCtx, bool instructionFault = false, bool* fIsExceptionIntercepted = null)
{
return InternalCalls.RhpSfiInit(ref this, pStackwalkCtx, instructionFault, fIsExceptionIntercepted);
Expand Down
14 changes: 12 additions & 2 deletions src/coreclr/vm/eetwain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ bool VarIsInReg(ICorDebugInfo::VarLoc varLoc)
* could easily duplicate what they do, which is why we're calling into them.
*/

HRESULT EECodeManager::FixContextForEnC(PCONTEXT pCtx,
HRESULT EECodeManager::FixContextForEnC(PCONTEXT pCtx,
EECodeInfo * pOldCodeInfo,
const ICorDebugInfo::NativeVarInfo * oldMethodVars,
SIZE_T oldMethodVarsCount,
Expand Down Expand Up @@ -206,6 +206,11 @@ HRESULT EECodeManager::FixContextForEnC(PCONTEXT pCtx,
return E_FAIL; // stack should be empty - <TODO> @TODO : Barring localloc</TODO>
}

#ifdef FEATURE_EH_FUNCLETS
// EnC remap inside handlers is not supported
if (pOldCodeInfo->IsFunclet() || pNewCodeInfo->IsFunclet())
return CORDBG_E_ENC_IN_FUNCLET;
#else
if (oldInfo->handlers)
{
bool hasInnerFilter;
Expand All @@ -229,13 +234,16 @@ HRESULT EECodeManager::FixContextForEnC(PCONTEXT pCtx,
}
}
}
#endif // FEATURE_EH_FUNCLETS

/* @TODO: Check if we have grown out of space for locals, in the face of localloc */
_ASSERTE(!oldInfo->localloc && !newInfo->localloc);

#ifndef FEATURE_EH_FUNCLETS
// @TODO: If nesting level grows above the MAX_EnC_HANDLER_NESTING_LEVEL,
// we should return EnC_NESTED_HANLDERS
_ASSERTE(oldInfo->handlers && newInfo->handlers);
#endif

LOG((LF_ENC, LL_INFO100, "EECM::FixContextForEnC: Checks out\n"));

Expand Down Expand Up @@ -1542,7 +1550,7 @@ OBJECTREF EECodeManager::GetInstance( PREGDISPLAY pContext,
#endif

#else // FEATURE_EH_FUNCLETS
if (pCodeInfo->GetMethodDesc()->AcquiresInstMethodTableFromThis()) // Generic Context is "this"
if (pCodeInfo->GetMethodDesc()->AcquiresInstMethodTableFromThis() && (hdrInfoBody->genericsContext)) // Generic Context is "this"
{
// Untracked table must have at least one entry - this pointer
_ASSERTE(hdrInfoBody->untrackedCnt > 0);
Expand Down Expand Up @@ -1797,6 +1805,7 @@ bool EECodeManager::IsInPrologOrEpilog(DWORD relPCoffset,
(info.epilogOffs != hdrInfo::NOT_IN_EPILOG));
}

#ifndef FEATURE_EH_FUNCLETS
/*****************************************************************************
*
* Returns true if the given IP is in the synchronized region of the method (valid for synchronized functions only)
Expand Down Expand Up @@ -1827,6 +1836,7 @@ bool EECodeManager::IsInSynchronizedRegion(DWORD relOffset,
// Everything after the epilog is also in synchronized region.
(info.epilogCnt != 0 && info.syncEpilogStart + info.epilogSize <= relOffset);
}
#endif // FEATURE_EH_FUNCLETS
#endif // !USE_GC_INFO_DECODER

/*****************************************************************************
Expand Down
8 changes: 7 additions & 1 deletion src/coreclr/vm/excep.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6999,7 +6999,7 @@ LONG WINAPI CLRVectoredExceptionHandlerShim(PEXCEPTION_POINTERS pExceptionInfo)
return EXCEPTION_CONTINUE_SEARCH;
}

#if defined(TARGET_X86)
#if defined(TARGET_X86) && !defined(FEATURE_EH_FUNCLETS)
if (dwCode == EXCEPTION_BREAKPOINT || dwCode == EXCEPTION_SINGLE_STEP)
{
// For interop debugging, debugger bashes our managed exception handler.
Expand Down Expand Up @@ -11324,7 +11324,13 @@ void SoftwareExceptionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool u
ENUM_CALLEE_SAVED_REGISTERS();
#undef CALLEE_SAVED_REGISTER

#if defined(DACCESS_COMPILE) && defined(TARGET_X86)
// X86 unwinding always works in terms of context pointers, so they need to be in the correct address space when debugging
// This may work for other architectures as well, but that isn't tested.
#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = &pRD->pCurrentContext->regname;
#else
#define CALLEE_SAVED_REGISTER(regname) pRD->pCurrentContextPointers->regname = m_ContextPointers.regname;
#endif
ENUM_CALLEE_SAVED_REGISTERS();
#undef CALLEE_SAVED_REGISTER

Expand Down
6 changes: 5 additions & 1 deletion src/coreclr/vm/exceptionhandling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2956,7 +2956,11 @@ void MarkInlinedCallFrameAsEHHelperCall(Frame* pFrame)
static TADDR GetSpForDiagnosticReporting(REGDISPLAY *pRD)
{
#ifdef ESTABLISHER_FRAME_ADDRESS_IS_CALLER_SP
return CallerStackFrame::FromRegDisplay(pRD).SP;
TADDR sp = CallerStackFrame::FromRegDisplay(pRD).SP;
#if defined(FEATURE_EH_FUNCLETS) && defined(TARGET_X86)
sp -= sizeof(TADDR); // For X86 with funclets we want the address 1 pointer into the callee.
#endif // defined(FEATURE_EH_FUNCLETS) && defined(TARGET_X86)
return sp;
#else
return GetSP(pRD->pCurrentContext);
#endif
Expand Down
10 changes: 10 additions & 0 deletions src/coreclr/vm/gc_unwind_x86.inl
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,15 @@ inline size_t GetSizeOfFrameHeaderForEnC(hdrInfo * info)
{
WRAPPER_NO_CONTRACT;

#ifdef FEATURE_EH_FUNCLETS
_ASSERTE(info->ebpFrame);
unsigned position = info->savedRegsCountExclFP +
info->localloc +
info->genericsContext + // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG
((info->syncStartOffset != INVALID_SYNC_OFFSET) ? 1 : 0) // Is this method synchronized
+ 1; // for ebpFrame
Comment on lines +677 to +679
Copy link
Member

@filipnavara filipnavara May 16, 2025

Choose a reason for hiding this comment

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

Suggested change
info->genericsContext + // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG
((info->syncStartOffset != INVALID_SYNC_OFFSET) ? 1 : 0) // Is this method synchronized
+ 1; // for ebpFrame
((info->syncStartOffset != INVALID_SYNC_OFFSET) ? 1 : 0) + // Is this method synchronized
info->genericsContext + // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG
1; // for ebpFrame

Let's use an order that's closer to the reality.

We also need to apply the same fix to GetParamTypeArgOffset in gc_unwind_x86.inl:

inline
size_t GetParamTypeArgOffset(hdrInfo * info)
{
    LIMITED_METHOD_DAC_CONTRACT;

    _ASSERTE((info->genericsContext || info->handlers) && info->ebpFrame);

#ifdef FEATURE_EH_FUNCLETS
    unsigned position = info->savedRegsCountExclFP +
                        info->localloc +
                        ((info->syncStartOffset != INVALID_SYNC_OFFSET) ? 1 : 0) + // Is this method synchronized
                        1;  // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG
#else
    unsigned position = info->savedRegsCountExclFP +
                        info->localloc +
                        1;  // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG
#endif

    return position * sizeof(TADDR);
}

return position * sizeof(TADDR);
#else
// See comment above Compiler::lvaAssignFrameOffsets() in src\jit\il\lclVars.cpp
// for frame layout

Expand All @@ -664,6 +673,7 @@ inline size_t GetSizeOfFrameHeaderForEnC(hdrInfo * info)
// to get the total size of the header.
return sizeof(TADDR) +
GetEndShadowSPSlotsOffset(info, MAX_EnC_HANDLER_NESTING_LEVEL);
#endif // FEATURE_EH_FUNCLETS
}
#endif // FEATURE_NATIVEAOT

Expand Down
Loading