|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Tracing;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using MethodBase = System.Reflection.MethodBase;
namespace System
{
public partial class Exception
{
public MethodBase? TargetSite
{
[RequiresUnreferencedCode("Metadata for the method might be incomplete or removed")]
get
{
if (!HasBeenThrown)
return null;
return new StackFrame(_corDbgStackTrace[0], needFileInfo: false).GetMethod();
}
}
private static ListDictionaryInternal CreateDataContainer() => new ListDictionaryInternal();
private static string? SerializationWatsonBuckets => null;
internal string? _message;
private IDictionary? _data;
private Exception? _innerException;
private string? _helpURL;
private string? _source; // Mainly used by VB.
private int _HResult; // HResult
// To maintain compatibility across runtimes, if this object was deserialized, it will store its stack trace as a string
private string? _stackTraceString;
private string? _remoteStackTraceString;
internal IntPtr[] GetStackIPs()
{
IntPtr[] ips = new IntPtr[_idxFirstFreeStackTraceEntry];
if (_corDbgStackTrace != null)
{
Array.Copy(_corDbgStackTrace, ips, ips.Length);
}
return ips;
}
private IntPtr[]? _corDbgStackTrace;
private int _idxFirstFreeStackTraceEntry;
internal static IntPtr EdiSeparator => (IntPtr)1; // Marks a boundary where an ExceptionDispatchInfo rethrew an exception.
private void AppendStackIP(IntPtr IP)
{
if (_idxFirstFreeStackTraceEntry == 0)
{
_corDbgStackTrace = new IntPtr[16];
}
if (_idxFirstFreeStackTraceEntry >= _corDbgStackTrace.Length)
GrowStackTrace();
_corDbgStackTrace[_idxFirstFreeStackTraceEntry++] = IP;
}
private void GrowStackTrace()
{
IntPtr[] newArray = new IntPtr[_corDbgStackTrace.Length * 2];
for (int i = 0; i < _corDbgStackTrace.Length; i++)
{
newArray[i] = _corDbgStackTrace[i];
}
_corDbgStackTrace = newArray;
}
private bool HasBeenThrown => _idxFirstFreeStackTraceEntry != 0;
private enum RhEHFrameType
{
RH_EH_FIRST_FRAME = 1,
RH_EH_FIRST_RETHROW_FRAME = 2,
}
// Performance metric to count the number of exceptions thrown
private static uint s_exceptionCount;
internal static uint GetExceptionCount() => s_exceptionCount;
[RuntimeExport("AppendExceptionStackFrame")]
internal static void AppendExceptionStackFrame(object exceptionObj, IntPtr IP, int flags)
{
// This method is called by the runtime's EH dispatch code and is not allowed to leak exceptions
// back into the dispatcher.
try
{
Exception? ex = exceptionObj as Exception;
if (ex == null)
Environment.FailFast("Exceptions must derive from the System.Exception class");
if (!RuntimeExceptionHelpers.SafeToPerformRichExceptionSupport)
return;
bool isFirstFrame = (flags & (int)RhEHFrameType.RH_EH_FIRST_FRAME) != 0;
bool isFirstRethrowFrame = (flags & (int)RhEHFrameType.RH_EH_FIRST_RETHROW_FRAME) != 0;
// track count for metrics
if (isFirstFrame && !isFirstRethrowFrame)
Interlocked.Increment(ref s_exceptionCount);
// When we're throwing an exception object, we first need to clear its stacktrace with two exceptions:
// 1. Don't clear if we're rethrowing with `throw;`.
// 2. Don't clear if we're throwing through ExceptionDispatchInfo.
// This is done through invoking RestoreDispatchState which sets the last frame to EdiSeparator followed by throwing normally using `throw ex;`.
if (!isFirstRethrowFrame && isFirstFrame && ex._idxFirstFreeStackTraceEntry > 0 && ex._corDbgStackTrace[ex._idxFirstFreeStackTraceEntry - 1] != EdiSeparator)
ex._idxFirstFreeStackTraceEntry = 0;
// If out of memory, avoid any calls that may allocate. Otherwise, they may fail
// with another OutOfMemoryException, which may lead to infinite recursion.
bool fatalOutOfMemory = ex == PreallocatedOutOfMemoryException.Instance;
if (!isFirstRethrowFrame && !fatalOutOfMemory)
ex.AppendStackIP(IP);
#if FEATURE_PERFTRACING
if (isFirstFrame && NativeRuntimeEventSource.Log.IsEnabled())
{
string typeName = !fatalOutOfMemory ? ex.GetType().ToString() : "System.OutOfMemoryException";
string message = !fatalOutOfMemory ? ex.Message :
"Insufficient memory to continue the execution of the program.";
NativeRuntimeEventSource.Log.ExceptionThrown_V1(typeName, message, IP, (uint)ex.HResult, 0);
}
#endif
}
catch
{
// We may end up with a confusing stack trace or a confusing ETW trace log, but at least we
// can continue to dispatch this exception.
}
}
//==================================================================================================================
// Support for ExceptionDispatchInfo class - imports and exports the stack trace.
//==================================================================================================================
internal DispatchState CaptureDispatchState()
{
IntPtr[]? stackTrace = _corDbgStackTrace;
if (stackTrace != null)
{
IntPtr[] newStackTrace = new IntPtr[stackTrace.Length];
Array.Copy(stackTrace, 0, newStackTrace, 0, stackTrace.Length);
stackTrace = newStackTrace;
}
return new DispatchState(stackTrace);
}
internal void RestoreDispatchState(DispatchState DispatchState)
{
IntPtr[]? stackTrace = DispatchState.StackTrace;
int idxFirstFreeStackTraceEntry = 0;
if (stackTrace != null)
{
IntPtr[] newStackTrace = new IntPtr[stackTrace.Length + 1];
Array.Copy(stackTrace, 0, newStackTrace, 0, stackTrace.Length);
stackTrace = newStackTrace;
while (stackTrace[idxFirstFreeStackTraceEntry] != (IntPtr)0)
idxFirstFreeStackTraceEntry++;
stackTrace[idxFirstFreeStackTraceEntry++] = EdiSeparator;
}
// Since EDI can be created at various points during exception dispatch (e.g. at various frames on the stack) for the same exception instance,
// they can have different data to be restored. Thus, to ensure atomicity of restoration from each EDI, perform the restore under a lock.
lock (s_DispatchStateLock)
{
_corDbgStackTrace = stackTrace;
_idxFirstFreeStackTraceEntry = idxFirstFreeStackTraceEntry;
}
}
internal readonly struct DispatchState
{
public readonly IntPtr[]? StackTrace;
public DispatchState(IntPtr[]? stackTrace)
{
StackTrace = stackTrace;
}
}
// This is the object against which a lock will be taken
// when attempt to restore the EDI. Since its static, its possible
// that unrelated exception object restorations could get blocked
// for a small duration but that sounds reasonable considering
// such scenarios are going to be extremely rare, where timing
// matches precisely.
private static readonly object s_DispatchStateLock = new object();
/// <summary>
/// This is the binary format for serialized exceptions that get saved into a special buffer that is
/// known to WER (by way of a runtime API) and will be saved into triage dumps. This format is known
/// to SOS, so any changes must update CurrentSerializationVersion and have corresponding updates to
/// SOS.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
private struct SERIALIZED_EXCEPTION_HEADER
{
internal IntPtr ExceptionEEType;
internal int HResult;
internal int StackTraceElementCount;
// IntPtr * N : StackTrace elements
}
internal const int CurrentSerializationSignature = 0x31305845; // 'EX01'
/// <summary>
/// This method performs the serialization of one Exception object into the returned byte[].
/// </summary>
internal unsafe byte[] SerializeForDump()
{
checked
{
int nStackTraceElements = _idxFirstFreeStackTraceEntry;
int cbBuffer = sizeof(SERIALIZED_EXCEPTION_HEADER) + (nStackTraceElements * IntPtr.Size);
byte[] buffer = new byte[cbBuffer];
fixed (byte* pBuffer = &buffer[0])
{
SERIALIZED_EXCEPTION_HEADER* pHeader = (SERIALIZED_EXCEPTION_HEADER*)pBuffer;
pHeader->HResult = _HResult;
pHeader->ExceptionEEType = (nint)this.GetMethodTable();
pHeader->StackTraceElementCount = nStackTraceElements;
IntPtr* pStackTraceElements = (IntPtr*)(pBuffer + sizeof(SERIALIZED_EXCEPTION_HEADER));
for (int i = 0; i < nStackTraceElements; i++)
{
pStackTraceElements[i] = _corDbgStackTrace[i];
}
}
return buffer;
}
}
// Returns true if setting the _remoteStackTraceString field is legal, false if not (immutable exception).
// A false return value means the caller should early-exit the operation.
// Can also throw InvalidOperationException if a stack trace is already set or if object has been thrown.
private bool CanSetRemoteStackTrace()
{
// Check to see if the exception already has a stack set in it.
if (HasBeenThrown || _stackTraceString != null || _remoteStackTraceString != null)
{
ThrowHelper.ThrowInvalidOperationException();
}
return true; // NativeAOT runtime doesn't have immutable agile exceptions, always return true
}
}
}
|