[release/8.0] Cache debugger patches to speed up x64 stackwalk epilogue/prologue scanning#125486
[release/8.0] Cache debugger patches to speed up x64 stackwalk epilogue/prologue scanning#125486hoyosjs wants to merge 2 commits intodotnet:release/8.0-stagingfrom
Conversation
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
There was a problem hiding this comment.
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
DacReplacePatchesInHostMemoryto 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) | ||
| { |
There was a problem hiding this comment.
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.
| { | |
| { | |
| DacError(E_UNEXPECTED); | |
| UNREACHABLE(); |
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
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.