Skip to content

[WIP] [cDAC] Adding symbols and using heap dumps#126385

Draft
rcj1 wants to merge 6 commits intodotnet:mainfrom
rcj1:metadata-fallback
Draft

[WIP] [cDAC] Adding symbols and using heap dumps#126385
rcj1 wants to merge 6 commits intodotnet:mainfrom
rcj1:metadata-fallback

Conversation

@rcj1
Copy link
Copy Markdown
Contributor

@rcj1 rcj1 commented Mar 31, 2026

For cDAC dump testing, we need metadata for our locally built System.Private.CoreLib for reading certain statics etc., as well as for debuggees for metadata tests. So that we can use heap dumps instead of full dumps, we add the symbol paths when we open the dump in ClrMD.

@rcj1 rcj1 added NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) NO-REVIEW Experimental/testing PR, do NOT review it labels Mar 31, 2026
Copilot AI review requested due to automatic review settings March 31, 2026 23:44
@rcj1 rcj1 marked this pull request as draft March 31, 2026 23:44
@rcj1 rcj1 changed the title [WIP] Adding symbols and using heap dumps [WIP] [cDAC] Adding symbols and using heap dumps Mar 31, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 updates the cDAC DumpTests infrastructure to prefer heap dumps (instead of full dumps) and makes dump analysis more reliable by providing ClrMD with local symbol/module search paths (including a self-contained symbol layout for Helix runs).

Changes:

  • Switch DumpTests to default to heap dumps (removing per-test DumpType => "full" overrides) and update debuggee projects to generate heap dumps.
  • Add symbol/module path discovery in DumpTestBase and plumb these paths into ClrMdDumpHost.Open.
  • Update Helix dump-generation to copy System.Private.CoreLib.dll and debuggee DLLs into a symbols/ folder alongside dumps so analysis can resolve modules without the original testhost layout.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/native/managed/cdac/tests/DumpTests/SyncBlockDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/StackWalkDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/RCWDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/RCWCleanupListDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/IXCLRDataValueDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/IXCLRDataFrameDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/IXCLRDataAppDomainDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/ISOSDacInterface13Tests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/ExceptionHandlingInfoDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/EcmaMetadataDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/DumpTestBase.cs Passes additional symbol paths to ClrMD and adds logic to discover symbol/module directories (Helix symbols/ and local artifacts fallback).
src/native/managed/cdac/tests/DumpTests/ClrMdDumpHost.cs Extends dump opening to accept additional symbol paths and configures ClrMD symbol search paths.
src/native/managed/cdac/tests/DumpTests/cdac-dump-helix.proj Copies CoreLib + debuggee DLLs into a symbols/ tree next to dumps to enable symbol/module resolution on Helix machines.
src/native/managed/cdac/tests/DumpTests/ComWrappersDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/CCWDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/AsyncContinuationDumpTests.cs Removes full-dump override so the test uses default heap dump behavior.
src/native/managed/cdac/tests/DumpTests/Debuggees/SyncBlock/SyncBlock.csproj Switches debuggee dump generation to heap dumps.
src/native/managed/cdac/tests/DumpTests/Debuggees/StackWalk/StackWalk.csproj Switches debuggee dump generation to heap dumps.
src/native/managed/cdac/tests/DumpTests/Debuggees/RCWCleanupList/RCWCleanupList.csproj Switches debuggee dump generation to heap dumps.
src/native/managed/cdac/tests/DumpTests/Debuggees/RCW/RCW.csproj Switches debuggee dump generation to heap dumps.
src/native/managed/cdac/tests/DumpTests/Debuggees/MultiModule/MultiModule.csproj Switches debuggee dump generation to heap dumps.
src/native/managed/cdac/tests/DumpTests/Debuggees/LocalVariables/LocalVariables.csproj Switches debuggee dump generation to heap dumps.
src/native/managed/cdac/tests/DumpTests/Debuggees/ExceptionHandlingInfo/ExceptionHandlingInfo.csproj Switches debuggee dump generation to heap dumps.
src/native/managed/cdac/tests/DumpTests/Debuggees/ComWrappers/ComWrappers.csproj Switches debuggee dump generation to heap dumps.
src/native/managed/cdac/tests/DumpTests/Debuggees/CCW/CCW.csproj Switches debuggee dump generation to heap dumps.
src/native/managed/cdac/tests/DumpTests/Debuggees/AsyncContinuation/AsyncContinuation.csproj Switches debuggee dump generation to heap dumps (and removes prior “full dump needed” comment).

@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.

Copilot AI review requested due to automatic review settings March 31, 2026 23:55
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 4 comments.

Copilot AI review requested due to automatic review settings April 1, 2026 04:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings April 1, 2026 17:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 1 comment.

Copilot AI review requested due to automatic review settings April 1, 2026 23:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated no new comments.

@rcj1 rcj1 force-pushed the metadata-fallback branch from 099d709 to d23c4a9 Compare April 2, 2026 00:52
Copilot AI review requested due to automatic review settings April 2, 2026 02:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 8 comments.

Comment on lines 99 to 103
}

_host = ClrMdDumpHost.Open(dumpPath);
_host = ClrMdDumpHost.Open(dumpPath, GetSymbolPaths(debuggeeName, versionDir));
ulong contractDescriptor = _host.FindContractDescriptorAddress();

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

GetSymbolPaths can legitimately return an empty list (e.g., if symbols/ isn't present and the test isn't running from a repo layout). Passing an empty list into ClrMdDumpHost.Open currently results in SetSymbolPath("") (clearing defaults). Consider passing null when no paths are found, or having GetSymbolPaths return null/empty and letting Open skip SetSymbolPath in that case.

Copilot uses AI. Check for mistakes.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DumpTypes>Full</DumpTypes>
<DumpTypes>Heap</DumpTypes>
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Switching this debuggee to DumpTypes=Heap means no dumps will be generated for net10.0 (DumpTests.targets explicitly skips heap dumps for net10.0, and Full is no longer requested). Any tests that still intend to run against net10.0 for this debuggee will now be skipped due to missing dumps. If cross-version coverage is still desired, consider keeping Full (or Heap;Full) or adding logic to generate/consume Full dumps for net10 only.

Suggested change
<DumpTypes>Heap</DumpTypes>
<DumpTypes>Heap;Full</DumpTypes>

Copilot uses AI. Check for mistakes.
<PropertyGroup>
<DumpTypes>Full</DumpTypes>
<DumpTypes>Heap</DumpTypes>
</PropertyGroup>
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Switching this debuggee to DumpTypes=Heap means no dumps will be generated for net10.0 (DumpTests.targets skips heap dumps for net10.0, and Full is no longer requested). Tests using this debuggee will therefore never exercise net10.0 unless they explicitly switch back to Full for that version.

Suggested change
</PropertyGroup>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net10.0'">
<DumpTypes>Full</DumpTypes>
</PropertyGroup>

Copilot uses AI. Check for mistakes.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DumpTypes>Full</DumpTypes>
<DumpTypes>Heap</DumpTypes>
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Switching this debuggee to DumpTypes=Heap means no dumps will be generated for net10.0 (DumpTests.targets skips heap dumps for net10.0, and Full is no longer requested). If the COM dump tests are expected to run cross-version, this will remove net10.0 coverage.

Suggested change
<DumpTypes>Heap</DumpTypes>
<DumpTypes>Full;Heap</DumpTypes>

Copilot uses AI. Check for mistakes.
<!-- This debuggee is intentionally Windows-only (COM interop). -->
<NoWarn>$(NoWarn);CA1416</NoWarn>
<DumpTypes>Full</DumpTypes>
<DumpTypes>Heap</DumpTypes>
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Switching this debuggee to DumpTypes=Heap means no dumps will be generated for net10.0 (DumpTests.targets skips heap dumps for net10.0, and Full is no longer requested). If RCW tests are expected to run against net10.0 using full dumps, this change will prevent that.

