Skip to content

Commit

Permalink
Report unhandled exceptions/FailFast in event log (#98152)
Browse files Browse the repository at this point in the history
Fixes #73998.

`EventReporter` is basically taken from eventreporter.cpp in the VM.
  • Loading branch information
MichalStrehovsky authored Feb 12, 2024
1 parent 8e2dcfa commit 271f5f1
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,22 @@
</Compile>
</ItemGroup>
<ItemGroup Condition="'$(TargetsWindows)'=='true'">
<Compile Include="System\EventReporter.cs" />
<Compile Include="Internal\Runtime\FrozenObjectHeapManager.Windows.cs" />
<Compile Include="System\Runtime\InteropServices\NativeLibrary.NativeAot.Windows.cs" />
<Compile Include="System\Runtime\InteropServices\PInvokeMarshal.Windows.cs" />
<Compile Include="$(CommonPath)\System\Runtime\InteropServices\BuiltInVariantExtensions.cs">
<Link>System\Runtime\InteropServices\BuiltInVariantExtensions.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegisterEventSource_IntPtr.cs">
<Link>Interop\Windows\Advapi32\Interop.RegisterEventSource_IntPtr.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.DeregisterEventSource.cs">
<Link>Interop\Windows\Advapi32\Interop.DeregisterEventSource.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.ReportEvent_IntPtr.cs">
<Link>Common\Interop\Windows\Advapi32\Interop.ReportEvent_IntPtr.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.IsDebuggerPresent.cs">
<Link>Interop\Windows\Kernel32\Interop.IsDebuggerPresent.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/eventreporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 4 additions & 11 deletions src/coreclr/vm/eventreporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}

0 comments on commit 271f5f1

Please sign in to comment.