Skip to content

Commit 271f5f1

Browse files
Report unhandled exceptions/FailFast in event log (#98152)
Fixes #73998. `EventReporter` is basically taken from eventreporter.cpp in the VM.
1 parent 8e2dcfa commit 271f5f1

File tree

7 files changed

+264
-12
lines changed

7 files changed

+264
-12
lines changed

src/coreclr/nativeaot/System.Private.CoreLib/src/System.Private.CoreLib.csproj

+10
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,22 @@
251251
</Compile>
252252
</ItemGroup>
253253
<ItemGroup Condition="'$(TargetsWindows)'=='true'">
254+
<Compile Include="System\EventReporter.cs" />
254255
<Compile Include="Internal\Runtime\FrozenObjectHeapManager.Windows.cs" />
255256
<Compile Include="System\Runtime\InteropServices\NativeLibrary.NativeAot.Windows.cs" />
256257
<Compile Include="System\Runtime\InteropServices\PInvokeMarshal.Windows.cs" />
257258
<Compile Include="$(CommonPath)\System\Runtime\InteropServices\BuiltInVariantExtensions.cs">
258259
<Link>System\Runtime\InteropServices\BuiltInVariantExtensions.cs</Link>
259260
</Compile>
261+
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegisterEventSource_IntPtr.cs">
262+
<Link>Interop\Windows\Advapi32\Interop.RegisterEventSource_IntPtr.cs</Link>
263+
</Compile>
264+
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.DeregisterEventSource.cs">
265+
<Link>Interop\Windows\Advapi32\Interop.DeregisterEventSource.cs</Link>
266+
</Compile>
267+
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.ReportEvent_IntPtr.cs">
268+
<Link>Common\Interop\Windows\Advapi32\Interop.ReportEvent_IntPtr.cs</Link>
269+
</Compile>
260270
<Compile Include="$(CommonPath)\Interop\Windows\Kernel32\Interop.IsDebuggerPresent.cs">
261271
<Link>Interop\Windows\Kernel32\Interop.IsDebuggerPresent.cs</Link>
262272
</Compile>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.IO;
6+
using System.Runtime;
7+
using System.Text;
8+
using System.Threading;
9+
10+
namespace System
11+
{
12+
internal class EventReporter
13+
{
14+
private readonly RhFailFastReason _eventType;
15+
private readonly StringBuilder _description = new StringBuilder();
16+
private bool _bufferFull;
17+
18+
public unsafe EventReporter(RhFailFastReason eventType)
19+
{
20+
_eventType = eventType;
21+
22+
string? processPath = Environment.ProcessPath;
23+
24+
_description.Append("Application: ");
25+
26+
// If we were able to get an app name.
27+
if (processPath != null)
28+
{
29+
// If app name has a '\', consider the part after that; otherwise consider whole name.
30+
_description.AppendLine(Path.GetFileName(processPath));
31+
}
32+
else
33+
{
34+
_description.AppendLine("unknown");
35+
}
36+
37+
_description.Append("CoreCLR Version: ");
38+
39+
byte* utf8version = RuntimeImports.RhGetRuntimeVersion(out int cbLength);
40+
_description.AppendLine(new string((sbyte*)utf8version));
41+
42+
switch (_eventType)
43+
{
44+
case RhFailFastReason.UnhandledException:
45+
case RhFailFastReason.UnhandledExceptionFromPInvoke:
46+
_description.AppendLine("Description: The process was terminated due to an unhandled exception.");
47+
break;
48+
case RhFailFastReason.EnvironmentFailFast:
49+
case RhFailFastReason.AssertionFailure:
50+
_description.AppendLine("Description: The application requested process termination through System.Environment.FailFast.");
51+
break;
52+
case RhFailFastReason.InternalError:
53+
_description.AppendLine("Description: The process was terminated due to an internal error in the .NET Runtime ");
54+
break;
55+
default:
56+
Debug.Fail($"Unknown {nameof(RhFailFastReason)}");
57+
break;
58+
}
59+
}
60+
61+
public void AddDescription(string s)
62+
{
63+
Debug.Assert(_eventType is RhFailFastReason.UnhandledException
64+
or RhFailFastReason.EnvironmentFailFast or RhFailFastReason.AssertionFailure
65+
or RhFailFastReason.UnhandledExceptionFromPInvoke or RhFailFastReason.InternalError);
66+
if (_eventType is RhFailFastReason.EnvironmentFailFast or RhFailFastReason.AssertionFailure)
67+
{
68+
_description.Append("Message: ");
69+
}
70+
else if (_eventType == RhFailFastReason.UnhandledException)
71+
{
72+
_description.Append("Exception Info: ");
73+
}
74+
_description.AppendLine(s);
75+
}
76+
77+
public void BeginStackTrace()
78+
{
79+
Debug.Assert(_eventType is RhFailFastReason.UnhandledException
80+
or RhFailFastReason.EnvironmentFailFast or RhFailFastReason.AssertionFailure
81+
or RhFailFastReason.UnhandledExceptionFromPInvoke);
82+
_description.AppendLine("Stack:");
83+
}
84+
85+
public void AddStackTrace(string s)
86+
{
87+
// The (approx.) maximum size that EventLog appears to allow.
88+
//
89+
// An event entry comprises of string to be written and event header information.
90+
// The total permissible length of the string and event header is 32K.
91+
const int MAX_SIZE_EVENTLOG_ENTRY_STRING = 0x7C62; // decimal 31842
92+
93+
// Continue to append to the buffer until we are full
94+
if (!_bufferFull)
95+
{
96+
_description.AppendLine(s);
97+
98+
// Truncate the buffer if we have exceeded the limit based upon the OS we are on
99+
if (_description.Length > MAX_SIZE_EVENTLOG_ENTRY_STRING)
100+
{
101+
// Load the truncation message
102+
string truncate = "\nThe remainder of the message was truncated.\n";
103+
104+
int truncCount = truncate.Length;
105+
106+
// Go back "truncCount" characters from the end of the string.
107+
int ext = MAX_SIZE_EVENTLOG_ENTRY_STRING - truncCount;
108+
109+
// Now look for a "\n" from the last position we got
110+
for (; ext > 0 && _description[ext] != '\n'; ext--) ;
111+
112+
// Truncate the string till our current position and append
113+
// the truncation message
114+
_description.Length = ext;
115+
116+
_description.Append(truncate);
117+
118+
// Set the flag that we are full - no point appending more stack details
119+
_bufferFull = true;
120+
}
121+
}
122+
}
123+
124+
public void Report()
125+
{
126+
uint eventID;
127+
switch (_eventType)
128+
{
129+
case RhFailFastReason.UnhandledException:
130+
case RhFailFastReason.UnhandledExceptionFromPInvoke:
131+
eventID = 1026;
132+
break;
133+
case RhFailFastReason.EnvironmentFailFast:
134+
case RhFailFastReason.AssertionFailure:
135+
eventID = 1025;
136+
break;
137+
case RhFailFastReason.InternalError:
138+
eventID = 1023;
139+
break;
140+
default:
141+
Debug.Fail("Invalid event type");
142+
eventID = 1023;
143+
break;
144+
}
145+
146+
if (_description.Length > 0)
147+
{
148+
ClrReportEvent(".NET Runtime",
149+
1 /* EVENTLOG_ERROR_TYPE */,
150+
0,
151+
eventID,
152+
_description.ToString()
153+
);
154+
}
155+
}
156+
157+
private static unsafe void ClrReportEvent(string eventSource, short type, ushort category, uint eventId, string message)
158+
{
159+
IntPtr handle = Interop.Advapi32.RegisterEventSource(
160+
null, // uses local computer
161+
eventSource);
162+
163+
if (handle == IntPtr.Zero)
164+
return;
165+
166+
fixed (char* pMessage = message)
167+
{
168+
Interop.Advapi32.ReportEvent(handle, type, category, eventId, null, 1, 0, (nint)(&pMessage), null);
169+
}
170+
171+
Interop.Advapi32.DeregisterEventSource(handle);
172+
}
173+
174+
private static byte s_once;
175+
176+
public static bool ShouldLogInEventLog
177+
{
178+
get
179+
{
180+
if (Interop.Kernel32.IsDebuggerPresent())
181+
return false;
182+
183+
if (s_once == 1 || Interlocked.Exchange(ref s_once, 1) == 1)
184+
return false;
185+
186+
return true;
187+
}
188+
}
189+
}
190+
}

src/coreclr/nativeaot/System.Private.CoreLib/src/System/RuntimeExceptionHelpers.cs

+21
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,27 @@ internal static unsafe void FailFast(string? message = null, Exception? exceptio
228228
Internal.Console.Error.WriteLine();
229229
}
230230

231+
#if TARGET_WINDOWS
232+
if (EventReporter.ShouldLogInEventLog)
233+
{
234+
var reporter = new EventReporter(reason);
235+
if (exception != null && reason is not RhFailFastReason.AssertionFailure)
236+
{
237+
reporter.AddDescription($"{exception.GetType()}: {exception.Message}");
238+
reporter.AddStackTrace(exception.StackTrace);
239+
}
240+
else
241+
{
242+
if (message != null)
243+
reporter.AddDescription(message);
244+
reporter.BeginStackTrace();
245+
reporter.AddStackTrace(new StackTrace().ToString());
246+
}
247+
248+
reporter.Report();
249+
}
250+
#endif
251+
231252
if (exception != null)
232253
{
233254
crashInfo.WriteException(exception);

src/coreclr/vm/eventreporter.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ void EventReporter::AddStackTrace(SString& s)
287287
COUNT_T curSize = m_Description.GetCount();
288288

289289
// Truncate the buffer if we have exceeded the limit based upon the OS we are on
290-
DWORD dwMaxSizeLimit = MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA;
290+
DWORD dwMaxSizeLimit = MAX_SIZE_EVENTLOG_ENTRY_STRING;
291291
if (curSize >= dwMaxSizeLimit)
292292
{
293293
// Load the truncation message

src/coreclr/vm/eventreporter.h

+4-11
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,11 @@
1515
#include "contract.h"
1616
#include "sstring.h"
1717

18-
// Maximum size for a string in event log entry
19-
#define MAX_SIZE_EVENTLOG_ENTRY_STRING 0x8000 // decimal 32768
20-
21-
// The (approx.) maximum size that Vista appears to allow. Post discussion with the OS event log team,
22-
// it has been identified that Vista has taken a breaking change in ReportEventW API implementation
23-
// without getting it publicly documented.
18+
// The (approx.) maximum size that EventLog appears to allow.
2419
//
25-
// An event entry comprises of string to be written and event header information. Prior to Vista,
26-
// 32K length strings were allowed and event header size was over it. Vista onwards, the total
27-
// permissible length of the string and event header became 32K, resulting in strings becoming
28-
// shorter in length. Hence, the change in size.
29-
#define MAX_SIZE_EVENTLOG_ENTRY_STRING_WINVISTA 0x7C62 // decimal 31842
20+
// An event entry comprises of string to be written and event header information.
21+
// The total permissible length of the string and event header is 32K.
22+
#define MAX_SIZE_EVENTLOG_ENTRY_STRING 0x7C62 // decimal 31842
3023

3124
class EventReporter
3225
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class Advapi32
10+
{
11+
[LibraryImport(Libraries.Advapi32, EntryPoint = "RegisterEventSourceW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
12+
internal static partial IntPtr RegisterEventSource(string lpUNCServerName, string lpSourceName);
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
internal static partial class Interop
8+
{
9+
internal static partial class Advapi32
10+
{
11+
[LibraryImport(Libraries.Advapi32, EntryPoint = "ReportEventW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)]
12+
[return: MarshalAs(UnmanagedType.Bool)]
13+
public static partial bool ReportEvent(
14+
IntPtr hEventLog,
15+
short wType,
16+
ushort wcategory,
17+
uint dwEventID,
18+
byte[] lpUserSid,
19+
short wNumStrings,
20+
int dwDataSize,
21+
IntPtr lpStrings,
22+
byte[] lpRawData);
23+
}
24+
}

0 commit comments

Comments
 (0)