mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-15 05:04:15 +01:00
feat: add VectoredExceptionHandler
This commit is contained in:
parent
37275d9397
commit
64ecdb58fd
6 changed files with 490 additions and 5 deletions
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
|
@ -70,6 +71,8 @@ namespace Dalamud
|
|||
Log.Information(new string('-', 80));
|
||||
Log.Information("Initializing a session..");
|
||||
|
||||
var vehManager = new VehManager(0, OnVectoredException);
|
||||
|
||||
// This is due to GitHub not supporting TLS 1.0, so we enable all TLS versions globally
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls;
|
||||
|
||||
|
|
@ -81,6 +84,7 @@ namespace Dalamud
|
|||
dalamud.WaitForUnload();
|
||||
|
||||
dalamud.Dispose();
|
||||
vehManager.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -192,6 +196,88 @@ namespace Dalamud
|
|||
}
|
||||
}
|
||||
|
||||
private static unsafe int OnVectoredException(ref VehManager.ExceptionPointers ex)
|
||||
{
|
||||
if (!Enum.IsDefined(typeof(VehManager.ExceptionCode), ex.ExceptionRecord->ExceptionCode))
|
||||
return VehManager.ExceptionContinueSearch;
|
||||
|
||||
var code = (VehManager.ExceptionCode)ex.ExceptionRecord->ExceptionCode;
|
||||
var address = ex.ExceptionRecord->ExceptionAddress;
|
||||
var info = $"{code}(0x{ex.ExceptionRecord->ExceptionCode:X})";
|
||||
|
||||
var module = Process.GetCurrentProcess().Modules.Cast<ProcessModule>().FirstOrDefault(m => address >= m.BaseAddress && address < m.BaseAddress + m.ModuleMemorySize);
|
||||
if (module != null)
|
||||
{
|
||||
var rva = address - module.BaseAddress;
|
||||
|
||||
info += $"\nat {module.ModuleName}+{rva:X}";
|
||||
}
|
||||
else
|
||||
{
|
||||
info += $"\nat {ex.ExceptionRecord->ExceptionAddress:X}";
|
||||
}
|
||||
|
||||
const MessageBoxType flags = NativeFunctions.MessageBoxType.AbortRetryIgnore | NativeFunctions.MessageBoxType.IconError | NativeFunctions.MessageBoxType.SystemModal;
|
||||
var result = MessageBoxW(
|
||||
Process.GetCurrentProcess().MainWindowHandle,
|
||||
$"An error within the game occurred and Dalamud handled it. This may indicate a malfunctioning plugin.\nThe game must close.\n\n{info}\n\nMore information has been recorded separately, please contact us in our Discord or on GitHub.\n\n" +
|
||||
"Click \"Abort\" to save further information.\n" +
|
||||
"Click \"Retry\" to disable all plugins.\n" +
|
||||
"Click \"Ignore\" to do nothing and quit.",
|
||||
"Dalamud",
|
||||
flags);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case (int)User32.MessageBoxResult.IDRETRY:
|
||||
{
|
||||
Log.Information("User chose to disable plugins on next launch...");
|
||||
var config = Service<DalamudConfiguration>.Get();
|
||||
config.PluginSafeMode = true;
|
||||
config.Save();
|
||||
break;
|
||||
}
|
||||
|
||||
case (int)User32.MessageBoxResult.IDABORT:
|
||||
{
|
||||
// TODO: We can also do this for managed exceptions, but do we want to? It requires doing dumps with full memory.
|
||||
Log.Information("User chose to save minidump...");
|
||||
|
||||
#if DEBUG
|
||||
var path = Path.Combine(Path.GetDirectoryName(typeof(EntryPoint).Assembly.Location), $"dalamud_appcrashd_{DateTimeOffset.Now.ToUnixTimeSeconds()}.dmp");
|
||||
#else
|
||||
var path = Path.Combine(Path.GetDirectoryName(typeof(EntryPoint).Assembly.Location), "..", "..", "..", $"dalamud_appcrash_{DateTimeOffset.Now.ToUnixTimeSeconds()}.dmp");
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
var file = new FileStream(path, FileMode.Create);
|
||||
|
||||
fixed (void* pEx = &ex)
|
||||
{
|
||||
var mdmpInfo = default(MinidumpExceptionInformation);
|
||||
mdmpInfo.ClientPointers = 1;
|
||||
|
||||
mdmpInfo.ExceptionPointers = new IntPtr(pEx);
|
||||
mdmpInfo.ThreadId = GetCurrentThreadId();
|
||||
|
||||
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file.SafeFileHandle.DangerousGetHandle(), (int)MiniDumpType.MiniDumpWithDataSegs, ref mdmpInfo, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Failed to save minidump");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Environment.Exit(-1);
|
||||
|
||||
return VehManager.ExceptionContinueSearch;
|
||||
}
|
||||
|
||||
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
|
||||
{
|
||||
switch (args.ExceptionObject)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
|
@ -21,6 +20,8 @@ using Dalamud.Logging;
|
|||
using Dalamud.Logging.Internal;
|
||||
using Dalamud.Plugin.Internal;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI;
|
||||
using ImGuiNET;
|
||||
using PInvoke;
|
||||
using Serilog.Events;
|
||||
|
|
@ -467,12 +468,24 @@ namespace Dalamud.Interface.Internal
|
|||
Process.GetCurrentProcess().Kill();
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Cause AccessViolation"))
|
||||
ImGui.Separator();
|
||||
|
||||
if (ImGui.MenuItem("Access Violation"))
|
||||
{
|
||||
Marshal.ReadByte(IntPtr.Zero);
|
||||
}
|
||||
|
||||
if (ImGui.MenuItem("Crash game"))
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var framework = Framework.Instance();
|
||||
framework->UIModule = (UIModule*)0;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
if (ImGui.MenuItem("Enable Dalamud testing", string.Empty, configuration.DoDalamudTest))
|
||||
{
|
||||
configuration.DoDalamudTest ^= true;
|
||||
|
|
|
|||
|
|
@ -369,8 +369,7 @@ namespace Dalamud.Interface.Internal
|
|||
if (iniFileInfo.Length > 1200000)
|
||||
{
|
||||
Log.Warning("dalamudUI.ini was over 1mb, deleting");
|
||||
iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName,
|
||||
$"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini"));
|
||||
iniFileInfo.CopyTo(Path.Combine(iniFileInfo.DirectoryName, $"dalamudUI-{DateTimeOffset.Now.ToUnixTimeSeconds()}.ini"));
|
||||
iniFileInfo.Delete();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1777,6 +1777,90 @@ namespace Dalamud
|
|||
byte[] lpBuffer,
|
||||
int dwSize,
|
||||
out IntPtr lpNumberOfBytesWritten);
|
||||
|
||||
/// <summary>
|
||||
/// Get a handle to the current process.
|
||||
/// </summary>
|
||||
/// <returns>Handle to the process.</returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern IntPtr GetCurrentProcess();
|
||||
|
||||
/// <summary>
|
||||
/// Get the current process ID.
|
||||
/// </summary>
|
||||
/// <returns>The process ID.</returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern uint GetCurrentProcessId();
|
||||
|
||||
/// <summary>
|
||||
/// Get the current thread ID.
|
||||
/// </summary>
|
||||
/// <returns>The thread ID.</returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern uint GetCurrentThreadId();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Native dbghelp functions.
|
||||
/// </summary>
|
||||
internal static partial class NativeFunctions
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of minidump to create.
|
||||
/// </summary>
|
||||
public enum MiniDumpType : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Normal minidump.
|
||||
/// </summary>
|
||||
MiniDumpNormal,
|
||||
|
||||
/// <summary>
|
||||
/// Minidump with data segments.
|
||||
/// </summary>
|
||||
MiniDumpWithDataSegs,
|
||||
|
||||
/// <summary>
|
||||
/// Minidump with full memory.
|
||||
/// </summary>
|
||||
MiniDumpWithFullMemory,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a minidump.
|
||||
/// </summary>
|
||||
/// <param name="hProcess">Target process handle.</param>
|
||||
/// <param name="processId">Target process ID.</param>
|
||||
/// <param name="hFile">Output file handle.</param>
|
||||
/// <param name="dumpType">Type of dump to take.</param>
|
||||
/// <param name="exceptionInfo">Exception information.</param>
|
||||
/// <param name="userStreamParam">User information.</param>
|
||||
/// <param name="callback">Callback.</param>
|
||||
/// <returns>Whether or not the minidump succeeded.</returns>
|
||||
[DllImport("dbghelp.dll")]
|
||||
public static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, IntPtr hFile, int dumpType, ref MinidumpExceptionInformation exceptionInfo, IntPtr userStreamParam, IntPtr callback);
|
||||
|
||||
/// <summary>
|
||||
/// Structure describing minidump exception information.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct MinidumpExceptionInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// ID of the thread that caused the exception.
|
||||
/// </summary>
|
||||
public uint ThreadId;
|
||||
|
||||
/// <summary>
|
||||
/// Pointer to the exception record.
|
||||
/// </summary>
|
||||
public IntPtr ExceptionPointers;
|
||||
|
||||
/// <summary>
|
||||
/// ClientPointers field.
|
||||
/// </summary>
|
||||
public int ClientPointers;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ using System.IO.Compression;
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
using Dalamud.Configuration.Internal;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Logging.Internal;
|
||||
using ImGuiNET;
|
||||
using Microsoft.Win32;
|
||||
using Serilog;
|
||||
|
|
|
|||
303
Dalamud/VehManager.cs
Normal file
303
Dalamud/VehManager.cs
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud
|
||||
{
|
||||
/// <summary>
|
||||
/// Class encapsulating a vectored exception handler.
|
||||
/// </summary>
|
||||
internal unsafe class VehManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute the exception handler.
|
||||
/// </summary>
|
||||
public const int ExceptionExecuteHandler = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Continue the search for another handler.
|
||||
/// </summary>
|
||||
public const int ExceptionContinueSearch = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Continue execution after the handler.
|
||||
/// </summary>
|
||||
public const int ExceptionContinueExecution = -1;
|
||||
|
||||
private VectoredExceptionHandler? myHandler; // to keep a reference, just in case, idk if it's needed
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VehManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="first">Position of this VEH. 0 = Last, 1 = First.</param>
|
||||
/// <param name="myHandler">The handler to register.</param>
|
||||
public VehManager(uint first, VectoredExceptionHandler myHandler)
|
||||
{
|
||||
this.myHandler = myHandler;
|
||||
this.Handle = AddVectoredExceptionHandler(first, this.myHandler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VEH Delegate.
|
||||
/// </summary>
|
||||
/// <param name="ex">Exception information.</param>
|
||||
/// <returns>Code that determines which action to take.</returns>
|
||||
public delegate int VectoredExceptionHandler(ref ExceptionPointers ex);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the handle to the VEH.
|
||||
/// </summary>
|
||||
public nint Handle { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dispose and remove this VEH.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (this.Handle == 0)
|
||||
return;
|
||||
|
||||
if (!RemoveVectoredExceptionHandler(this.Handle))
|
||||
return;
|
||||
|
||||
this.Handle = 0;
|
||||
this.myHandler = null;
|
||||
}
|
||||
|
||||
#region DllImports
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
private static extern nint AddVectoredExceptionHandler(uint first, VectoredExceptionHandler handler);
|
||||
|
||||
[DllImport("kernel32", SetLastError = true)]
|
||||
private static extern bool RemoveVectoredExceptionHandler(nint handle);
|
||||
|
||||
#endregion
|
||||
|
||||
#pragma warning disable SA1600
|
||||
#pragma warning disable SA1602
|
||||
#pragma warning disable SA1201
|
||||
|
||||
#region Enums
|
||||
|
||||
public enum ExceptionCode : uint
|
||||
{
|
||||
AccessViolation = 0xC0000005,
|
||||
InPageError = 0xC0000006,
|
||||
InvalidHandle = 0xC0000008,
|
||||
InvalidParameter = 0xC000000D,
|
||||
NoMemory = 0xC0000017,
|
||||
IllegalInstruction = 0xC000001D,
|
||||
NoncontinuableException = 0xC0000025,
|
||||
InvalidDisposition = 0xC0000026,
|
||||
ArrayBoundsExceeded = 0xC000008C,
|
||||
FloatDenormalOperand = 0xC000008D,
|
||||
FloatDivideByZero = 0xC000008E,
|
||||
FloatInexactResult = 0xC000008F,
|
||||
FloatInvalidOperation = 0xC0000090,
|
||||
FloatOverflow = 0xC0000091,
|
||||
FloatStackCheck = 0xC0000092,
|
||||
FloatUnderflow = 0xC0000093,
|
||||
IntegerDivideByZero = 0xC0000094,
|
||||
IntegerOverflow = 0xC0000095,
|
||||
PrivilegedInstruction = 0xC0000096,
|
||||
StackOverflow = 0xC00000FD,
|
||||
DllNotFound = 0xC0000135,
|
||||
OrdinalNotFound = 0xC0000138,
|
||||
EntrypointNotFound = 0xC0000139,
|
||||
ControlCExit = 0xC000013A,
|
||||
DllInitFailed = 0xC0000142,
|
||||
ControlStackViolation = 0xC00001B2,
|
||||
FloatMultipleFaults = 0xC00002B4,
|
||||
FloatMultipleTraps = 0xC00002B5,
|
||||
RegNatConsumption = 0xC00002C9,
|
||||
HeapCorruption = 0xC0000374,
|
||||
StackBufferOverrun = 0xC0000409,
|
||||
InvalidCruntimeParameter = 0xC0000417,
|
||||
AssertionFailure = 0xC0000420,
|
||||
EnclaveViolation = 0xC00004A2,
|
||||
Interrupted = 0xC0000515,
|
||||
ThreadNotRunning = 0xC0000516,
|
||||
AlreadyRegistered = 0xC0000718,
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Structures
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ExceptionPointers
|
||||
{
|
||||
public ExceptionRecord64* ExceptionRecord;
|
||||
public Context* ContextRecord;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ExceptionRecord64
|
||||
{
|
||||
public uint ExceptionCode;
|
||||
public uint ExceptionFlags;
|
||||
public nint ExceptionRecord;
|
||||
public nint ExceptionAddress;
|
||||
public uint NumberParameters;
|
||||
public uint UnusedAlignment;
|
||||
public fixed ulong ExceptionInformation[15];
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Context
|
||||
{
|
||||
// Register parameter home addresses.
|
||||
//
|
||||
// N.B. These fields are for convience - they could be used to extend the
|
||||
// context record in the future.
|
||||
|
||||
public nint P1Home;
|
||||
public nint P2Home;
|
||||
public nint P3Home;
|
||||
public nint P4Home;
|
||||
public nint P5Home;
|
||||
public nint P6Home;
|
||||
|
||||
// Control flags.
|
||||
|
||||
public uint ContextFlags;
|
||||
public uint MxCsr;
|
||||
|
||||
// Segment Registers and processor flags.
|
||||
|
||||
public ushort SegCs;
|
||||
public ushort SegDs;
|
||||
public ushort SegEs;
|
||||
public ushort SegFs;
|
||||
public ushort SegGs;
|
||||
public ushort SegSs;
|
||||
public uint EFlags;
|
||||
|
||||
// Debug registers
|
||||
|
||||
public nint Dr0;
|
||||
public nint Dr1;
|
||||
public nint Dr2;
|
||||
public nint Dr3;
|
||||
public nint Dr6;
|
||||
public nint Dr7;
|
||||
|
||||
// Integer registers.
|
||||
|
||||
public nint Rax;
|
||||
public nint Rcx;
|
||||
public nint Rdx;
|
||||
public nint Rbx;
|
||||
public nint Rsp;
|
||||
public nint Rbp;
|
||||
public nint Rsi;
|
||||
public nint Rdi;
|
||||
public nint R8;
|
||||
public nint R9;
|
||||
public nint R10;
|
||||
public nint R11;
|
||||
public nint R12;
|
||||
public nint R13;
|
||||
public nint R14;
|
||||
public nint R15;
|
||||
|
||||
// Program counter.
|
||||
|
||||
public nint Rip;
|
||||
|
||||
// Floating point state.
|
||||
|
||||
public XmmRegisters FloatRegisters;
|
||||
|
||||
// Vector registers.
|
||||
|
||||
public VectorRegisters VectorRegister;
|
||||
public nint VectorControl;
|
||||
|
||||
// Special debug control registers.
|
||||
|
||||
public nint DebugControl;
|
||||
public nint LastBranchToRip;
|
||||
public nint LastBranchFromRip;
|
||||
public nint LastExceptionToRip;
|
||||
|
||||
public nint LastExceptionFromRip;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct M128A
|
||||
{
|
||||
public ulong Low;
|
||||
public long High;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct VectorRegisters
|
||||
{
|
||||
public M128A V0;
|
||||
public M128A V1;
|
||||
public M128A V2;
|
||||
public M128A V3;
|
||||
public M128A V4;
|
||||
public M128A V5;
|
||||
public M128A V6;
|
||||
public M128A V7;
|
||||
public M128A V8;
|
||||
public M128A V9;
|
||||
public M128A V10;
|
||||
public M128A V11;
|
||||
public M128A V12;
|
||||
public M128A V13;
|
||||
public M128A V14;
|
||||
public M128A V15;
|
||||
public M128A V16;
|
||||
public M128A V17;
|
||||
public M128A V18;
|
||||
public M128A V19;
|
||||
public M128A V20;
|
||||
public M128A V21;
|
||||
public M128A V22;
|
||||
public M128A V23;
|
||||
public M128A V24;
|
||||
public M128A V25;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct XmmRegisters
|
||||
{
|
||||
public readonly M128A Header0;
|
||||
public readonly M128A Header1;
|
||||
|
||||
public M128A Float0;
|
||||
public M128A Float1;
|
||||
public M128A Float2;
|
||||
public M128A Float3;
|
||||
public M128A Float4;
|
||||
public M128A Float5;
|
||||
public M128A Float6;
|
||||
public M128A Float7;
|
||||
|
||||
public M128A Xmm0;
|
||||
public M128A Xmm1;
|
||||
public M128A Xmm2;
|
||||
public M128A Xmm3;
|
||||
public M128A Xmm4;
|
||||
public M128A Xmm5;
|
||||
public M128A Xmm6;
|
||||
public M128A Xmm7;
|
||||
public M128A Xmm8;
|
||||
public M128A Xmm9;
|
||||
public M128A Xmm10;
|
||||
public M128A Xmm11;
|
||||
public M128A Xmm12;
|
||||
public M128A Xmm13;
|
||||
public M128A Xmm14;
|
||||
public M128A Xmm15;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#pragma warning restore SA1600
|
||||
#pragma warning restore SA1602
|
||||
#pragma warning restore SA1201
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue