Migrating from ClrMD 3.x to 4.0

May 6, 2026 · View on GitHub

This guide covers the breaking changes introduced in ClrMD 4.0 and how to update your code.


CustomDataTarget removed — use DataTargetOptions

The CustomDataTarget class has been removed. All configuration is now done through DataTargetOptions, which is passed to DataTarget.LoadDump, DataTarget.AttachToProcess, DataTarget.CreateSnapshotAndAttach, and DataTarget.CreateFromDbgEng.

v3:

CustomDataTarget customTarget = new(myDataReader)
{
    FileLocator = myLocator,
};
customTarget.CacheOptions.CacheTypes = false;

using DataTarget dt = customTarget.CreateDataTarget();

v4:

DataTargetOptions options = new()
{
    FileLocator = myLocator,
    CacheOptions = new CacheOptions { CacheTypes = false },
};

using DataTarget dt = new DataTarget(myDataReader, options);

DataTarget factory methods now take DataTargetOptions

All DataTarget static factory methods now accept DataTargetOptions? instead of CacheOptions?.

v3:

using DataTarget dt = DataTarget.LoadDump("crash.dmp", new CacheOptions { ... });

v4:

using DataTarget dt = DataTarget.LoadDump("crash.dmp", new DataTargetOptions
{
    CacheOptions = new CacheOptions { ... },
    SymbolPaths = new[] { "https://msdl.microsoft.com/download/symbols" },
});

The DataTargetOptions class provides:

PropertyDescriptionDefault
CacheOptionsControls what ClrMD caches in memoryDefault cache settings
SymbolPathsSymbol server URLs to contact["https://msdl.microsoft.com/download/symbols"]
SymbolCachePathLocal directory for downloaded symbols%TEMP%\symbols
SymbolTokenCredentialAzure.Core.TokenCredential for authenticated symbol serversnull
VerifyDacSignatureVerify DAC Authenticode signature on Windows before loadingtrue
FileLocatorCustom IFileLocator (overrides built-in symbol chain)Built-in symbol server chain
ForceCompleteRuntimeEnumerationSearch all modules for single-file runtimesfalse
TraceSymbolRequestsEmit diagnostic traces for symbol resolutionfalse
UseLockFreeMemoryMapReaderWrap file-based dumps with a lock-free, memory-mapped reader (see below)false
LimitsConfigurable parsing and network bounds (see below)Default limits

Symbol server configuration changes

In v3, symbol server configuration was done via _NT_SYMBOL_PATH, DataTarget.SetSymbolPath, or by providing a custom IFileLocator.

In v4, the primary way to configure symbol servers is through DataTargetOptions:

v3:

// Option 1: environment variable
Environment.SetEnvironmentVariable("_NT_SYMBOL_PATH", "srv*C:\\symbols*https://msdl.microsoft.com/download/symbols");

// Option 2: programmatic
using DataTarget dt = DataTarget.LoadDump("crash.dmp");
dt.SetSymbolPath("srv*C:\\symbols*https://msdl.microsoft.com/download/symbols");

v4:

using DataTarget dt = DataTarget.LoadDump("crash.dmp", new DataTargetOptions
{
    SymbolPaths = new[] { "https://msdl.microsoft.com/download/symbols" },
    SymbolCachePath = @"C:\symbols",
});

