From 271f5f1893e4bc977c67c8002dedcfb56ddcd051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 13 Feb 2024 04:14:21 +0900 Subject: [PATCH] Report unhandled exceptions/FailFast in event log (#98152) Fixes #73998. `EventReporter` is basically taken from eventreporter.cpp in the VM. --- .../src/System.Private.CoreLib.csproj | 10 + .../src/System/EventReporter.cs | 190 ++++++++++++++++++ .../src/System/RuntimeExceptionHelpers.cs | 21 ++ src/coreclr/vm/eventreporter.cpp | 2 +- src/coreclr/vm/eventreporter.h | 15 +- .../Interop.RegisterEventSource_IntPtr.cs | 14 ++ .../Advapi32/Interop.ReportEvent_IntPtr.cs | 24 +++ 7 files changed, 264 insertions(+), 12 deletions(-) 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..efb46025dbf5b --- /dev/null +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/EventReporter.cs @@ -0,0 +1,190 @@ +// 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 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. + const int MAX_SIZE_EVENTLOG_ENTRY_STRING = 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) + { + // 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 - 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..0ed5f375cc27c 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs @@ -228,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.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 9b600e76cd4ae..dced671f6db98 100644 --- a/src/coreclr/vm/eventreporter.h +++ b/src/coreclr/vm/eventreporter.h @@ -15,18 +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 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. -#define MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA 0x7C62 // decimal 31842 +// 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 0x7C62 // decimal 31842 class EventReporter { 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); + } +}