dotnet / dotnet/runtime

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

Open
#133,087 5 comments 1 reaction 0 assignees View on GitHub
api-suggestion area-System.Diagnostics
Dominant language
C#
Stars
18.3k
Forks
5.6k
PR merge metrics
PR metrics pending

Description

### 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** (#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 (#119142) and now crashes under a debugger on .NET 10 (#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](https://github.com/dotnet/runtime/issues/101560#issuecomment-5506453172)):

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

```csharp
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 #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

```csharp
// 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:

```csharp
// 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 #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

- Is calling the managed `WriteCrashDump` from a `SetFatalErrorHandler` callback (native, possibly corrupted process) supported? Otherwise another option is to expose a native function pointer (as in #131959 for crash log function).
- While a fatal error handler is active, should on-demand dumps (this API *and* the diagnostics IPC dump command used by `dotnet-dump collect`) attach the stored crash context? (Offered in #101560; natural follow-up.)

### Risks

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

Contributor guide

Open the contributing guide

Research direction

Start with the existing ExceptionHandling APIs, SetUnhandledExceptionHandler, and the createdump integration described in the proposal. Resolve the open questions around fatal-error handlers and platform support, then define and validate WriteCrashDump, EnableDumpOnCrash, and DisableDumpOnCrash semantics, including dump paths, launch arguments, and errors.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
api, operating-systems
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.