Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ If you make code changes, do not complete without checking the relevant code bui

Before completing, use the `code-review` skill to review your code changes. Any issues flagged as errors or warnings should be addressed before completing.

Before making changes to a directory, search for `README.md` files in that directory and its parent directories up to the repository root. Read any you find — they contain conventions, patterns, and architectural context relevant to your work.

Copy link
Member Author

Choose a reason for hiding this comment

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

@stephentoub - Since earlier you mentioned context window sizes were a concern, any thoughts on using something like this as a mechanism for progressive discovery? I wanted to add more component-specific guidance (see READMEs elsewhere in this PR as an example) but I didn't want to bloat the context window for prompts working in unrelated parts of the code.

If the changes are intended to improve performance, or if they could negatively impact performance, use the `performance-benchmark` skill to validate the impact before completing.

You MUST follow all code-formatting and naming conventions defined in [`.editorconfig`](/.editorconfig).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Microsoft.Diagnostics.DataContractReader.Legacy

This project contains `SOSDacImpl`, which implements the `ISOSDacInterface*` and
`IXCLRDataProcess` COM-style APIs by delegating to the cDAC contract layer.

## Implementing a new SOSDacImpl method

When a method currently delegates to `_legacyImpl` (returning `E_NOTIMPL` when null),
replace it with a cDAC implementation following this pattern:

```csharp
int ISOSDacInterface8.ExampleMethod(uint* pResult)
{
// 1. Validate pointer arguments before the try block
if (pResult == null)
return HResults.E_INVALIDARG;

int hr = HResults.S_OK;
try
{
// 2. Get the relevant contract and call it
IGC gc = _target.Contracts.GC;
*pResult = gc.SomeMethod();
}
catch (System.Exception ex)
{
hr = ex.HResult;
}

// 3. Cross-validate with legacy DAC in debug builds
#if DEBUG
if (_legacyImpl8 is not null)
{
uint resultLocal;
int hrLocal = _legacyImpl8.ExampleMethod(&resultLocal);
Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}");
if (hr == HResults.S_OK)
{
Debug.Assert(*pResult == resultLocal);
}
}
#endif
return hr;
}
```

### Key conventions

- **HResult returns**: Methods return `int` HResult codes, not exceptions.
Use `HResults.S_OK`, `HResults.S_FALSE`, `HResults.E_INVALIDARG`, etc.
- **Null pointer checks**: Validate output pointer arguments *before* the try block
and return `E_INVALIDARG`. This matches the native DAC behavior.
- **Exception handling**: Wrap all contract calls in try/catch. The catch converts
exceptions to HResult codes via `ex.HResult`.
- **Debug cross-validation**: In `#if DEBUG`, call the legacy implementation (if
available) and assert the results match. This catches discrepancies during testing.

### Sized-buffer protocol

Several `ISOSDacInterface8` methods use a two-call pattern where the caller first
queries the needed buffer size, then calls again with a sufficiently large buffer:

```csharp
int GetSomeTable(uint count, Data* buffer, uint* pNeeded)
```

The protocol is:
1. Always set `*pNeeded` to the required count (if `pNeeded` is not null).
2. If `count > 0 && buffer == null`: return `E_INVALIDARG`.
3. If `count < needed`: return `S_FALSE` (buffer too small, but `*pNeeded` is set).
4. If `count >= needed`: populate `buffer` and return `S_OK`.

This matches the native implementation in `src/coreclr/debug/daccess/request.cpp`.

### Pointer conversions

- `TargetPointer` → `ClrDataAddress`: use `pointer.ToClrDataAddress(_target)`.
On 32-bit targets, this **sign-extends** the value (e.g., `0xAA000000` becomes
`0xFFFFFFFF_AA000000`). This matches native DAC behavior.
- `ClrDataAddress` → `TargetPointer`: use `address.ToTargetPointer(_target)`.

Both are defined in `ConversionExtensions.cs`.
Original file line number Diff line number Diff line change
Expand Up @@ -3984,17 +3984,189 @@ int ISOSDacInterface8.GetNumberGenerations(uint* pGenerations)
return hr;
}

