Skip to content

[API Proposal]: Programmatic crash dump control: ExceptionHandling.WriteCrashDump / EnableDumpOnCrash #133087

Description

@xen2

Background and motivation

Today the runtime's crash dump generation (createdump) can only be controlled with environment variables (DOTNET_DbgEnableMiniDump, DOTNET_DbgMiniDumpType, DOTNET_DbgMiniDumpName) set before the process starts — the createdump command line is prebuilt at startup so the crash handler does no work. This leaves several scenarios uncovered:

  • A user-launched application cannot arm crash dumps for itself (Enable minidump creation directly from application #56135): a GUI app started from the shell has no cooperative parent to set the variables, and SetEnvironmentVariable at runtime is a no-op for this purpose.
  • An application cannot write a dump of itself on demand without the diagnostics IPC. The IPC route (DiagnosticsClient.WriteDump) requires a second process and a healthy diagnostics server in the target — the very thing that may be broken during a crash — and the resulting dump has no crash context (no exception stream on Windows, no crashing-thread information on Linux).
  • Applications work around this in unsupported ways. The common workaround for capturing native crashes — a vectored exception handler in managed code calling MiniDumpWriteDump — is explicitly unsupported ([API Proposal]: Calling UnmanagedCallersOnly method regardless of GC mode #119142) and now crashes under a debugger on .NET 10 (Regression in net10.0: vectored exception handler crashes under the debugger #133066).
  • After the dump, nothing user-visible happens. For interactive apps the crashed process is dead (and if it is a GUI app, cannot render); showing a crash-report UI or uploading the dump requires a fresh process, which today means shipping a separate pre-spawned monitor.

In the discussion on #101560, @noahfalk suggested splitting this into two complementary APIs (comment):

  • New BCL CreateDump() style API - flexible programatic control for dump creation at any point
  • New BCL ConfigureDumpOnCrash() style API - more limited options but covers more failure types

This proposal is exactly that pair, on the class that already hosts the crash-handling APIs (ExceptionHandling.SetUnhandledExceptionHandler / the upcoming SetFatalErrorHandler, #129543):

  1. WriteCrashDump — write a dump of the current process on demand, by launching createdump directly (no IPC). When the calling thread has an exception in flight (e.g. inside an AppDomain.UnhandledException handler), the dump records that exception and thread as the crash, so debuggers and analysis tools open it at the failure point.
  2. EnableDumpOnCrash / DisableDumpOnCrash — enable/disable/reconfigure the runtime's own on-crash dump at any time from managed code, overriding the environment. Because no managed code needs to run after the failure, this covers crash types no managed handler can (native threads, stack overflow, runtime corruption). An overload also specifies a program (e.g. a crash reporter) that createdump launches once the dump has been written, from a fresh process, closing the "show UI / upload from a dead process" gap.

API Proposal

namespace System.Runtime.ExceptionServices;

public static partial class ExceptionHandling
{
    // existing: SetUnhandledExceptionHandler, RaiseAppDomainUnhandledExceptionEvent

    /// Writes a dump of the current process by launching createdump directly (no diagnostics IPC);
    /// blocks until the dump is written. When the calling thread has an exception in flight, the
    /// dump records it as the crash (exception stream on Windows).
    public static void WriteCrashDump(string path, CrashDumpType type = CrashDumpType.WithHeap);

    /// Enables/changes the dump the runtime writes when the process crashes, overriding
    /// the DOTNET_DbgEnableMiniDump* environment variables. Callable at any time; last call wins.
    public static void EnableDumpOnCrash(CrashDumpType type = CrashDumpType.WithHeap,
        string? pathTemplate = null);

    /// Same, with a program createdump launches once the crash dump has been written. %f in the
    /// arguments is replaced with the dump path (plus the usual %p/%e/%h/%t specifiers).
    public static void EnableDumpOnCrash(CrashDumpType type, string? pathTemplate,
        string launchProgram, params string[]? launchArguments);

    /// Disables dump on process crash
    public static void DisableDumpOnCrash();
}

public enum CrashDumpType
{
    Normal = 1,     // same values as the existing DumpType in
    WithHeap = 2,   // Microsoft.Diagnostics.NETCore.Client and DOTNET_DbgMiniDumpType
    Triage = 3,
    Full = 4,
}

Note: I hesitated between a single ConfigureDumpOnCrash and EnableDumpOnCrash with overloads plus DisableDumpOnCrash. I didn't like the former much because it needs an enabled flag (or a nullable CrashDumpType? with null meaning disabled), which makes contradictory calls representable (e.g. disabled but with a launch program) and gives disabling an unclear call site (ConfigureDumpOnCrash(false, ...) / ConfigureDumpOnCrash(null)), while EnableDumpOnCrash/DisableDumpOnCrash are self-describing.

Semantics:

  • pathTemplate/path support the existing dump name specifiers (%p pid, %e executable name, %h hostname, %t timestamp).
  • EnableDumpOnCrash does all its work at call time, in a healthy context; nothing is added to what the crash handler does today.
  • The launch program can only be configured through the API, never from the environment — with the environment variables alone, a crash still only ever runs createdump itself. This avoids turning the knob into arbitrary-code-execution-on-crash for anyone who can set environment variables (the concern that applied to the env-var shape of Register a custom crash handler #95461). Precedents for the mechanism: Linux core_pattern pipes (but system-global, root-only) and Windows WerRegisterRuntimeExceptionModule (but native-DLL-only, Windows-only); this is the portable, per-process, in-app-registered equivalent.
  • The launched program only starts once the dump has been fully written, runs in a fresh process, and is not waited on.
  • Errors: WriteCrashDump throws IOException if the dump could not be written; all of these methods throw PlatformNotSupportedException where unsupported (NativeAOT, Mono, iOS/tvOS/wasm initially).

API Usage

// Game engine editor (user-launched GUI app, cannot use DOTNET_DbgEnableMiniDump):
// arm the runtime dumper and have it start our crash reporter after writing the dump.
ExceptionHandling.EnableDumpOnCrash(
    CrashDumpType.WithHeap,
    Path.Combine(crashDir, "editor-%p-%t.dmp"),
    "crashreporter.exe",
    "--dump", "%f", "--pid", "%p");

// On-demand dump with the in-flight exception recorded, e.g. before showing an error dialog:
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{
    ExceptionHandling.WriteCrashDump(Path.Combine(crashDir, "managed-%p-%t.dmp"), CrashDumpType.Normal);
    // ... report/upload, then let the process die
};

// CI/tools can turn dumps off for intentionally-crashing child scenarios:
ExceptionHandling.DisableDumpOnCrash();

Composition with SetFatalErrorHandler (#129543), once it ships: for fatal errors (native-thread faults, FailFast, runtime errors), EnableDumpOnCrash alone already produces the dump and launches the reporter with no managed code running after the failure — no handler needed. A fatal error handler adds value when the app wants to write the dump at a moment of its choosing or capture app-specific state first:

// Illustrative only — #129543 is in progress, and whether managed code may run in the fatal
// error handler is one of the open questions below (the alternative is a native function
// pointer exposing the same operation, see Open questions).
[UnmanagedCallersOnly]
static int OnFatalError(nint context)
{
    ExceptionHandling.WriteCrashDump(fatalDumpPath, CrashDumpType.WithHeap);
    // ... spawn/notify the reporter with fatalDumpPath ...
    return RunDefaultHandler;
}
ExceptionHandling.SetFatalErrorHandler(&OnFatalError);

Alternative Designs

  • runtimeconfig.json knobs for the arming half (the direction once suggested on Enable minidump creation directly from application #56135): solves the build-time case but not per-user dump paths (%LOCALAPPDATA%-style) or runtime decisions; could still be added later on top of the same internals.
  • A parameter object for the launch pair (CrashDumpLaunch { Program, Arguments }) instead of an overload: more extensible but more verbose/heavier; ProcessStartInfo is not reusable (wrong assembly, and most of its surface (environment, redirects) would not be honored).

Open Questions

Risks

  • The launch feature can execute on crash; it's API only and should never be configurable from env variable due to security risks.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions