1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 
5 namespace System.Runtime
6 {
7     using System;
8     using System.Collections.ObjectModel;
9     using System.Diagnostics;
10     using System.Globalization;
11     using System.Reflection;
12     using System.Runtime.CompilerServices;
13     using System.Runtime.Diagnostics;
14     using System.Runtime.Interop;
15     using System.Runtime.Versioning;
16     using System.Security;
17 
18     class ExceptionTrace
19     {
20         const ushort FailFastEventLogCategory = 6;
21         string eventSourceName;
22         readonly EtwDiagnosticTrace diagnosticTrace;
23 
ExceptionTrace(string eventSourceName, EtwDiagnosticTrace diagnosticTrace)24         public ExceptionTrace(string eventSourceName, EtwDiagnosticTrace diagnosticTrace)
25         {
26             Fx.Assert(diagnosticTrace != null, "'diagnosticTrace' MUST NOT be NULL.");
27 
28             this.eventSourceName = eventSourceName;
29             this.diagnosticTrace = diagnosticTrace;
30         }
31 
AsInformation(Exception exception)32         public void AsInformation(Exception exception)
33         {
34             //Traces an informational trace message
35             TraceCore.HandledException(this.diagnosticTrace, exception != null ? exception.ToString() : string.Empty, exception);
36         }
37 
AsWarning(Exception exception)38         public void AsWarning(Exception exception)
39         {
40             //Traces a warning trace message
41             TraceCore.HandledExceptionWarning(this.diagnosticTrace, exception != null ? exception.ToString() : string.Empty, exception);
42         }
43 
AsError(Exception exception)44         public Exception AsError(Exception exception)
45         {
46             // AggregateExceptions are automatically unwrapped.
47             AggregateException aggregateException = exception as AggregateException;
48             if (aggregateException != null)
49             {
50                 return AsError<Exception>(aggregateException);
51             }
52 
53             // TargetInvocationExceptions are automatically unwrapped.
54             TargetInvocationException targetInvocationException = exception as TargetInvocationException;
55             if (targetInvocationException != null && targetInvocationException.InnerException != null)
56             {
57                 return AsError(targetInvocationException.InnerException);
58             }
59 
60             return TraceException<Exception>(exception);
61         }
62 
AsError(Exception exception, string eventSource)63         public Exception AsError(Exception exception, string eventSource)
64         {
65             // AggregateExceptions are automatically unwrapped.
66             AggregateException aggregateException = exception as AggregateException;
67             if (aggregateException != null)
68             {
69                 return AsError<Exception>(aggregateException, eventSource);
70             }
71 
72             // TargetInvocationExceptions are automatically unwrapped.
73             TargetInvocationException targetInvocationException = exception as TargetInvocationException;
74             if (targetInvocationException != null && targetInvocationException.InnerException != null)
75             {
76                 return AsError(targetInvocationException.InnerException, eventSource);
77             }
78 
79             return TraceException<Exception>(exception, eventSource);
80         }
81 
AsError(TargetInvocationException targetInvocationException, string eventSource)82         public Exception AsError(TargetInvocationException targetInvocationException, string eventSource)
83         {
84             Fx.Assert(targetInvocationException != null, "targetInvocationException cannot be null.");
85 
86             // If targetInvocationException contains any fatal exceptions, return it directly
87             // without tracing it or any inner exceptions.
88             if (Fx.IsFatal(targetInvocationException))
89             {
90                 return targetInvocationException;
91             }
92 
93             // A non-null inner exception could require further unwrapping in AsError.
94             Exception innerException = targetInvocationException.InnerException;
95             if (innerException != null)
96             {
97                 return AsError(innerException, eventSource);
98             }
99 
100             // A null inner exception is unlikely but possible.
101             // In this case, trace and return the targetInvocationException itself.
102             return TraceException<Exception>(targetInvocationException, eventSource);
103         }
104 
AsError(AggregateException aggregateException)105         public Exception AsError<TPreferredException>(AggregateException aggregateException)
106         {
107             return AsError<TPreferredException>(aggregateException, this.eventSourceName);
108         }
109 
110         /// <summary>
111         /// Extracts the first inner exception of type <typeparamref name="TPreferredException"/>
112         /// from the <see cref="AggregateException"/> if one is present.
113         /// </summary>
114         /// <remarks>
115         /// If no <typeparamref name="TPreferredException"/> inner exception is present, this
116         /// method returns the first inner exception.   All inner exceptions will be traced,
117         /// including the one returned.   The containing <paramref name="aggregateException"/>
118         /// will not be traced unless there are no inner exceptions.
119         /// </remarks>
120         /// <typeparam name="TPreferredException">The preferred type of inner exception to extract.
121         /// Use <c>typeof(Exception)</c> to extract the first exception regardless of type.</typeparam>
122         /// <param name="aggregateException">The <see cref="AggregateException"/> to examine.</param>
123         /// <param name="eventSource">The event source to trace.</param>
124         /// <returns>The extracted exception.  It will not be <c>null</c>
125         /// but it may not be of type <typeparamref name="TPreferredException"/>.</returns>
AsError(AggregateException aggregateException, string eventSource)126         public Exception AsError<TPreferredException>(AggregateException aggregateException, string eventSource)
127         {
128             Fx.Assert(aggregateException != null, "aggregateException cannot be null.");
129 
130             // If aggregateException contains any fatal exceptions, return it directly
131             // without tracing it or any inner exceptions.
132             if (Fx.IsFatal(aggregateException))
133             {
134                 return aggregateException;
135             }
136 
137             // Collapse possibly nested graph into a flat list.
138             // Empty inner exception list is unlikely but possible via public api.
139             ReadOnlyCollection<Exception> innerExceptions = aggregateException.Flatten().InnerExceptions;
140             if (innerExceptions.Count == 0)
141             {
142                 return TraceException(aggregateException, eventSource);
143             }
144 
145             // Find the first inner exception, giving precedence to TPreferredException
146             Exception favoredException = null;
147             foreach (Exception nextInnerException in innerExceptions)
148             {
149                 // AggregateException may wrap TargetInvocationException, so unwrap those as well
150                 TargetInvocationException targetInvocationException = nextInnerException as TargetInvocationException;
151 
152                 Exception innerException = (targetInvocationException != null && targetInvocationException.InnerException != null)
153                                                 ? targetInvocationException.InnerException
154                                                 : nextInnerException;
155 
156                 if (innerException is TPreferredException && favoredException == null)
157                 {
158                     favoredException = innerException;
159                 }
160 
161                 // All inner exceptions are traced
162                 TraceException<Exception>(innerException, eventSource);
163             }
164 
165             if (favoredException == null)
166             {
167                 Fx.Assert(innerExceptions.Count > 0, "InnerException.Count is known to be > 0 here.");
168                 favoredException = innerExceptions[0];
169             }
170 
171             return favoredException;
172         }
173 
Argument(string paramName, string message)174         public ArgumentException Argument(string paramName, string message)
175         {
176             return TraceException<ArgumentException>(new ArgumentException(message, paramName));
177         }
178 
ArgumentNull(string paramName)179         public ArgumentNullException ArgumentNull(string paramName)
180         {
181             return TraceException<ArgumentNullException>(new ArgumentNullException(paramName));
182         }
183 
ArgumentNull(string paramName, string message)184         public ArgumentNullException ArgumentNull(string paramName, string message)
185         {
186             return TraceException<ArgumentNullException>(new ArgumentNullException(paramName, message));
187         }
188 
ArgumentNullOrEmpty(string paramName)189         public ArgumentException ArgumentNullOrEmpty(string paramName)
190         {
191             return this.Argument(paramName, InternalSR.ArgumentNullOrEmpty(paramName));
192         }
193 
ArgumentOutOfRange(string paramName, object actualValue, string message)194         public ArgumentOutOfRangeException ArgumentOutOfRange(string paramName, object actualValue, string message)
195         {
196             return TraceException<ArgumentOutOfRangeException>(new ArgumentOutOfRangeException(paramName, actualValue, message));
197         }
198 
199         // When throwing ObjectDisposedException, it is highly recommended that you use this ctor
200         // [C#]
201         // public ObjectDisposedException(string objectName, string message);
202         // And provide null for objectName but meaningful and relevant message for message.
203         // It is recommended because end user really does not care or can do anything on the disposed object, commonly an internal or private object.
ObjectDisposed(string message)204         public ObjectDisposedException ObjectDisposed(string message)
205         {
206             // pass in null, not disposedObject.GetType().FullName as per the above guideline
207             return TraceException<ObjectDisposedException>(new ObjectDisposedException(null, message));
208         }
209 
TraceUnhandledException(Exception exception)210         public void TraceUnhandledException(Exception exception)
211         {
212             TraceCore.UnhandledException(this.diagnosticTrace, exception != null ? exception.ToString() : string.Empty, exception);
213         }
214 
TraceHandledException(Exception exception, TraceEventType traceEventType)215         public void TraceHandledException(Exception exception, TraceEventType traceEventType)
216         {
217             switch (traceEventType)
218             {
219                 case TraceEventType.Error:
220                     if (TraceCore.HandledExceptionErrorIsEnabled(this.diagnosticTrace))
221                     {
222                         TraceCore.HandledExceptionError(this.diagnosticTrace, exception != null ? exception.ToString() : string.Empty, exception);
223                     }
224                     break;
225                 case TraceEventType.Warning:
226                     if (TraceCore.HandledExceptionWarningIsEnabled(this.diagnosticTrace))
227                     {
228                         TraceCore.HandledExceptionWarning(this.diagnosticTrace, exception != null ? exception.ToString() : string.Empty, exception);
229                     }
230                     break;
231                 case TraceEventType.Verbose:
232                     if (TraceCore.HandledExceptionVerboseIsEnabled(this.diagnosticTrace))
233                     {
234                         TraceCore.HandledExceptionVerbose(this.diagnosticTrace, exception != null ? exception.ToString() : string.Empty, exception);
235                     }
236                     break;
237                 default:
238                     if (TraceCore.HandledExceptionIsEnabled(this.diagnosticTrace))
239                     {
240                         TraceCore.HandledException(this.diagnosticTrace, exception != null ? exception.ToString() : string.Empty, exception);
241                     }
242                     break;
243             }
244         }
245 
TraceEtwException(Exception exception, TraceEventType eventType)246         public void TraceEtwException(Exception exception, TraceEventType eventType)
247         {
248             switch (eventType)
249             {
250                 case TraceEventType.Error:
251                 case TraceEventType.Warning:
252                     if (TraceCore.ThrowingEtwExceptionIsEnabled(this.diagnosticTrace))
253                     {
254                         TraceCore.ThrowingEtwException(this.diagnosticTrace, this.eventSourceName, exception != null ? exception.ToString() : string.Empty, exception);
255                     }
256                     break;
257                 case TraceEventType.Critical:
258                     if (TraceCore.EtwUnhandledExceptionIsEnabled(this.diagnosticTrace))
259                     {
260                         TraceCore.EtwUnhandledException(this.diagnosticTrace, exception != null ? exception.ToString() : string.Empty, exception);
261                     }
262                     break;
263                 default:
264                     if (TraceCore.ThrowingEtwExceptionVerboseIsEnabled(this.diagnosticTrace))
265                     {
266                         TraceCore.ThrowingEtwExceptionVerbose(this.diagnosticTrace, this.eventSourceName, exception != null ? exception.ToString() : string.Empty, exception);
267                     }
268                     break;
269             }
270         }
271 
272         TException TraceException<TException>(TException exception)
273             where TException : Exception
274         {
275             return TraceException<TException>(exception, this.eventSourceName);
276         }
277 
278         [ResourceConsumption(ResourceScope.Process)]
279         [Fx.Tag.SecurityNote(Critical = "Calls 'System.Runtime.Interop.UnsafeNativeMethods.IsDebuggerPresent()' which is a P/Invoke method",
280             Safe = "Does not leak any resource, needed for debugging")]
281         [SecuritySafeCritical]
282         TException TraceException<TException>(TException exception, string eventSource)
283             where TException : Exception
284         {
285             if (TraceCore.ThrowingExceptionIsEnabled(this.diagnosticTrace))
286             {
287                 TraceCore.ThrowingException(this.diagnosticTrace, eventSource, exception != null ? exception.ToString() : string.Empty, exception);
288             }
289 
290             BreakOnException(exception);
291 
292             return exception;
293         }
294 
295         [Fx.Tag.SecurityNote(Critical = "Calls into critical method UnsafeNativeMethods.IsDebuggerPresent and UnsafeNativeMethods.DebugBreak",
296         Safe = "Safe because it's a no-op in retail builds.")]
297         [SecuritySafeCritical]
BreakOnException(Exception exception)298         void BreakOnException(Exception exception)
299         {
300 #if DEBUG
301             if (Fx.BreakOnExceptionTypes != null)
302             {
303                 foreach (Type breakType in Fx.BreakOnExceptionTypes)
304                 {
305                     if (breakType.IsAssignableFrom(exception.GetType()))
306                     {
307                         // This is intended to "crash" the process so that a debugger can be attached.  If a managed
308                         // debugger is already attached, it will already be able to hook these exceptions.  We don't
309                         // want to simulate an unmanaged crash (DebugBreak) in that case.
310                         if (!Debugger.IsAttached && !UnsafeNativeMethods.IsDebuggerPresent())
311                         {
312                             UnsafeNativeMethods.DebugBreak();
313                         }
314                     }
315                 }
316             }
317 #endif
318         }
319 
320         [MethodImpl(MethodImplOptions.NoInlining)]
TraceFailFast(string message)321         internal void TraceFailFast(string message)
322         {
323             EventLogger logger = null;
324 #pragma warning disable 618
325             logger = new EventLogger(this.eventSourceName, this.diagnosticTrace);
326 #pragma warning restore 618
327             TraceFailFast(message, logger);
328         }
329 
330         // Generate an event Log entry for failfast purposes
331         // To force a Watson on a dev machine, do the following:
332         // 1. Set \HKLM\SOFTWARE\Microsoft\PCHealth\ErrorReporting ForceQueueMode = 0
333         // 2. In the command environment, set COMPLUS_DbgJitDebugLaunchSetting=0
334         [MethodImpl(MethodImplOptions.NoInlining)]
TraceFailFast(string message, EventLogger logger)335         internal void TraceFailFast(string message, EventLogger logger)
336         {
337             if (logger != null)
338             {
339                 try
340                 {
341                     string stackTrace = null;
342                     try
343                     {
344                         stackTrace = new StackTrace().ToString();
345                     }
346                     catch (Exception exception)
347                     {
348                         stackTrace = exception.Message;
349                         if (Fx.IsFatal(exception))
350                         {
351                             throw;
352                         }
353                     }
354                     finally
355                     {
356                         logger.LogEvent(TraceEventType.Critical,
357                             FailFastEventLogCategory,
358                             (uint)System.Runtime.Diagnostics.EventLogEventId.FailFast,
359                             message,
360                             stackTrace);
361                     }
362                 }
363                 catch (Exception ex)
364                 {
365                     logger.LogEvent(TraceEventType.Critical,
366                         FailFastEventLogCategory,
367                         (uint)System.Runtime.Diagnostics.EventLogEventId.FailFastException,
368                         ex.ToString());
369                     if (Fx.IsFatal(ex))
370                     {
371                         throw;
372                     }
373                 }
374             }
375         }
376     }
377 }
378