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 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.Runtime;
8 using System.Runtime.CompilerServices;
9 using System.Runtime.InteropServices;
10 using System.Threading;
11 using Internal.DeveloperExperience;
12 using Internal.Runtime.Augments;
13 
14 namespace System
15 {
16     internal class PreallocatedOutOfMemoryException
17     {
18         public static OutOfMemoryException Instance { get; private set; }
19 
20         // Eagerly preallocate instance of out of memory exception to avoid infinite recursion once we run out of memory
Initialize()21         internal static void Initialize()
22         {
23             Instance = new OutOfMemoryException(message: null);  // Cannot call the nullary constructor as that triggers non-trivial resource manager logic.
24         }
25     }
26 
27     public class RuntimeExceptionHelpers
28     {
29         //------------------------------------------------------------------------------------------------------------
30         // @TODO: this function is related to throwing exceptions out of Rtm. If we did not have to throw
31         // out of Rtm, then we would note have to have the code below to get a classlib exception object given
32         // an exception id, or the special functions to back up the MDIL THROW_* instructions, or the allocation
33         // failure helper. If we could move to a world where we never throw out of Rtm, perhaps by moving parts
34         // of Rtm that do need to throw out to Bartok- or Binder-generated functions, then we could remove all of this.
35         //------------------------------------------------------------------------------------------------------------
36 
37         // This is the classlib-provided "get exception" function that will be invoked whenever the runtime
38         // needs to throw an exception back to a method in a non-runtime module. The classlib is expected
39         // to convert every code in the ExceptionIDs enum to an exception object.
40         [RuntimeExport("GetRuntimeException")]
GetRuntimeException(ExceptionIDs id)41         public static Exception GetRuntimeException(ExceptionIDs id)
42         {
43             // This method is called by the runtime's EH dispatch code and is not allowed to leak exceptions
44             // back into the dispatcher.
45             try
46             {
47                 // @TODO: this function should return pre-allocated exception objects, either frozen in the image
48                 // or preallocated during DllMain(). In particular, this function will be called when out of memory,
49                 // and failure to create an exception will result in infinite recursion and therefore a stack overflow.
50                 switch (id)
51                 {
52                     case ExceptionIDs.OutOfMemory:
53                         return PreallocatedOutOfMemoryException.Instance;
54 
55                     case ExceptionIDs.Arithmetic:
56                         return new ArithmeticException();
57 
58                     case ExceptionIDs.ArrayTypeMismatch:
59                         return new ArrayTypeMismatchException();
60 
61                     case ExceptionIDs.DivideByZero:
62                         return new DivideByZeroException();
63 
64                     case ExceptionIDs.IndexOutOfRange:
65                         return new IndexOutOfRangeException();
66 
67                     case ExceptionIDs.InvalidCast:
68                         return new InvalidCastException();
69 
70                     case ExceptionIDs.Overflow:
71                         return new OverflowException();
72 
73                     case ExceptionIDs.NullReference:
74                         return new NullReferenceException();
75 
76                     case ExceptionIDs.AccessViolation:
77                         FailFast("Access Violation: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. The application will be terminated since this platform does not support throwing an AccessViolationException.");
78                         return null;
79 
80                     case ExceptionIDs.DataMisaligned:
81                         return new DataMisalignedException();
82 
83                     default:
84                         FailFast("The runtime requires an exception for a case that this class library does not understand.");
85                         return null;
86                 }
87             }
88             catch
89             {
90                 return null; // returning null will cause the runtime to FailFast via the class library.
91             }
92         }
93 
94         public enum RhFailFastReason
95         {
96             Unknown = 0,
97             InternalError = 1,                                   // "Runtime internal error"
98             UnhandledException_ExceptionDispatchNotAllowed = 2,  // "Unhandled exception: no handler found before escaping a finally clause or other fail-fast scope."
99             UnhandledException_CallerDidNotHandle = 3,           // "Unhandled exception: no handler found in calling method."
100             ClassLibDidNotTranslateExceptionID = 4,              // "Unable to translate failure into a classlib-specific exception object."
101             IllegalNativeCallableEntry = 5,                      // "Invalid Program: attempted to call a NativeCallable method from runtime-typesafe code."
102             PN_UnhandledException = 6,                           // ProjectN: "Unhandled exception: a managed exception was not handled before reaching unmanaged code"
103             PN_UnhandledExceptionFromPInvoke = 7,                // ProjectN: "Unhandled exception: an unmanaged exception was thrown out of a managed-to-native transition."
104             Max
105         }
106 
GetStringForFailFastReason(RhFailFastReason reason)107         private static string GetStringForFailFastReason(RhFailFastReason reason)
108         {
109             switch (reason)
110             {
111                 case RhFailFastReason.InternalError:
112                     return "Runtime internal error";
113                 case RhFailFastReason.UnhandledException_ExceptionDispatchNotAllowed:
114                     return "Unhandled exception: no handler found before escaping a finally clause or other fail-fast scope.";
115                 case RhFailFastReason.UnhandledException_CallerDidNotHandle:
116                     return "Unhandled exception: no handler found in calling method.";
117                 case RhFailFastReason.ClassLibDidNotTranslateExceptionID:
118                     return "Unable to translate failure into a classlib-specific exception object.";
119                 case RhFailFastReason.IllegalNativeCallableEntry:
120                     return "Invalid Program: attempted to call a NativeCallable method from runtime-typesafe code.";
121                 case RhFailFastReason.PN_UnhandledException:
122                     return "Unhandled exception: a managed exception was not handled before reaching unmanaged code.";
123                 case RhFailFastReason.PN_UnhandledExceptionFromPInvoke:
124                     return "Unhandled exception: an unmanaged exception was thrown out of a managed-to-native transition.";
125                 default:
126                     return "Unknown reason.";
127             }
128         }
129 
130 
FailFast(String message)131         public static void FailFast(String message)
132         {
133             FailFast(message, null, RhFailFastReason.Unknown, IntPtr.Zero, IntPtr.Zero);
134         }
135 
FailFast(string message, Exception exception)136         public static unsafe void FailFast(string message, Exception exception)
137         {
138             FailFast(message, exception, RhFailFastReason.Unknown, IntPtr.Zero, IntPtr.Zero);
139         }
140 
141         // Used to report exceptions that *logically* go unhandled in the Fx code.  For example, an
142         // exception that escapes from a ThreadPool workitem, or from a void-returning async method.
ReportUnhandledException(Exception exception)143         public static void ReportUnhandledException(Exception exception)
144         {
145             // ReportUnhandledError will also call this in APPX scenarios,
146             // but WinRT can failfast before we get another chance
147             // (in APPX scenarios, this one will get overwritten by the one with the CCW pointer)
148             GenerateExceptionInformationForDump(exception, IntPtr.Zero);
149 
150 #if ENABLE_WINRT
151             // If possible report the exception to GEH, if not fail fast.
152             WinRTInteropCallbacks callbacks = WinRTInterop.UnsafeCallbacks;
153             if (callbacks == null || !callbacks.ReportUnhandledError(exception))
154                 FailFast(GetStringForFailFastReason(RhFailFastReason.PN_UnhandledException), exception);
155 #else
156             FailFast(GetStringForFailFastReason(RhFailFastReason.PN_UnhandledException), exception);
157 #endif
158         }
159 
160         // This is the classlib-provided fail-fast function that will be invoked whenever the runtime
161         // needs to cause the process to exit. It is the classlib's opprotunity to customize the
162         // termination behavior in whatever way necessary.
163         [RuntimeExport("FailFast")]
RuntimeFailFast(RhFailFastReason reason, Exception exception, IntPtr pExAddress, IntPtr pExContext)164         public static void RuntimeFailFast(RhFailFastReason reason, Exception exception, IntPtr pExAddress, IntPtr pExContext)
165         {
166             // This method is called by the runtime's EH dispatch code and is not allowed to leak exceptions
167             // back into the dispatcher.
168             try
169             {
170                 if (!SafeToPerformRichExceptionSupport)
171                     return;
172 
173                 // Avoid complex processing and allocations if we are already in failfast or out of memory.
174                 // We do not set InFailFast.Value here, because we want rich diagnostics in the FailFast
175                 // call below and reentrancy is not possible for this method (all exceptions are ignored).
176                 bool minimalFailFast = InFailFast.Value || (exception is OutOfMemoryException);
177                 string failFastMessage = "";
178 
179                 if (!minimalFailFast)
180                 {
181                     if ((reason == RhFailFastReason.PN_UnhandledException) && (exception != null))
182                     {
183                         Debug.WriteLine("Unhandled Exception: " + exception.ToString());
184                     }
185 
186                     failFastMessage = String.Format("Runtime-generated FailFast: ({0}): {1}{2}",
187                         reason.ToString(),  // Explicit call to ToString() to avoid MissingMetadataException inside String.Format()
188                         GetStringForFailFastReason(reason),
189                         exception != null ? " [exception object available]" : "");
190                 }
191 
192                 FailFast(failFastMessage, exception, reason, pExAddress, pExContext);
193             }
194             catch
195             {
196                 // Returning from this callback will cause the runtime to FailFast without involving the class
197                 // library.
198             }
199         }
200 
FailFast(string message, Exception exception, RhFailFastReason reason, IntPtr pExAddress, IntPtr pExContext)201         internal static void FailFast(string message, Exception exception, RhFailFastReason reason, IntPtr pExAddress, IntPtr pExContext)
202         {
203             // If this a recursive call to FailFast, avoid all unnecessary and complex actitivy the second time around to avoid the recursion
204             // that got us here the first time (Some judgement is required as to what activity is "unnecessary and complex".)
205             bool minimalFailFast = InFailFast.Value || (exception is OutOfMemoryException);
206             InFailFast.Value = true;
207 
208             if (!minimalFailFast)
209             {
210                 String output = (exception != null) ?
211                     "Unhandled Exception: " + exception.ToString()
212                     : message;
213                 DeveloperExperience.Default.WriteLine(output);
214 
215                 GenerateExceptionInformationForDump(exception, IntPtr.Zero);
216             }
217 
218             uint errorCode = 0x80004005; // E_FAIL
219             // To help enable testing to bucket the failures we choose one of the following as errorCode:
220             // * hashcode of EETypePtr if it is an unhandled managed exception
221             // * HRESULT, if available
222             // * RhFailFastReason, if it is one of the known reasons
223             if (exception != null)
224             {
225                 if (reason == RhFailFastReason.PN_UnhandledException)
226                     errorCode = (uint)(exception.EETypePtr.GetHashCode());
227                 else if (exception.HResult != 0)
228                     errorCode = (uint)exception.HResult;
229             }
230             else if (reason != RhFailFastReason.Unknown)
231             {
232                 errorCode = (uint)reason + 0x1000; // Add something to avoid common low level exit codes
233             }
234 
235             Interop.mincore.RaiseFailFastException(errorCode, pExAddress, pExContext);
236         }
237 
238         // Use a nested class to avoid running the class constructor of the outer class when
239         // accessing this flag.
240         private static class InFailFast
241         {
242             // This boolean is used to stop runaway FailFast recursions. Though this is technically a concurrently set field, it only gets set during
243             // fatal process shutdowns and it's only purpose is a reasonable-case effort to make a bad situation a little less bad.
244             // Trying to use locks or other concurrent access apis would actually defeat the purpose of making FailFast as robust as possible.
245             public static bool Value;
246         }
247 
248 #pragma warning disable 414 // field is assigned, but never used -- This is because C# doesn't realize that we
249         //                                      copy the field into a buffer.
250         /// <summary>
251         /// This is the header that describes our 'error report' buffer to the minidump auxillary provider.
252         /// Its format is know to that system-wide DLL, so do not change it.  The remainder of the buffer is
253         /// opaque to the minidump auxillary provider, so it'll have its own format that is more easily
254         /// changed.
255         /// </summary>
256         [StructLayout(LayoutKind.Sequential)]
257         private struct ERROR_REPORT_BUFFER_HEADER
258         {
259             private int _headerSignature;
260             private int _bufferByteCount;
261 
WriteHeaderSystem.RuntimeExceptionHelpers.ERROR_REPORT_BUFFER_HEADER262             public void WriteHeader(int cbBuffer)
263             {
264                 _headerSignature = 0x31304244;   // 'DB01'
265                 _bufferByteCount = cbBuffer;
266             }
267         }
268 
269         /// <summary>
270         /// This header describes the contents of the serialized error report to DAC, which can deserialize it
271         /// from a dump file or live debugging session.  This format is easier to change than the
272         /// ERROR_REPORT_BUFFER_HEADER, but it is still well-known to DAC, so any changes must update the
273         /// version number and also have corresponding changes made to DAC.
274         /// </summary>
275         [StructLayout(LayoutKind.Sequential)]
276         private struct SERIALIZED_ERROR_REPORT_HEADER
277         {
278             private int _errorReportSignature;           // This is the version of the 'container format'.
279             private int _exceptionSerializationVersion;  // This is the version of the Exception format.  It is
280                                                          // separate from the 'container format' version since the
281                                                          // implementation of the Exception serialization is owned by
282                                                          // the Exception class.
283             private int _exceptionCount;                 // We just contain a logical array of exceptions.
284             private int _loadedModuleCount;              // Number of loaded modules. present when signature >= ER02.
285             // {ExceptionCount} serialized Exceptions follow.
286             // {LoadedModuleCount} module handles follow. present when signature >= ER02.
287 
WriteHeaderSystem.RuntimeExceptionHelpers.SERIALIZED_ERROR_REPORT_HEADER288             public void WriteHeader(int nExceptions, int nLoadedModules)
289             {
290                 _errorReportSignature = 0x32305245;  // 'ER02'
291                 _exceptionSerializationVersion = Exception.CurrentSerializationSignature;
292                 _exceptionCount = nExceptions;
293                 _loadedModuleCount = nLoadedModules;
294             }
295         }
296 
297         /// <summary>
298         /// Holds metadata about an exception in flight. Class because ConditionalWeakTable only accepts reference types
299         /// </summary>
300         private class ExceptionData
301         {
ExceptionData()302             public ExceptionData()
303             {
304                 // Set this to a non-zero value so that logic mapping entries to threads
305                 // doesn't think an uninitialized ExceptionData is on thread 0
306                 ExceptionMetadata.ThreadId = 0xFFFFFFFF;
307             }
308 
309             public struct ExceptionMetadataStruct
310             {
311                 public UInt32 ExceptionId { get; set; } // Id assigned to the exception. May not be contiguous or start at 0.
312                 public UInt32 InnerExceptionId { get; set; } // ID of the inner exception or 0xFFFFFFFF for 'no inner exception'
313                 public UInt32 ThreadId { get; set; } // Managed thread ID the eception was thrown on
314                 public Int32 NestingLevel { get; set; } // If multiple exceptions are currently active on a thread, this gives the ordering for them.
315                                                         // The highest number is the most recent exception. -1 means the exception is not currently in flight
316                                                         // (but it may still be an InnerException).
317                 public IntPtr ExceptionCCWPtr { get; set; } // If the exception was thrown in an interop scenario, this contains the CCW pointer, otherwise, IntPtr.Zero
318             }
319 
320             public ExceptionMetadataStruct ExceptionMetadata;
321 
322             /// <summary>
323             /// Data created by Exception.SerializeForDump()
324             /// </summary>
325             public byte[] SerializedExceptionData { get; set; }
326 
327             /// <summary>
328             /// Serializes the exception metadata and SerializedExceptionData
329             /// </summary>
Serialize()330             public unsafe byte[] Serialize()
331             {
332                 checked
333                 {
334                     byte[] serializedData = new byte[sizeof(ExceptionMetadataStruct) + SerializedExceptionData.Length];
335                     fixed (byte* pSerializedData = &serializedData[0])
336                     {
337                         ExceptionMetadataStruct* pMetadata = (ExceptionMetadataStruct*)pSerializedData;
338                         pMetadata->ExceptionId = ExceptionMetadata.ExceptionId;
339                         pMetadata->InnerExceptionId = ExceptionMetadata.InnerExceptionId;
340                         pMetadata->ThreadId = ExceptionMetadata.ThreadId;
341                         pMetadata->NestingLevel = ExceptionMetadata.NestingLevel;
342                         pMetadata->ExceptionCCWPtr = ExceptionMetadata.ExceptionCCWPtr;
343 
344                         PInvokeMarshal.CopyToNative(SerializedExceptionData, 0, (IntPtr)(pSerializedData + sizeof(ExceptionMetadataStruct)), SerializedExceptionData.Length);
345                     }
346                     return serializedData;
347                 }
348             }
349         }
350 
351         /// <summary>
352         /// Table of exceptions that were on stacks triggering GenerateExceptionInformationForDump
353         /// </summary>
354         private static readonly ConditionalWeakTable<Exception, ExceptionData> s_exceptionDataTable = new ConditionalWeakTable<Exception, ExceptionData>();
355 
356         /// <summary>
357         /// Counter for exception ID assignment
358         /// </summary>
359         private static int s_currentExceptionId = 0;
360 
361         /// <summary>
362         /// This method will call the runtime to gather the Exception objects from every exception dispatch in
363         /// progress on the current thread.  It will then serialize them into a new buffer and pass that
364         /// buffer back to the runtime, which will publish it to a place where a global "minidump auxillary
365         /// provider" will be able to save the buffer's contents into triage dumps.
366         ///
367         /// Thread safety information: The guarantee of this method is that the buffer it produces will have
368         /// complete and correct information for all live exceptions on the current thread (as long as the same exception object
369         /// is not thrown simultaneously on multiple threads). It will do a best-effort attempt to serialize information about exceptions
370         /// already recorded on other threads, but that data can be lost or corrupted. The restrictions are:
371         /// 1. Only exceptions active or recorded on the current thread have their table data modified.
372         /// 2. After updating data in the table, we serialize a snapshot of the table (provided by ConditionalWeakTable.Values),
373         ///    regardless of what other threads might do to the table before or after. However, because of #1, this thread's
374         ///    exception data should stay stable
375         /// 3. There is a dependency on the fact that ConditionalWeakTable's members are all threadsafe and that .Values returns a snapshot
376         /// </summary>
GenerateExceptionInformationForDump(Exception currentException, IntPtr exceptionCCWPtr)377         public static void GenerateExceptionInformationForDump(Exception currentException, IntPtr exceptionCCWPtr)
378         {
379             LowLevelList<byte[]> serializedExceptions = new LowLevelList<byte[]>();
380 
381             // If currentException is null, there's a state corrupting exception in flight and we can't serialize it
382             if (currentException != null)
383             {
384                 SerializeExceptionsForDump(currentException, exceptionCCWPtr, serializedExceptions);
385             }
386 
387             GenerateErrorReportForDump(serializedExceptions);
388         }
389 
SerializeExceptionsForDump(Exception currentException, IntPtr exceptionCCWPtr, LowLevelList<byte[]> serializedExceptions)390         private static void SerializeExceptionsForDump(Exception currentException, IntPtr exceptionCCWPtr, LowLevelList<byte[]> serializedExceptions)
391         {
392             const UInt32 NoInnerExceptionValue = 0xFFFFFFFF;
393 
394             // Approximate upper size limit for the serialized exceptions (but we'll always serialize currentException)
395             // If we hit the limit, because we serialize in arbitrary order, there may be missing InnerExceptions or nested exceptions.
396             const int MaxBufferSize = 20000;
397 
398             int nExceptions;
399             RuntimeImports.RhGetExceptionsForCurrentThread(null, out nExceptions);
400             Exception[] curThreadExceptions = new Exception[nExceptions];
401             RuntimeImports.RhGetExceptionsForCurrentThread(curThreadExceptions, out nExceptions);
402             LowLevelList<Exception> exceptions = new LowLevelList<Exception>(curThreadExceptions);
403             LowLevelList<Exception> nonThrownInnerExceptions = new LowLevelList<Exception>();
404 
405             uint currentThreadId = (uint)Environment.CurrentNativeThreadId;
406 
407             // Reset nesting levels for exceptions on this thread that might not be currently in flight
408             foreach (KeyValuePair<Exception, ExceptionData> item in s_exceptionDataTable)
409             {
410                 ExceptionData exceptionData = item.Value;
411                 if (exceptionData.ExceptionMetadata.ThreadId == currentThreadId)
412                 {
413                     exceptionData.ExceptionMetadata.NestingLevel = -1;
414                 }
415             }
416 
417             // Find all inner exceptions, even if they're not currently being handled
418             for (int i = 0; i < exceptions.Count; i++)
419             {
420                 if (exceptions[i].InnerException != null && !exceptions.Contains(exceptions[i].InnerException))
421                 {
422                     exceptions.Add(exceptions[i].InnerException);
423                     nonThrownInnerExceptions.Add(exceptions[i].InnerException);
424                 }
425             }
426 
427             int currentNestingLevel = curThreadExceptions.Length - 1;
428 
429             // Make sure we serialize currentException
430             if (!exceptions.Contains(currentException))
431             {
432                 // When this happens, currentException is probably passed to this function through System.Environment.FailFast(), we
433                 // would want to treat as if this exception is last thrown in the current thread.
434                 exceptions.Insert(0, currentException);
435                 currentNestingLevel++;
436             }
437 
438             // Populate exception data for all exceptions interesting to this thread.
439             // Whether or not there was previously data for that object, it might have changed.
440             for (int i = 0; i < exceptions.Count; i++)
441             {
442                 ExceptionData exceptionData = s_exceptionDataTable.GetOrCreateValue(exceptions[i]);
443 
444                 exceptionData.ExceptionMetadata.ExceptionId = (UInt32)System.Threading.Interlocked.Increment(ref s_currentExceptionId);
445                 if (exceptionData.ExceptionMetadata.ExceptionId == NoInnerExceptionValue)
446                 {
447                     exceptionData.ExceptionMetadata.ExceptionId = (UInt32)System.Threading.Interlocked.Increment(ref s_currentExceptionId);
448                 }
449 
450                 exceptionData.ExceptionMetadata.ThreadId = currentThreadId;
451 
452                 // Only include nesting information for exceptions that were thrown on this thread
453                 if (!nonThrownInnerExceptions.Contains(exceptions[i]))
454                 {
455                     exceptionData.ExceptionMetadata.NestingLevel = currentNestingLevel;
456                     currentNestingLevel--;
457                 }
458                 else
459                 {
460                     exceptionData.ExceptionMetadata.NestingLevel = -1;
461                 }
462 
463                 // Only match the CCW pointer up to the current exception
464                 if (Object.ReferenceEquals(exceptions[i], currentException))
465                 {
466                     exceptionData.ExceptionMetadata.ExceptionCCWPtr = exceptionCCWPtr;
467                 }
468 
469                 byte[] serializedEx = exceptions[i].SerializeForDump();
470                 exceptionData.SerializedExceptionData = serializedEx;
471             }
472 
473             // Populate inner exception ids now that we have all of them in the table
474             for (int i = 0; i < exceptions.Count; i++)
475             {
476                 ExceptionData exceptionData;
477                 if (!s_exceptionDataTable.TryGetValue(exceptions[i], out exceptionData))
478                 {
479                     // This shouldn't happen, but we can't meaningfully throw here
480                     continue;
481                 }
482 
483                 if (exceptions[i].InnerException != null)
484                 {
485                     ExceptionData innerExceptionData;
486                     if (s_exceptionDataTable.TryGetValue(exceptions[i].InnerException, out innerExceptionData))
487                     {
488                         exceptionData.ExceptionMetadata.InnerExceptionId = innerExceptionData.ExceptionMetadata.ExceptionId;
489                     }
490                 }
491                 else
492                 {
493                     exceptionData.ExceptionMetadata.InnerExceptionId = NoInnerExceptionValue;
494                 }
495             }
496 
497             int totalSerializedExceptionSize = 0;
498             // Make sure we include the current exception, regardless of buffer size
499             ExceptionData currentExceptionData = null;
500             if (s_exceptionDataTable.TryGetValue(currentException, out currentExceptionData))
501             {
502                 byte[] serializedExceptionData = currentExceptionData.Serialize();
503                 serializedExceptions.Add(serializedExceptionData);
504                 totalSerializedExceptionSize = serializedExceptionData.Length;
505             }
506 
507             checked
508             {
509                 foreach (KeyValuePair<Exception, ExceptionData> item in s_exceptionDataTable)
510                 {
511                     ExceptionData exceptionData = item.Value;
512 
513                     // Already serialized currentException
514                     if (currentExceptionData != null && exceptionData.ExceptionMetadata.ExceptionId == currentExceptionData.ExceptionMetadata.ExceptionId)
515                     {
516                         continue;
517                     }
518 
519                     byte[] serializedExceptionData = exceptionData.Serialize();
520                     if (totalSerializedExceptionSize + serializedExceptionData.Length >= MaxBufferSize)
521                     {
522                         break;
523                     }
524 
525                     serializedExceptions.Add(serializedExceptionData);
526                     totalSerializedExceptionSize += serializedExceptionData.Length;
527                 }
528             }
529         }
530 
GenerateErrorReportForDump(LowLevelList<byte[]> serializedExceptions)531         private static unsafe void GenerateErrorReportForDump(LowLevelList<byte[]> serializedExceptions)
532         {
533             checked
534             {
535                 int loadedModuleCount = (int)RuntimeImports.RhGetLoadedOSModules(null);
536                 int cbModuleHandles = sizeof(System.IntPtr) * loadedModuleCount;
537                 int cbFinalBuffer = sizeof(ERROR_REPORT_BUFFER_HEADER) + sizeof(SERIALIZED_ERROR_REPORT_HEADER) + cbModuleHandles;
538                 for (int i = 0; i < serializedExceptions.Count; i++)
539                 {
540                     cbFinalBuffer += serializedExceptions[i].Length;
541                 }
542 
543                 byte[] finalBuffer = new byte[cbFinalBuffer];
544                 fixed (byte* pBuffer = &finalBuffer[0])
545                 {
546                     byte* pCursor = pBuffer;
547                     int cbRemaining = cbFinalBuffer;
548 
549                     ERROR_REPORT_BUFFER_HEADER* pDacHeader = (ERROR_REPORT_BUFFER_HEADER*)pCursor;
550                     pDacHeader->WriteHeader(cbFinalBuffer);
551                     pCursor += sizeof(ERROR_REPORT_BUFFER_HEADER);
552                     cbRemaining -= sizeof(ERROR_REPORT_BUFFER_HEADER);
553 
554                     SERIALIZED_ERROR_REPORT_HEADER* pPayloadHeader = (SERIALIZED_ERROR_REPORT_HEADER*)pCursor;
555                     pPayloadHeader->WriteHeader(serializedExceptions.Count, loadedModuleCount);
556                     pCursor += sizeof(SERIALIZED_ERROR_REPORT_HEADER);
557                     cbRemaining -= sizeof(SERIALIZED_ERROR_REPORT_HEADER);
558 
559                     // copy the serialized exceptions to report buffer
560                     for (int i = 0; i < serializedExceptions.Count; i++)
561                     {
562                         int cbChunk = serializedExceptions[i].Length;
563                         PInvokeMarshal.CopyToNative(serializedExceptions[i], 0, (IntPtr)pCursor, cbChunk);
564                         cbRemaining -= cbChunk;
565                         pCursor += cbChunk;
566                     }
567 
568                     // copy the module-handle array to report buffer
569                     IntPtr[] loadedModuleHandles = new IntPtr[loadedModuleCount];
570                     RuntimeImports.RhGetLoadedOSModules(loadedModuleHandles);
571                     PInvokeMarshal.CopyToNative(loadedModuleHandles, 0, (IntPtr)pCursor, loadedModuleHandles.Length);
572                     cbRemaining -= cbModuleHandles;
573                     pCursor += cbModuleHandles;
574 
575                     Debug.Assert(cbRemaining == 0);
576                 }
577                 UpdateErrorReportBuffer(finalBuffer);
578             }
579         }
580 
581         // This returns "true" once enough of the framework has been initialized to safely perform operations
582         // such as filling in the stack frame and generating diagnostic support.
583         public static bool SafeToPerformRichExceptionSupport
584         {
585             get
586             {
587                 // Reflection needs to work as the exception code calls GetType() and GetType().ToString()
588                 if (RuntimeAugments.CallbacksIfAvailable == null)
589                     return false;
590                 return true;
591             }
592         }
593 
594         private static GCHandle s_ExceptionInfoBufferPinningHandle;
595         private static Lock s_ExceptionInfoBufferLock = new Lock();
596 
UpdateErrorReportBuffer(byte[] finalBuffer)597         private static unsafe void UpdateErrorReportBuffer(byte[] finalBuffer)
598         {
599             Debug.Assert(finalBuffer?.Length > 0);
600 
601             using (LockHolder.Hold(s_ExceptionInfoBufferLock))
602             {
603                 fixed (byte* pBuffer = &finalBuffer[0])
604                 {
605                     byte* pPrevBuffer = (byte*)RuntimeImports.RhSetErrorInfoBuffer(pBuffer);
606                     Debug.Assert(s_ExceptionInfoBufferPinningHandle.IsAllocated == (pPrevBuffer != null));
607                     if (pPrevBuffer != null)
608                     {
609                         byte[] currentExceptionInfoBuffer = (byte[])s_ExceptionInfoBufferPinningHandle.Target;
610                         Debug.Assert(currentExceptionInfoBuffer?.Length > 0);
611                         fixed (byte* pPrev = &currentExceptionInfoBuffer[0])
612                             Debug.Assert(pPrev == pPrevBuffer);
613                     }
614                     if (!s_ExceptionInfoBufferPinningHandle.IsAllocated)
615                     {
616                         // We allocate a pinning GC handle because we are logically giving the runtime 'unmanaged memory'.
617                         s_ExceptionInfoBufferPinningHandle = GCHandle.Alloc(finalBuffer, GCHandleType.Pinned);
618                     }
619                     else
620                     {
621                         s_ExceptionInfoBufferPinningHandle.Target = finalBuffer;
622                     }
623                 }
624             }
625         }
626     }
627 }
628