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