Skip to content

Cache debugger patches to speed up x64 stackwalk epilogue/prologue scanning - #125459

Merged
hoyosjs merged 2 commits into
dotnet:mainfrom
hoyosjs:juhoyosa/dac-patch-cache
Mar 15, 2026
Merged

Cache debugger patches to speed up x64 stackwalk epilogue/prologue scanning#125459
hoyosjs merged 2 commits into
dotnet:mainfrom
hoyosjs:juhoyosa/dac-patch-cache

Conversation

@hoyosjs

@hoyosjshoyosjs commented Mar 11, 2026

Copy link
Copy Markdown
Member

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

CopilotAI review requested due to automatic review settings March 11, 2026 21:55
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

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 optimizes DAC-side x64 stackwalk instruction scanning by caching debugger breakpoint patches so the unwinder can quickly replace patched opcodes without repeatedly iterating the target’s patch hash table.

Changes:

  • Introduces a DacPatchCache that snapshots patch addresses/opcodes on first use and reuses them for subsequent patch replacement.
  • Updates DacReplacePatchesInHostMemory to use the cached patch list instead of enumerating the patch table each time.
  • Flushes the patch cache from ClrDataAccess::Flush() to invalidate cached entries when DAC caches are reset.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

FileDescription
src/coreclr/debug/daccess/dacimpl.hAdds DacPatchCache/entry types and stores the cache on ClrDataAccess.
src/coreclr/debug/daccess/dacfn.cppUses the cache in DacReplacePatchesInHostMemory and implements cache population.
src/coreclr/debug/daccess/daccess.cppFlushes the patch cache during ClrDataAccess::Flush().

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

Comment threadsrc/coreclr/debug/daccess/dacfn.cpp
Comment threadsrc/coreclr/debug/daccess/dacimpl.h Outdated
@hoyosjs

Copy link
Copy Markdown
MemberAuthor

/backport to release/8.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0 (link to workflow run)

@hoyosjs

Copy link
Copy Markdown
MemberAuthor

/backport to release/10.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/10.0 (link to workflow run)

@github-actions

Copy link
Copy Markdown
Contributor

@hoyosjs backporting to release/8.0 failed, the patch most likely resulted in conflicts. Please backport manually!

git am output
$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patchApplying: Cache debugger patches to speed up x64 stackwalk epilogue/prologue scanningUsing index info to reconstruct a base tree...M	src/coreclr/debug/daccess/daccess.cppM	src/coreclr/debug/daccess/dacfn.cppM	src/coreclr/debug/daccess/dacimpl.hFalling back to patching base and 3-way merge...Auto-merging src/coreclr/debug/daccess/daccess.cppAuto-merging src/coreclr/debug/daccess/dacfn.cppAuto-merging src/coreclr/debug/daccess/dacimpl.hCONFLICT (content): Merge conflict in src/coreclr/debug/daccess/dacimpl.herror: Failed to merge in the changes.hint: Use 'git am --show-current-patch=diff' to see the failed patchhint: When you have resolved this problem, run "git am --continue".hint: If you prefer to skip this patch, run "git am --skip" instead.hint: To restore the original branch and stop patching, run "git am --abort".hint: Disable this message with "git config set advice.mergeConflict false"Patch failed at 0001 Cache debugger patches to speed up x64 stackwalk epilogue/prologue scanningError: The process '/usr/bin/git' failed with exit code 128

Link to workflow output

Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@hoyosjs@noahfalk