Suggested change
<DumpTypes>Heap</DumpTypes>
<DumpTypes>Full</DumpTypes>

Copilot uses AI. Check for mistakes.
<!-- This debuggee is intentionally Windows-only (COM interop). -->
<NoWarn>$(NoWarn);CA1416</NoWarn>
<DumpTypes>Full</DumpTypes>
<DumpTypes>Heap</DumpTypes>
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

Switching this debuggee to DumpTypes=Heap means no dumps will be generated for net10.0 (DumpTests.targets skips heap dumps for net10.0, and Full is no longer requested). If RCW cleanup list tests are expected to run against net10.0 using full dumps, this change will prevent that.

Suggested change
<DumpTypes>Heap</DumpTypes>
<DumpTypes>Heap;Full</DumpTypes>

Copilot uses AI. Check for mistakes.
@rcj1 rcj1 force-pushed the metadata-fallback branch from 0051ad5 to 63365e2 Compare April 2, 2026 23:30
Copilot AI review requested due to automatic review settings April 3, 2026 00:25
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 13 comments.

Comment on lines +78 to +79
int filled = 0;
ulong current = address;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

ReadFromTarget retries by reading from a PE on disk when DataReader.Read returns a short read, but it ignores bytesRead and overwrites the entire buffer from the PE image. This can clobber valid bytes already read from the dump and also loses the opportunity to only backfill the missing tail. Preserve the initial bytesRead and only fill the remaining portion starting at address + bytesRead.

Suggested change
int filled = 0;
ulong current = address;
int filled = bytesRead;
ulong current = address + (ulong)bytesRead;

Copilot uses AI. Check for mistakes.
ulong current = address;
while (filled < buffer.Length)
{
PEMemoryBlock block = peReader.GetSectionData((int)(current - info.ImageBase));
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

ReadFromTarget casts (current - info.ImageBase) to int for PEReader.GetSectionData. This can overflow for large RVAs and will produce incorrect reads or exceptions. Add a bounds check (e.g., > int.MaxValue) and fail the PE fallback if it doesn’t fit.

Suggested change
PEMemoryBlock block = peReader.GetSectionData((int)(current - info.ImageBase));
if (current < info.ImageBase)
return -1;
ulong rva = current - info.ImageBase;
if (rva > int.MaxValue)
return -1;
PEMemoryBlock block = peReader.GetSectionData((int)rva);

Copilot uses AI. Check for mistakes.
if (info is null || info.FileName is null)
return -1;

string? foundFile = _dataTarget.FileLocator?.FindPEImage(info.FileName, info.IndexTimeStamp, info.IndexFileSize, checkProperties: false);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

FindPEImage is called with checkProperties:false. If multiple copies of the same module name exist in the symbol path (very likely with the helix copy step), this can return the wrong PE and cause incorrect metadata reads. Prefer enabling property checks so the located PE matches the dump module’s timestamp/file size.

Suggested change
string? foundFile = _dataTarget.FileLocator?.FindPEImage(info.FileName, info.IndexTimeStamp, info.IndexFileSize, checkProperties: false);
string? foundFile = _dataTarget.FileLocator?.FindPEImage(info.FileName, info.IndexTimeStamp, info.IndexFileSize, checkProperties: true);

Copilot uses AI. Check for mistakes.
Comment on lines +194 to +197
Include="mkdir -p $HELIX_WORKITEM_PAYLOAD/dumps/local/symbols/runtime" />
<_HelixCommandLines Condition="'$(TargetOS)' != 'windows'"
Include="cp $HELIX_CORRELATION_PAYLOAD/shared/Microsoft.NETCore.App/*/System.Private.CoreLib.dll $HELIX_WORKITEM_PAYLOAD/dumps/local/symbols/runtime/" />
<!-- Unix: copy each debuggee DLL -->
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

On Unix, cp $HELIX_CORRELATION_PAYLOAD/shared/Microsoft.NETCore.App/*/System.Private.CoreLib.dll ... can match multiple versions and the last one copied wins, which can lead to mismatched metadata at test time. Consider copying only the specific shared framework version used by the work item (or preserve per-version subdirectories) to avoid accidental mismatches.

Copilot uses AI. Check for mistakes.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DumpTypes>Full</DumpTypes>
<DumpTypes>Heap</DumpTypes>
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

DumpTests.targets explicitly skips generating heap dumps for net10.0. With DumpTypes set to Heap only, this debuggee will produce no net10.0 dumps. If cross-version coverage is desired, consider Heap;Full so net10.0 can still generate a full dump.

Suggested change
<DumpTypes>Heap</DumpTypes>
<DumpTypes>Heap;Full</DumpTypes>

Copilot uses AI. Check for mistakes.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DumpTypes>Full</DumpTypes>
<DumpTypes>Heap</DumpTypes>
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

DumpTests.targets explicitly skips generating heap dumps for net10.0. With DumpTypes set to Heap only, this debuggee will produce no net10.0 dumps. If these tests are meant to validate behavior across released runtimes, consider Heap;Full so net10.0 still gets a full dump.

Suggested change
<DumpTypes>Heap</DumpTypes>
<DumpTypes>Heap;Full</DumpTypes>

Copilot uses AI. Check for mistakes.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<DumpTypes>Full</DumpTypes>
<DumpTypes>Heap</DumpTypes>
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

DumpTests.targets explicitly skips generating heap dumps for net10.0. With DumpTypes set to Heap only, this debuggee will produce no net10.0 dumps. If cross-version validation is desired for this scenario, consider Heap;Full so net10.0 still gets a full dump.

Suggested change
<DumpTypes>Heap</DumpTypes>
<DumpTypes>Heap;Full</DumpTypes>

Copilot uses AI. Check for mistakes.
<NoWarn>$(NoWarn);CA2007;CA2252</NoWarn>
<!-- Full dump needed so that module metadata is available for type lookup -->
<DumpTypes>Full</DumpTypes>
<DumpTypes>Heap</DumpTypes>
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

DumpTests.targets explicitly skips generating heap dumps for net10.0. With DumpTypes set to Heap only, this debuggee will produce no net10.0 dumps. If the async continuation scenario is expected to run against net10.0, consider Heap;Full (heap for local, full for net10.0).

Suggested change
<DumpTypes>Heap</DumpTypes>
<DumpTypes>Heap;Full</DumpTypes>

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +77
using FileStream fs = File.OpenRead(foundFile);
using PEReader peReader = new PEReader(fs);

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The PE fallback opens the located module file and constructs a PEReader on every short read. If many reads hit module-backed ranges (common when heap dumps omit module images), this can add significant I/O overhead. Consider caching the resolved PE path/PEReader (or the relevant section bytes) per ModuleInfo to avoid repeated file opens during a single test run.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 3, 2026 00:34
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 5 comments.

Comment on lines +66 to +93
// If we couldn't read the full buffer, maybe it's in a PE image
ModuleInfo? info = GetModuleForAddress(address);
if (info is null || info.FileName is null)
return -1;

string? foundFile = _dataTarget.FileLocator?.FindPEImage(info.FileName, info.IndexTimeStamp, info.IndexFileSize, checkProperties: false);
if (foundFile is null)
return -1;

using FileStream fs = File.OpenRead(foundFile);
using PEReader peReader = new PEReader(fs);

int filled = 0;
ulong current = address;
while (filled < buffer.Length)
{
PEMemoryBlock block = peReader.GetSectionData((int)(current - info.ImageBase));
if (block.Length == 0)
return -1;

int toCopy = Math.Min(block.Length, buffer.Length - filled);
unsafe
{
new ReadOnlySpan<byte>(block.Pointer, toCopy).CopyTo(buffer.Slice(filled));
}
filled += toCopy;
current += (ulong)toCopy;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The PE-file fallback currently applies to any module range (including native modules like coreclr). For native modules, reading from the on-disk image can silently return incorrect data for writable sections (.data/.bss) that differ from the in-memory state, which can lead to incorrect cDAC reads instead of a clean failure. Consider restricting this fallback to managed PE images (e.g., only when the located file has managed metadata) or to sections that are safe to source from disk, and otherwise return failure.

Copilot uses AI. Check for mistakes.
Comment on lines +71 to +93
string? foundFile = _dataTarget.FileLocator?.FindPEImage(info.FileName, info.IndexTimeStamp, info.IndexFileSize, checkProperties: false);
if (foundFile is null)
return -1;

using FileStream fs = File.OpenRead(foundFile);
using PEReader peReader = new PEReader(fs);

int filled = 0;
ulong current = address;
while (filled < buffer.Length)
{
PEMemoryBlock block = peReader.GetSectionData((int)(current - info.ImageBase));
if (block.Length == 0)
return -1;

int toCopy = Math.Min(block.Length, buffer.Length - filled);
unsafe
{
new ReadOnlySpan<byte>(block.Pointer, toCopy).CopyTo(buffer.Slice(filled));
}
filled += toCopy;
current += (ulong)toCopy;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The PEReader-based fallback has several potential throw points (FindPEImage result, File.OpenRead, PEReader.GetSectionData with an out-of-range/overflowed RVA cast, etc.). Right now any of these exceptions will bubble out of ReadFromTarget and fail the test run unexpectedly. Consider validating (address - info.ImageBase) fits in int, and wrapping the fallback path in exception handling that returns a failure code when the PE image can't be used.

Suggested change
string? foundFile = _dataTarget.FileLocator?.FindPEImage(info.FileName, info.IndexTimeStamp, info.IndexFileSize, checkProperties: false);
if (foundFile is null)
return -1;
using FileStream fs = File.OpenRead(foundFile);
using PEReader peReader = new PEReader(fs);
int filled = 0;
ulong current = address;
while (filled < buffer.Length)
{
PEMemoryBlock block = peReader.GetSectionData((int)(current - info.ImageBase));
if (block.Length == 0)
return -1;
int toCopy = Math.Min(block.Length, buffer.Length - filled);
unsafe
{
new ReadOnlySpan<byte>(block.Pointer, toCopy).CopyTo(buffer.Slice(filled));
}
filled += toCopy;
current += (ulong)toCopy;
}
try
{
string? foundFile = _dataTarget.FileLocator?.FindPEImage(info.FileName, info.IndexTimeStamp, info.IndexFileSize, checkProperties: false);
if (foundFile is null)
return -1;
using FileStream fs = File.OpenRead(foundFile);
using PEReader peReader = new PEReader(fs);
int filled = 0;
ulong current = address;
while (filled < buffer.Length)
{
if (current < info.ImageBase)
return -1;
ulong relativeAddress = current - info.ImageBase;
if (relativeAddress > int.MaxValue)
return -1;
PEMemoryBlock block = peReader.GetSectionData((int)relativeAddress);
if (block.Length == 0)
return -1;
int toCopy = Math.Min(block.Length, buffer.Length - filled);
unsafe
{
new ReadOnlySpan<byte>(block.Pointer, toCopy).CopyTo(buffer.Slice(filled));
}
filled += toCopy;
current += (ulong)toCopy;
}
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or BadImageFormatException or InvalidOperationException or ArgumentException or NotSupportedException)
{
return -1;
}

Copilot uses AI. Check for mistakes.
Comment on lines +107 to +115
private ModuleInfo? GetModuleForAddress(ulong address)
{
foreach (ModuleInfo module in _dataTarget.DataReader.EnumerateModules())
{
if (address >= module.ImageBase && address < module.ImageBase + module.ImageSize)
return module;
}
return null;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

GetModuleForAddress enumerates all modules on every failed read. With heap dumps, short reads may be common, which can make this a hot path and add significant overhead. Consider caching the module list (and their ranges) once per dump, and doing a faster lookup (e.g., a sorted range list / binary search) instead of re-enumerating each time.

Copilot uses AI. Check for mistakes.
Comment on lines 130 to 141
artifacts/dumps/cdac/
local/
dump-info.json
heap/
BasicThreads/BasicThreads.dmp
full/
StackWalk/StackWalk.dmp
PInvokeStub/PInvokeStub.dmp
net10.0/
dump-info.json
full/
heap/
TypeHierarchy/TypeHierarchy.dmp
```
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The directory layout example still omits the per-R2R-mode subdirectory (r2r/jit), but DumpTestBase and DumpTests.targets construct paths as {version}/{dumpType}/{r2rMode}/{debuggee}/{debuggee}.dmp. Updating the example to include the r2r/jit folder would avoid confusion when locating dumps on disk.

Copilot uses AI. Check for mistakes.
Comment on lines +176 to +201
<ItemGroup>
<!-- Windows: copy SPC from testhost shared framework -->
<_HelixCommandLines Condition="'$(TargetOS)' == 'windows'"
Include="echo === Copying symbols ===" />
<_HelixCommandLines Condition="'$(TargetOS)' == 'windows'"
Include="mkdir %25HELIX_WORKITEM_PAYLOAD%25\dumps\local\symbols\runtime" />
<_HelixCommandLines Condition="'$(TargetOS)' == 'windows'"
Include="for /D %25%25v in (%25HELIX_CORRELATION_PAYLOAD%25\shared\Microsoft.NETCore.App\*) do copy &quot;%25%25v\System.Private.CoreLib.dll&quot; &quot;%25HELIX_WORKITEM_PAYLOAD%25\dumps\local\symbols\runtime\&quot;" />
<!-- Windows: copy each debuggee DLL -->
<_HelixCommandLines Condition="'$(TargetOS)' == 'windows'"
Include="@(_UniqueDebuggee->'mkdir %25HELIX_WORKITEM_PAYLOAD%25\dumps\local\symbols\debuggees\%(Identity)')" />
<_HelixCommandLines Condition="'$(TargetOS)' == 'windows'"
Include="@(_UniqueDebuggee->'copy &quot;%25HELIX_WORKITEM_PAYLOAD%25\debuggees\%(Identity)\%(Identity).dll&quot; &quot;%25HELIX_WORKITEM_PAYLOAD%25\dumps\local\symbols\debuggees\%(Identity)\&quot;')" />

<!-- Unix: copy SPC from testhost shared framework -->
<_HelixCommandLines Condition="'$(TargetOS)' != 'windows'"
Include="echo '=== Copying symbols ==='" />
<_HelixCommandLines Condition="'$(TargetOS)' != 'windows'"
Include="mkdir -p $HELIX_WORKITEM_PAYLOAD/dumps/local/symbols/runtime" />
<_HelixCommandLines Condition="'$(TargetOS)' != 'windows'"
Include="cp $HELIX_CORRELATION_PAYLOAD/shared/Microsoft.NETCore.App/*/System.Private.CoreLib.dll $HELIX_WORKITEM_PAYLOAD/dumps/local/symbols/runtime/" />
<!-- Unix: copy each debuggee DLL -->
<_HelixCommandLines Condition="'$(TargetOS)' != 'windows'"
Include="@(_UniqueDebuggee->'mkdir -p $HELIX_WORKITEM_PAYLOAD/dumps/local/symbols/debuggees/%(Identity)')" />
<_HelixCommandLines Condition="'$(TargetOS)' != 'windows'"
Include="@(_UniqueDebuggee->'cp $HELIX_WORKITEM_PAYLOAD/debuggees/%(Identity)/%(Identity).dll $HELIX_WORKITEM_PAYLOAD/dumps/local/symbols/debuggees/%(Identity)/')" />
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The new symbol-copy commands assume the shared framework layout/globs always match. On Unix, cp $HELIX_CORRELATION_PAYLOAD/shared/Microsoft.NETCore.App/*/System.Private.CoreLib.dll ... will fail the command if the glob doesn't match (or if multiple matches behave unexpectedly), and on Windows the for /D ... copy will emit errors for directories missing the DLL. Consider adding existence checks / tolerant copy (e.g., conditional copy or || true) so missing/unexpected payload layouts don't cause the Helix work item to fail before tests run.

Copilot uses AI. Check for mistakes.
Copilot AI review requested due to automatic review settings April 3, 2026 03:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

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

Comments suppressed due to low confidence (1)

src/native/managed/cdac/tests/DumpTests/LoaderDumpTests.cs:24

  • With the DumpType override removed, this test class now always uses the default heap dumps. Since heap dumps are explicitly skipped / not generated for net10.0, any Loader tests without [SkipOnVersion("net10.0", ...)] (e.g., Loader_CanGetRootAssembly, Loader_AppDomainHasFriendlyName, Loader_GlobalLoaderAllocatorIsValid) will no longer run against net10.0. If cross-version coverage is still intended, consider conditionally selecting full for net10.0 (or generating both dump types).
public class LoaderDumpTests : DumpTestBase
{
    protected override string DebuggeeName => "MultiModule";

    [ConditionalTheory]
    [MemberData(nameof(TestConfigurations))]
    public void Loader_CanGetRootAssembly(TestConfiguration config)
    {
        InitializeDumpTest(config);
        ILoader loader = Target.Contracts.Loader;

Comment on lines 60 to 105
public int ReadFromTarget(ulong address, Span<byte> buffer)
{
int bytesRead = _dataTarget.DataReader.Read(address, buffer);
return bytesRead == buffer.Length ? 0 : -1;
if (bytesRead == buffer.Length)
return 0; // success

// If we couldn't read the full buffer, maybe it's in a PE image
ModuleInfo? info = GetModuleForAddress(address);
if (info is null || info.FileName is null)
{
Console.WriteLine($"Failed to find module for {buffer.Length} at 0x{address:x8}.");
return -1;
}

string? foundFile = _dataTarget.FileLocator?.FindPEImage(info.FileName, info.IndexTimeStamp, info.IndexFileSize, checkProperties: false);
if (foundFile is null)
{
Console.WriteLine($"Failed to find {info.FileName}");
return -1;
}

using FileStream fs = File.OpenRead(foundFile);
using PEReader peReader = new PEReader(fs);

int filled = 0;
ulong current = address;
while (filled < buffer.Length)
{
PEMemoryBlock block = peReader.GetSectionData((int)(current - info.ImageBase));
if (block.Length == 0)
{
Console.WriteLine($"Failed to read {foundFile} at RVA 0x{(current-info.ImageBase):x8}.");
return -1;
}

int toCopy = Math.Min(block.Length, buffer.Length - filled);
unsafe
{
new ReadOnlySpan<byte>(block.Pointer, toCopy).CopyTo(buffer.Slice(filled));
}
filled += toCopy;
current += (ulong)toCopy;
}

return 0;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

ReadFromTarget overwrites the entire buffer from the PE image when the dump read is short, but DataReader.Read can return a partial read (non-zero) rather than an all-or-nothing result. In that case, this code discards valid bytes already read from the dump and may return incorrect data to the cDAC reader. Consider only falling back to PE data when bytesRead == 0, or preserve the first bytesRead bytes and fill only the remaining range from the PE image (and ensure the request doesn’t cross the module boundary).

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +72
// If we couldn't read the full buffer, maybe it's in a PE image
ModuleInfo? info = GetModuleForAddress(address);
if (info is null || info.FileName is null)
{
Console.WriteLine($"Failed to find module for {buffer.Length} at 0x{address:x8}.");
return -1;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The failure log uses 0x{address:x8}, which truncates addresses on 64-bit targets and can be misleading when diagnosing dump read failures. Consider formatting based on pointer size (e.g., x16 for 64-bit) or using a helper that prints the full ulong address consistently.

Copilot uses AI. Check for mistakes.
@rcj1 rcj1 force-pushed the metadata-fallback branch from 6f48c44 to 94c4d42 Compare April 3, 2026 15:39
Copilot AI review requested due to automatic review settings April 3, 2026 20:16
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 4 comments.


using System;
using System.Collections.Generic;
using System.Linq;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

using System.Linq; appears unused in this file (e.g., additionalSymbolPaths.ToArray() calls List<T>.ToArray, not LINQ). If warnings are treated as errors (CS8019), this will break the build—please remove the unused using (or replace code with a LINQ call if intended).

Suggested change
using System.Linq;

Copilot uses AI. Check for mistakes.
Comment on lines 57 to +65
public int ReadFromTarget(ulong address, Span<byte> buffer)
{
int bytesRead = _dataTarget.DataReader.Read(address, buffer);
return bytesRead == buffer.Length ? 0 : -1;
if (bytesRead == buffer.Length)
return 0; // success

// If we couldn't read the full buffer, maybe it's in a PE image
ModuleInfo? info = GetModuleForAddress(address);
if (info is null || info.FileName is null)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

When DataReader.Read returns a partial read (bytesRead > 0 but < buffer.Length), the fallback path re-reads from the module file starting at the original address and overwrites the already-read bytes. This can corrupt results when the dump contains valid in-memory bytes for the first part (e.g., relocations/patching) but not the rest. Consider preserving the initial bytesRead bytes and only filling the remaining tail from disk (advance current/filled accordingly).

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +88
PEMemoryBlock block = peReader.GetSectionData((int)(current - info.ImageBase));
if (block.Length == 0)
{
Console.WriteLine($"Failed to read {foundFile} at RVA 0x{(current-info.ImageBase):x8}.");
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

peReader.GetSectionData((int)(current - info.ImageBase)) truncates a 64-bit RVA to int. If the requested offset exceeds int.MaxValue (or if current < ImageBase due to unexpected module selection), this will overflow and can produce incorrect reads or exceptions. Please validate the range before casting and fail gracefully (or otherwise constrain reads to supported RVA sizes).

Suggested change
PEMemoryBlock block = peReader.GetSectionData((int)(current - info.ImageBase));
if (block.Length == 0)
{
Console.WriteLine($"Failed to read {foundFile} at RVA 0x{(current-info.ImageBase):x8}.");
if (current < info.ImageBase)
{
Console.WriteLine($"Failed to read {foundFile}: address 0x{current:x8} is below module base 0x{info.ImageBase:x8}.");
return -1;
}
ulong rva = current - info.ImageBase;
if (rva > int.MaxValue)
{
Console.WriteLine($"Failed to read {foundFile}: RVA 0x{rva:x8} exceeds supported PEReader range.");
return -1;
}
PEMemoryBlock block = peReader.GetSectionData((int)rva);
if (block.Length == 0)
{
Console.WriteLine($"Failed to read {foundFile} at RVA 0x{rva:x8}.");

Copilot uses AI. Check for mistakes.
Comment on lines 31 to 36
| ServerGC | Server GC mode heap structures | Heap |
| StackWalk | Deterministic call stack (Main→A→B→C→FailFast) | Full |
| MultiModule | Multi-assembly metadata resolution | Full |
| StackWalk | Deterministic call stack (Main→A→B→C→FailFast) | Heap |
| MultiModule | Multi-assembly metadata resolution | Heap |
| TypeHierarchy | Type inheritance, method tables | Heap |
| PInvokeStub | P/Invoke with SetLastError ILStub | Full |
| VarargPInvoke | Vararg P/Invoke via __arglist (sprintf) | Full |
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This README now marks StackWalk/MultiModule (and others below) as using Heap dumps, but the later “.NET 10 did not support cDAC in heap dumps… Only full dump tests (RuntimeTypeSystem, Loader, EcmaMetadata) run against net10.0.” note appears inconsistent with these changes (those tests/debuggees are no longer described as Full). Please update that note to reflect which tests still run for net10.0 after switching these debuggees to heap dumps.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-Diagnostics-coreclr NO-MERGE The PR is not ready for merge yet (see discussion for detailed reasons) NO-REVIEW Experimental/testing PR, do NOT review it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants