') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); Debugger fixes for the Windows X86 EH Funclets model by davidwrighton · Pull Request #115630 · dotnet/runtime · GitHub
Skip to content

Debugger fixes for the Windows X86 EH Funclets model - #115630

Merged
davidwrighton merged 9 commits into
dotnet:mainfrom
davidwrighton:EHFuncletsDebuggerFixes
May 22, 2025
Merged

Debugger fixes for the Windows X86 EH Funclets model#115630
davidwrighton merged 9 commits into
dotnet:mainfrom
davidwrighton:EHFuncletsDebuggerFixes

Conversation

@davidwrighton

@davidwrightondavidwrighton commented May 15, 2025

Copy link
Copy Markdown
Member

Notable areas improved

  • The funclets model uses the vectored exception handler to funnel debug events to the debugger
  • FramePointer details for the debugger are inscrutable and weird. I've made it work, but enough of this is driven by subtleties in the stackwalker that I would not be surprised if there are additional issues here
  • RtlpGetFunctionEndAddress needs to use a DAC pointer to read the unwind info.
  • Funclet prologs for CoreCLR X86 Funclets need to have at least 1 instruction in them so that the Native->IL mapping is correct. Otherwise it gets shadowed by a mapping which says it is a PROLOG instruction. This is probably fixable by allowing an Native -> IL mapping for both the PROLOG as well as the IL offset in the funclet, but a nop here is both easy to generate an unlikely to be a major cost.
  • Likewise, EnC frame generation is no longer able to rely on the shadow stack pointer logic injected by EH handling, and needed 1 bit of info not in the current emitted stack information. Notably, that there is synchronized codegen in the method. Instead of modifying the gcinfo to include that data, we just put in fake offsets for where the synchronized region is, and use the existence of any data as a flag in the EnC layout. NOTE: I also removed the general purpose logic to read the synchronized range from the data. The fix for this also needs to fix the runtime behavior around stackwalking, and in another case in the presence of a locallloc instruction in the method.

Contributes to #113985

filipnavaraand others added 5 commits May 8, 2025 16:09
Notable areas improved
- The funclets model uses the vectored exception handler to funnel debug events to the debugger
- FramePointer details for the debugger are inscrutable and weird. I've made it work, but enough of this is driven by subtleties in the stackwalker that I would not be surprised if there are additional issues here
- RtlpGetFunctionEndAddress needs to use a DAC pointer to read the unwind info.
- Funclet prologs for CoreCLR X86 Funclets need to have at least 1 instruction in them so that the Native->IL mapping is correct. Otherwise it gets shadowed by a mapping which says it is a PROLOG instruction. This is probably fixable by allowing an Native -> IL mapping for both the PROLOG as well as the IL offset in the funclet, but a nop here is both easy to generate an unlikely to be a major cost.
- Likewise, EnC frame generation is no longer able to rely on the shadow stack pointer logic injected by EH handling, and needed 1 bit of info not in the current emitted stack information. Notably, that there is synchronized codegen in the method. Instead of modifying the gcinfo to include that data, we just put in fake offsets for where the synchronized region is, and use the existence of any data as a flag in the EnC layout. NOTE: I also removed the general purpose logic to read the synchronized range from the data. Also NOTE: Before enabling funclets by default, manually test a set of scenarios which do EnC with various frame layouts. Notably, frame layouts with localloc, frame layouts with locals that trigger GSCookies to be used, frame layouts of static generic methods.
Co-authored-by: Filip Navara <filip.navara@gmail.com>
CopilotAI review requested due to automatic review settings May 15, 2025 22:07
@github-actionsgithub-actionsBot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label May 15, 2025

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull Request Overview

This PR improves the debugger support for Windows X86 EH funclets by updating exception handling and stack frame management logic. Key changes include:

  • Adjustments to stack frame header and frame pointer calculations to support EH funclets.
  • Enhanced exception handling logic on X86 with funclets and updates for DAC pointer usage.
  • Minor logging improvements and documentation update for EnC and funclet support.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
src/coreclr/vm/gc_unwind_x86.inlAdded funclet-specific frame header calculation logic
src/coreclr/vm/exceptionhandling.cppAdjusted SP computation for X86 with funclets
src/coreclr/vm/excep.cppRefined exception handling conditionals and DAC handling
src/coreclr/vm/eetwain.cppPrevented EnC remapping inside funclets
src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/StackFrameIterator.csInserted a stack trace hiding attribute
src/coreclr/jit/lclvars.cppUpdated comments for funclet support conditions
src/coreclr/jit/gcencode.cppEncoded synchronization markers for EnC when using funclets
src/coreclr/jit/codegenxarch.cppAdded a NOP in funclet prologs to ensure correct IL mapping
src/coreclr/inc/regdisp.hModified frame pointer retrieval to account for funclets
src/coreclr/inc/eetwain.hWrapped synchronized region functions with preprocessor guards
src/coreclr/inc/clrnt.hUpdated unwind info pointer to a DAC pointer
src/coreclr/debug/ee/frameinfo.cppAdjusted frame pointer logic conditionals for X86 funclets
src/coreclr/debug/ee/debugger.cppEnhanced logging details in exception handling
Comments suppressed due to low confidence (2)

