Implement DacDbi cDAC APIs CreateRefWalk, DeleteRefWalk, WalkRefs#129345
Implement DacDbi cDAC APIs CreateRefWalk, DeleteRefWalk, WalkRefs#129345rcj1 wants to merge 4 commits into
Conversation
Co-authored-by: rcj1 <77995559+rcj1@users.noreply.github.com>
|
Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag |
There was a problem hiding this comment.
Pull request overview
This PR adds cDAC-managed implementations for the DacDbi reference-walking APIs (CreateRefWalk/DeleteRefWalk/WalkRefs), updates the corresponding native DacDbi interface signatures to remove the walkFQ parameter, and adds dump-based validation tests (cross-checking results against GC handles and stack-walk reference enumeration).
Changes:
- Implement managed ref-walk enumeration plumbing in
Microsoft.Diagnostics.DataContractReader.Legacy(including a newRefWalkhelper). - Update DacDbi interop shapes (managed COM projection + native IDL/headers + DI usage) to match the revised reference-walk API signature.
- Extend stack-walk reference data to expose
IsInteriorPointer, and add dump-based tests for handle-only and stack reference walks.
Show a summary per file
| File | Description |
|---|---|
| src/native/managed/cdac/tests/DumpTests/DacDbi/DacDbiRefWalkDumpTests.cs | Adds dump-based integration tests for ref-walk APIs vs GC handle table and stack-walk references. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/IDacDbiInterface.cs | Introduces DacGcReference + CorGCReferenceType and updates COM method signatures for ref walking. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/Helpers/RefWalk.cs | Adds managed iterator implementing the ref-walk behavior over handles and stack references. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Legacy/Dbi/DacDbiImpl.cs | Implements CreateRefWalk/DeleteRefWalk/WalkRefs in the cDAC DacDbi implementation (with DEBUG parity hooks). |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/StackWalk_1.cs | Plumbs IsInteriorPointer into emitted StackReferenceData. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/StackRefData.cs | Adds IsInteriorPointer tracking to internal stack ref capture data. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/StackWalk/GC/GcScanContext.cs | Sets IsInteriorPointer based on GC_CALL_INTERIOR flags while recording stack refs. |
| src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs | Adds a new public IsInteriorPointer property to StackReferenceData. |
| src/coreclr/inc/dacdbi.idl | Updates the native DacDbi interface signature for CreateRefWalk (removes walkFQ). |
| src/coreclr/debug/inc/dacdbiinterface.h | Updates the native interface declaration + comments to match the new signature. |
| src/coreclr/debug/di/rspriv.h | Renames stored state from mEnumStacksFQ to mEnumStacks after walkFQ removal. |
| src/coreclr/debug/di/process.cpp | Updates DAC CreateRefWalk call site to match the new signature. |
| src/coreclr/debug/daccess/dacdbiimpl.h | Updates DacDbiInterfaceImpl and DacRefWalker signatures (removes walkFQ). |
| src/coreclr/debug/daccess/dacdbiimpl.cpp | Implements the updated CreateRefWalk signature and updates DacRefWalker ctor accordingly. |
Copilot's findings
Comments suppressed due to low confidence (1)
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Abstractions/Contracts/IStackWalk.cs:44
StackReferenceDatais a public type and this adds a new public property. The PR description doesn't link anapi-approvedissue / proposal for the new public surface, which is normally required for additions to public APIs. If this is intended to be internal-only plumbing between cDAC assemblies, consider keeping it non-public (e.g., make the propertyinternaland add InternalsVisibleTo for the legacy consumer) or link the approved API review issue.
public class StackReferenceData
{
public bool HasRegisterInformation { get; init; }
public bool IsInteriorPointer { get; init; }
public int Register { get; init; }
public int Offset { get; init; }
public TargetPointer Address { get; init; }
public TargetPointer Object { get; init; }
- Files reviewed: 14/14 changed files
- Comments generated: 5
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| int hr = HResults.S_OK; | ||
| RefWalk? walk = null; | ||
| try | ||
| { | ||
| if (pHandle is null) | ||
| throw new NullReferenceException(nameof(pHandle)); | ||
| walk = new RefWalk(_target, walkStacks != Interop.BOOL.FALSE, handleWalkMask); | ||
| *pHandle = (nuint)((IEnum<DacGcReference>)walk).GetHandle(); | ||
| } | ||
| catch (System.Exception ex) | ||
| { | ||
| hr = ex.HResult; | ||
| } |
| // Should be called repeatedly until it returns S_FALSE. | ||
| public int WalkRefs(nuint handle, uint count, [In, MarshalUsing(CountElementName = "count"), Out] DacGcReference[] refs, uint* pFetched) | ||
| { | ||
| RefWalk walk; | ||
| try | ||
| { | ||
| if (pFetched is null) | ||
| throw new NullReferenceException(nameof(pFetched)); | ||
| if (handle == 0) | ||
| throw new ArgumentException("Handle is invalid.", nameof(handle)); | ||
| GCHandle gcHandle = GCHandle.FromIntPtr((nint)handle); | ||
| if (gcHandle.Target is not RefWalk rw) | ||
| throw new ArgumentException("Handle does not reference a valid RefWalk instance.", nameof(handle)); | ||
| walk = rw; | ||
| *pFetched = 0; | ||
| } | ||
| catch (System.Exception ex) | ||
| { | ||
| return ex.HResult; | ||
| } |
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
| if (pHandle is null) | ||
| throw new NullReferenceException(nameof(pHandle)); | ||
| walk = new RefWalk(_target, walkStacks != Interop.BOOL.FALSE, handleWalkMask); | ||
| *pHandle = (nuint)((IEnum<DacGcReference>)walk).GetHandle(); |
| if (pFetched is null) | ||
| throw new NullReferenceException(nameof(pFetched)); | ||
| if (handle == 0) | ||
| throw new ArgumentException("Handle is invalid.", nameof(handle)); | ||
| GCHandle gcHandle = GCHandle.FromIntPtr((nint)handle); | ||
| if (gcHandle.Target is not RefWalk rw) | ||
| throw new ArgumentException("Handle does not reference a valid RefWalk instance.", nameof(handle)); | ||
| walk = rw; | ||
| *pFetched = 0; |
| [Flags] | ||
| public enum CorGCReferenceType : uint | ||
| { | ||
| CorHandleStrong = 1 << 0, | ||
| CorHandleStrongPinning = 1 << 1, | ||
| CorHandleWeakShort = 1 << 2, | ||
| CorHandleWeakLong = 1 << 3, | ||
| CorHandleWeakRefCount = 1 << 4, | ||
| CorHandleStrongRefCount = 1 << 5, | ||
| CorHandleStrongDependent = 1 << 6, | ||
| CorReferenceStack = 0x80000001, | ||
| } |
🤖 Copilot Code Review — PR #129345Note This review was generated by Copilot. Holistic AssessmentMotivation: This PR implements three cDAC DacDbi APIs ( Approach: The implementation follows established cDAC patterns cleanly. Summary: ✅ LGTM. The code is correct, follows established patterns, and the earlier review feedback (compile errors with pointer/array mismatch, test mask bug) has been addressed in subsequent commits. Minor robustness suggestions below are non-blocking and consistent with existing native behavior. Detailed Findings✅
|
| { | ||
| ThreadData threadData = threadContract.GetThreadData(threadAddr); | ||
|
|
||
| foreach (StackReferenceData stackRef in stackWalkContract.WalkStackReferences(threadData)) |
There was a problem hiding this comment.
We need to thread through resolveInteriorPointers. If I recall correctly, ISOSDacInterface does not want to resolve interior pointers, but ICorDebug does.
Currently StackWalk_1.WalkStackReferences hardcodes GcScanContext scanContext = new(_target, resolveInteriorPointers: false);
There was a problem hiding this comment.
Also if it wasn't clear, the StackWalk_1 doesn't support interior refs at all currently, but I still think it makes sense to use the APIs as if it were.
There was a problem hiding this comment.
I thought the idea was to implement this
There was a problem hiding this comment.
It is, but we haven't done it yet.
There was a problem hiding this comment.
Yes, I thought you wanted it in this PR. Do you not?
There was a problem hiding this comment.
Threading through resolveInteriorPointers, yes. But actually dealing with them is more complex:
if (flags.HasFlag(GcScanFlags.GC_CALL_INTERIOR) && ResolveInteriorPointers)
{
// TODO(stackref): handle interior pointers
// https://github.com/dotnet/runtime/issues/125728
throw new NotImplementedException();
}There was a problem hiding this comment.
I understand that, and I would be willing to implement it.
There was a problem hiding this comment.
This looks good to me. I'm fine if you want to add that interior pointer functionality in this PR or in a separate one.
Also fyi I filed an issue tracking the pre-existing missing finalizer queue enumeration functionality that this PR made more apparent: #129559. I don't think it is a high priority to fix given nobody noticed it was missing for at least a decade.
EDIT: Correction - it was noticed six years ago: #39333.
|
|
||
| [ConditionalTheory] | ||
| [MemberData(nameof(TestConfigurations))] | ||
| public unsafe void WalkRefs_StrongHandles_MatchHandleTable(TestConfiguration config) |
There was a problem hiding this comment.
I see this test for strong handles and the one below for stack only but it didn't look like any other kind of handle has coverage yet.
Implement these APIs. Debug validation only on handles because of existing delta w/parity on GC stack refs.