From c4a2a6f02dd05ec37fbf16f1b313d503ace78818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 8 Feb 2024 13:17:40 +0100 Subject: [PATCH 1/3] Report unhandled exceptions/FailFast in event log Fixes #73998. `EventReporter` is basically taken from eventreporter.cpp in the VM. --- .../src/System.Private.CoreLib.csproj | 10 + .../src/System/EventReporter.cs | 194 ++++++++++++++++++ .../src/System/RuntimeExceptionHelpers.cs | 21 ++ .../Interop.RegisterEventSource_IntPtr.cs | 14 ++ .../Advapi32/Interop.ReportEvent_IntPtr.cs | 24 +++ 5 files changed, 263 insertions(+) create mode 100644 src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs create mode 100644 src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegisterEventSource_IntPtr.cs create mode 100644 src/libraries/Common/src/Interop/Windows/Advapi32/Interop.ReportEvent_IntPtr.cs diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj index 4f7702c821c20..4ca91458c70e7 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj @@ -251,12 +251,22 @@ + System\Runtime\InteropServices\BuiltInVariantExtensions.cs + + Interop\Windows\Advapi32\Interop.RegisterEventSource_IntPtr.cs + + + Interop\Windows\Advapi32\Interop.DeregisterEventSource.cs + + + Common\Interop\Windows\Advapi32\Interop.ReportEvent_IntPtr.cs + Interop\Windows\Kernel32\Interop.IsDebuggerPresent.cs diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs new file mode 100644 index 0000000000000..2b342de2739ee --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs @@ -0,0 +1,194 @@ +// 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.IO; +using System.Runtime; +using System.Text; +using System.Threading; + +namespace System +{ + internal class EventReporter + { + private readonly RhFailFastReason _eventType; + private readonly StringBuilder _description = new StringBuilder(); + private bool _bufferFull; + + public unsafe EventReporter(RhFailFastReason eventType) + { + _eventType = eventType; + + string? processPath = Environment.ProcessPath; + + _description.Append("Application: "); + + // If we were able to get an app name. + if (processPath != null) + { + // If app name has a '\', consider the part after that; otherwise consider whole name. + _description.AppendLine(Path.GetFileName(processPath)); + } + else + { + _description.AppendLine("unknown"); + } + + _description.Append("CoreCLR Version: "); + + byte* utf8version = RuntimeImports.RhGetRuntimeVersion(out int cbLength); + _description.AppendLine(new string((sbyte*)utf8version)); + + switch (_eventType) + { + case RhFailFastReason.UnhandledException: + case RhFailFastReason.UnhandledExceptionFromPInvoke: + _description.AppendLine("Description: The process was terminated due to an unhandled exception."); + break; + case RhFailFastReason.EnvironmentFailFast: + case RhFailFastReason.AssertionFailure: + _description.AppendLine("Description: The application requested process termination through System.Environment.FailFast."); + break; + case RhFailFastReason.InternalError: + _description.AppendLine("Description: The process was terminated due to an internal error in the .NET Runtime "); + break; + default: + Debug.Fail($"Unknown {nameof(RhFailFastReason)}"); + break; + } + } + + public void AddDescription(string s) + { + Debug.Assert(_eventType is RhFailFastReason.UnhandledException + or RhFailFastReason.EnvironmentFailFast or RhFailFastReason.AssertionFailure + or RhFailFastReason.UnhandledExceptionFromPInvoke or RhFailFastReason.InternalError); + if (_eventType is RhFailFastReason.EnvironmentFailFast or RhFailFastReason.AssertionFailure) + { + _description.Append("Message: "); + } + else if (_eventType == RhFailFastReason.UnhandledException) + { + _description.Append("Exception Info: "); + } + _description.AppendLine(s); + } + + public void BeginStackTrace() + { + Debug.Assert(_eventType is RhFailFastReason.UnhandledException + or RhFailFastReason.EnvironmentFailFast or RhFailFastReason.AssertionFailure + or RhFailFastReason.UnhandledExceptionFromPInvoke); + _description.AppendLine("Stack:"); + } + + public void AddStackTrace(string s) + { + // The (approx.) maximum size that Vista appears to allow. Post discussion with the OS event log team, + // it has been identified that Vista has taken a breaking change in ReportEventW API implementation + // without getting it publicly documented. + // + // An event entry comprises of string to be written and event header information. Prior to Vista, + // 32K length strings were allowed and event header size was over it. Vista onwards, the total + // permissible length of the string and event header became 32K, resulting in strings becoming + // shorter in length. Hence, the change in size. + const int MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA = 0x7C62; // decimal 31842 + + // Continue to append to the buffer until we are full + if (!_bufferFull) + { + _description.AppendLine(s); + + // Truncate the buffer if we have exceeded the limit based upon the OS we are on + if (_description.Length > MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA) + { + // Load the truncation message + string truncate = "\nThe remainder of the message was truncated.\n"; + + int truncCount = truncate.Length; + + // Go back "truncCount" characters from the end of the string. + int ext = MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA - truncCount; + + // Now look for a "\n" from the last position we got + for (; ext > 0 && _description[ext] != '\n'; ext--) ; + + // Truncate the string till our current position and append + // the truncation message + _description.Length = ext; + + _description.Append(truncate); + + // Set the flag that we are full - no point appending more stack details + _bufferFull = true; + } + } + } + + public void Report() + { + uint eventID; + switch (_eventType) + { + case RhFailFastReason.UnhandledException: + case RhFailFastReason.UnhandledExceptionFromPInvoke: + eventID = 1026; + break; + case RhFailFastReason.EnvironmentFailFast: + case RhFailFastReason.AssertionFailure: + eventID = 1025; + break; + case RhFailFastReason.InternalError: + eventID = 1023; + break; + default: + Debug.Fail("Invalid event type"); + eventID = 1023; + break; + } + + if (_description.Length > 0) + { + ClrReportEvent(".NET Runtime", + 1 /* EVENTLOG_ERROR_TYPE */, + 0, + eventID, + _description.ToString() + ); + } + } + + private static unsafe void ClrReportEvent(string eventSource, short type, ushort category, uint eventId, string message) + { + IntPtr handle = Interop.Advapi32.RegisterEventSource( + null, // uses local computer + eventSource); + + if (handle == IntPtr.Zero) + return; + + fixed (char* pMessage = message) + { + Interop.Advapi32.ReportEvent(handle, type, category, eventId, null, 1, 0, (nint)(&pMessage), null); + } + + Interop.Advapi32.DeregisterEventSource(handle); + } + + private static byte s_once; + + public static bool ShouldLogInEventLog + { + get + { + if (Interop.Kernel32.IsDebuggerPresent()) + return false; + + if (s_once == 1 || Interlocked.Exchange(ref s_once, 1) == 1) + return false; + + return true; + } + } + } +} diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs index 427f75014ec56..db07c76cb54a2 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs @@ -196,6 +196,27 @@ internal static unsafe void FailFast(string? message = null, Exception? exceptio bool minimalFailFast = (exception == PreallocatedOutOfMemoryException.Instance); if (!minimalFailFast) { +#if TARGET_WINDOWS + if (EventReporter.ShouldLogInEventLog) + { + var reporter = new EventReporter(reason); + if (exception != null && reason is not RhFailFastReason.AssertionFailure) + { + reporter.AddDescription($"{exception.GetType()}: {exception.Message}"); + reporter.AddStackTrace(exception.StackTrace); + } + else + { + if (message != null) + reporter.AddDescription(message); + reporter.BeginStackTrace(); + reporter.AddStackTrace(new StackTrace().ToString()); + } + + reporter.Report(); + } +#endif + Internal.Console.Error.Write(((exception == null) || (reason is RhFailFastReason.EnvironmentFailFast or RhFailFastReason.AssertionFailure)) ? "Process terminated. " : "Unhandled exception. "); diff --git a/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegisterEventSource_IntPtr.cs b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegisterEventSource_IntPtr.cs new file mode 100644 index 0000000000000..66b58f06e7b5a --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegisterEventSource_IntPtr.cs @@ -0,0 +1,14 @@ +// 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; + +internal static partial class Interop +{ + internal static partial class Advapi32 + { + [LibraryImport(Libraries.Advapi32, EntryPoint = "RegisterEventSourceW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + internal static partial IntPtr RegisterEventSource(string lpUNCServerName, string lpSourceName); + } +} diff --git a/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.ReportEvent_IntPtr.cs b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.ReportEvent_IntPtr.cs new file mode 100644 index 0000000000000..d605a21a4c923 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.ReportEvent_IntPtr.cs @@ -0,0 +1,24 @@ +// 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; + +internal static partial class Interop +{ + internal static partial class Advapi32 + { + [LibraryImport(Libraries.Advapi32, EntryPoint = "ReportEventW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + [return: MarshalAs(UnmanagedType.Bool)] + public static partial bool ReportEvent( + IntPtr hEventLog, + short wType, + ushort wcategory, + uint dwEventID, + byte[] lpUserSid, + short wNumStrings, + int dwDataSize, + IntPtr lpStrings, + byte[] lpRawData); + } +} From 39e89103e5579c32c6b39da47327fe50e649f11e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 8 Feb 2024 14:08:08 +0100 Subject: [PATCH 2/3] FB --- .../src/System/EventReporter.cs | 10 ++--- .../src/System/RuntimeExceptionHelpers.cs | 42 +++++++++---------- src/coreclr/vm/eventreporter.h | 10 ++--- 3 files changed, 27 insertions(+), 35 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs index 2b342de2739ee..cdbcd6434ae73 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs @@ -84,14 +84,10 @@ or RhFailFastReason.EnvironmentFailFast or RhFailFastReason.AssertionFailure public void AddStackTrace(string s) { - // The (approx.) maximum size that Vista appears to allow. Post discussion with the OS event log team, - // it has been identified that Vista has taken a breaking change in ReportEventW API implementation - // without getting it publicly documented. + // The (approx.) maximum size that EventLog appears to allow. // - // An event entry comprises of string to be written and event header information. Prior to Vista, - // 32K length strings were allowed and event header size was over it. Vista onwards, the total - // permissible length of the string and event header became 32K, resulting in strings becoming - // shorter in length. Hence, the change in size. + // An event entry comprises of string to be written and event header information. + // The total permissible length of the string and event header is 32K. const int MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA = 0x7C62; // decimal 31842 // Continue to append to the buffer until we are full diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs index db07c76cb54a2..0ed5f375cc27c 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs @@ -196,27 +196,6 @@ internal static unsafe void FailFast(string? message = null, Exception? exceptio bool minimalFailFast = (exception == PreallocatedOutOfMemoryException.Instance); if (!minimalFailFast) { -#if TARGET_WINDOWS - if (EventReporter.ShouldLogInEventLog) - { - var reporter = new EventReporter(reason); - if (exception != null && reason is not RhFailFastReason.AssertionFailure) - { - reporter.AddDescription($"{exception.GetType()}: {exception.Message}"); - reporter.AddStackTrace(exception.StackTrace); - } - else - { - if (message != null) - reporter.AddDescription(message); - reporter.BeginStackTrace(); - reporter.AddStackTrace(new StackTrace().ToString()); - } - - reporter.Report(); - } -#endif - Internal.Console.Error.Write(((exception == null) || (reason is RhFailFastReason.EnvironmentFailFast or RhFailFastReason.AssertionFailure)) ? "Process terminated. " : "Unhandled exception. "); @@ -249,6 +228,27 @@ internal static unsafe void FailFast(string? message = null, Exception? exceptio Internal.Console.Error.WriteLine(); } +#if TARGET_WINDOWS + if (EventReporter.ShouldLogInEventLog) + { + var reporter = new EventReporter(reason); + if (exception != null && reason is not RhFailFastReason.AssertionFailure) + { + reporter.AddDescription($"{exception.GetType()}: {exception.Message}"); + reporter.AddStackTrace(exception.StackTrace); + } + else + { + if (message != null) + reporter.AddDescription(message); + reporter.BeginStackTrace(); + reporter.AddStackTrace(new StackTrace().ToString()); + } + + reporter.Report(); + } +#endif + if (exception != null) { crashInfo.WriteException(exception); diff --git a/src/coreclr/vm/eventreporter.h b/src/coreclr/vm/eventreporter.h index 9b600e76cd4ae..a2b389d95c0e9 100644 --- a/src/coreclr/vm/eventreporter.h +++ b/src/coreclr/vm/eventreporter.h @@ -18,14 +18,10 @@ // Maximum size for a string in event log entry #define MAX_SIZE_EVENTLOG_ENTRY_STRING 0x8000 // decimal 32768 -// The (approx.) maximum size that Vista appears to allow. Post discussion with the OS event log team, -// it has been identified that Vista has taken a breaking change in ReportEventW API implementation -// without getting it publicly documented. +// The (approx.) maximum size that EventLog appears to allow. // -// An event entry comprises of string to be written and event header information. Prior to Vista, -// 32K length strings were allowed and event header size was over it. Vista onwards, the total -// permissible length of the string and event header became 32K, resulting in strings becoming -// shorter in length. Hence, the change in size. +// An event entry comprises of string to be written and event header information. +// The total permissible length of the string and event header is 32K. #define MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA 0x7C62 // decimal 31842 class EventReporter From d929fd8c4ca7427000a7e6c1fbc8e45d329c3a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 12 Feb 2024 09:05:02 +0100 Subject: [PATCH 3/3] FB --- .../System.Private.CoreLib/src/System/EventReporter.cs | 6 +++--- src/coreclr/vm/eventreporter.cpp | 2 +- src/coreclr/vm/eventreporter.h | 5 +---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs index cdbcd6434ae73..efb46025dbf5b 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs @@ -88,7 +88,7 @@ public void AddStackTrace(string s) // // An event entry comprises of string to be written and event header information. // The total permissible length of the string and event header is 32K. - const int MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA = 0x7C62; // decimal 31842 + const int MAX_SIZE_EVENTLOG_ENTRY_STRING = 0x7C62; // decimal 31842 // Continue to append to the buffer until we are full if (!_bufferFull) @@ -96,7 +96,7 @@ public void AddStackTrace(string s) _description.AppendLine(s); // Truncate the buffer if we have exceeded the limit based upon the OS we are on - if (_description.Length > MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA) + if (_description.Length > MAX_SIZE_EVENTLOG_ENTRY_STRING) { // Load the truncation message string truncate = "\nThe remainder of the message was truncated.\n"; @@ -104,7 +104,7 @@ public void AddStackTrace(string s) int truncCount = truncate.Length; // Go back "truncCount" characters from the end of the string. - int ext = MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA - truncCount; + int ext = MAX_SIZE_EVENTLOG_ENTRY_STRING - truncCount; // Now look for a "\n" from the last position we got for (; ext > 0 && _description[ext] != '\n'; ext--) ; diff --git a/src/coreclr/vm/eventreporter.cpp b/src/coreclr/vm/eventreporter.cpp index 2065ed5ea3204..614c95bfbcce9 100644 --- a/src/coreclr/vm/eventreporter.cpp +++ b/src/coreclr/vm/eventreporter.cpp @@ -287,7 +287,7 @@ void EventReporter::AddStackTrace(SString& s) COUNT_T curSize = m_Description.GetCount(); // Truncate the buffer if we have exceeded the limit based upon the OS we are on - DWORD dwMaxSizeLimit = MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA; + DWORD dwMaxSizeLimit = MAX_SIZE_EVENTLOG_ENTRY_STRING; if (curSize >= dwMaxSizeLimit) { // Load the truncation message diff --git a/src/coreclr/vm/eventreporter.h b/src/coreclr/vm/eventreporter.h index a2b389d95c0e9..dced671f6db98 100644 --- a/src/coreclr/vm/eventreporter.h +++ b/src/coreclr/vm/eventreporter.h @@ -15,14 +15,11 @@ #include "contract.h" #include "sstring.h" -// Maximum size for a string in event log entry -#define MAX_SIZE_EVENTLOG_ENTRY_STRING 0x8000 // decimal 32768 - // The (approx.) maximum size that EventLog appears to allow. // // An event entry comprises of string to be written and event header information. // The total permissible length of the string and event header is 32K. -#define MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA 0x7C62 // decimal 31842 +#define MAX_SIZE_EVENTLOG_ENTRY_STRING 0x7C62 // decimal 31842 class EventReporter {