src/coreclr/vm/excep.cpp:2959

  • [nitpick] The conditional now excludes FEATURE_EH_FUNCLETS when adjusting the SP for X86. Please confirm that this change correctly handles breakpoint and single-step exceptions in funclet mode.
TADDR sp = CallerStackFrame::FromRegDisplay(pRD).SP;

src/coreclr/debug/ee/frameinfo.cpp:1268

  • [nitpick] Please verify that switching the conditional from FEATURE_EH_FUNCLETS to !TARGET_X86 is intentional and correctly supports the intended behavior for X86, ensuring consistent frame pointer retrieval.
#if !defined(TARGET_X86)

// 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;

CopilotAIMay 15, 2025

Copy link

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.
@jkotasjkotas added area-ExceptionHandling-coreclr only use for closed issues and removed needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners labels May 16, 2025
Comment threadsrc/coreclr/jit/gcencode.cpp Outdated
assert(header->epilogCount <= 1);
}
#endif
if (compiler->UsesFunclets() && compiler->info.compFlags & CORINFO_FLG_SYNCH && compiler->opts.compDbgEnC)

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
MemberAuthor

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.

Comment on lines +660 to +662
info->genericsContext + // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG
((info->syncStartOffset != INVALID_SYNC_OFFSET) ? 1 : 0) // Is this method synchronized
+ 1; // for ebpFrame

@filipnavarafilipnavaraMay 16, 2025

Copy link
Copy Markdown
Member

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:

inlinesize_tGetParamTypeArgOffset(hdrInfo*info)
{
LIMITED_METHOD_DAC_CONTRACT;
_ASSERTE((info->genericsContext||info->handlers) &&info->ebpFrame);
#ifdefFEATURE_EH_FUNCLETSunsignedposition=info->savedRegsCountExclFP+info->localloc+
((info->syncStartOffset!=INVALID_SYNC_OFFSET) ? 1 : 0) +// Is this method synchronized1; // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG#elseunsignedposition=info->savedRegsCountExclFP+info->localloc+1; // For CORINFO_GENERICS_CTXT_FROM_PARAMTYPEARG#endifreturnposition*sizeof(TADDR);
}

@filipnavarafilipnavara mentioned this pull request May 16, 2025
6 tasks
)
{
PUNWIND_INFO pUnwindInfo = (PUNWIND_INFO)(ImageBase + FunctionEntry->UnwindData);
DPTR(UNWIND_INFO) pUnwindInfo = dac_cast<DPTR(UNWIND_INFO)>(ImageBase + FunctionEntry->UnwindData);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ouch :)

Comment threadsrc/coreclr/jit/gcencode.cpp Outdated

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

Copy link
Copy Markdown
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
Copy Markdown
MemberAuthor

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.

Comment threadsrc/coreclr/jit/gcencode.cpp Outdated
// 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;
header->syncEndOffset = 2;

Copy link
Copy Markdown
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
Copy Markdown
MemberAuthor

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
Copy Markdown
MemberAuthor

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.

Co-authored-by: Juan Hoyos <19413848+hoyosjs@users.noreply.github.com>
@davidwrighton

Copy link
Copy Markdown
MemberAuthor

/ba-g the android build issue is unrelated to this failure and already known.

@davidwrighton
davidwrighton merged commit 1a5af14 into dotnet:mainMay 22, 2025
SimaTian pushed a commit that referenced this pull request May 27, 2025
Notable areas improved
- The funclets model uses the vectored exception handler to funnel debug events to the debugger
- FramePointer details for the debugger are inscrutable and weird. I've made it work, but enough of this is driven by subtleties in the stackwalker that I would not be surprised if there are additional issues here
- RtlpGetFunctionEndAddress needs to use a DAC pointer to read the unwind info.
- Funclet prologs for CoreCLR X86 Funclets need to have at least 1 instruction in them so that the Native->IL mapping is correct. Otherwise it gets shadowed by a mapping which says it is a PROLOG instruction. This is probably fixable by allowing an Native -> IL mapping for both the PROLOG as well as the IL offset in the funclet, but a nop here is both easy to generate an unlikely to be a major cost.
- Likewise, EnC frame generation is no longer able to rely on the shadow stack pointer logic injected by EH handling, and needed 1 bit of info not in the current emitted stack information. Notably, that there is synchronized codegen in the method. Instead of modifying the gcinfo to include that data, we just put in fake offsets for where the synchronized region is, and use the existence of any data as a flag in the EnC layout. NOTE: I also removed the general purpose logic to read the synchronized range from the data. The fix for this also needs to fix the runtime behavior around stackwalking, and in another case in the presence of a locallloc instruction in the method.
Contributes to #113985
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 22, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-ExceptionHandling-coreclronly use for closed issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@davidwrighton@filipnavara@hoyosjs@jkotas