From 547c6878a3810dd0aedb25e0c7be31ae2227fd86 Mon Sep 17 00:00:00 2001
From: vsadov <8218165+VSadov@users.noreply.github.com>
Date: Tue, 27 May 2025 17:20:18 -0700
Subject: [PATCH 1/7] API defined
---
.../ExceptionServices/ExceptionHandling.cs | 12 ++++++
.../System.Runtime/ref/System.Runtime.cs | 2 +
src/native/public/FatalErrorHandling.h | 38 +++++++++++++++++++
3 files changed, 52 insertions(+)
create mode 100644 src/native/public/FatalErrorHandling.h
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
index 116c94982f03d1..50932fab50e4a5 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
@@ -55,5 +55,17 @@ public static void RaiseAppDomainUnhandledExceptionEvent(object exception)
AppContext.OnUnhandledException(exception);
}
+
+ ///
+ /// .NET runtime is going to call `fatalErrorHandler` set by this method before its own
+ /// fatal error handling (creating .NET runtime-specific crash dump, etc.).
+ ///
+ /// If fatalErrorHandler is null
+ /// If a handler is already set
+ [System.CLSCompliantAttribute(false)]
+ public static unsafe void SetFatalErrorHandler(delegate* unmanaged fatalErrorHandler)
+ {
+
+ }
}
}
diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs
index 89a5c81e868e40..b892d498f395b4 100644
--- a/src/libraries/System.Runtime/ref/System.Runtime.cs
+++ b/src/libraries/System.Runtime/ref/System.Runtime.cs
@@ -14133,6 +14133,8 @@ public static partial class ExceptionHandling
{
public static void SetUnhandledExceptionHandler(System.Func handler) { }
public static void RaiseAppDomainUnhandledExceptionEvent(object exception) { }
+ [System.CLSCompliantAttribute(false)]
+ public unsafe static void SetFatalErrorHandler(delegate* unmanaged fatalErrorHandler) { }
}
public partial class FirstChanceExceptionEventArgs : System.EventArgs
{
diff --git a/src/native/public/FatalErrorHandling.h b/src/native/public/FatalErrorHandling.h
new file mode 100644
index 00000000000000..ea2b5928a1044d
--- /dev/null
+++ b/src/native/public/FatalErrorHandling.h
@@ -0,0 +1,38 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+//
+
+enum FatalErrorHandlerResult : int32_t
+{
+ RunDefaultHandler = 0,
+ SkipDefaultHandler = 1,
+};
+
+#if defined(_MSC_VER) && defined(_M_IX86)
+#define DOTNET_CALLCONV __stdcall
+#else
+#define DOTNET_CALLCONV
+#endif
+
+struct FatalErrorInfo
+{
+ size_t size; // size of the FatalErrorInfo instance
+ void* address; // code location correlated with the failure (i.e. location where FailFast was called)
+
+ // exception/signal information, if available
+ void* info; // Cast to PEXCEPTION_RECORD on Windows or siginfo_t* on non-Windows.
+ void* context; // Cast to PCONTEXT on Windows or ucontext_t* on non-Windows.
+
+ // An entry point for logging additional information about the crash.
+ // As runtime finds information suitable for logging, it will invoke pfnLogAction and pass the information in logString.
+ // The callback may be called multiple times.
+ // Combined, the logString will contain the same parts as in the console output of the default crash handler.
+ // The errorLog string will have UTF-8 encoding.
+ void (DOTNET_CALLCONV *pfnGetFatalErrorLog)(
+ FatalErrorInfo* errorData,
+ void (DOTNET_CALLCONV *pfnLogAction)(char8_t* logString, void *userContext),
+ void* userContext);
+
+ // More information can be exposed for querying in the future by adding
+ // entry points with similar pattern as in pfnGetFatalErrorLog
+};
From d5b741b1b244b602ffb3475ecacba9ae90519752 Mon Sep 17 00:00:00 2001
From: vsadov <8218165+VSadov@users.noreply.github.com>
Date: Tue, 27 May 2025 17:30:27 -0700
Subject: [PATCH 2/7] arg checks
---
.../System.Private.CoreLib/src/Resources/Strings.resx | 3 +++
.../System/Runtime/ExceptionServices/ExceptionHandling.cs | 8 ++++++++
2 files changed, 11 insertions(+)
diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
index 475d54061b4d0b..69396ce002e71f 100644
--- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
+++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
@@ -2581,6 +2581,9 @@
A handler for unhandled exceptions is already set.
+
+ A handler for fatal errors is already set.
+
Cannot restore context flow when it is not suppressed.
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
index 50932fab50e4a5..40464c32e5a8fa 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
@@ -8,6 +8,7 @@ namespace System.Runtime.ExceptionServices
public static class ExceptionHandling
{
private static Func? s_handler;
+ private static bool s_crashHandlerSet;
internal static bool IsHandledByGlobalHandler(Exception ex)
{
@@ -65,7 +66,14 @@ public static void RaiseAppDomainUnhandledExceptionEvent(object exception)
[System.CLSCompliantAttribute(false)]
public static unsafe void SetFatalErrorHandler(delegate* unmanaged fatalErrorHandler)
{
+ ArgumentNullException.ThrowIfNull(fatalErrorHandler);
+ if (Interlocked.CompareExchange(ref s_crashHandlerSet, true, false))
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_CannotRegisterSecondFatalHandler);
+ }
+
+ // set the handler here. (QCall)
}
}
}
From 48a04ab991739b20b2522abb0375049ee3aa12a2 Mon Sep 17 00:00:00 2001
From: vsadov <8218165+VSadov@users.noreply.github.com>
Date: Thu, 29 May 2025 12:23:09 -0700
Subject: [PATCH 3/7] move the declaration
---
.../ExceptionServices/ExceptionHandling.cs | 34 +++++++++----------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
index 40464c32e5a8fa..7c0054ac2573ea 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
@@ -40,23 +40,6 @@ public static void SetUnhandledExceptionHandler(Func handler)
}
}
- ///
- /// Raises the event.
- ///
- /// Exception to pass to event handlers.
- ///
- /// This method will raise the
- /// event and then return.
- ///
- /// It will not raise the the handler registered with .
- ///
- public static void RaiseAppDomainUnhandledExceptionEvent(object exception)
- {
- ArgumentNullException.ThrowIfNull(exception);
-
- AppContext.OnUnhandledException(exception);
- }
-
///
/// .NET runtime is going to call `fatalErrorHandler` set by this method before its own
/// fatal error handling (creating .NET runtime-specific crash dump, etc.).
@@ -75,5 +58,22 @@ public static unsafe void SetFatalErrorHandler(delegate* unmanaged
+ /// Raises the event.
+ ///
+ /// Exception to pass to event handlers.
+ ///
+ /// This method will raise the
+ /// event and then return.
+ ///
+ /// It will not raise the the handler registered with .
+ ///
+ public static void RaiseAppDomainUnhandledExceptionEvent(object exception)
+ {
+ ArgumentNullException.ThrowIfNull(exception);
+
+ AppContext.OnUnhandledException(exception);
+ }
}
}
From b967f3a05b994e22a53bd6832248241a1fab8fb3 Mon Sep 17 00:00:00 2001
From: vsadov <8218165+VSadov@users.noreply.github.com>
Date: Thu, 29 May 2025 13:06:50 -0700
Subject: [PATCH 4/7] added simple test
---
.../FatalErrorHandler/UnhandledException.cs | 30 +++++++++++++++++++
.../UnhandledException.csproj | 16 ++++++++++
2 files changed, 46 insertions(+)
create mode 100644 src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.cs
create mode 100644 src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.csproj
diff --git a/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.cs b/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.cs
new file mode 100644
index 00000000000000..ea1b5c98363e06
--- /dev/null
+++ b/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.cs
@@ -0,0 +1,30 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.ExceptionServices;
+using System.Threading;
+using System.Threading.Tasks;
+using TestLibrary;
+using Xunit;
+
+unsafe public class UnhandledException
+{
+ [UnmanagedCallersOnly]
+ static int Handler(int i, void* ptr)
+ {
+ Environment.Exit(100);
+
+ // unreachable
+ return 42;
+ }
+
+ public static int Main()
+ {
+ // set handler
+ ExceptionHandling.SetFatalErrorHandler((delegate* unmanaged)&Handler);
+
+ // throw
+ throw new Exception();
+ }
+}
diff --git a/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.csproj b/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.csproj
new file mode 100644
index 00000000000000..e3d0a6199f92d1
--- /dev/null
+++ b/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.csproj
@@ -0,0 +1,16 @@
+
+
+
+ true
+
+ false
+ true
+ 0
+
+
+
+
+
+
+
+
From ace0d9eb38308742be8f7b835d0972dd9156ca68 Mon Sep 17 00:00:00 2001
From: vsadov <8218165+VSadov@users.noreply.github.com>
Date: Thu, 29 May 2025 13:14:38 -0700
Subject: [PATCH 5/7] exclude the test on mono and NativeAOT
---
src/tests/issues.targets | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/tests/issues.targets b/src/tests/issues.targets
index 32a31908cb2e99..60eeb2b695c73f 100644
--- a/src/tests/issues.targets
+++ b/src/tests/issues.targets
@@ -1072,6 +1072,10 @@
Dynamic code generation is not supported on this platform
+
+
+ NYI on NativeAOT
+
@@ -1782,7 +1786,10 @@
https://github.com/dotnet/runtime/issues/98628
- NYI on Mono
+ NYI on Mono
+
+
+ NYI on Mono
From 73411e5cd26eb8bb04f8ab23ec01ccd64496b339 Mon Sep 17 00:00:00 2001
From: vsadov <8218165+VSadov@users.noreply.github.com>
Date: Tue, 3 Jun 2025 08:15:12 -0700
Subject: [PATCH 6/7] make simple test to pass
---
src/coreclr/classlibnative/bcltype/system.cpp | 11 ++++++++
src/coreclr/classlibnative/bcltype/system.h | 2 ++
src/coreclr/vm/eepolicy.cpp | 26 +++++++++++++++++++
src/coreclr/vm/eepolicy.h | 2 ++
src/coreclr/vm/qcallentrypoints.cpp | 1 +
.../ExceptionServices/ExceptionHandling.cs | 23 +++++++++++++---
.../FatalErrorHandler/UnhandledException.cs | 4 ++-
7 files changed, 64 insertions(+), 5 deletions(-)
diff --git a/src/coreclr/classlibnative/bcltype/system.cpp b/src/coreclr/classlibnative/bcltype/system.cpp
index 30b215f6c9d344..a628edefa421fd 100644
--- a/src/coreclr/classlibnative/bcltype/system.cpp
+++ b/src/coreclr/classlibnative/bcltype/system.cpp
@@ -32,6 +32,17 @@
#include
+extern "C" void QCALLTYPE ExceptionHandling_SetFatalErrorHandler(PVOID handler)
+{
+ QCALL_CONTRACT;
+
+ BEGIN_QCALL;
+
+ EEPolicy::SetFatalErrorHandler(handler);
+
+ END_QCALL;
+}
+
extern "C" VOID QCALLTYPE Environment_Exit(INT32 exitcode)
{
QCALL_CONTRACT;
diff --git a/src/coreclr/classlibnative/bcltype/system.h b/src/coreclr/classlibnative/bcltype/system.h
index 11b4939cc715ea..94139ac2001628 100644
--- a/src/coreclr/classlibnative/bcltype/system.h
+++ b/src/coreclr/classlibnative/bcltype/system.h
@@ -43,6 +43,8 @@ class SystemNative
static FCDECL0(FC_BOOL_RET, IsServerGC);
};
+extern "C" void QCALLTYPE ExceptionHandling_SetFatalErrorHandler(PVOID handler);
+
extern "C" void QCALLTYPE Environment_Exit(INT32 exitcode);
extern "C" void QCALLTYPE Environment_FailFast(QCall::StackCrawlMarkHandle mark, PCWSTR message, QCall::ObjectHandleOnStack exception, PCWSTR errorSource);
diff --git a/src/coreclr/vm/eepolicy.cpp b/src/coreclr/vm/eepolicy.cpp
index b3f4a568a6696f..b9a929e3ec8165 100644
--- a/src/coreclr/vm/eepolicy.cpp
+++ b/src/coreclr/vm/eepolicy.cpp
@@ -23,6 +23,17 @@
#include "eventtrace.h"
#undef ExitProcess
+using char8_t = unsigned char;
+
+#include "../native/public/FatalErrorHandling.h"
+
+FatalErrorHandlerResult (*g_fatalErrorHandler)(int32_t hresult, struct FatalErrorInfo* data);
+
+void EEPolicy::SetFatalErrorHandler(PVOID handler)
+{
+ g_fatalErrorHandler = (FatalErrorHandlerResult (*)(int32_t hresult, struct FatalErrorInfo* data))handler;
+}
+
void SafeExitProcess(UINT exitCode, ShutdownCompleteAction sca = SCA_ExitProcessWhenShutdownComplete)
{
STRESS_LOG2(LF_SYNC, LL_INFO10, "SafeExitProcess: exitCode = %d sca = %d\n", exitCode, sca);
@@ -801,6 +812,21 @@ int NOINLINE EEPolicy::HandleFatalError(UINT exitCode, UINT_PTR address, LPCWSTR
{
WRAPPER_NO_CONTRACT;
+ // TODO: VS this is probably not the place where the handler needs to be called, or not the only one place.
+ // Also handler nees various data passed. Just test that the handler is called fro now.
+
+ // We are going to call a handler, if set up.
+ // We shoulb not be in COOP mode whan calling random native code.
+ GCX_PREEMP_NO_DTOR();
+
+ if (g_fatalErrorHandler != NULL)
+ {
+ if (g_fatalErrorHandler(exitCode, NULL) == SkipDefaultHandler)
+ {
+ return -1;
+ }
+ }
+
// All of the code from here on out is robust to any failures in any API's that are called.
FAULT_NOT_FATAL();
diff --git a/src/coreclr/vm/eepolicy.h b/src/coreclr/vm/eepolicy.h
index e8f821c065ab6e..675f5bd7ee783d 100644
--- a/src/coreclr/vm/eepolicy.h
+++ b/src/coreclr/vm/eepolicy.h
@@ -38,6 +38,8 @@ class EEPolicy
static void DECLSPEC_NORETURN HandleFatalStackOverflow(EXCEPTION_POINTERS *pException, BOOL fSkipDebugger = FALSE);
+ static void SetFatalErrorHandler(PVOID handler);
+
private:
static void LogFatalError(UINT exitCode, UINT_PTR address, LPCWSTR pMessage, PEXCEPTION_POINTERS pExceptionInfo, LPCWSTR errorSource, LPCWSTR argExceptionString=NULL);
};
diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp
index ae3c91bc9c4b21..78235841a5e2a9 100644
--- a/src/coreclr/vm/qcallentrypoints.cpp
+++ b/src/coreclr/vm/qcallentrypoints.cpp
@@ -99,6 +99,7 @@ static const Entry s_QCall[] =
DllImportEntry(Delegate_Construct)
DllImportEntry(Delegate_FindMethodHandle)
DllImportEntry(Delegate_InternalEqualMethodHandles)
+ DllImportEntry(ExceptionHandling_SetFatalErrorHandler)
DllImportEntry(Environment_Exit)
DllImportEntry(Environment_FailFast)
DllImportEntry(Environment_GetProcessorCount)
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
index 7c0054ac2573ea..88c719ec600b11 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/ExceptionServices/ExceptionHandling.cs
@@ -1,14 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using System.Threading;
namespace System.Runtime.ExceptionServices
{
- public static class ExceptionHandling
+ public static partial class ExceptionHandling
{
private static Func? s_handler;
- private static bool s_crashHandlerSet;
+
+#if CORECLR
+ private static bool s_fatalHandlerSet;
+#endif
internal static bool IsHandledByGlobalHandler(Exception ex)
{
@@ -40,6 +46,11 @@ public static void SetUnhandledExceptionHandler(Func handler)
}
}
+#if CORECLR
+ [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ExceptionHandling_SetFatalErrorHandler")]
+ private static unsafe partial void _SetFatalErrorHandler(delegate* unmanaged fatalErrorHandler);
+#endif
+
///
/// .NET runtime is going to call `fatalErrorHandler` set by this method before its own
/// fatal error handling (creating .NET runtime-specific crash dump, etc.).
@@ -49,14 +60,18 @@ public static void SetUnhandledExceptionHandler(Func handler)
[System.CLSCompliantAttribute(false)]
public static unsafe void SetFatalErrorHandler(delegate* unmanaged fatalErrorHandler)
{
+#if CORECLR
ArgumentNullException.ThrowIfNull(fatalErrorHandler);
- if (Interlocked.CompareExchange(ref s_crashHandlerSet, true, false))
+ if (Interlocked.CompareExchange(ref s_fatalHandlerSet, true, false))
{
throw new InvalidOperationException(SR.InvalidOperation_CannotRegisterSecondFatalHandler);
}
- // set the handler here. (QCall)
+ _SetFatalErrorHandler(fatalErrorHandler);
+#else
+ throw new PlatformNotSupportedException();
+#endif
}
///
diff --git a/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.cs b/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.cs
index ea1b5c98363e06..4d24ae3ed8a403 100644
--- a/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.cs
+++ b/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.cs
@@ -25,6 +25,8 @@ public static int Main()
ExceptionHandling.SetFatalErrorHandler((delegate* unmanaged)&Handler);
// throw
- throw new Exception();
+ Environment.FailFast("hello");
+
+ return 42;
}
}
From 4200d52585abcd4a79c95b8d8cbe9cea6f8bf61c Mon Sep 17 00:00:00 2001
From: vsadov <8218165+VSadov@users.noreply.github.com>
Date: Wed, 4 Jun 2025 14:13:55 -0700
Subject: [PATCH 7/7] make GCC happy about char8_t
---
src/coreclr/vm/eepolicy.cpp | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/coreclr/vm/eepolicy.cpp b/src/coreclr/vm/eepolicy.cpp
index b9a929e3ec8165..861baa3ef9227a 100644
--- a/src/coreclr/vm/eepolicy.cpp
+++ b/src/coreclr/vm/eepolicy.cpp
@@ -23,8 +23,19 @@
#include "eventtrace.h"
#undef ExitProcess
+#if __cplusplus < 202002L
+#if defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wc++20-compat"
+#endif
+
using char8_t = unsigned char;
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+#endif
+
#include "../native/public/FatalErrorHandling.h"
FatalErrorHandlerResult (*g_fatalErrorHandler)(int32_t hresult, struct FatalErrorInfo* data);