-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add - doc - Added crash handler tool
--- We've added an essential tool for this library: crash handling --- Type: add Breaking: False Doc Required: True Backport Required: False Part: 1/1
- Loading branch information
Showing
3 changed files
with
320 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,313 @@ | ||
// | ||
// Aptivestigate Copyright (C) 2024-2025 Aptivi | ||
// | ||
// This file is part of Aptivestigate | ||
// | ||
// Aptivestigate is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Aptivestigate is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY, without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
// | ||
|
||
using SpecProbe.Software.Platform; | ||
using System; | ||
using System.Diagnostics; | ||
using System.IO; | ||
using System.Reflection; | ||
using System.Runtime.InteropServices; | ||
using System.Text; | ||
|
||
namespace Aptivestigate.CrashHandler | ||
{ | ||
/// <summary> | ||
/// Application crash handling tools | ||
/// </summary> | ||
public static class CrashTools | ||
{ | ||
private static bool crashHandling = false; | ||
private static Action<UnhandledExceptionEventArgs>? crashHandler = null; | ||
|
||
/// <summary> | ||
/// Whether the crash handler is installed or not | ||
/// </summary> | ||
public static bool CrashHandling => | ||
crashHandling; | ||
|
||
/// <summary> | ||
/// Installs the application crash handler | ||
/// </summary> | ||
/// <param name="crashHandler">Crash handler to use</param> | ||
/// <exception cref="InvalidOperationException"></exception> | ||
public static void InstallCrashHandler(Action<UnhandledExceptionEventArgs>? crashHandler = null) | ||
{ | ||
// Check to see if we already have a handler installed | ||
if (CrashHandling) | ||
throw new InvalidOperationException("The crash handler is already installed."); | ||
|
||
AppDomain.CurrentDomain.UnhandledException += HandleCrash; | ||
CrashTools.crashHandler = crashHandler; | ||
crashHandling = true; | ||
} | ||
|
||
private static void HandleCrash(object sender, UnhandledExceptionEventArgs e) | ||
{ | ||
bool isDefault = crashHandler is null; | ||
var finalDelegate = crashHandler ?? new Action<UnhandledExceptionEventArgs>(DefaultCrashHandler); | ||
try | ||
{ | ||
// Run the crash handler (first chance) | ||
finalDelegate.Invoke(e); | ||
} | ||
catch (Exception ex) | ||
{ | ||
try | ||
{ | ||
// Looks like that the crash handler has crashed, so check the type and re-run if necessary | ||
// with the default crash handler | ||
if (!isDefault) | ||
{ | ||
finalDelegate = new Action<UnhandledExceptionEventArgs>(DefaultCrashHandler); | ||
HandleCrash(sender, e); | ||
} | ||
else | ||
throw ex; | ||
} | ||
catch (Exception ex2) | ||
{ | ||
// Looks like that we've crashed again (second fault). As a last chance, try to make a crash | ||
// file representing a fatal crash and dump only the necessary details. | ||
try | ||
{ | ||
FatalCrashHandler(ex2); | ||
} | ||
catch (Exception ex3) | ||
{ | ||
// We're totally screwed! Bail out! | ||
Console.WriteLine("---- FATAL CRASH DETECTED ----"); | ||
Console.WriteLine(); | ||
Console.WriteLine("We apologize for your inconvenience, but this program can't continue."); | ||
Environment.FailFast( | ||
$""" | ||
FATAL CRASH DETECTED (TRIPLE FAULT) | ||
=================================== | ||
First chance | ||
------------ | ||
{ex} | ||
Second chance | ||
------------- | ||
{ex2} | ||
""", ex3); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private static void DefaultCrashHandler(UnhandledExceptionEventArgs eventArgs) | ||
{ | ||
Console.WriteLine("---- CRASH DETECTED ----"); | ||
Console.WriteLine(); | ||
Console.WriteLine("A problem has been detected in the application that has to shut down. Writing crash dump..."); | ||
|
||
// Write the crash dump | ||
using var crashFileWriter = CreateCrashFile(out Guid crashId); | ||
bool isException = eventArgs.ExceptionObject is Exception; | ||
crashFileWriter.WriteLine( | ||
$""" | ||
=============================== Crash report =============================== | ||
Below is the crash report about what happened in the application while it | ||
was performing your requested operation. | ||
Crash ID: {crashId} | ||
Time of incident: {DateTimeOffset.Now} | ||
--------------------------- Exception Information -------------------------- | ||
Terminating: {eventArgs.IsTerminating} | ||
{(isException ? eventArgs.ExceptionObject : "General fault. This may indicate a serious problem.")} | ||
---------------------------- Exception Analysis ---------------------------- | ||
Below may help you find out why: | ||
{AnalyzeException(eventArgs.ExceptionObject as Exception)} | ||
---------------------------- Runtime Information --------------------------- | ||
Operating system: {PlatformHelper.GetPlatform()} | ||
OS description: {RuntimeInformation.OSDescription} | ||
OS architecture: {RuntimeInformation.OSArchitecture} | ||
Process architecture: {RuntimeInformation.ProcessArchitecture} | ||
.NET runtime: {RuntimeInformation.FrameworkDescription} | ||
Running on dotnetfx: {PlatformHelper.IsDotNetFx()} | ||
Generic runtime ID: {PlatformHelper.GetCurrentGenericRid()} | ||
General runtime ID: {PlatformHelper.GetCurrentGenericRid(false)} | ||
--------------------------- Operation Information -------------------------- | ||
Running from N-KS: {PlatformHelper.IsRunningFromNitrocid()} | ||
Running from Mono: {PlatformHelper.IsRunningFromMono()} | ||
Running from Screen: {PlatformHelper.IsRunningFromScreen()} | ||
Running from TMUX: {PlatformHelper.IsRunningFromTmux()} | ||
Running on WSL: {PlatformHelper.IsOnUnixWsl()} | ||
Running on MUSL: {PlatformHelper.IsOnUnixMusl()} | ||
Running on Android: {PlatformHelper.IsOnAndroid()} | ||
Running on GUI: {PlatformHelper.IsOnGui()} | ||
Running on X.Org: {PlatformHelper.IsOnX11()} | ||
Running on Wayland: {PlatformHelper.IsOnWayland()} | ||
Terminal type: {PlatformHelper.GetTerminalType()} | ||
Terminal emulator: {PlatformHelper.GetTerminalEmulator()} | ||
=============================== Crash report =============================== | ||
""" | ||
); | ||
crashFileWriter.Close(); | ||
} | ||
|
||
private static void FatalCrashHandler(Exception exception) | ||
{ | ||
Console.WriteLine("---- SECOND CRASH DETECTED ----"); | ||
Console.WriteLine(); | ||
Console.WriteLine("While writing crash dump, we've found a second crash."); | ||
|
||
// Write the crash dump | ||
using var crashFileWriter = CreateCrashFile(out Guid crashId, true); | ||
crashFileWriter.WriteLine( | ||
$""" | ||
============================ Fatal crash report ============================ | ||
Below is the fatal crash report about what happened in the application while | ||
it was handling the error. | ||
Crash ID: {crashId} | ||
Time of incident: {DateTimeOffset.Now} | ||
--------------------------- Exception Information -------------------------- | ||
{exception} | ||
=============================== Crash report =============================== | ||
""" | ||
); | ||
crashFileWriter.Close(); | ||
} | ||
|
||
private static string AnalyzeException(Exception? exception) | ||
{ | ||
// Check the exception | ||
if (exception is null) | ||
return "There is no exception to analyze."; | ||
|
||
// Now, analyze the exception | ||
var analysisBuilder = new StringBuilder(); | ||
var stackTrace = new StackTrace(exception, true); | ||
var traceFrames = stackTrace.GetFrames(); | ||
analysisBuilder.AppendLine( | ||
$""" | ||
Frame count: {stackTrace.FrameCount} | ||
""" | ||
); | ||
for (int i = 0; i < traceFrames.Length; i++) | ||
{ | ||
StackFrame? traceFrame = traceFrames[i]; | ||
analysisBuilder.AppendLine( | ||
$""" | ||
.............................................. | ||
Frame number: {i + 1}/{stackTrace.FrameCount} | ||
""" | ||
); | ||
if (traceFrame.HasMethod()) | ||
{ | ||
var method = traceFrame.GetMethod(); | ||
var parameters = method.GetParameters(); | ||
analysisBuilder.AppendLine( | ||
$""" | ||
Method: {method.Name} | ||
Static method: {method.IsStatic} | ||
Method type: {method.MemberType} | ||
Declaring type: {method.DeclaringType} | ||
Reflected type: {method.ReflectedType} | ||
Attributes: {method.Attributes} | ||
Implementation flags: {method.MethodImplementationFlags} | ||
Method address: 0x{method.MethodHandle.Value.ToInt64():X2} | ||
""" | ||
); | ||
foreach (var parameter in parameters) | ||
{ | ||
analysisBuilder.AppendLine( | ||
$""" | ||
Parameter type name: {parameter.ParameterType.Name} | ||
Parameter full type: {parameter.ParameterType.FullName} | ||
Parameter name: {parameter.Name} | ||
Position (from 0): {parameter.Position} | ||
Contains default: {parameter.HasDefaultValue} | ||
Default value: {parameter.DefaultValue} | ||
""" | ||
); | ||
} | ||
} | ||
if (traceFrame.HasILOffset()) | ||
{ | ||
analysisBuilder.AppendLine( | ||
$""" | ||
IL offset: 0x{traceFrame.GetILOffset():X2} | ||
""" | ||
); | ||
} | ||
if (traceFrame.HasNativeImage()) | ||
{ | ||
analysisBuilder.AppendLine( | ||
$""" | ||
Native image base: 0x{traceFrame.GetNativeImageBase().ToInt64():X2} | ||
Native image IP: 0x{traceFrame.GetNativeIP().ToInt64():X2} | ||
Native image offset: 0x{traceFrame.GetNativeOffset():X2} | ||
""" | ||
); | ||
} | ||
if (traceFrame.HasSource()) | ||
{ | ||
analysisBuilder.AppendLine( | ||
$""" | ||
File name: {traceFrame.GetFileName()} | ||
File line number: {traceFrame.GetFileLineNumber()} | ||
File column number: {traceFrame.GetFileColumnNumber()} | ||
""" | ||
); | ||
} | ||
} | ||
return analysisBuilder.ToString(); | ||
} | ||
|
||
private static StreamWriter CreateCrashFile(out Guid crashId, bool fatal = false) | ||
{ | ||
string dumpFilePath = | ||
PlatformHelper.IsOnWindows() ? | ||
$"{Environment.GetEnvironmentVariable("LOCALAPPDATA")}\\Aptivi\\Crashes" : | ||
$"{Environment.GetEnvironmentVariable("HOME")}/.config/Aptivi/Crashes"; | ||
string assembly = Assembly.GetEntryAssembly().GetName().Name; | ||
crashId = Guid.NewGuid(); | ||
Directory.CreateDirectory(dumpFilePath); | ||
return File.CreateText(Path.Combine(dumpFilePath, $"{(fatal ? "f_" : "")}crash_{assembly}_{DateTimeOffset.Now:yyyyMMddhhmmssfffffff}_{crashId}.txt")); | ||
} | ||
} | ||
} |