Skip to content

[release/8.0] Cache debugger patches to speed up x64 stackwalk epilogue/prologue scanning#125486

Open
hoyosjs wants to merge 2 commits intodotnet:release/8.0-stagingfrom
hoyosjs:juhoyosa/bp-125459-8
Open

[release/8.0] Cache debugger patches to speed up x64 stackwalk epilogue/prologue scanning#125486
hoyosjs wants to merge 2 commits intodotnet:release/8.0-stagingfrom
hoyosjs:juhoyosa/bp-125459-8

Conversation

@hoyosjs
Copy link
Member

@hoyosjs hoyosjs commented Mar 12, 2026

Backport of #125459

Second partial fix for #122459

Caches the list of debugger breakpoint patches in the DAC so that x64 stack unwinding doesn't re-scan the patch hash table on every frame. During mini dump collection, each stack frame triggers DacReplacePatchesInHostMemory to restore original opcodes before reading memory — even though there are typically zero active patches during a dump. The patch hash table has 1,000 fixed buckets, so each call walked all of them regardless. The cache is populated once on first access and invalidated only on Flush().

Measured minidump collection against the same repro app with 10,000 iterations across 10 threads. The baseline was 55s, this change alone brings it to ~7s

/cc @hoyosjs

Customer Impact

  • Customer reported
  • Found internally

WER tries to collect dumps of crashing processes - this process can be extremely slow if the app has a lot of memory marshalling happening through the DAC. This particular hash is pretty bad for DAC access patterns.

Regression

This is a regression WRT to .NET Framework.

Testing

Manually collected dump and local perf testing

Risk

Low - DAC only path and this caches preexisting information.

Copilot AI review requested due to automatic review settings March 12, 2026 11:56
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Backport to the .NET 8 release branch that improves DAC stack-unwinding performance by avoiding repeated full scans of the debugger patch table during x64 epilogue/prologue instruction scanning (notably impacting minidump collection).

Changes:

  • Introduces a DAC-side cache (DacPatchCache) for debugger breakpoint patch entries (address + original opcode).
  • Updates DacReplacePatchesInHostMemory to use the cached patch list instead of re-walking the patch hash table for every frame.
  • Flushes the new cache as part of ClrDataAccess::Flush().

Reviewed changes

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

File Description
src/coreclr/debug/daccess/dacimpl.h Adds DacPatchCache / DacPatchCacheEntry types and stores the cache on ClrDataAccess.
src/coreclr/debug/daccess/dacfn.cpp Switches patch replacement to use the cache; implements cache population on first use.
src/coreclr/debug/daccess/daccess.cpp Invalidates the patch cache during ClrDataAccess::Flush().

You can also share your feedback on Copilot code review. Take the survey.

}

if (g_dacImpl == nullptr)
{
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

DacReplacePatchesInHostMemory adds a g_dacImpl == nullptr check that returns E_UNEXPECTED. In this file, other DAC helpers treat g_dacImpl being null as an invariant violation and call DacError(E_UNEXPECTED) + UNREACHABLE() (e.g., DacGlobalBase / DacReadAll). To keep error-handling consistent (and avoid silently degrading unwinding by returning a failing HRESULT), consider removing this check or switching it to the same fail-fast pattern used elsewhere.

Suggested change
{
{
DacError(E_UNEXPECTED);
UNREACHABLE();

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants