Repository navigation
Description
Description
Current main supports debugger function-evaluation results backed by GC-tracked external memory, including byref-like value types that cannot be boxed. The Left Side transfers the DebuggerExternalMemoryOwner to the Right Side, where CordbVCObjectValue::Init reads the external buffer once into m_pObjectCopy.
The external buffer remains live and its embedded object references/byrefs are updated by ExternalMemoryHandle during GC. However, CordbVCObjectValue::SetExternalMemoryOwner only stores the owner; subsequent GetValue and GetFieldValueForType calls continue reading the original m_pObjectCopy. After a moving GC, the authoritative external buffer can therefore contain relocated references while the debugger exposes stale cached bytes and nested fields.
This is distinct from ordinary CordbVCObjectValue snapshot semantics: ordinary values are neutered/recreated across resumes, while externally owned values intentionally survive and must remain coherent with their GC-updated backing storage.
Reproduction Steps
A deterministic debugger-harness test can reproduce the problem as follows:
- Stop a debuggee and function-evaluate a method returning a byref-like value type whose fields include an object reference or managed byref.
- Call
ICorDebugEval::GetResultand retain the resultingICorDebugValue/ICorDebugObjectValue. - Trigger a compacting GC, for example through a subsequent function evaluation, while the external-memory owner remains alive.
- Read the retained result again through
GetValueand/orGetFieldValue. - Compare those values with the current bytes in the Left Side external buffer.
This was observed during managed func-eval validation: after a moving GC, the native external buffer contained the relocated values, but the existing CordbVCObjectValue continued returning its pre-GC cached bytes/fields. A small public standalone repro is not currently available because deterministic timing and result retention require the debugger test harness.
Expected behavior
Externally backed debugger values should reflect the current GC-updated external buffer whenever their bytes or fields are read. Nested value-type field views should inherit the same external-memory behavior.
Actual behavior
CordbVCObjectValue exposes the snapshot captured by Init, even after GC updates the external buffer. Object references or managed byrefs in the cached copy can therefore be stale.
Regression?
This appears to have been introduced with debugger external-memory result ownership. It is present in current main as inspected at e0bb657a25900b08558449f05b3ab059a2330c20. The exact introducing commit has not been isolated.
Known Workarounds
Avoid retaining or reusing externally backed byref-like func-eval results across any operation that can trigger GC. Re-running the evaluation may produce a fresh value, but this is not a reliable general workaround.
Configuration
Observed while validating a Windows x64 Checked runtime build. The affected Right Side caching and external-memory ownership code is shared debugger logic and does not appear inherently Windows- or x64-specific.
Other information
Relevant current-main flow:
Debugger::FuncEvalCompletepublishesm_externalMemoryOwnerwith the result and transfers ownership after the completion event.CordbEval::GetResultpasses the owner toCordbValue::CreateValueByTypeand transfers it to the unboxedCordbVCObjectValue.CordbVCObjectValue::Initcopies the complete remote value intom_pObjectCopyonce.CordbVCObjectValue::SetExternalMemoryOwnerstores ownership but does not mark the cache as refreshable.CordbVCObjectValue::GetValueandGetFieldValueForTypereadm_pObjectCopywithout first re-reading the external value home.
A fix should preserve ordinary snapshot behavior while marking externally owned CordbVCObjectValue instances as external-backed and refreshing m_pObjectCopy from m_pValueHome before exposing bytes or fields. Nested value-type field values should carry that marker as well, so nested field access refreshes from the authoritative external storage.
Related debugger func-eval lifetime issues #135247 and #135249 are separate problems.
Note
This issue was drafted with GitHub Copilot.
Activity
dotnet-policy-service commentedon Oct 5, 2026
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.
Invoke debugger function evaluations through managed UCO (#133659)