[WIP]: Wire on-demand in-proc crash report generation into the fatal error handler API- #131414 - #131959
Draft
lateralusX wants to merge 55 commits into
Draft
Conversation
Implement the ExceptionHandling.SetFatalErrorHandler API for NativeAOT. The handler is invoked from RuntimeExceptionHelpers.FailFast before the runtime performs its default crash handling (crash dump + abort). - Add src/native/public/FatalErrorHandling.h defining the native FatalErrorInfo struct and FatalErrorHandlerResult enum - Wire RegisterFatalErrorHandler as a no-op for NativeAOT (handler pointer stored in managed s_fatalErrorHandler field) - Add crash log capture in FailFast alongside existing stderr output - Implement pfnGetFatalErrorLog callback via UnmanagedCallersOnly - SkipDefaultHandler exits via _Exit/ExitProcess instead of crash dump - Consolidate ExceptionHandling partials: MONO||CORECLR throws PNSE inline, eliminating per-runtime partial files - Add subprocess-based smoke tests validating handler invocation, SkipDefaultHandler/RunDefaultHandler, pfnGetFatalErrorLog callback, and API contract (null/double-set) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wire up the user-registered fatal error handler in the CoreCLR runtime. Read the managed ExceptionHandling.s_fatalErrorHandler static field via CoreLibBinder and invoke the handler after LogFatalError completes in both HandleFatalError and HandleFatalStackOverflow. If the handler returns SkipDefaultHandler, exit without crash dump. - Add ExceptionHandling class/field bindings to corelib.h - Enable s_fatalErrorHandler field and SetFatalErrorHandler for CoreCLR - Add crash log capture in PrintToStdErrA for pfnGetFatalErrorLog - Include public/FatalErrorHandling.h for shared type definitions - Fix test subprocess launch for CoreCLR (pass DLL path to corerun) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The SkipDefaultHandler path should terminate immediately without running atexit handlers, which can deadlock in a corrupted process. Replace the call to exit() (via Interop.Sys.Exit) with _exit() (via a new Interop.Sys._Exit P/Invoke) in the NativeAOT FailFast path, matching CoreCLR's native _exit() semantics. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use C99 _Exit() instead of _exit() to avoid unistd.h dependency - Add COR_E_FAILFAST to IsCrashExitCode for Windows CoreCLR - Suppress unused parameter warning in GetFatalErrorLogCallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On Windows, WatsonLastChance calls RaiseFailFastException which terminates the process before InvokeFatalErrorHandler is reached. Move the handler invocation before the Watson/debugger code path in both HandleFatalError and HandleFatalStackOverflow. In HandleFatalError, call LogInfoForFatalError directly first to populate the crash log buffer for the handler, then invoke the handler, then proceed with LogFatalError for ETW and Watson. Exclude FatalErrorHandlerTest from Mono runs since SetFatalErrorHandler throws PlatformNotSupportedException on Mono. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Split PrintToStdErrW to write crash log as UTF-8 regardless of console codepage, skip re-conversion when console is already CP_UTF8 - Extract AppendToCrashLog helper for crash log buffer management - Fix NativeAOT AppendToCrashLog to use TryGetBytes with truncation fallback instead of throwing on buffer overflow - Relax TestRunHandler exit code assertion to non-zero check - Remove COR_E_FAILFAST from IsCrashExitCode Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move fatal error handler implementations from managed [UnmanagedCallersOnly] methods to a native C++ shared library. This avoids managed allocations during fatal errors (which fail on Windows) and validates that the public FatalErrorHandling.h header is usable from third-party C++ code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Replace the sink-based redirection in util.cpp/util.hpp with a CrashInfoWriter struct local to eepolicy.cpp. EmitCrashInfo now accepts a writer parameter — stderr writer for LogInfoForFatalError, callback writer for GetFatalErrorLogCallback. util.hpp and util.cpp are unchanged from upstream/main. HandleFatalStackOverflow now invokes the fatal error handler before any stderr output, matching HandleFatalError's pattern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move InvokeFatalErrorHandler after the FaultingExceptionFrame setup and stack trace logging to avoid access violations on an exhausted stack. For stack overflow, stderr output cannot be suppressed but SkipDefaultHandler still skips Watson/crash dump. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Use SCA_ExitProcessWhenShutdownComplete for SkipDefaultHandler to avoid crash dump/Watson on the skip path. - Add DOTNET_CALLCONV to callback function pointer via FatalErrorLogAction typedef (fixes x86 calling convention mismatch). - Move FatalErrorHandling.h include earlier and consolidate CallbackState. - Wrap GetFatalErrorLog in try/catch for exception safety. - Hoist Encoder outside the fragment loop; use Span<byte> stackalloc and fixed statement for the callback pointer. - Remove unused SystemNative__Exit entrypoint. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add PAL_GetNativeExceptionPointers to retrieve siginfo_t/ucontext_t from thread-local storage set during signal handling. Use RAII holder (NativeExceptionPointerHolder) to manage TLS lifetime in common_signal_handler. Extract CreateFatalErrorInfo helper in eepolicy.cpp that calls the PAL export on Unix and passes EXCEPTION_RECORD/CONTEXT directly on Windows. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace CoreLibBinder field access with a native static set via ExceptionHandling_TrySetFatalErrorHandler QCall. This eliminates GCX_COOP and type loader interaction on the fatal error path, fixing the stack overflow AV on Windows CI. Make ExceptionHandling partial: CoreCLR uses QCall, NativeAOT keeps the managed IntPtr field. Fix Encoder.Convert flush semantics to avoid surrogate pair corruption at chunk boundaries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ad code - Change BOOL to CLR_BOOL for ExceptionHandling_TrySetFatalErrorHandler QCall - Add PALIMPORT/PALAPI decorators to PAL_GetNativeExceptionPointers definition - Remove incorrect assert in CreateFatalErrorInfo for non-signal paths - Remove dead SystemNative__Exit from pal_threading_wasi.c Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Refactor CallStackLogger to accept a CrashInfoWriter pointer so that managed stack traces are emitted through the writer abstraction rather than directly to stderr. This enables the fatal error handler callback to receive the same stack trace output as stderr. - Move CrashInfoWriter definition above CallStackLogger - Add Write(const char*)/Write(const WCHAR*) members to CrashInfoWriter - Pass writer through LogCallstackForLogWorker into CallStackLogger - Remove stderr-only guard in EmitCrashInfo Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…acOS On Apple platforms hardware faults are delivered through Mach exceptions rather than POSIX signals, so the Mach path did not stash the native exception pointers that FatalErrorInfo.info/context are populated from. As a result a managed access violation reached a user-registered fatal error handler with both pointers NULL. Share the NativeExceptionPointerHolder via seh.hpp (implemented on the exported PAL_SetNativeExceptionPointers) and use it around SEHProcessException in the Mach dispatch path so the fatal error handler receives the Mach thread state through FatalErrorInfo.context. info remains NULL on Apple platforms (the fault address is already available in FatalErrorInfo.address); document this contract in FatalErrorHandling.h. Add a managed access-violation test scenario with a platform-aware assertion (info is NULL on macOS, populated elsewhere). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the three generic void* property slots (address/info/context) with a
strongly-typed split between platform-agnostic and platform-specific state:
* Common core (crash-log entry point and crash address) is served directly
by each runtime's property getter.
* All platform-specific native exception state is forwarded to a single
GetFatalErrorPlatformProperty with no #ifdef in the forwarding switch.
CoreCLR: the Windows implementation reads typed EXCEPTION_RECORD/CONTEXT
statics captured from the exception pointers; on Unix/macOS the request is
forwarded to the PAL, which serves the property directly from its live
thread-local signal (Linux) / Mach (Apple) exception stash. This retires
PAL_GetNativeExceptionPointers (its sole non-PAL caller was eepolicy.cpp) in
favor of PAL_GetFatalErrorPlatformProperty.
NativeAOT: extract the platform-specific cases into a private
GetFatalErrorPlatformProperty helper for parity (behavior unchanged).
The property-getter out parameter is const void**: the values are pointers to
read-only crash state owned by the runtime and must not be mutated by the
handler.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- HandleFatalError's RunDefault path called LogInfoForFatalError directly and then LogFatalError (which calls it again), tripping the same-thread re-entrancy guard and printing a second, spurious crash log. Emit the crash log once. - EmitCrashInfo wrote the "Fatal error." header for every non-FailFast exit code, including stack overflow, whose default output is "Stack overflow.". Skip the header for COR_E_STACKOVERFLOW so the two paths agree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously the native signal/exception records captured for an unhandled hardware fault were tracked in a single per-thread slot. When a nested hardware fault was handled while an outer fault was still in flight (e.g. an exception filter that triggers and swallows its own fault during the outer fault's first pass), swallowing the inner fault cleared the shared slot and lost the outer fault's records. The outer fault then reached the fatal error handler with no platform records to surface. Store the captured records on each fault's ExInfo instead. RhThrowHwEx stackallocs a per-fault buffer (a two-pointer header followed by the raw siginfo_t/ucontext_t on Unix, or EXCEPTION_RECORD/CONTEXT on Windows), copies the transient signal-handler records into it, and publishes it as the pending unhandled hand-off. Because the buffer lives on the throwing frame (which is never unwound before the unhandled FailFast) and is keyed to the fault's ExInfo, a nested fault handled in between cannot clobber it. The capture+publish happens before GetClasslibException so that uncatchable faults (access violation, illegal/privileged instruction, in-page error), which FailFast directly out of the classlib GetRuntimeException and never return, still surface their records. Catchable faults that dispatch and are caught clear the hand-off in the second pass (gated on HardwareFault so a nested software throw does not clear an in-flight hardware fault's records); faults that go unhandled re-publish from their ExInfo in UnhandledExceptionFailFastViaClasslib, which is correct even when nested faults ran in between. RhpCaptureHardwareExceptionRecordsToBuffer takes the buffer length and asserts it is at least RhpGetHardwareExceptionRecordsBufferSize. Adds a NativeAOT nested-hardware-fault regression test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The per-fault buffer laid out siginfo_t/ucontext_t (Unix) and EXCEPTION_RECORD/CONTEXT (Windows) at running byte offsets. Because sizeof(siginfo_t) and sizeof(EXCEPTION_RECORD) are not multiples of the alignment required by the following record, the ucontext/CONTEXT copy landed at a misaligned address. ucontext (Apple mcontext NEON state) and CONTEXT (x64/arm64) require 16-byte alignment, so the struct copy and any later typed access through the header pointers were misaligned (UB, and a compiler may emit aligned SIMD stores). Place each record at an ALIGN_UP-ed address for its own alignment, grow RhpGetHardwareExceptionRecordsBufferSize by the worst-case padding, and assert the incoming buffer is at least pointer-aligned for the header. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the heap-allocated string?[] backing s_crashLogFragments with an inline InlineArray16<string?>, moving the 16 slots into the already-rooted static storage and removing a persistent heap object and an indirection. Delete the now-unused bespoke InlineArray16Strings struct in favor of the shared generic. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Preserve outer fault's native exception pointers across nested faults - Make crash-context storage thread-local in eepolicy - Assign explicit values to FatalErrorProperty enumerators - Reorder qcall entry and fix a stale test comment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Read the fatal error handler pointer with a volatile load on both runtimes - Mark unused parameters in the native test callbacks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rtable function-pointer cast, resx ordering Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…and output emission during stack overflow
… memory efficiency and eliminating truncation risks
…iling thread invokes the handler, preventing reentrancy and blocking other threads during process termination.
…dler # Conflicts: # src/coreclr/vm/eepolicy.cpp
Rename the public header to use C-style casing, stage it with the native host artifacts, and include it in the app host packages. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0aba7fe1-771f-44ec-9d5c-7b552c759f7a
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 299682bb-b7ba-48bb-839f-4ebf3fb4cab3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 299682bb-b7ba-48bb-839f-4ebf3fb4cab3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 299682bb-b7ba-48bb-839f-4ebf3fb4cab3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 299682bb-b7ba-48bb-839f-4ebf3fb4cab3
…lter and streamline exception handling logic
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 299682bb-b7ba-48bb-839f-4ebf3fb4cab3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 299682bb-b7ba-48bb-839f-4ebf3fb4cab3
Windows server GC worker threads (and other native utility threads) have no managed Thread object, so a genuinely-unhandled native fault on them never reaches the fatal error handler through the vectored-exception path, which gates on a managed Thread. Hook the fatal handler at the top-level unhandled filter for NativeThreadUnhandledException so these threads are covered. - Add EEPolicy::HandleFatalErrorForNativeException(PEXCEPTION_POINTERS) and invoke it from InternalUnhandledExceptionFilter_Worker on Windows. - Make the losing-thread wait in InvokeFatalErrorHandler safe for threads with no managed Thread object. - Add a cross-platform regression test that faults on a raw OS thread with no managed Thread object, gated to the platforms where it is meaningful. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 299682bb-b7ba-48bb-839f-4ebf3fb4cab3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Azure Pipelines: Successfully started running 4 pipeline(s). 12 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
There was a problem hiding this comment.
Pull request overview
Wires ExceptionHandling.SetFatalErrorHandler through CoreCLR and NativeAOT fatal-error paths so a native handler can query crash properties (address, platform records) and (on supported platforms) request in-proc diagnostic report data on-demand via a new fatal_error_handling.h public header. Adds a new baseservices test suite plus a native helper library to validate the end-to-end behavior.
Changes:
- Introduces
ExceptionHandling.SetFatalErrorHandler(delegate* unmanaged<int, void*, int>)as a public API and connects it to runtime fatal error handling in CoreCLR and NativeAOT. - Adds a public native header (
fatal_error_handling.h) and ships it via host packaging so third-party native code can consume the property-getter contract. - Adds a new subprocess-based test suite (
FatalErrorHandler) validating handler invocation, crash-log callback, platform record exposure, and diagnostic-data behavior.
Reviewed changes
Copilot reviewed 40 out of 40 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tests/baseservices/exceptions/FatalErrorHandler/FatalErrorHandlerTest.csproj | New standalone test project (process-isolated) for fatal error handler scenarios. |
| src/tests/baseservices/exceptions/FatalErrorHandler/FatalErrorHandlerTest.cs | Test harness that launches crashing child processes and validates stderr markers/output. |
| src/tests/baseservices/exceptions/FatalErrorHandler/FatalErrorHandlerNative.cpp | Native test helper exporting handler callbacks and fault triggers; validates header usability. |
| src/tests/baseservices/exceptions/FatalErrorHandler/CMakeLists.txt | Builds/installs the native helper library for the test. |
| src/native/public/fatal_error_handling.h | New public native contract for fatal-error handler properties and on-demand diagnostic data. |
| src/native/corehost/nethost/CMakeLists.txt | Installs fatal_error_handling.h into the corehost include set. |
| src/installer/pkg/sfx/Microsoft.NETCore.App/Microsoft.NETCore.App.Host.sfxproj | Ships fatal_error_handling.h as a native runtime asset in the host pack. |
| src/installer/pkg/projects/Microsoft.NETCore.DotNetAppHost/Microsoft.NETCore.DotNetAppHost.pkgproj | Ships fatal_error_handling.h with the apphost package. |
| src/libraries/System.Runtime/ref/System.Runtime.cs | Adds the new public ExceptionHandling.SetFatalErrorHandler ref surface. |
| src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs | Implements SetFatalErrorHandler and makes ExceptionHandling partial for runtime-specific backing. |
| src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.CoreCLR.cs | CoreCLR QCall implementation stub for TrySetFatalErrorHandler. |
| src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj | Includes the new CoreCLR-specific ExceptionHandling.CoreCLR.cs file. |
| src/libraries/System.Private.CoreLib/src/Resources/Strings.resx | Adds SR string for “handler already set” error. |
| src/coreclr/vm/qcallentrypoints.cpp | Adds QCall entry for ExceptionHandling_TrySetFatalErrorHandler. |
| src/coreclr/vm/comutilnative.h | Declares new QCall for setting the fatal error handler. |
| src/coreclr/vm/comutilnative.cpp | Implements QCall to publish handler pointer and (when enabled) initialize crash report support. |
| src/coreclr/vm/exceptionhandling.cpp | Routes unmanaged-corrupted-state exceptions through the fatal handler with live exception info. |
| src/coreclr/vm/excep.cpp | Invokes fatal handler for certain native-thread unhandled exceptions (Windows). |
| src/coreclr/vm/eepolicy.h | Adds fatal-handler state and native-exception helper entrypoints. |
| src/coreclr/vm/eepolicy.cpp | Adds crash-info replay plumbing, property getter, handler serialization, and diagnostic-data provider hook. |
| src/coreclr/vm/util.cpp | Adds asserts guarding null inputs to stderr printing helpers. |
| src/coreclr/vm/ceemain.cpp | Registers PAL callback for native fatal exceptions (Unix). |
| src/coreclr/pal/inc/pal.h | Adds PAL callback typedefs + setter for native-fatal exception notification. |
| src/coreclr/pal/src/thread/process.cpp | Stores and invokes the registered native-fatal callback. |
| src/coreclr/pal/src/include/pal/process.h | Declares PROCInvokeFatalErrorHandlerForNativeException. |
| src/coreclr/pal/src/exception/signal.cpp | Calls the native-fatal callback for unhandled Unix hardware signals and abort. |
| src/coreclr/pal/src/exception/machexception.cpp | Calls the native-fatal callback for Mach-dispatched fatal exceptions. |
| src/coreclr/debug/crashreport/inproccrashreporter.h | Makes crash-report context pointer const void*. |
| src/coreclr/debug/crashreport/inproccrashreporter.cpp | Propagates const void* crash context across in-proc crash reporter call paths. |
| src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.NativeAot.cs | NativeAOT backing storage + native registration for fatal handler pointer. |
| src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs | Adds NativeAOT imports for registering/acquiring fatal-error handler ownership. |
| src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs | Builds crash-log text once, exposes property getter/log callback, and invokes handler during FailFast. |
| src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Runtime/MethodTable.Runtime.cs | Updates call to new GetRuntimeException signature with faulting IP. |
| src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj | Includes the new NativeAOT ExceptionHandling.NativeAot.cs file. |
| src/coreclr/nativeaot/Test.CoreLib/src/System/RuntimeExceptionHelpers.cs | Updates test corelib export signature to include faulting IP parameter. |
| src/coreclr/nativeaot/Test.CoreLib/src/Internal/Runtime/MethodTable.Runtime.cs | Updates test corelib call to new GetRuntimeException signature. |
| src/coreclr/nativeaot/Runtime/unix/HardwareExceptions.cpp | Invokes user fatal handler for genuinely-unmanaged Unix faults that weren’t translated to managed exceptions. |
| src/coreclr/nativeaot/Runtime/EHHelpers.cpp | Implements native fatal-handler invocation + property getter and registers/serializes ownership. |
| src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/ExceptionHandling.cs | Updates runtime-base call sites to the new GetRuntimeException(ExceptionIDs, IntPtr) export shape. |
| src/libraries/Common/src/Interop/Windows/Kernel32/Interop.TerminateProcess.cs | Removes an unused using System;. |
Contributor
|
Tagging subscribers to this area: @agocke |
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
WIP:
Based off #129543, will be rebased on that when landed.
Summary
Builds on the ExceptionHandling.SetFatalErrorHandler API (#129543) to expose the in-proc crash reporter as an on-demand diagnostic-data source that a registered native fatal error handler can invoke.
A handler can now request FEP_DiagnosticDataFunc through the property getter and stream a JSON or Log crash report back through its own sink, from within the fatal error callback — independently of whether the in-proc reporter is configured to replace createdump.
Testing
Extends the existing baseservices/exceptions/FatalErrorHandler suite.