Skip to content

[prototype] cdac-lite: native crash-dump memory enumeration#130277

Draft
max-charlamb wants to merge 13 commits into
dotnet:mainfrom
max-charlamb:max-charlamb/cdac-lite-prototype
Draft

[prototype] cdac-lite: native crash-dump memory enumeration#130277
max-charlamb wants to merge 13 commits into
dotnet:mainfrom
max-charlamb:max-charlamb/cdac-lite-prototype

Conversation

@max-charlamb

Copy link
Copy Markdown
Member

Prototype: cdac-lite — native crash-dump memory enumeration

Draft / prototype — not for merge. Exploratory work on a small native component (cdac-lite) that reproduces the DAC's crash-dump managed-memory enumeration by reading the runtime's contract/data descriptors directly, without a version-matched mscordaccore. Integrated into createdump behind DOTNET_DbgUseCdacLite=1.

What's here

  • src/coreclr/debug/cdaclite/** — the cdac-lite component: descriptor model, Target, per-subsystem memory enumerators (tier-aware, uniform table), conservative stack scan + JIT/R2R method resolution, GC-info + dumpdomain emission.
  • src/coreclr/debug/createdump/** — env-gated cdac-lite path (DOTNET_DbgUseCdacLite, DOTNET_DbgCdacLitePath).
  • src/native/managed/cdac/tests/DumpTests/**MiniDumpTests (walk/thread/SOS-DAC coverage) + a UseCdacLite dump-generation flag.
  • eng/pipelines/** — opt-in cdacUseCdacLite parameter wiring the runtime-diagnostics pipeline to generate + validate cdac-lite dumps on Helix (Windows).

Status

30/30 MiniDumpTests pass against cdac-lite-generated normal dumps (clrthreads / clrstack / dumpmd / ip2md / dumpmt / dumpmodule / dumpdomain via ISOSDacInterface). cdac-lite normal dumps are ~3.4–8x smaller than the DAC's.

This PR exists primarily to exercise the runtime-diagnostics pipeline with the new cdacUseCdacLite mode.

Note

This PR description was generated with GitHub Copilot (AI-assisted).

Max Charlamb and others added 2 commits July 2, 2026 17:02
Prototype of a small standalone native component (cdac-lite) that reproduces
the DAC's crash-dump memory enumeration by reading the runtime's contract/data
descriptors directly, without a version-matched mscordaccore.

- src/coreclr/debug/cdaclite: exports CLRDataCreateInstance +
  ICLRDataEnumMemoryRegions; contract walks (gc, thread, loader, handles, jit,
  statics, syncblock, stresslog, interop) + conservative stack scan for the
  Normal tier; size-optimized build (~114 KB on windows-x64).
- createdump integration (env-gated DOTNET_DbgUseCdacLite), Normal + Heap tiers.
- cdac DumpTests: add Mini (MiniDumpNormal) tier + MiniDumpTests covering the
  clrstack/clrthreads scenarios supported by a normal minidump.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…numerators, CI flag

Builds on the base cdac-lite prototype commit with this session's work:

- Refactor stackscan into a CodeEnumerator class (one deduped Emit + per-type
  Enum* methods mirroring the DAC EnumMemoryRegions pattern).
- Rearchitect the driver into a uniform table of tier-aware enumerators; each
  self-selects by target.Tier() (Target now carries ClrBase/ContractDescriptorAddr).
  New contracts/bootstrap.cpp for the export dir + global singletons.
- Close the JIT stack-walk gap (native-code-slot re-resolution, frame-chain
  transition frames, unwind info) and emit GC info + full dumpdomain state
  (global LoaderAllocator + assembly-list arrays) so SOS clrthreads/clrstack/
  dumpmd/ip2md/dumpmt/dumpmodule/dumpdomain work on cdac-lite normal dumps.
- Add MiniDumpSosDacTests exercising the ISOSDacInterface APIs those commands use.
- CI: UseCdacLite flag (DumpTests.targets + RunDumpTests.ps1) and an opt-in
  cdacUseCdacLite parameter wiring the runtime-diagnostics pipeline to generate +
  validate cdac-lite dumps on Helix (Windows).

30/30 MiniDumpTests pass; cdac-lite builds clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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 left a comment

Copy link
Copy Markdown
Contributor

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 prototypes a new native cdac-lite component under CoreCLR debug infrastructure and wires it into Windows createdump behind environment-variable gates, then expands the managed cDAC dump-test suite (including new MiniDumpNormal coverage) and runtime-diagnostics pipeline wiring to exercise the new dump-generation mode.

Changes:

  • Add src/coreclr/debug/cdaclite/**: a native CLRDataCreateInstance + ICLRDataEnumMemoryRegions implementation plus contract-driven region enumeration.
  • Add an env-gated Windows createdump path (DOTNET_DbgUseCdacLite / DOTNET_DbgCdacLitePath) that uses cdac-lite to feed managed regions to MiniDumpWriteDump.
  • Extend cDAC DumpTests to generate/use Mini dumps and add Helix/pipeline parameters to run the suite in “use cdac-lite for dump generation” mode (Windows).

Reviewed changes

Copilot reviewed 62 out of 62 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/native/managed/cdac/tests/TestInfrastructure/DumpTestBase.cs Extend net10.0 skip logic to cover mini dumps and improve skip message.
src/native/managed/cdac/tests/TestInfrastructure/ClrMdDumpHost.cs Add PE-header fallback reads to satisfy mini/normal dump scenarios.
src/native/managed/cdac/tests/DumpTests/RunDumpTests.ps1 Add -UseCdacLite switch and plumb it into dump-generation build properties.
src/native/managed/cdac/tests/DumpTests/README.md Document Mini dumps, supported SOS scenarios, and new mini-tier test classes.
src/native/managed/cdac/tests/DumpTests/MiniDumpTests.cs New integration tests for clrstack / clrthreads expectations on MiniDumpNormal.
src/native/managed/cdac/tests/DumpTests/DumpTests.targets Add Mini dump tier support; add UseCdacLite env-var injection for dump generation.
src/native/managed/cdac/tests/DumpTests/Debuggees/StackWalk/StackWalk.csproj Request both Heap;Mini dumps for StackWalk debuggee.
src/native/managed/cdac/tests/DumpTests/Debuggees/Directory.Build.props Update dump-tier documentation to include Mini and combinations.
src/native/managed/cdac/tests/DumpTests/cdac-dump-helix.proj Add Helix precommand to enable cdac-lite mode via env var.
src/native/managed/cdac/scripts/ThreadsCommand.cs Fix OS ID formatting to use .Value.
src/native/managed/cdac/scripts/StacksCommand.cs Fix OS ID formatting to use .Value.
src/native/managed/cdac/scripts/DumpHelpers.cs Add dump-read fallback to on-disk PE images for missing module bytes.
src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader/ContractDescriptorTarget.cs Add optional read-failure logging (env gated) and route read failures through helper.
src/coreclr/debug/createdump/createdumpwindows.cpp Add Windows createdump integration to use cdac-lite region enumeration (env gated).
src/coreclr/debug/CMakeLists.txt Add cdaclite subdirectory to the debug build (non-cross-components builds).
src/coreclr/debug/cdaclite/CMakeLists.txt New build definition for the cdac-lite shared library and helper static libs.
src/coreclr/debug/cdaclite/cdaclite.h New cdac-lite COM type definition (ICLRDataEnumMemoryRegions).
src/coreclr/debug/cdaclite/cdaclite.cpp Implement CLRDataCreateInstance, symbol lookup, COM lifetime, and logging.
src/coreclr/debug/cdaclite/cdaclite.src Export definition for Windows build.
src/coreclr/debug/cdaclite/cdaclite_unixexports.src Export list for Unix builds.
src/coreclr/debug/cdaclite/enumerate.cpp Implement tier-aware enumeration driver over contract enumerators.
src/coreclr/debug/cdaclite/datatarget.h Define ICLRDataTarget adapter / read callback plumbing.
src/coreclr/debug/cdaclite/datatarget.cpp Implement target memory read callback.
src/coreclr/debug/cdaclite/cdaclitetest.cpp New Windows-only standalone harness to exercise cdac-lite against a live process.
src/coreclr/debug/cdaclite/json/CMakeLists.txt Build definition for the JSON parser static lib.
src/coreclr/debug/cdaclite/json/json.h Define tiny JSON value model and parsing API.
src/coreclr/debug/cdaclite/json/json.cpp Implement tiny JSON parser (with jsonc comments support).
src/coreclr/debug/cdaclite/data/CMakeLists.txt Build definition for descriptor/target data-layer static lib.
src/coreclr/debug/cdaclite/data/datatype.h Infrastructure for descriptor-driven struct field reads (CDAC_* macros).
src/coreclr/debug/cdaclite/data/runtimetypes.h Descriptor-driven “Data types” for runtime structures used by contracts.
src/coreclr/debug/cdaclite/data/datadescriptor.h Data descriptor model (types/globals/contracts) and merge/resolve APIs.
src/coreclr/debug/cdaclite/data/datadescriptor.cpp Read/parse/merge logic for in-memory descriptor + sub-descriptors.
src/coreclr/debug/cdaclite/data/target.h Target abstraction for typed reads and enum-mem recording.
src/coreclr/debug/cdaclite/data/target.cpp Implement Target raw reads, field/global helpers, and enum-mem emission.
src/coreclr/debug/cdaclite/contracts/CMakeLists.txt Build definition for contract enumerators library.
src/coreclr/debug/cdaclite/contracts/contracts.h Shared RegionCallback contract type.
src/coreclr/debug/cdaclite/contracts/bootstrap.h Bootstrap enumerator declarations.
src/coreclr/debug/cdaclite/contracts/bootstrap.cpp Emit PE headers/export dir and key singleton structs needed for bootstrap.
src/coreclr/debug/cdaclite/contracts/thread.h Thread enumerator declarations.
src/coreclr/debug/cdaclite/contracts/thread.cpp Enumerate thread stacks and thread-locals emission.
src/coreclr/debug/cdaclite/contracts/loader.h Loader/module enumeration declarations.
src/coreclr/debug/cdaclite/contracts/loader.cpp Enumerate modules and emit metadata locator structs / in-memory PDB streams.
src/coreclr/debug/cdaclite/contracts/loaderheaps.h Loader-heap enumeration declarations.
src/coreclr/debug/cdaclite/contracts/loaderheaps.cpp Enumerate loader-allocator heaps and report heap blocks.
src/coreclr/debug/cdaclite/contracts/jit.h JIT code heap enumeration declarations.
src/coreclr/debug/cdaclite/contracts/jit.cpp Enumerate JIT code heaps and capture associated heap structs.
src/coreclr/debug/cdaclite/contracts/stackscan.h Normal-tier stack-scan enumeration declarations.
src/coreclr/debug/cdaclite/contracts/stackscan.cpp Implement conservative stack scanning and method/code emission (normal tier).
src/coreclr/debug/cdaclite/contracts/gc.h GC heap enumeration declarations.
src/coreclr/debug/cdaclite/contracts/gc.cpp Enumerate GC heap segments and supporting GC bookkeeping/free-region data.
src/coreclr/debug/cdaclite/contracts/handles.h GC handle-table enumeration declarations.
src/coreclr/debug/cdaclite/contracts/handles.cpp Enumerate handle-table segments and required pointer arrays.
src/coreclr/debug/cdaclite/contracts/syncblock.h Syncblock enumeration declarations.
src/coreclr/debug/cdaclite/contracts/syncblock.cpp Enumerate sync table/cache and in-use syncblocks (heap tier).
src/coreclr/debug/cdaclite/contracts/stresslog.h Stresslog enumeration declarations.
src/coreclr/debug/cdaclite/contracts/stresslog.cpp Enumerate stresslog chunks and emit chunk buffers (heap tier).
src/coreclr/debug/cdaclite/contracts/interop.h COM interop enumeration declarations.
src/coreclr/debug/cdaclite/contracts/interop.cpp Enumerate RCW cleanup structures (heap tier).
src/coreclr/debug/cdaclite/contracts/statics.h “Statics” / bootstrap descriptor emission declarations.
src/coreclr/debug/cdaclite/contracts/statics.cpp Emit contract descriptor structs/JSON/pointer_data and indirect global storage.
eng/pipelines/runtime-diagnostics.yml Add pipeline parameter and pass it through Helix send to enable cdac-lite mode.
eng/pipelines/cdac/prepare-cdac-helix-steps.yml Add opt-in step to copy cdaclite.dll into testhost payload next to coreclr.dll (Windows).

Comment on lines +264 to +277
HMODULE cdacLite = LoadLibraryA(cdacLitePath.c_str());
if (cdacLite == nullptr)
{
printf_error("cdac-lite: LoadLibrary(%s) FAILED - %s\n", cdacLitePath.c_str(), GetLastErrorString().c_str());
return false;
}

bool result = false;
PFN_CLRDataCreateInstance pfnCreate = (PFN_CLRDataCreateInstance)GetProcAddress(cdacLite, "CLRDataCreateInstance");
if (pfnCreate == nullptr)
{
printf_error("cdac-lite: GetProcAddress(CLRDataCreateInstance) FAILED\n");
return false;
}
Comment on lines +64 to +82
STDMETHOD(GetMachineType)(ULONG32* machine)
{
#if defined(_M_ARM64)
*machine = IMAGE_FILE_MACHINE_ARM64;
#elif defined(_M_ARM)
*machine = IMAGE_FILE_MACHINE_ARMNT;
#elif defined(_M_IX86)
*machine = IMAGE_FILE_MACHINE_I386;
#else
*machine = IMAGE_FILE_MACHINE_AMD64;
#endif
return S_OK;
}

STDMETHOD(GetPointerSize)(ULONG32* size)
{
*size = sizeof(void*);
return S_OK;
}
Comment on lines +88 to +89
int rva = (int)(current - info.ImageBase);
PEMemoryBlock block = peReader.GetSectionData(rva);
Comment on lines +100 to +104
while (filled < buffer.Length)
{
int rva = (int)(current - info.ImageBase);
PEMemoryBlock block = peReader.GetSectionData(rva);
if (block.Length > 0)
Comment on lines +35 to +37
// Stress log is opt-in; if disabled there is nothing to capture.
// COPILOT TODO: We shouldn't need this fallback for different sized things. Either it is pointer sized or a set size.
uint64_t enabled = 0;
Comment on lines +379 to +383
value.type = Type::Number;
value.rawNumber.assign(start, (size_t)(m_cur - start));

value.number = strtod(value.rawNumber.c_str(), nullptr);
if (!isFloat)
Comment on lines +182 to +191
int count = 0;
HashSet<uint> seenIds = new();
TargetPointer currentThread = storeData.FirstThread;
while (currentThread != TargetPointer.Null)
{
ThreadData threadData = threadContract.GetThreadData(currentThread);
count++;
Assert.True(seenIds.Add(threadData.Id), $"Duplicate thread ID: {threadData.Id}");
currentThread = threadData.NextThread;
}
Max Charlamb and others added 2 commits July 7, 2026 08:55
…acOS -dead_strip)

CI runtime-diagnostics (build 1496310) surfaced two prototype build breaks on
platforms I hadn't built locally (windows-x64 + linux were fine):

- windows-x86: cdaclitetest.cpp(588) 'wmain must be __cdecl' (C4007) -- x86 enforces
  the calling convention on wmain; add __cdecl.
- macOS: 'ld: unknown options: --gc-sections' -- Apple's ld doesn't support
  --gc-sections; use -dead_strip on APPLE, keep --gc-sections on other Unix.

The windows-x64 CdacDumpTest and windows-arm64 CdacDumpTest legs PASSED with
cdacUseCdacLite=true, so cdac-lite dump generation + validation works in CI on Windows.
(The windows-x64 AllSubsets failure is the known 'VSIX Auto Update' infra error --
it fails identically in the DAC baseline run and has no compile error.)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Removes the per-platform CDACLITE_SIZE_* block. It only bought size-over-speed
(/O1, -Os) + /OPT:ICF folding -- negligible for a small component and not important
yet -- while dead-code stripping (/OPT:REF, --gc-sections, -dead_strip) is already
applied per-platform by configurecompiler.cmake. Removing it also eliminates the
hand-rolled platform flags that broke the macOS build. cdac-lite now just uses
coreclr's default Release optimization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 7, 2026 13:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 62 out of 62 changed files in this pull request and generated 4 comments.

Comment on lines +28 to +43
bool Value::TryGetUInt64(uint64_t& out) const
{
if (type == Type::Number)
{
if (isInteger)
{
out = (uint64_t)integer;
return true;
}
if (number >= 0.0)
{
out = (uint64_t)number;
return true;
}
return false;
}
Comment on lines +379 to +394
value.type = Type::Number;
value.rawNumber.assign(start, (size_t)(m_cur - start));

value.number = strtod(value.rawNumber.c_str(), nullptr);
if (!isFloat)
{
char* end = nullptr;
errno = 0;
long long parsed = strtoll(value.rawNumber.c_str(), &end, 10);
if (errno == 0 && end != value.rawNumber.c_str() && *end == '\0')
{
value.integer = (int64_t)parsed;
value.isInteger = true;
}
}
return true;
Comment on lines +264 to +277
HMODULE cdacLite = LoadLibraryA(cdacLitePath.c_str());
if (cdacLite == nullptr)
{
printf_error("cdac-lite: LoadLibrary(%s) FAILED - %s\n", cdacLitePath.c_str(), GetLastErrorString().c_str());
return false;
}

bool result = false;
PFN_CLRDataCreateInstance pfnCreate = (PFN_CLRDataCreateInstance)GetProcAddress(cdacLite, "CLRDataCreateInstance");
if (pfnCreate == nullptr)
{
printf_error("cdac-lite: GetProcAddress(CLRDataCreateInstance) FAILED\n");
return false;
}
Comment on lines +352 to +356
if (depth == 0)
{
const char* dumpPath = getenv("CDACLITE_DUMP_JSON");
if (dumpPath != nullptr)
{
Max Charlamb and others added 2 commits July 7, 2026 12:09
cdaclite is installed into the CoreCLR runtime pack (install_clr ... DESTINATIONS
sharedFramework COMPONENT debug), so the shared-framework manifest validation
(sharedfx.targets) requires a PlatformManifestFileEntry for it, the same way
mscordaccore/mscordbi are listed. Without it the product build (SOSTests /
AllSubsets_CoreCLR legs) fails with 'files are missing entries in the templated
manifest: cdaclite.dll'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…asses

After adding cdaclite.dll to the platform manifest, sharedfx.targets(519)
additionally requires each Windows shared-framework file to carry a FileVersion
resource ('Missing FileVersion in 1 shared framework files: cdaclite.dll').

Add a Native.rc (FX_VER_FILEDESCRIPTION_STR + fxver.h/fxver.rc) and, on Windows,
define FX_VER_INTERNALNAME_STR=cdaclite.dll and compile Native.rc into the DLL --
the same pattern mscordaccore/mscordbi use. Verified locally: cdaclite.dll now
reports FileVersion 42.42.42.42424. The resource is Windows-only; Unix .so/.dylib
ship without one, matching the peer debug components.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 7, 2026 18:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 64 out of 64 changed files in this pull request and generated 10 comments.

Comment on lines +32 to +36
if (isInteger)
{
out = (uint64_t)integer;
return true;
}
Comment on lines +382 to +395
value.number = strtod(value.rawNumber.c_str(), nullptr);
if (!isFloat)
{
char* end = nullptr;
errno = 0;
long long parsed = strtoll(value.rawNumber.c_str(), &end, 10);
if (errno == 0 && end != value.rawNumber.c_str() && *end == '\0')
{
value.integer = (int64_t)parsed;
value.isInteger = true;
}
}
return true;
}
Comment on lines +333 to +337
if (descriptorSize == 0 || descriptorSize > MaxDescriptorSize)
{
error = "descriptor_size out of range";
return false;
}
Comment on lines +37 to +44
void CDacLite::RegionSinkThunk(void* context, const char* kind, uint64_t start, uint64_t size)
{
RegionSinkState* state = (RegionSinkState*)context;
state->callback->EnumMemoryRegion((CLRDATA_ADDRESS)start, (ULONG32)size);
state->count++;
state->owner->Log(state->callback, "region %s [0x%llx, 0x%llx)",
kind, (unsigned long long)start, (unsigned long long)(start + size));
}
Comment on lines +49 to +54
void CDacLite::EnumMemThunk(void* context, uint64_t address, uint32_t size)
{
RegionSinkState* state = (RegionSinkState*)context;
state->callback->EnumMemoryRegion((CLRDATA_ADDRESS)address, (ULONG32)size);
state->count++;
}
Comment on lines +64 to +76
STDMETHOD(GetMachineType)(ULONG32* machine)
{
#if defined(_M_ARM64)
*machine = IMAGE_FILE_MACHINE_ARM64;
#elif defined(_M_ARM)
*machine = IMAGE_FILE_MACHINE_ARMNT;
#elif defined(_M_IX86)
*machine = IMAGE_FILE_MACHINE_I386;
#else
*machine = IMAGE_FILE_MACHINE_AMD64;
#endif
return S_OK;
}
Comment on lines +78 to +82
STDMETHOD(GetPointerSize)(ULONG32* size)
{
*size = sizeof(void*);
return S_OK;
}
Comment on lines +264 to +277
HMODULE cdacLite = LoadLibraryA(cdacLitePath.c_str());
if (cdacLite == nullptr)
{
printf_error("cdac-lite: LoadLibrary(%s) FAILED - %s\n", cdacLitePath.c_str(), GetLastErrorString().c_str());
return false;
}

bool result = false;
PFN_CLRDataCreateInstance pfnCreate = (PFN_CLRDataCreateInstance)GetProcAddress(cdacLite, "CLRDataCreateInstance");
if (pfnCreate == nullptr)
{
printf_error("cdac-lite: GetProcAddress(CLRDataCreateInstance) FAILED\n");
return false;
}
Comment on lines +310 to +313
MINIDUMP_TYPE dumpType = heapTier
? (MINIDUMP_TYPE)(MiniDumpNormal | MiniDumpWithPrivateReadWriteMemory)
: (MINIDUMP_TYPE)(MiniDumpNormal | MiniDumpWithoutAuxiliaryState);

Comment on lines 11 to 17
Debuggees are auto-discovered from the Debuggees/ subdirectory. Each debuggee csproj
can set a DumpTypes property to control which dump types are generated:
"Mini" — normal minidump only (type 1)
"Heap" — heap dump only (type 2, default)
"Full" — full dump only (type 4)
"Heap;Full" — both heap and full dumps
"Heap;Full" — any combination separated by ';'

The createdump cdac-lite integration previously existed only on Windows
(createdumpwindows.cpp), so the CI runtime-diagnostics runs exercised cdac-lite
dump collection on Windows only. Wire it into the Unix createdump so the
Linux/macOS CdacDumpTest legs exercise it too and can uncover cross-platform bugs.

- crashinfo.cpp InitializeDAC: when DOTNET_DbgUseCdacLite=1 (non-full dump), load
  libcdaclite.so/.dylib (from DOTNET_DbgCdacLitePath or next to coreclr) instead of
  mscordaccore and create ICLRDataEnumMemoryRegions from it. cdac-lite does not
  implement IXCLRDataProcess, so it is left null; the two consumers
  (EnumerateManagedModules, UnwindAllThreads) are already null-guarded.
- prepare-cdac-helix-steps.yml: copy the platform cdac-lite library
  (cdaclite.dll / libcdaclite.so / libcdaclite.dylib) into the testhost payload on
  all platforms, not just Windows.
- cdac-dump-helix.proj: add the Unix 'export DOTNET_DbgUseCdacLite=1' pre-command.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 65 out of 65 changed files in this pull request and generated 13 comments.

Comment on lines +73 to +75
Currently wired for Windows only (the createdump cdac-lite path is Windows-only). Use a clean
dump directory when switching modes (the tests key off the dump directory, not how it was made).
-->
Comment on lines +80 to +84
<_CdacLiteArch>$(TargetArchitecture)</_CdacLiteArch>
<_CdacLiteArch Condition="'$(_CdacLiteArch)' == ''">x64</_CdacLiteArch>
<CdacLitePath Condition="'$(UseCdacLite)' == 'true' AND '$(CdacLitePath)' == ''">$([MSBuild]::NormalizePath('$(RepoRoot)', 'artifacts', 'bin', 'coreclr', '$(HostOS).$(_CdacLiteArch).$(_CdacLiteConfig)', 'cdaclite.dll'))</CdacLitePath>
<!-- Extra env vars appended to the dump-generation command when cdac-lite mode is enabled. -->
<_CdacLiteEnvVars Condition="'$(UseCdacLite)' == 'true'">;DOTNET_DbgUseCdacLite=1;DOTNET_DbgCdacLitePath=$(CdacLitePath)</_CdacLiteEnvVars>
Comment on lines +68 to +71
- name: cdacUseCdacLite
displayName: Use cdac-lite for dump generation (Windows only)
type: boolean
default: false
Comment on lines +64 to +69
When set, generates the crash dumps with cdac-lite selecting the managed memory
instead of the legacy DAC (createdump honors DOTNET_DbgUseCdacLite and loads the
cdac-lite DLL from the coreclr build output). The cDAC DumpTests then run unchanged
against the cdac-lite-generated dumps. Windows only. Use a clean dump directory
(delete artifacts\dumps\cdac or pass -Force) when switching between DAC and cdac-lite
modes, since existing dumps are reused as-is.
Comment on lines +64 to +66
STDMETHOD(GetMachineType)(ULONG32* machine)
{
#if defined(_M_ARM64)
Comment on lines +292 to +297
hr = enumRegions->EnumMemoryRegions(&collector, enumFlags, CLRDATA_ENUM_MEM_DEFAULT);
if (FAILED(hr))
{
printf_error("cdac-lite: EnumMemoryRegions FAILED (%08x)\n", hr);
return false;
}
Comment on lines +319 to +328
if (MiniDumpWriteDump(hProcess, pid, hFile, dumpType, NULL, NULL, &callbackInfo))
{
result = true;
}
else
{
printf_error("cdac-lite: MiniDumpWriteDump - %s\n", GetLastErrorString().c_str());
}

return result;
Comment on lines +35 to +50
// Stress log is opt-in; if disabled there is nothing to capture.
// COPILOT TODO: We shouldn't need this fallback for different sized things. Either it is pointer sized or a set size.
uint64_t enabled = 0;
if (!target.TryGetGlobalValue(GlobalStressLogEnabled, enabled))
{
// Fall back to reading it as a byte at the global's address.
uint32_t enabled32 = 0;
if (target.TryReadGlobalUInt32(GlobalStressLogEnabled, enabled32))
{
enabled = enabled32 & 0xFF;
}
}
if (enabled == 0)
{
return 0;
}
Comment on lines +9 to +13
#include "json.h"

#include <stdlib.h>
#include <string.h>

Comment on lines +121 to +135
// Indeterminate types (offsets only): max(field offset) + 8 covers every scalar/pointer.
const TypeInfo* type = m_descriptor->FindType(typeName);
if (type == nullptr || type->fields.empty())
{
return false;
}
uint32_t maxEnd = 0;
for (std::map<std::string, FieldInfo>::const_iterator it = type->fields.begin(); it != type->fields.end(); ++it)
{
uint32_t end = it->second.offset + (uint32_t)sizeof(uint64_t);
if (end > maxEnd)
{
maxEnd = end;
}
}
CI (runtime-diagnostics on Linux arm64/arm32) revealed the Unix createdump
cdac-lite path failing at load: GetProcAddress(CLRDataCreateInstance) FAILED
-- libcdaclite.so: undefined symbol: CLRDataCreateInstance.

libcdaclite.so dlopen'd fine (no PAL-init problem), but CLRDataCreateInstance was
not in its dynamic symbol table. coreclr compiles with -fvisibility=hidden, so the
extern C symbol was hidden; the --version-script cannot un-hide a compile-time
hidden symbol. The legacy DAC avoids this by declaring the entry point STDAPI
DLLEXPORT (default visibility on Unix). Add an equivalent default-visibility
attribute, guarded so it is a no-op on Windows (export comes from cdaclite.def).
Verified cdaclite.dll still builds + links on Windows.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 03:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 65 out of 65 changed files in this pull request and generated 13 comments.

Comment on lines +64 to +76
STDMETHOD(GetMachineType)(ULONG32* machine)
{
#if defined(_M_ARM64)
*machine = IMAGE_FILE_MACHINE_ARM64;
#elif defined(_M_ARM)
*machine = IMAGE_FILE_MACHINE_ARMNT;
#elif defined(_M_IX86)
*machine = IMAGE_FILE_MACHINE_I386;
#else
*machine = IMAGE_FILE_MACHINE_AMD64;
#endif
return S_OK;
}
Comment on lines +78 to +82
STDMETHOD(GetPointerSize)(ULONG32* size)
{
*size = sizeof(void*);
return S_OK;
}
Comment on lines +272 to +277
PFN_CLRDataCreateInstance pfnCreate = (PFN_CLRDataCreateInstance)GetProcAddress(cdacLite, "CLRDataCreateInstance");
if (pfnCreate == nullptr)
{
printf_error("cdac-lite: GetProcAddress(CLRDataCreateInstance) FAILED\n");
return false;
}
Comment on lines +281 to +286
HRESULT hr = pfnCreate(__uuidof(ICLRDataEnumMemoryRegions), dataTarget, (void**)&enumRegions);
if (FAILED(hr) || enumRegions == nullptr)
{
printf_error("cdac-lite: CLRDataCreateInstance(ICLRDataEnumMemoryRegions) FAILED (%08x)\n", hr);
return false;
}
Comment on lines +292 to +297
hr = enumRegions->EnumMemoryRegions(&collector, enumFlags, CLRDATA_ENUM_MEM_DEFAULT);
if (FAILED(hr))
{
printf_error("cdac-lite: EnumMemoryRegions FAILED (%08x)\n", hr);
return false;
}
Comment on lines +347 to +395
bool ParseNumber(Value& value)
{
const char* start = m_cur;
bool isFloat = false;

if (Peek() == '-')
{
m_cur++;
}
while (!AtEnd())
{
char c = *m_cur;
if (c >= '0' && c <= '9')
{
m_cur++;
}
else if (c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-')
{
isFloat = (c == '.' || c == 'e' || c == 'E') ? true : isFloat;
m_cur++;
}
else
{
break;
}
}

if (m_cur == start)
{
return Fail("invalid number");
}

value.type = Type::Number;
value.rawNumber.assign(start, (size_t)(m_cur - start));

value.number = strtod(value.rawNumber.c_str(), nullptr);
if (!isFloat)
{
char* end = nullptr;
errno = 0;
long long parsed = strtoll(value.rawNumber.c_str(), &end, 10);
if (errno == 0 && end != value.rawNumber.c_str() && *end == '\0')
{
value.integer = (int64_t)parsed;
value.isInteger = true;
}
}
return true;
}
Comment on lines +372 to +374
// Read pointer_data and resolve this descriptor's indirect globals.
std::vector<uint64_t> pointerData(pointerDataCount, 0);
for (uint32_t i = 0; i < pointerDataCount; i++)
Comment on lines +37 to +44
void CDacLite::RegionSinkThunk(void* context, const char* kind, uint64_t start, uint64_t size)
{
RegionSinkState* state = (RegionSinkState*)context;
state->callback->EnumMemoryRegion((CLRDATA_ADDRESS)start, (ULONG32)size);
state->count++;
state->owner->Log(state->callback, "region %s [0x%llx, 0x%llx)",
kind, (unsigned long long)start, (unsigned long long)(start + size));
}
Comment on lines +49 to +54
void CDacLite::EnumMemThunk(void* context, uint64_t address, uint32_t size)
{
RegionSinkState* state = (RegionSinkState*)context;
state->callback->EnumMemoryRegion((CLRDATA_ADDRESS)address, (ULONG32)size);
state->count++;
}
Comment on lines +30 to +43
if (type == Type::Number)
{
if (isInteger)
{
out = (uint64_t)integer;
return true;
}
if (number >= 0.0)
{
out = (uint64_t)number;
return true;
}
return false;
}
Max Charlamb and others added 2 commits July 8, 2026 12:05
…eadability)

CI on Linux/macOS revealed the cdac-lite createdump path generating dumps the
managed cDAC reader could not read: 133 VirtualReadException failures across
frames, stackwalk, appdomain, and metadata tests (Windows cdac-lite passed 220/257,
Linux/macOS ~116/257).

Root-caused with a local WSL repro + ELF-core analysis: a managed assembly on Linux
is mapped as multiple non-contiguous file-backed segments, and createdump's ELF/
Mach-O core omits read-only file-backed segments (metadata). The managed cDAC reader
(EcmaMetadata_1.GetReadOnlyMetadataAddress) reads read-only metadata directly from
target memory with no on-disk fallback (unlike the legacy DAC), so the metadata bytes
must be in the dump. On Windows they happen to be captured; on Linux/macOS they are not.

loader.cpp previously emitted only the PEImageLayout locator struct (comment: "the
metadata bytes themselves come from the on-disk image"), which holds on Windows but
not for the managed reader on Linux/macOS. Emit the bytes the reader touches:
  - mapped image: PE headers [Base,SizeOfHeaders] + CLI(COR20) header + ECMA metadata
    (RVA == offset-from-base), parsed from the module's PE headers
  - flat image: the whole (small) file
Managed assemblies are PE on every platform, so the PE parse is valid on Linux/macOS.

Verified in a local WSL build: the CoreLib and debuggee metadata regions the reader
reads flipped from OUT to IN in the ELF core, with only a lean (~4MB) dump-size
increase (vs ~17MB for whole-image emission). Builds clean on Windows and Linux/clang.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…data from disk

Replace the Linux/macOS metadata-embedding approach with DAC-faithful PE header
emission. PEImage::EnumMemoryRegions enumerates both m_pLayouts[IMAGE_FLAT] and
[IMAGE_LOADED], and PEDecoder::EnumMemoryRegions emits the DOS header, NT headers,
section table, COR (CLI) header and R2R header; the loaded layout additionally
emits the debug directory. cdac-lite now mirrors this exactly for each layout and
does NOT emit the ECMA metadata bytes -- a reader reads those from the on-disk
image, just like the DAC. createdump excludes module image content from heap dumps
by design (only full dumps write module mappings), so re-emitting these headers is
required for the reader's on-disk metadata fallback to resolve the module.

Expose PEImage::m_pLayouts[IMAGE_FLAT] as cdac_data<PEImage>::FlatImageLayout in the
data descriptor so the flat layout's headers can be emitted.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 19:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 67 out of 67 changed files in this pull request and generated 8 comments.

Comment on lines +64 to +76
STDMETHOD(GetMachineType)(ULONG32* machine)
{
#if defined(_M_ARM64)
*machine = IMAGE_FILE_MACHINE_ARM64;
#elif defined(_M_ARM)
*machine = IMAGE_FILE_MACHINE_ARMNT;
#elif defined(_M_IX86)
*machine = IMAGE_FILE_MACHINE_I386;
#else
*machine = IMAGE_FILE_MACHINE_AMD64;
#endif
return S_OK;
}
Comment on lines +78 to +82
STDMETHOD(GetPointerSize)(ULONG32* size)
{
*size = sizeof(void*);
return S_OK;
}
Comment on lines +264 to +286
HMODULE cdacLite = LoadLibraryA(cdacLitePath.c_str());
if (cdacLite == nullptr)
{
printf_error("cdac-lite: LoadLibrary(%s) FAILED - %s\n", cdacLitePath.c_str(), GetLastErrorString().c_str());
return false;
}

bool result = false;
PFN_CLRDataCreateInstance pfnCreate = (PFN_CLRDataCreateInstance)GetProcAddress(cdacLite, "CLRDataCreateInstance");
if (pfnCreate == nullptr)
{
printf_error("cdac-lite: GetProcAddress(CLRDataCreateInstance) FAILED\n");
return false;
}

ReleaseHolder<ProcessDataTarget> dataTarget = new ProcessDataTarget(hProcess);
ReleaseHolder<ICLRDataEnumMemoryRegions> enumRegions;
HRESULT hr = pfnCreate(__uuidof(ICLRDataEnumMemoryRegions), dataTarget, (void**)&enumRegions);
if (FAILED(hr) || enumRegions == nullptr)
{
printf_error("cdac-lite: CLRDataCreateInstance(ICLRDataEnumMemoryRegions) FAILED (%08x)\n", hr);
return false;
}
Comment on lines +11 to +13
#include <stdlib.h>
#include <string.h>

Comment on lines +32 to +36
if (isInteger)
{
out = (uint64_t)integer;
return true;
}
Comment on lines +379 to +395
value.type = Type::Number;
value.rawNumber.assign(start, (size_t)(m_cur - start));

value.number = strtod(value.rawNumber.c_str(), nullptr);
if (!isFloat)
{
char* end = nullptr;
errno = 0;
long long parsed = strtoll(value.rawNumber.c_str(), &end, 10);
if (errno == 0 && end != value.rawNumber.c_str() && *end == '\0')
{
value.integer = (int64_t)parsed;
value.isInteger = true;
}
}
return true;
}
Comment on lines +35 to +37
// Stress log is opt-in; if disabled there is nothing to capture.
// COPILOT TODO: We shouldn't need this fallback for different sized things. Either it is pointer sized or a set size.
uint64_t enabled = 0;
Comment on lines +82 to +84
<CdacLitePath Condition="'$(UseCdacLite)' == 'true' AND '$(CdacLitePath)' == ''">$([MSBuild]::NormalizePath('$(RepoRoot)', 'artifacts', 'bin', 'coreclr', '$(HostOS).$(_CdacLiteArch).$(_CdacLiteConfig)', 'cdaclite.dll'))</CdacLitePath>
<!-- Extra env vars appended to the dump-generation command when cdac-lite mode is enabled. -->
<_CdacLiteEnvVars Condition="'$(UseCdacLite)' == 'true'">;DOTNET_DbgUseCdacLite=1;DOTNET_DbgCdacLitePath=$(CdacLitePath)</_CdacLiteEnvVars>
Max Charlamb and others added 3 commits July 8, 2026 19:34
The dump-test memory host resolved on-disk module images via ClrMD's raw module
list. For a managed ReadyToRun assembly on Linux/macOS that has two mappings -- a
flat/file layout and a separate loaded/converted layout far apart in the address
space -- ClrMD registers the module at the flat base only. Reads of loaded-layout
addresses (e.g. ECMA metadata, which heap/mini dumps do not capture) then fail
their on-disk fallback, non-deterministically depending on ASLR ordering.

Source managed module mappings from the cDAC Loader contract instead, mirroring
dotnet/diagnostics' ManagedModuleService: DumpTestBase enumerates modules via
ILoader (loaded base + size via TryGetLoadedImageContents, path via GetPath, and
the PE identity key via GetFileHeadersInfo) and registers them with the host.
ClrMdDumpHost's read fallback now resolves the owning module solely from this
table and no longer consults ClrMD's module list. ClrMD is retained only for raw
memory reads, thread context, and the one-time contract-descriptor export lookup
needed to bootstrap the Target.

Each module's on-disk file is located and verified against its PE identity key
(COFF TimeDateStamp + optional-header SizeOfImage) once at registration, so a
stale or mismatched same-named assembly is rejected rather than read silently.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A managed module's loaded image can be either a mapped (loaded/converted) layout
or a flat (file) layout. For a flat layout -- e.g. an IL-only assembly whose
loaded layout is the raw file mapping -- the runtime reports metadata and other
data as base + file offset, not base + RVA. The on-disk fallback assumed a mapped
layout and translated the offset via GetSectionData, which fails for a flat module
(the file offset is not a section RVA), so reads that crossed out of the captured
header page into the assembly's metadata failed.

Thread the module's FLAG_MAPPED bit from the Loader contract through to the
fallback and branch on it: mapped layouts translate the RVA via the section table,
flat layouts read the raw image at the offset directly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cdac-lite previously emitted DAC-faithful PE header regions (DOS/NT/COR/R2R
headers, section table, and debug directory) for both the flat and loaded image
layouts of every managed module. Now that the reader pages a module's data in from
the on-disk assembly (located and key-verified via the Loader contract), the dump
only needs enough to identify the module and reach its loaded image.

Emit just the Module -> PEAssembly -> PEImage -> PEImageLayout locator chain (auto-
emitted as the walk reads each struct) plus the loaded image's PE header page
([Base, SizeOfHeaders]) -- enough for a reader to read the module's identity key
(COFF TimeDateStamp + OptionalHeader SizeOfImage) and section table. The COR/R2R
headers, ECMA metadata, debug directory, code, and the flat layout are no longer
emitted; a reader reads those from the on-disk image, exactly like the DAC.

Also drops the now-unused PEImage.FlatImageLayout data descriptor field.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 9, 2026 00:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 65 out of 65 changed files in this pull request and generated 7 comments.

Comment on lines +99 to +103
foreach (ResolvedModule module in _modules)
{
if (address >= module.Base && address < module.Base + module.Size)
return module;
}
Comment on lines +100 to +104
while (filled < buffer.Length)
{
int rva = (int)(current - info.ImageBase);
PEMemoryBlock block = peReader.GetSectionData(rva);
if (block.Length > 0)
Comment on lines +78 to +84
<_CdacLiteConfig>$(_DebuggeeRuntimeConfig)</_CdacLiteConfig>
<_CdacLiteConfig Condition="'$(_CdacLiteConfig)' == ''">Release</_CdacLiteConfig>
<_CdacLiteArch>$(TargetArchitecture)</_CdacLiteArch>
<_CdacLiteArch Condition="'$(_CdacLiteArch)' == ''">x64</_CdacLiteArch>
<CdacLitePath Condition="'$(UseCdacLite)' == 'true' AND '$(CdacLitePath)' == ''">$([MSBuild]::NormalizePath('$(RepoRoot)', 'artifacts', 'bin', 'coreclr', '$(HostOS).$(_CdacLiteArch).$(_CdacLiteConfig)', 'cdaclite.dll'))</CdacLitePath>
<!-- Extra env vars appended to the dump-generation command when cdac-lite mode is enabled. -->
<_CdacLiteEnvVars Condition="'$(UseCdacLite)' == 'true'">;DOTNET_DbgUseCdacLite=1;DOTNET_DbgCdacLitePath=$(CdacLitePath)</_CdacLiteEnvVars>
Comment on lines +35 to +50
// Stress log is opt-in; if disabled there is nothing to capture.
// COPILOT TODO: We shouldn't need this fallback for different sized things. Either it is pointer sized or a set size.
uint64_t enabled = 0;
if (!target.TryGetGlobalValue(GlobalStressLogEnabled, enabled))
{
// Fall back to reading it as a byte at the global's address.
uint32_t enabled32 = 0;
if (target.TryReadGlobalUInt32(GlobalStressLogEnabled, enabled32))
{
enabled = enabled32 & 0xFF;
}
}
if (enabled == 0)
{
return 0;
}
Comment on lines +9 to +13
#include "json.h"

#include <stdlib.h>
#include <string.h>

Comment on lines +64 to +82
STDMETHOD(GetMachineType)(ULONG32* machine)
{
#if defined(_M_ARM64)
*machine = IMAGE_FILE_MACHINE_ARM64;
#elif defined(_M_ARM)
*machine = IMAGE_FILE_MACHINE_ARMNT;
#elif defined(_M_IX86)
*machine = IMAGE_FILE_MACHINE_I386;
#else
*machine = IMAGE_FILE_MACHINE_AMD64;
#endif
return S_OK;
}

STDMETHOD(GetPointerSize)(ULONG32* size)
{
*size = sizeof(void*);
return S_OK;
}
Comment on lines +379 to +395
value.type = Type::Number;
value.rawNumber.assign(start, (size_t)(m_cur - start));

value.number = strtod(value.rawNumber.c_str(), nullptr);
if (!isFloat)
{
char* end = nullptr;
errno = 0;
long long parsed = strtoll(value.rawNumber.c_str(), &end, 10);
if (errno == 0 && end != value.rawNumber.c_str() && *end == '\0')
{
value.integer = (int64_t)parsed;
value.isInteger = true;
}
}
return true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants