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..861baa3ef9227a 100644 --- a/src/coreclr/vm/eepolicy.cpp +++ b/src/coreclr/vm/eepolicy.cpp @@ -23,6 +23,28 @@ #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); + +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 +823,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/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 116c94982f03d1..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,21 @@ // 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; +#if CORECLR + private static bool s_fatalHandlerSet; +#endif + internal static bool IsHandledByGlobalHandler(Exception ex) { return s_handler?.Invoke(ex) == true; @@ -39,6 +46,34 @@ 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.). + /// + /// If fatalErrorHandler is null + /// If a handler is already set + [System.CLSCompliantAttribute(false)] + public static unsafe void SetFatalErrorHandler(delegate* unmanaged fatalErrorHandler) + { +#if CORECLR + ArgumentNullException.ThrowIfNull(fatalErrorHandler); + + if (Interlocked.CompareExchange(ref s_fatalHandlerSet, true, false)) + { + throw new InvalidOperationException(SR.InvalidOperation_CannotRegisterSecondFatalHandler); + } + + _SetFatalErrorHandler(fatalErrorHandler); +#else + throw new PlatformNotSupportedException(); +#endif + } + /// /// Raises the event. /// 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 +}; diff --git a/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.cs b/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.cs new file mode 100644 index 00000000000000..4d24ae3ed8a403 --- /dev/null +++ b/src/tests/baseservices/exceptions/FatalErrorHandler/UnhandledException.cs @@ -0,0 +1,32 @@ +// 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 + Environment.FailFast("hello"); + + return 42; + } +} 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 + + + + + + + + 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