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 = ¤tExceptionInfoBuffer[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