559 lines
23 KiB
C#
559 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using Bloomberg.Client.Components;
|
|
using Bloomberg.Client.Report;
|
|
using Bloomberg.Message.Entities;
|
|
using JD.Bloomberg;
|
|
using Module=Bloomberg.Message.Entities.Module;
|
|
|
|
namespace Bloomberg.Client.Crash
|
|
{
|
|
/// <summary>
|
|
/// Exception Handler to catch exception and redirect to report manager
|
|
/// This class is the first to be called when a crash occur. With its own implementation
|
|
/// it computes all the crash data needed and calls the ReportManager (singleton) to
|
|
/// send the xml to the crash server or reporter (pc).
|
|
///
|
|
// You can Trigger manual crash by calling directly TriggerCrash and inputting your crash information.
|
|
/// </summary>
|
|
public class CrashHandler : IDisposable
|
|
{
|
|
#region Dll imports
|
|
[Flags]
|
|
public enum SetErrorModeMode : uint
|
|
{
|
|
SEM_FAILCRITICALERRORS = 0x0001,
|
|
SEM_NOGPFAULTERRORBOX = 0x0002,
|
|
SEM_NOALIGNMENTFAULTEXCEPT = 0x0004,
|
|
SEM_NOOPENFILEERRORBOX = 0x8000 }
|
|
|
|
[DllImport("kernel32.dll", EntryPoint = "SetErrorMode", CharSet = CharSet.Ansi, SetLastError = true)]
|
|
public static extern SetErrorModeMode SetErrorMode(SetErrorModeMode mode);
|
|
|
|
[DllImport("kernel32.dll")]
|
|
public static extern uint GetCurrentThreadId();
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// This is a buffer to avoid problem when out of memory. It's really hard to avoid memory allocation in C#
|
|
/// Since we dont have a real limit, we prefer allowing fake data and forcing a GC collect
|
|
/// </summary>
|
|
|
|
// ReSharper disable NotAccessedField.Local
|
|
private byte[] _buffer = new byte[1024 * 256];
|
|
// ReSharper restore NotAccessedField.Local
|
|
|
|
private Semaphore _crashLock = new Semaphore(1, 1);
|
|
private bool _exceptionHandlerAttached;
|
|
|
|
private SetErrorModeMode _previousErrorMode = 0;
|
|
|
|
public bool IsEnabled { get; set; }
|
|
|
|
public CrashHandler()
|
|
{
|
|
IsEnabled = true;
|
|
}
|
|
|
|
#region Initialization
|
|
/// <summary>
|
|
/// Initialize the crash handler
|
|
/// </summary>
|
|
public void Initialize()
|
|
{
|
|
if (IsEnabled)
|
|
{
|
|
_previousErrorMode = SetErrorMode(SetErrorModeMode.SEM_NOGPFAULTERRORBOX | SetErrorModeMode.SEM_FAILCRITICALERRORS);
|
|
|
|
// Initialize CrashReport fixed data
|
|
ReportCrash.Instance.SetType(ReportType.Exception);
|
|
|
|
// Initialize the unhandled exception filter and other error callback
|
|
// We attach the exception handle if:
|
|
// 1 - No debugger is attached
|
|
// 2 - Debugger is attached, but is not enabled for Exception (so we assume we want to debug Exception handling)
|
|
if (!Debugger.IsAttached || !ReportCrash.Instance.HasEnableDebuggerDetectionFlag)
|
|
{
|
|
// If Bloomberg is initialized in the default application domain,
|
|
// It will capture any unhandled exception in any thread, from all application domains.
|
|
// This includes the UI thread.
|
|
//
|
|
// If Bloomberg is initialized in a non-default application domain,
|
|
// It will only capture unhandled exceptions from threads started in that domain.
|
|
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
|
|
|
// Winforms
|
|
// In Windows Forms, unhandled excpetions in the main application thread (UI thread) raise a ThreadException event; the UnhandledException event is not raised.
|
|
// By changing the UnhandledExceptionMode to ThrowException, we ensure the UnhandledException event is raised instead of ThreadException
|
|
//ChangeUnhandledExceptionMode(System.Windows.Forms.UnhandledExceptionMode.ThrowException); // <= This is original Bloomberg code changed for JD.
|
|
|
|
// BEGIN JUST DANCE MODIFICATIONS
|
|
ChangeUnhandledExceptionMode(System.Windows.Forms.UnhandledExceptionMode.CatchException);
|
|
System.Windows.Forms.Application.ThreadException += (sender, e) => AnalyzeException(e.Exception);
|
|
if (System.Windows.Application.Current != null) // Not in a WPF application
|
|
{
|
|
System.Windows.Application.Current.DispatcherUnhandledException += (sender, e) =>
|
|
{
|
|
e.Handled = true;
|
|
AnalyzeException(e.Exception);
|
|
};
|
|
}
|
|
// END
|
|
|
|
// MSDN DOC: Debug and trace share the same Listeners collection, so if you add a listener object to a Debug.Listeners collection in your application, it gets added to the Trace.Listeners collection as well.
|
|
// We overwrite the listeners to handle the assert. This one inherits from DefaultTraceListener (the one used by .NET by default).
|
|
for (int i = Trace.Listeners.Count - 1; i >= 0; i--)
|
|
if (Trace.Listeners[i] is DefaultTraceListener)
|
|
Trace.Listeners.RemoveAt(i);
|
|
Trace.Listeners.Insert(0, new BloombergTraceListener());
|
|
|
|
Tracer.LogFormat(TracerLogLevel.INFO, "CrashHandler.Initialize: Exception handler was attached.");
|
|
|
|
_exceptionHandlerAttached = true;
|
|
}
|
|
else
|
|
{
|
|
Tracer.LogFormat(TracerLogLevel.INFO,
|
|
"CrashHandler.Initialize: Bloomberg trace listener was not instanciated because debugger is attached ({0}) or debugger detection is activated ({1})",
|
|
Debugger.IsAttached, ReportCrash.Instance.HasEnableDebuggerDetectionFlag);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void UnInitialize()
|
|
{
|
|
SetErrorMode(_previousErrorMode);
|
|
|
|
if (_exceptionHandlerAttached)
|
|
{
|
|
AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
|
|
ChangeUnhandledExceptionMode(System.Windows.Forms.UnhandledExceptionMode.Automatic);
|
|
|
|
for (int i = Trace.Listeners.Count - 1; i >= 0; i--)
|
|
if (Trace.Listeners[i] is BloombergTraceListener)
|
|
Trace.Listeners.RemoveAt(i);
|
|
Trace.Listeners.Insert(0, new DefaultTraceListener());
|
|
|
|
_exceptionHandlerAttached = false;
|
|
|
|
Tracer.LogFormat(TracerLogLevel.INFO, "CrashHandler.UnInitialize: Exception handler was disabled.");
|
|
}
|
|
|
|
IsEnabled = false;
|
|
}
|
|
|
|
public void RegisterCrashHandler(bool registerCrashHandler)
|
|
{
|
|
if (registerCrashHandler)
|
|
Initialize();
|
|
else
|
|
UnInitialize();
|
|
}
|
|
|
|
public bool IsRegistered()
|
|
{
|
|
return _exceptionHandlerAttached;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Exception handlers
|
|
|
|
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
|
{
|
|
AnalyzeException(e.ExceptionObject as Exception);
|
|
}
|
|
|
|
private void AnalyzeException(Exception e)
|
|
{
|
|
Tracer.LogFormat(TracerLogLevel.INFO, "Bloomberg has caught exception : {0}.", e.Message);
|
|
|
|
//while (dontWarnMe())
|
|
//{
|
|
// Thread.Sleep(100);
|
|
//}
|
|
|
|
ReportCrash.Instance.SetType(ReportType.Exception);
|
|
ReportCrash.Instance.SetSubType(Bloomberg.Message.Entities.TypeInfo.SUBTYPE_DEFAULT);
|
|
|
|
AnalyzeException(ReportCrash.Instance, null, e);
|
|
}
|
|
|
|
private void ChangeUnhandledExceptionMode(System.Windows.Forms.UnhandledExceptionMode mode)
|
|
{
|
|
try
|
|
{
|
|
System.Windows.Forms.Application.SetUnhandledExceptionMode(mode);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
//There is a constraint about changing the exception mode after a control was created. Since the application is going to quit after this it might not be necessary to do it anyway
|
|
//Exception actual message: Thread exception mode cannot be changed once any Controls are created on the thread
|
|
//ie: Will happen in a WPF application if a Winform control is hosted in a WPF window.
|
|
Tracer.LogFormat(TracerLogLevel.WARN, "System.Windows.Forms.Application.SetUnhandledExceptionMode(System.Windows.Forms.UnhandledExceptionMode.Automatic); failed. There is a constraint about changing the exception mode after a control was created. Since the application is going to quit after this it might not be necessary to do it anyway: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Exception parsing
|
|
|
|
// always true
|
|
private bool dontWarnMe()
|
|
{
|
|
Random rnd = new Random();
|
|
return rnd.Next(1) < 2;
|
|
}
|
|
|
|
private ActionResult AnalyzeException(ReportCrash reportCrash, string errorMessage, Exception e)
|
|
{
|
|
_crashLock.WaitOne();
|
|
|
|
var result = ActionResult.None;
|
|
|
|
if (Reporter.Instance.CheckIfCrashAlreadyHandled())
|
|
{
|
|
// Crash already handled by native client
|
|
_crashLock.Release();
|
|
return result;
|
|
}
|
|
|
|
try
|
|
{
|
|
Exception innerException = GetInnerException(e);
|
|
|
|
// Free the temporary buffer
|
|
_buffer = null;
|
|
GC.Collect();
|
|
|
|
if (!AnalyzePrologue(reportCrash))
|
|
{
|
|
_crashLock.Release();
|
|
return ActionResult.Error;
|
|
}
|
|
|
|
// Find the reason
|
|
if (!String.IsNullOrEmpty(errorMessage))
|
|
{
|
|
reportCrash.Reason = innerException != null
|
|
? innerException.GetType() + ": " + errorMessage
|
|
: errorMessage;
|
|
}
|
|
else if (innerException != null)
|
|
{
|
|
reportCrash.Reason = innerException.GetType() + ": " + innerException.Message;
|
|
}
|
|
else
|
|
{
|
|
Tracer.LogFormat(TracerLogLevel.ERROR,
|
|
"CrashHandler.AnalyzeException: Cannot obtain exception message for current thread.");
|
|
_crashLock.Release();
|
|
return ActionResult.Error;
|
|
}
|
|
|
|
Tracer.LogFormat(TracerLogLevel.DEBUG, "CrashHandler.AnalyzeException: Beginning analysis for {0}:{1}.",
|
|
reportCrash.TypeInfo.Description, reportCrash.Reason);
|
|
|
|
bool nativeClientIsEnabled = Reporter.Instance.CheckIfNativeClientEnabled();
|
|
|
|
if (nativeClientIsEnabled)
|
|
{
|
|
// CrashHandler.AnalyzeException: Native client is enabled.
|
|
// Since for now we don't have a method to check the native callstack from managed, we consider that we have a mixed callstack");
|
|
reportCrash.ThreadId = GetCurrentThreadId();
|
|
Reporter.Instance.CreateCrashAlreadyHandledFlag();
|
|
}
|
|
|
|
// Obtain the call stack & Module info
|
|
var callstackEntries = new List<CallstackEntry>();
|
|
var modules = new List<Module>();
|
|
|
|
if (reportCrash.HasEnableCallstackFlag)
|
|
{
|
|
GetCallstack(e, ref callstackEntries);
|
|
modules = GetModules();
|
|
}
|
|
|
|
reportCrash.CrashData = new CrashData
|
|
{
|
|
CallstackEntries = callstackEntries,
|
|
Modules = modules,
|
|
Details = innerException == null ? null : innerException.ToString(),
|
|
IsMixedCallstack = nativeClientIsEnabled
|
|
};
|
|
|
|
// [gharding] [BLOOM-1971]
|
|
// if there was more than one exception,
|
|
// Include all exceptions which were thrown in the report
|
|
var exceptions = new List<ExceptionEntry>();
|
|
GetExceptions(e, ref exceptions);
|
|
if (exceptions.Count > 1)
|
|
reportCrash.CrashData.Exceptions = exceptions;
|
|
|
|
if (ThreadsCallstack.Instance != null)
|
|
{
|
|
ThreadsCallstack.Instance.PrepareAdditionalCallstack();
|
|
}
|
|
|
|
//Send the CrashStatistic to Elasticsearch
|
|
using (var crashStatistic = new CrashStatistic())
|
|
{
|
|
crashStatistic.Callstack = e.StackTrace;
|
|
crashStatistic.Message = e.Message;
|
|
}
|
|
|
|
if (reportCrash.IsAsync)
|
|
{
|
|
result = Reporter.Instance.ReportManager.StartAsync(reportCrash);
|
|
if (result != ActionResult.Error)
|
|
{
|
|
ThreadPool.QueueUserWorkItem(state => FinishAnalysis(reportCrash, result));
|
|
}
|
|
else
|
|
{
|
|
_crashLock.Release();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Process the report
|
|
result = Reporter.Instance.ReportManager.Process(reportCrash);
|
|
FinishAnalysis(reportCrash, result);
|
|
_crashLock.Release();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Tracer.LogFormat(TracerLogLevel.ERROR, "CrashHandler.AnalyzeException: Error while processing report. Error: {0}.", ex.ToString());
|
|
_crashLock.Release();
|
|
return ActionResult.Error;
|
|
}
|
|
}
|
|
|
|
private void FinishAnalysis(ReportCrash reportCrash, ActionResult result)
|
|
{
|
|
if (reportCrash.IsAsync)
|
|
{
|
|
Reporter.Instance.ReportManager.FinishAsync(reportCrash);
|
|
_crashLock.Release();
|
|
}
|
|
|
|
AnalyzeEpilogue(reportCrash);
|
|
|
|
reportCrash.IsAsync = false;
|
|
|
|
Tracer.LogFormat(TracerLogLevel.DEBUG, "CrashHandler.AnalyzeException: Ending analysis for {0}:{1}.", reportCrash.TypeInfo.Description, reportCrash.Reason);
|
|
|
|
if ((result & ActionResult.Break) > 0 && reportCrash.TypeInfo.Type == ReportType.Exception)
|
|
{
|
|
Tracer.LogFormat(TracerLogLevel.DEBUG, "CrashHandler.AnalyzeException: Breaking application (Action result: {0})", result);
|
|
Debugger.Launch();
|
|
}
|
|
|
|
// Shutdown appl if reboot is set to be true for the current report type
|
|
if ((result & ActionResult.Kill) > 0)
|
|
{
|
|
Tracer.LogFormat(TracerLogLevel.DEBUG, "CrashHandler.AnalyzeException: Killing application (Action result: {0})", result);
|
|
Process.GetCurrentProcess().Kill();
|
|
}
|
|
}
|
|
|
|
private static void GetExceptions(Exception e, ref List<ExceptionEntry> exceptions)
|
|
{
|
|
int depth = 0;
|
|
while(e != null)
|
|
{
|
|
exceptions.Insert(0, new ExceptionEntry
|
|
{
|
|
Depth = depth,
|
|
Message = e.Message,
|
|
Type = e.GetType().ToString()
|
|
});
|
|
|
|
++depth;
|
|
e = e.InnerException;
|
|
}
|
|
}
|
|
|
|
private static Exception GetInnerException(Exception e)
|
|
{
|
|
if (e != null)
|
|
{
|
|
return e.InnerException != null ? GetInnerException(e.InnerException) : e;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static void GetCallstack(Exception e, ref List<CallstackEntry> callstackEntries, int depth = 0)
|
|
{
|
|
if (e != null)
|
|
{
|
|
if (e.InnerException != null)
|
|
GetCallstack(e.InnerException, ref callstackEntries, depth + 1);
|
|
callstackEntries.AddRange(InternalGetCallstack(e, depth));
|
|
return;
|
|
}
|
|
|
|
callstackEntries.AddRange(InternalGetCallstack(null, depth));
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method return the list of callstack for an exception (or the current frame if the exception is null)
|
|
/// </summary>
|
|
/// <param name="e"></param>
|
|
/// <param name="depth"></param>
|
|
/// <returns></returns>
|
|
private static IEnumerable<CallstackEntry> InternalGetCallstack(Exception e, int depth)
|
|
{
|
|
var callstackEntries = new List<CallstackEntry>();
|
|
|
|
// e.StackTrace can be null when someone creates an exception manually instead of throw
|
|
StackTrace stackTrace = e == null || e.StackTrace == null ? new StackTrace(true) : new StackTrace(e, true);
|
|
|
|
StackFrame[] stackFrames = stackTrace.GetFrames(); // get method calls (frames)
|
|
if (stackFrames == null) return callstackEntries;
|
|
|
|
// write call stack method names
|
|
foreach (StackFrame stackFrame in stackFrames)
|
|
{
|
|
var methodInfo = stackFrame.GetMethod();
|
|
if (methodInfo == null) continue;
|
|
|
|
if (methodInfo.ReflectedType != null)
|
|
{
|
|
callstackEntries.Add(new CallstackEntry
|
|
{
|
|
Address = GetAddress(stackFrame, methodInfo),
|
|
FunctionName = methodInfo.ReflectedType.FullName + "." + methodInfo.Name,
|
|
FunctionOffset = (uint)stackFrame.GetILOffset(),
|
|
ModuleName = methodInfo.ReflectedType.Module.Name,
|
|
SourceFileLine = (ulong)stackFrame.GetFileLineNumber(),
|
|
SourceFileName = stackFrame.GetFileName(),
|
|
ExceptionDepth = depth
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// This can happen when building lambda on the fly
|
|
callstackEntries.Add(new CallstackEntry
|
|
{
|
|
Address = GetAddress(stackFrame, methodInfo),
|
|
FunctionName = methodInfo.Name,
|
|
FunctionOffset = (uint)stackFrame.GetILOffset(),
|
|
ModuleName = methodInfo.Module.Assembly.FullName,
|
|
SourceFileLine = (ulong)stackFrame.GetFileLineNumber(),
|
|
SourceFileName = stackFrame.GetFileName(),
|
|
ExceptionDepth = depth
|
|
});
|
|
}
|
|
}
|
|
|
|
return callstackEntries;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will return the estimated address of the stack entry.
|
|
/// Sometime, the GetFunctionPointer returns an exception, the catch find another way to represent the address.
|
|
/// </summary>
|
|
/// <param name="stackFrame"></param>
|
|
/// <param name="methodInfo"></param>
|
|
/// <returns></returns>
|
|
private static ulong GetAddress(StackFrame stackFrame, MethodBase methodInfo)
|
|
{
|
|
try
|
|
{
|
|
return (ulong) (methodInfo.MethodHandle.GetFunctionPointer().ToInt64() + stackFrame.GetILOffset());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// TODO: THIS CAN CRASH methodInfo.MethodHandle.GetFunctionPointer() with
|
|
// new List<string>().Add(new string(Enumerable.Repeat('a', 100000000).ToArray()));
|
|
|
|
Tracer.LogFormat(TracerLogLevel.ERROR, "CrashHandler.GetAddress: Could not calculate the address for function {0} in module {1}. Error is: {2}", methodInfo.Name, methodInfo.ReflectedType.Assembly.FullName, ex);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method return the list of assemblies (modules) loaded in the current appdomain.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private static List<Module> GetModules()
|
|
{
|
|
var modules = new List<Module>();
|
|
|
|
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
|
|
|
foreach (Assembly assembly in assemblies)
|
|
{
|
|
System.Reflection.Module[] loadedModules = assembly.GetLoadedModules();
|
|
foreach(var loadedModule in loadedModules)
|
|
{
|
|
modules.Add(new Module
|
|
{
|
|
Name = loadedModule.Name,
|
|
Guid = loadedModule.ModuleVersionId.ToString(),
|
|
OriginalPdbPath = loadedModule.FullyQualifiedName,
|
|
Version = loadedModule.Assembly.GetName().Version.ToString()
|
|
});
|
|
}
|
|
}
|
|
|
|
return modules;
|
|
}
|
|
|
|
private bool AnalyzePrologue(ReportCrash reportCrash)
|
|
{
|
|
if (!IsEnabled)
|
|
{
|
|
Tracer.LogFormat(TracerLogLevel.INFO, "CrashHandler.AnalyzePrologue: Quitting analysis for {0} because of disabled crash reporting.", reportCrash.TypeInfo.Description);
|
|
return false;
|
|
}
|
|
|
|
if (reportCrash.HasEnableDebuggerDetectionFlag && Debugger.IsAttached)
|
|
{
|
|
Tracer.LogFormat(TracerLogLevel.INFO, "CrashHandler.AnalyzePrologue: Quitting analysis for {0} because of present debugger and because that report type is in the EnableDebuggerDetection mask.", reportCrash.TypeInfo.Description);
|
|
return false;
|
|
}
|
|
|
|
ReportCrash.Instance.SetType(reportCrash.TypeInfo.Type);
|
|
ReportCrash.Instance.SetSubType(reportCrash.TypeInfo.SubType);
|
|
|
|
return true;
|
|
}
|
|
|
|
private void AnalyzeEpilogue(ReportInterface reportCrash)
|
|
{
|
|
if (ReportCrash.Instance.HasDisableCrashReportingFlag)
|
|
{
|
|
Tracer.LogFormat(TracerLogLevel.INFO, "CrashHandler.AnalyzeEpilogue: Disabling crash reporting because {0} has been detected.", ReportCrash.Instance.TypeInfo.Description);
|
|
RegisterCrashHandler(false);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public ActionResult TriggerCrash(ReportCrash reportCrash, string errorMessage, Exception e)
|
|
{
|
|
return AnalyzeException(reportCrash, errorMessage, e);
|
|
}
|
|
|
|
public ActionResult TriggerCrash(ReportCrash reportCrash, string errorMessage)
|
|
{
|
|
return TriggerCrash(reportCrash, errorMessage, null);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_crashLock?.Dispose();
|
|
}
|
|
}
|
|
}
|