// WKS
int ISOSDacInterface8.GetGenerationTable(uint cGenerations, /*struct DacpGenerationData*/ void* pGenerationData, uint* pNeeded)
=> _legacyImpl8 is not null ? _legacyImpl8.GetGenerationTable(cGenerations, pGenerationData, pNeeded) : HResults.E_NOTIMPL;
{
if (cGenerations > 0 && pGenerationData == null)
return HResults.E_INVALIDARG;

int hr = HResults.S_OK;
try
{
IGC gc = _target.Contracts.GC;
uint totalGenerationCount = _target.ReadGlobal<uint>(Constants.Globals.TotalGenerationCount);

if (pNeeded != null)
*pNeeded = totalGenerationCount;

if (cGenerations < totalGenerationCount)
{
hr = HResults.S_FALSE;
}
else
{
GCHeapData heapData = gc.GetHeapData();
DacpGenerationData* genData = (DacpGenerationData*)pGenerationData;

for (int i = 0; i < (int)totalGenerationCount && i < heapData.GenerationTable.Count; i++)
{
GCGenerationData gen = heapData.GenerationTable[i];
genData[i].start_segment = gen.StartSegment.ToClrDataAddress(_target);
genData[i].allocation_start = gen.AllocationStart.ToClrDataAddress(_target);
genData[i].allocContextPtr = gen.AllocationContextPointer.ToClrDataAddress(_target);
genData[i].allocContextLimit = gen.AllocationContextLimit.ToClrDataAddress(_target);
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}

#if DEBUG
if (_legacyImpl8 is not null)
{
uint pNeededLocal;
int hrLocal = _legacyImpl8.GetGenerationTable(cGenerations, pGenerationData, &pNeededLocal);
Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}");
}
Comment on lines +4025 to +4031
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The debug cross-validation should also verify that the *pNeeded value matches between cDAC and legacy DAC, not just the HResult. Add an assertion like: if (pNeeded != null) Debug.Assert(*pNeeded == pNeededLocal); This follows the pattern used elsewhere in the file (e.g., line 933) and helps catch data mismatches during testing.

Copilot uses AI. Check for mistakes.
#endif
return hr;
}

int ISOSDacInterface8.GetFinalizationFillPointers(uint cFillPointers, ClrDataAddress* pFinalizationFillPointers, uint* pNeeded)
=> _legacyImpl8 is not null ? _legacyImpl8.GetFinalizationFillPointers(cFillPointers, pFinalizationFillPointers, pNeeded) : HResults.E_NOTIMPL;
{
if (cFillPointers > 0 && pFinalizationFillPointers == null)
return HResults.E_INVALIDARG;

int hr = HResults.S_OK;
try
{
IGC gc = _target.Contracts.GC;
GCHeapData heapData = gc.GetHeapData();
uint numFillPointers = (uint)heapData.FillPointers.Count;

if (pNeeded != null)
*pNeeded = numFillPointers;

if (cFillPointers < numFillPointers)
{
hr = HResults.S_FALSE;
}
else
{
for (int i = 0; i < (int)numFillPointers; i++)
{
pFinalizationFillPointers[i] = heapData.FillPointers[i].ToClrDataAddress(_target);
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}

#if DEBUG
if (_legacyImpl8 is not null)
{
uint pNeededLocal;
int hrLocal = _legacyImpl8.GetFinalizationFillPointers(cFillPointers, pFinalizationFillPointers, &pNeededLocal);
Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}");
}
Comment on lines +4068 to +4074
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The debug cross-validation should also verify that the *pNeeded value matches between cDAC and legacy DAC, not just the HResult. Add an assertion like: if (pNeeded != null) Debug.Assert(*pNeeded == pNeededLocal); This follows the pattern used elsewhere in the file (e.g., line 933) and helps catch data mismatches during testing.

Copilot uses AI. Check for mistakes.
#endif
return hr;
}

// SVR
int ISOSDacInterface8.GetGenerationTableSvr(ClrDataAddress heapAddr, uint cGenerations, /*struct DacpGenerationData*/ void* pGenerationData, uint* pNeeded)
=> _legacyImpl8 is not null ? _legacyImpl8.GetGenerationTableSvr(heapAddr, cGenerations, pGenerationData, pNeeded) : HResults.E_NOTIMPL;
{
if (heapAddr == 0 || (cGenerations > 0 && pGenerationData == null))
return HResults.E_INVALIDARG;

int hr = HResults.S_OK;
try
{
IGC gc = _target.Contracts.GC;
uint totalGenerationCount = _target.ReadGlobal<uint>(Constants.Globals.TotalGenerationCount);

if (pNeeded != null)
*pNeeded = totalGenerationCount;

if (cGenerations < totalGenerationCount)
{
hr = HResults.S_FALSE;
}
else
{
GCHeapData heapData = gc.GetHeapData(heapAddr.ToTargetPointer(_target));
DacpGenerationData* genData = (DacpGenerationData*)pGenerationData;

for (int i = 0; i < (int)totalGenerationCount && i < heapData.GenerationTable.Count; i++)
{
GCGenerationData gen = heapData.GenerationTable[i];
genData[i].start_segment = gen.StartSegment.ToClrDataAddress(_target);
genData[i].allocation_start = gen.AllocationStart.ToClrDataAddress(_target);
genData[i].allocContextPtr = gen.AllocationContextPointer.ToClrDataAddress(_target);
genData[i].allocContextLimit = gen.AllocationContextLimit.ToClrDataAddress(_target);
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}

#if DEBUG
if (_legacyImpl8 is not null)
{
uint pNeededLocal;
int hrLocal = _legacyImpl8.GetGenerationTableSvr(heapAddr, cGenerations, pGenerationData, &pNeededLocal);
Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}");
}
Comment on lines +4117 to +4123
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The debug cross-validation should also verify that the *pNeeded value matches between cDAC and legacy DAC, not just the HResult. Add an assertion like: if (pNeeded != null) Debug.Assert(*pNeeded == pNeededLocal); This follows the pattern used elsewhere in the file (e.g., line 933) and helps catch data mismatches during testing.

Copilot uses AI. Check for mistakes.
#endif
return hr;
}

int ISOSDacInterface8.GetFinalizationFillPointersSvr(ClrDataAddress heapAddr, uint cFillPointers, ClrDataAddress* pFinalizationFillPointers, uint* pNeeded)
=> _legacyImpl8 is not null ? _legacyImpl8.GetFinalizationFillPointersSvr(heapAddr, cFillPointers, pFinalizationFillPointers, pNeeded) : HResults.E_NOTIMPL;
{
if (heapAddr == 0 || (cFillPointers > 0 && pFinalizationFillPointers == null))
return HResults.E_INVALIDARG;

int hr = HResults.S_OK;
try
{
IGC gc = _target.Contracts.GC;
GCHeapData heapData = gc.GetHeapData(heapAddr.ToTargetPointer(_target));
uint numFillPointers = (uint)heapData.FillPointers.Count;

if (pNeeded != null)
*pNeeded = numFillPointers;

if (cFillPointers < numFillPointers)
{
hr = HResults.S_FALSE;
}
else
{
for (int i = 0; i < (int)numFillPointers; i++)
{
pFinalizationFillPointers[i] = heapData.FillPointers[i].ToClrDataAddress(_target);
}
}
}
catch (System.Exception ex)
{
hr = ex.HResult;
}

#if DEBUG
if (_legacyImpl8 is not null)
{
uint pNeededLocal;
int hrLocal = _legacyImpl8.GetFinalizationFillPointersSvr(heapAddr, cFillPointers, pFinalizationFillPointers, &pNeededLocal);
Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}");
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The debug cross-validation should also verify that the *pNeeded value matches between cDAC and legacy DAC, not just the HResult. Add an assertion like: if (pNeeded != null) Debug.Assert(*pNeeded == pNeededLocal); This follows the pattern used elsewhere in the file (e.g., line 933) and helps catch data mismatches during testing.

Suggested change
Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}");
Debug.Assert(hrLocal == hr, $"cDAC: {hr:x}, DAC: {hrLocal:x}");
if (pNeeded is not null)
{
Debug.Assert(*pNeeded == pNeededLocal);
}

Copilot uses AI. Check for mistakes.
}
#endif
return hr;
}

int ISOSDacInterface8.GetAssemblyLoadContext(ClrDataAddress methodTable, ClrDataAddress* assemblyLoadContext)
{
Expand Down
Loading
Loading