_NT_SYMBOL_PATH is no longer parsed in v4. If DataTargetOptions is not supplied, ClrMD defaults to a single symbol path pointing at the public Microsoft symbol server (https://msdl.microsoft.com/download/symbols) with a cache under Path.Combine(Path.GetTempPath(), "symbols").

To restore v3-like behavior (read _NT_SYMBOL_PATH and use it as the symbol path), parse the environment variable yourself and feed the results into DataTargetOptions. A typical sketch:

static DataTargetOptions BuildOptionsFromNtSymbolPath()
{
    string? ntSymbolPath = Environment.GetEnvironmentVariable("_NT_SYMBOL_PATH");

    // _NT_SYMBOL_PATH is a semicolon-separated list of entries. Each entry can be a
    // local directory or a "srv*[downstream-cache]*<server>" element. ClrMD v4 wants
    // the server URLs in SymbolPaths and a single local cache in SymbolCachePath.
    List<string> servers = new();
    string? cache = null;

    foreach (string entry in (ntSymbolPath ?? "").Split(';', StringSplitOptions.RemoveEmptyEntries))
    {
        if (entry.StartsWith("srv*", StringComparison.OrdinalIgnoreCase) ||
            entry.StartsWith("symsrv*", StringComparison.OrdinalIgnoreCase))
        {
            // srv*[cache1*cache2*...]*<server-url>
            string[] parts = entry.Split('*', StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length >= 2)
            {
                servers.Add(parts[^1]);
                if (parts.Length >= 3 && cache is null)
                    cache = parts[1]; // first downstream cache wins
            }
        }
        else
        {
            // Plain local directory acts as both a search location and a cache.
            servers.Add(entry);
            cache ??= entry;
        }
    }

    if (servers.Count == 0)
        servers.Add("https://msdl.microsoft.com/download/symbols");

    return new DataTargetOptions
    {
        SymbolPaths = servers.ToArray(),
        SymbolCachePath = cache ?? Path.Combine(Path.GetTempPath(), "symbols"),
    };
}

using DataTarget dt = DataTarget.LoadDump("crash.dmp", BuildOptionsFromNtSymbolPath());

If you only need to point at a private/UNC server or a custom cache, set DataTargetOptions.SymbolPaths and DataTargetOptions.SymbolCachePath directly — there is no need to round-trip through _NT_SYMBOL_PATH. For full control over how binaries are resolved (for example, to layer in your own search logic), assign a custom DataTargetOptions.FileLocator; when set, SymbolPaths and SymbolCachePath are ignored.


Azure-based symbol server authentication

In v3, authenticated symbol servers (e.g. Symweb) used DefaultAzureCredential automatically. In v4, Symweb authentication uses InteractiveBrowserCredential by default. You can provide any Azure.Core.TokenCredential via DataTargetOptions.SymbolTokenCredential:

v4:

using DataTarget dt = DataTarget.LoadDump("crash.dmp", new DataTargetOptions
{
    SymbolPaths = new[] { "https://symweb.azurefd.net/" },
    SymbolTokenCredential = new InteractiveBrowserCredential(),
});

IFileLocator simplified — FindElfImage and FindMachOImage removed

The IFileLocator interface no longer has FindElfImage or FindMachOImage methods. ClrMD does not download ELF or Mach-O binaries from symbol servers because there is no mechanism to verify their integrity. Only FindPEImage methods remain.

If you implemented IFileLocator in v3, remove those two methods from your implementation.

v3:

public class MyLocator : IFileLocator
{
    public string? FindPEImage(string fileName, int buildTimeStamp, int imageSize, bool checkProperties) => ...;
    public string? FindPEImage(string fileName, SymbolProperties archivedUnder, ImmutableArray<byte> buildIdOrUUID, OSPlatform originalPlatform, bool checkProperties) => ...;
    public string? FindElfImage(string fileName, SymbolProperties archivedUnder, ImmutableArray<byte> buildId, bool checkProperties) => ...;
    public string? FindMachOImage(string fileName, SymbolProperties archivedUnder, ImmutableArray<byte> uuid, bool checkProperties) => ...;
}

v4:

public class MyLocator : IFileLocator
{
    public string? FindPEImage(string fileName, int buildTimeStamp, int imageSize, bool checkProperties) => ...;
    public string? FindPEImage(string fileName, SymbolProperties archivedUnder, ImmutableArray<byte> buildIdOrUUID, OSPlatform originalPlatform, bool checkProperties) => ...;
}

On non-Windows platforms, the consumer is responsible for placing the DAC on disk through whatever mechanism they trust and providing the path via ClrInfo.CreateRuntime(dacPath).


DAC signature verification is now opt-out

In v3, DAC Authenticode verification was opt-in — the consumer had to explicitly pass verifySignature: true to CreateRuntime. In v4, verification is on by default and controlled by DataTargetOptions.VerifyDacSignature.

The verifySignature parameter has been removed from all ClrInfo.CreateRuntime overloads.

v3:

// Opt-in to signature verification
ClrRuntime runtime = clrInfo.CreateRuntime(dacPath, ignoreMismatch: false, verifySignature: true);

v4:

// Verification is on by default — nothing to change for most users.
ClrRuntime runtime = clrInfo.CreateRuntime();

// To disable for local dev builds:
using DataTarget dt = DataTarget.LoadDump("crash.dmp", new DataTargetOptions
{
    VerifyDacSignature = false,
});
ClrRuntime runtime = dt.ClrVersions[0].CreateRuntime();

ClrObject constructor is now internal

The ClrObject constructor was marked [Obsolete] in v3.1 and is now internal in v4. Use ClrHeap.GetObject(ulong address) instead.

v3:

ClrObject obj = new ClrObject(address, type);

v4:

ClrObject obj = heap.GetObject(address);

Command and CommandOptions utilities removed

The Command and CommandOptions helper classes for launching external processes have been removed from the library. Use System.Diagnostics.Process directly if you need this functionality.


New: lock-free memory-mapped data reader (opt-in)

ClrMD 4.0 adds an opt-in, single-threaded data reader that satisfies memory reads directly from a memory-mapped view of the dump file. It eliminates per-read locks and stream seeks compared to the default minidump readers, which can substantially speed up sequential read patterns such as heap walks and GC root traversal.

This feature is off by default. Enable it via DataTargetOptions.UseLockFreeMemoryMapReader:

using DataTarget dt = DataTarget.LoadDump("crash.dmp", new DataTargetOptions
{
    UseLockFreeMemoryMapReader = true,
});

Trade-offs:

  • The resulting IDataReader is not thread safe. ClrRuntime.IsThreadSafe will return false, and callers must serialize all access to the DataTarget, ClrRuntime, and ClrHeap themselves.
  • Only takes effect when loading a dump from a file path (Minidump, ELF coredump, or Mach-O coredump). Has no effect for stream-based dump loads or live process targets.
  • The entire dump file is memory-mapped into the current process. On a 32-bit host, user-mode address space is ~2 GB, so loading a large dump from a 32-bit process will fail with OutOfMemoryException. Run as a 64-bit process for large dumps, or leave this option disabled.

Removed: CacheOptions.UseOSMemoryFeatures

The CacheOptions.UseOSMemoryFeatures flag (and its backing AWE-based minidump reader) has been removed. The flag enabled an Address Windowing Extensions (AWE) cache that required SeLockMemoryPrivilege — a privilege that is not granted by default and required specialty machine setup on Windows. Combined with being under-maintained, the AWE path was removed in v4.

The property is still present and marked [Obsolete] for source compatibility, but it has no effect. Setting it in v4 will produce a compiler warning.

If you previously set this flag for performance reasons and do not need a thread safe IDataReader, set DataTargetOptions.UseLockFreeMemoryMapReader = true instead for a speed improvement on file-based dumps (see the section above for details and trade-offs).


DataTargetLimits — configurable parsing and network bounds

ClrMD 4.0 introduces DataTargetLimits, a class that centralizes all upper bounds for parsing and network operations. These limits prevent excessive memory allocation or processing time when reading untrusted or corrupted dump files.

All limits are configurable via DataTargetOptions.Limits:

using DataTarget dt = DataTarget.LoadDump("crash.dmp", new DataTargetOptions
{
    Limits = new DataTargetLimits
    {
        MaxMinidumpStreams = 50_000,         // Default: 10,000
        MaxFileDownloadSize = 100 * 1024 * 1024,  // Default: 16 MB
        SymbolTimeout = TimeSpan.FromSeconds(300), // Default: 180s
    },
});

Key defaults:

LimitDefault
MaxMinidumpStreams10,000
MaxMinidumpMemoryRanges10,000,000
MaxElfProgramHeaders10,000
MaxMachOLoadCommands10,000
MaxPESections10,000
MaxThreads20,000
MaxModules100,000
MaxFileDownloadSize16 MB
SymbolTimeout180 seconds

See the DataTargetLimits class for the full list.


macOS CreateSnapshotAndAttach support

DataTarget.CreateSnapshotAndAttach is now supported on macOS in addition to Windows and Linux. It creates a temporary coredump from the live process (similar to the Linux behavior).


cDAC support

ClrMD 4.0 adds support for the cDAC (contract-based DAC). The universal DAC binary is named mscordaccore_universal and is enumerated alongside the traditional platform-specific DAC in ClrInfo.DebuggingLibraries. No consumer action is required — CreateRuntime() automatically tries available DAC binaries.


Summary: quick reference

v3 Patternv4 Replacement
CustomDataTargetDataTargetOptions + new DataTarget(reader, options)
DataTarget.LoadDump(path, cacheOptions)DataTarget.LoadDump(path, dataTargetOptions)
DataTarget.SetSymbolPath(path)DataTargetOptions.SymbolPaths
IFileLocator.FindElfImage(...)(removed — not downloaded)
IFileLocator.FindMachOImage(...)(removed — not downloaded)
CreateRuntime(dac, mismatch, verifySignature)CreateRuntime(dac, mismatch) + DataTargetOptions.VerifyDacSignature
new ClrObject(address, type)heap.GetObject(address)
Command / CommandOptionsSystem.Diagnostics.Process
CacheOptions.UseOSMemoryFeatures(removed — set DataTargetOptions.UseLockFreeMemoryMapReader = true for a speed boost if thread safety is not required)