From 48f70c134a8fa0a8f303fd374a64540642828df4 Mon Sep 17 00:00:00 2001 From: pohky Date: Sat, 30 Oct 2021 21:10:24 +0200 Subject: [PATCH 1/3] Update dllmain.cpp add a VEH for testing --- Dalamud.Boot/dllmain.cpp | 149 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index b9c27ff16..d94e365de 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -2,11 +2,150 @@ #define DllExport extern "C" __declspec(dllexport) #include +#include #include +#include #include "..\lib\CoreCLR\CoreCLR.h" #include "..\lib\CoreCLR\boot.h" HMODULE g_hModule; +PVOID g_hVEH; // VEH Handle to remove the handler later in DLL_PROCESS_DETACH + +std::vector g_exception_whitelist({ + STATUS_ACCESS_VIOLATION, + STATUS_IN_PAGE_ERROR, + STATUS_INVALID_HANDLE, + STATUS_INVALID_PARAMETER, + STATUS_NO_MEMORY, + STATUS_ILLEGAL_INSTRUCTION, + STATUS_NONCONTINUABLE_EXCEPTION, + STATUS_INVALID_DISPOSITION, + STATUS_ARRAY_BOUNDS_EXCEEDED, + STATUS_FLOAT_DENORMAL_OPERAND, + STATUS_FLOAT_DIVIDE_BY_ZERO, + STATUS_FLOAT_INEXACT_RESULT, + STATUS_FLOAT_INVALID_OPERATION, + STATUS_FLOAT_OVERFLOW, + STATUS_FLOAT_STACK_CHECK, + STATUS_FLOAT_UNDERFLOW, + STATUS_INTEGER_DIVIDE_BY_ZERO, + STATUS_INTEGER_OVERFLOW, + STATUS_PRIVILEGED_INSTRUCTION, + STATUS_STACK_OVERFLOW, + STATUS_DLL_NOT_FOUND, + STATUS_ORDINAL_NOT_FOUND, + STATUS_ENTRYPOINT_NOT_FOUND, + STATUS_DLL_INIT_FAILED, + STATUS_CONTROL_STACK_VIOLATION, + STATUS_FLOAT_MULTIPLE_FAULTS, + STATUS_FLOAT_MULTIPLE_TRAPS, + STATUS_HEAP_CORRUPTION, + STATUS_STACK_BUFFER_OVERRUN, + STATUS_INVALID_CRUNTIME_PARAMETER, + STATUS_THREAD_NOT_RUNNING, + STATUS_ALREADY_REGISTERED +}); + +bool GetModuleFileAndBase(DWORD64 address, PDWORD64 moduleBase, std::filesystem::path& moduleFile) +{ + HMODULE handle; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(address), &handle)) + { + if (wchar_t path[1024]; GetModuleFileNameW(handle, path, sizeof path / 2) > 0) + { + *moduleBase = reinterpret_cast(handle); + moduleFile = path; + return true; + } + } + return false; +} + +bool GetCallStack(PEXCEPTION_POINTERS ex, std::vector& addressList, DWORD frames = 10) +{ + STACKFRAME64 sf; + sf.AddrPC.Offset = ex->ContextRecord->Rip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Offset = ex->ContextRecord->Rsp; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Offset = ex->ContextRecord->Rbp; + sf.AddrFrame.Mode = AddrModeFlat; + CONTEXT ctx = *ex->ContextRecord; + addressList.clear(); + do + { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, &ctx, nullptr, nullptr, nullptr, nullptr)) + return false; + addressList.push_back(sf.AddrPC.Offset); + } while (sf.AddrReturn.Offset != 0 && --frames); + return true; +} + +LONG ExceptionHandler(PEXCEPTION_POINTERS ex) +{ + // return if the exception is not in the whitelist + if (std::ranges::find(g_exception_whitelist, ex->ExceptionRecord->ExceptionCode) == g_exception_whitelist.end()) + return EXCEPTION_CONTINUE_SEARCH; + + DWORD64 module_base; + std::filesystem::path module_path; + + // return if the exception did not happen in ffxiv_dx11.exe + if (!GetModuleFileAndBase(ex->ContextRecord->Rip, &module_base, module_path) || module_path.filename() != L"ffxiv_dx11.exe") + return EXCEPTION_CONTINUE_SEARCH; + + wchar_t boot_mod_path[1024]; + GetModuleFileNameW(g_hModule, boot_mod_path, sizeof boot_mod_path / 2); + std::filesystem::path fs_module_path(boot_mod_path); +#ifndef NDEBUG + std::wstring dmp_path = _wcsdup(fs_module_path.replace_filename(L"dalamud_appcrashd.dmp").wstring().c_str()); +#else + std::wstring dmp_path = _wcsdup(fs_module_path.replace_filename(L"dalamud_appcrash.dmp").wstring().c_str()); +#endif + std::wstring log_path = _wcsdup(fs_module_path.replace_filename(L"dalamud_appcrash.log").wstring().c_str()); + + std::wofstream log; + log.open(log_path, std::ios::app); + + std::wstring time_stamp; + + std::time_t t = std::time(nullptr); + std::tm tm{}; + localtime_s(&tm, &t); + + log << L"[" << std::put_time(&tm, L"%d/%m/%Y %H:%M:%S") << L"] Exception " << std::uppercase << std::hex << ex->ExceptionRecord->ExceptionCode << " at "; + if(GetModuleFileAndBase(ex->ContextRecord->Rip, &module_base, module_path)) + log << module_path.filename() << "+" << std::uppercase << std::hex << ex->ContextRecord->Rip - module_base << std::endl; + else log << std::uppercase << std::hex << ex->ContextRecord->Rip << std::endl; + + std::vector callStack; + + if(GetCallStack(ex, callStack, -1)) + { + log << L"Call Stack:" << std::endl; + for(auto& addr : callStack) + { + if (GetModuleFileAndBase(addr, &module_base, module_path)) + log << L" " << module_path.filename().c_str() << "+" << std::uppercase << std::hex << addr - module_base << std::endl; + else log << L" " << std::uppercase << std::hex << addr << std::endl; + } + } + + MINIDUMP_EXCEPTION_INFORMATION ex_info; + ex_info.ClientPointers = true; + ex_info.ExceptionPointers = ex; + ex_info.ThreadId = GetCurrentThreadId(); + + auto file = CreateFileW(dmp_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpWithDataSegs, &ex_info, nullptr, nullptr); + CloseHandle(file); + + log << "Crash Dump: " << dmp_path << std::endl; + + log.close(); + + return EXCEPTION_CONTINUE_SEARCH; +} DllExport DWORD WINAPI Initialize(LPVOID lpParam) { @@ -44,6 +183,13 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam) entrypoint_fn(lpParam); printf("Done!\n"); + // ============================== VEH ======================================== // + + g_hVEH = AddVectoredExceptionHandler(0, ExceptionHandler); + if (g_hVEH) + printf("VEH Installed [%p]\n", g_hVEH); + else printf("Failed to Install VEH\n"); + // =========================================================================== // #ifndef NDEBUG @@ -65,6 +211,9 @@ BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpRese g_hModule = hModule; break; case DLL_PROCESS_DETACH: + // remove the VEH on unload + if (g_hVEH) + RemoveVectoredExceptionHandler(g_hVEH); break; } return TRUE; From 5015196bd34ea353333d212bee51b91a75ecd1fc Mon Sep 17 00:00:00 2001 From: pohky Date: Sat, 30 Oct 2021 21:18:25 +0200 Subject: [PATCH 2/3] Remove the broken VEH --- Dalamud/EntryPoint.cs | 87 +----------- Dalamud/VehManager.cs | 303 ------------------------------------------ 2 files changed, 1 insertion(+), 389 deletions(-) delete mode 100644 Dalamud/VehManager.cs diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 89f7aae37..a94d5ebc6 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -71,8 +71,6 @@ 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; @@ -84,7 +82,6 @@ namespace Dalamud dalamud.WaitForUnload(); dalamud.Dispose(); - vehManager.Dispose(); } catch (Exception ex) { @@ -195,89 +192,7 @@ 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().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.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) diff --git a/Dalamud/VehManager.cs b/Dalamud/VehManager.cs deleted file mode 100644 index b3adc99c1..000000000 --- a/Dalamud/VehManager.cs +++ /dev/null @@ -1,303 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Dalamud -{ - /// - /// Class encapsulating a vectored exception handler. - /// - internal unsafe class VehManager - { - /// - /// Execute the exception handler. - /// - public const int ExceptionExecuteHandler = 1; - - /// - /// Continue the search for another handler. - /// - public const int ExceptionContinueSearch = 0; - - /// - /// Continue execution after the handler. - /// - public const int ExceptionContinueExecution = -1; - - private VectoredExceptionHandler? myHandler; // to keep a reference, just in case, idk if it's needed - - /// - /// Initializes a new instance of the class. - /// - /// Position of this VEH. 0 = Last, 1 = First. - /// The handler to register. - public VehManager(uint first, VectoredExceptionHandler myHandler) - { - this.myHandler = myHandler; - this.Handle = AddVectoredExceptionHandler(first, this.myHandler); - } - - /// - /// VEH Delegate. - /// - /// Exception information. - /// Code that determines which action to take. - public delegate int VectoredExceptionHandler(ref ExceptionPointers ex); - - /// - /// Gets the handle to the VEH. - /// - public nint Handle { get; private set; } - - /// - /// Dispose and remove this VEH. - /// - 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 - } -} From e80a24fb97f6eada2acd0168dfd23a64d478227e Mon Sep 17 00:00:00 2001 From: pohky Date: Sat, 30 Oct 2021 23:02:18 +0200 Subject: [PATCH 3/3] move the VEH out of dllmain --- Dalamud.Boot/Dalamud.Boot.vcxproj | 4 +- Dalamud.Boot/Dalamud.Boot.vcxproj.filters | 6 + Dalamud.Boot/dllmain.cpp | 148 +------------------- Dalamud.Boot/veh.cpp | 163 ++++++++++++++++++++++ Dalamud.Boot/veh.h | 8 ++ 5 files changed, 184 insertions(+), 145 deletions(-) create mode 100644 Dalamud.Boot/veh.cpp create mode 100644 Dalamud.Boot/veh.h diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj index 95eb9d12c..957bc4c10 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj @@ -85,6 +85,7 @@ + @@ -94,6 +95,7 @@ + @@ -103,4 +105,4 @@ - + \ No newline at end of file diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters index afcc6e502..e357b90ea 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters @@ -27,6 +27,9 @@ Source Files + + Source Files + @@ -50,6 +53,9 @@ Header Files + + Header Files + diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index d94e365de..62673173e 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -2,150 +2,12 @@ #define DllExport extern "C" __declspec(dllexport) #include -#include #include -#include #include "..\lib\CoreCLR\CoreCLR.h" #include "..\lib\CoreCLR\boot.h" +#include "veh.h" HMODULE g_hModule; -PVOID g_hVEH; // VEH Handle to remove the handler later in DLL_PROCESS_DETACH - -std::vector g_exception_whitelist({ - STATUS_ACCESS_VIOLATION, - STATUS_IN_PAGE_ERROR, - STATUS_INVALID_HANDLE, - STATUS_INVALID_PARAMETER, - STATUS_NO_MEMORY, - STATUS_ILLEGAL_INSTRUCTION, - STATUS_NONCONTINUABLE_EXCEPTION, - STATUS_INVALID_DISPOSITION, - STATUS_ARRAY_BOUNDS_EXCEEDED, - STATUS_FLOAT_DENORMAL_OPERAND, - STATUS_FLOAT_DIVIDE_BY_ZERO, - STATUS_FLOAT_INEXACT_RESULT, - STATUS_FLOAT_INVALID_OPERATION, - STATUS_FLOAT_OVERFLOW, - STATUS_FLOAT_STACK_CHECK, - STATUS_FLOAT_UNDERFLOW, - STATUS_INTEGER_DIVIDE_BY_ZERO, - STATUS_INTEGER_OVERFLOW, - STATUS_PRIVILEGED_INSTRUCTION, - STATUS_STACK_OVERFLOW, - STATUS_DLL_NOT_FOUND, - STATUS_ORDINAL_NOT_FOUND, - STATUS_ENTRYPOINT_NOT_FOUND, - STATUS_DLL_INIT_FAILED, - STATUS_CONTROL_STACK_VIOLATION, - STATUS_FLOAT_MULTIPLE_FAULTS, - STATUS_FLOAT_MULTIPLE_TRAPS, - STATUS_HEAP_CORRUPTION, - STATUS_STACK_BUFFER_OVERRUN, - STATUS_INVALID_CRUNTIME_PARAMETER, - STATUS_THREAD_NOT_RUNNING, - STATUS_ALREADY_REGISTERED -}); - -bool GetModuleFileAndBase(DWORD64 address, PDWORD64 moduleBase, std::filesystem::path& moduleFile) -{ - HMODULE handle; - if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(address), &handle)) - { - if (wchar_t path[1024]; GetModuleFileNameW(handle, path, sizeof path / 2) > 0) - { - *moduleBase = reinterpret_cast(handle); - moduleFile = path; - return true; - } - } - return false; -} - -bool GetCallStack(PEXCEPTION_POINTERS ex, std::vector& addressList, DWORD frames = 10) -{ - STACKFRAME64 sf; - sf.AddrPC.Offset = ex->ContextRecord->Rip; - sf.AddrPC.Mode = AddrModeFlat; - sf.AddrStack.Offset = ex->ContextRecord->Rsp; - sf.AddrStack.Mode = AddrModeFlat; - sf.AddrFrame.Offset = ex->ContextRecord->Rbp; - sf.AddrFrame.Mode = AddrModeFlat; - CONTEXT ctx = *ex->ContextRecord; - addressList.clear(); - do - { - if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, &ctx, nullptr, nullptr, nullptr, nullptr)) - return false; - addressList.push_back(sf.AddrPC.Offset); - } while (sf.AddrReturn.Offset != 0 && --frames); - return true; -} - -LONG ExceptionHandler(PEXCEPTION_POINTERS ex) -{ - // return if the exception is not in the whitelist - if (std::ranges::find(g_exception_whitelist, ex->ExceptionRecord->ExceptionCode) == g_exception_whitelist.end()) - return EXCEPTION_CONTINUE_SEARCH; - - DWORD64 module_base; - std::filesystem::path module_path; - - // return if the exception did not happen in ffxiv_dx11.exe - if (!GetModuleFileAndBase(ex->ContextRecord->Rip, &module_base, module_path) || module_path.filename() != L"ffxiv_dx11.exe") - return EXCEPTION_CONTINUE_SEARCH; - - wchar_t boot_mod_path[1024]; - GetModuleFileNameW(g_hModule, boot_mod_path, sizeof boot_mod_path / 2); - std::filesystem::path fs_module_path(boot_mod_path); -#ifndef NDEBUG - std::wstring dmp_path = _wcsdup(fs_module_path.replace_filename(L"dalamud_appcrashd.dmp").wstring().c_str()); -#else - std::wstring dmp_path = _wcsdup(fs_module_path.replace_filename(L"dalamud_appcrash.dmp").wstring().c_str()); -#endif - std::wstring log_path = _wcsdup(fs_module_path.replace_filename(L"dalamud_appcrash.log").wstring().c_str()); - - std::wofstream log; - log.open(log_path, std::ios::app); - - std::wstring time_stamp; - - std::time_t t = std::time(nullptr); - std::tm tm{}; - localtime_s(&tm, &t); - - log << L"[" << std::put_time(&tm, L"%d/%m/%Y %H:%M:%S") << L"] Exception " << std::uppercase << std::hex << ex->ExceptionRecord->ExceptionCode << " at "; - if(GetModuleFileAndBase(ex->ContextRecord->Rip, &module_base, module_path)) - log << module_path.filename() << "+" << std::uppercase << std::hex << ex->ContextRecord->Rip - module_base << std::endl; - else log << std::uppercase << std::hex << ex->ContextRecord->Rip << std::endl; - - std::vector callStack; - - if(GetCallStack(ex, callStack, -1)) - { - log << L"Call Stack:" << std::endl; - for(auto& addr : callStack) - { - if (GetModuleFileAndBase(addr, &module_base, module_path)) - log << L" " << module_path.filename().c_str() << "+" << std::uppercase << std::hex << addr - module_base << std::endl; - else log << L" " << std::uppercase << std::hex << addr << std::endl; - } - } - - MINIDUMP_EXCEPTION_INFORMATION ex_info; - ex_info.ClientPointers = true; - ex_info.ExceptionPointers = ex; - ex_info.ThreadId = GetCurrentThreadId(); - - auto file = CreateFileW(dmp_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpWithDataSegs, &ex_info, nullptr, nullptr); - CloseHandle(file); - - log << "Crash Dump: " << dmp_path << std::endl; - - log.close(); - - return EXCEPTION_CONTINUE_SEARCH; -} DllExport DWORD WINAPI Initialize(LPVOID lpParam) { @@ -185,9 +47,8 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam) // ============================== VEH ======================================== // - g_hVEH = AddVectoredExceptionHandler(0, ExceptionHandler); - if (g_hVEH) - printf("VEH Installed [%p]\n", g_hVEH); + if (veh::add_handler()) + printf("VEH Installed\n"); else printf("Failed to Install VEH\n"); // =========================================================================== // @@ -212,8 +73,7 @@ BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpRese break; case DLL_PROCESS_DETACH: // remove the VEH on unload - if (g_hVEH) - RemoveVectoredExceptionHandler(g_hVEH); + veh::remove_handler(); break; } return TRUE; diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp new file mode 100644 index 000000000..273ac001f --- /dev/null +++ b/Dalamud.Boot/veh.cpp @@ -0,0 +1,163 @@ +#define WIN32_LEAN_AND_MEAN +#include "veh.h" +#include +#include +#include +#include + +std::vector g_exception_whitelist({ + STATUS_ACCESS_VIOLATION, + STATUS_IN_PAGE_ERROR, + STATUS_INVALID_HANDLE, + STATUS_INVALID_PARAMETER, + STATUS_NO_MEMORY, + STATUS_ILLEGAL_INSTRUCTION, + STATUS_NONCONTINUABLE_EXCEPTION, + STATUS_INVALID_DISPOSITION, + STATUS_ARRAY_BOUNDS_EXCEEDED, + STATUS_FLOAT_DENORMAL_OPERAND, + STATUS_FLOAT_DIVIDE_BY_ZERO, + STATUS_FLOAT_INEXACT_RESULT, + STATUS_FLOAT_INVALID_OPERATION, + STATUS_FLOAT_OVERFLOW, + STATUS_FLOAT_STACK_CHECK, + STATUS_FLOAT_UNDERFLOW, + STATUS_INTEGER_DIVIDE_BY_ZERO, + STATUS_INTEGER_OVERFLOW, + STATUS_PRIVILEGED_INSTRUCTION, + STATUS_STACK_OVERFLOW, + STATUS_DLL_NOT_FOUND, + STATUS_ORDINAL_NOT_FOUND, + STATUS_ENTRYPOINT_NOT_FOUND, + STATUS_DLL_INIT_FAILED, + STATUS_CONTROL_STACK_VIOLATION, + STATUS_FLOAT_MULTIPLE_FAULTS, + STATUS_FLOAT_MULTIPLE_TRAPS, + STATUS_HEAP_CORRUPTION, + STATUS_STACK_BUFFER_OVERRUN, + STATUS_INVALID_CRUNTIME_PARAMETER, + STATUS_THREAD_NOT_RUNNING, + STATUS_ALREADY_REGISTERED + }); + +bool GetModuleFileAndBase(DWORD64 address, PDWORD64 moduleBase, std::filesystem::path& moduleFile) +{ + HMODULE handle; + if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(address), &handle)) + { + if (wchar_t path[1024]; GetModuleFileNameW(handle, path, sizeof path / 2) > 0) + { + *moduleBase = reinterpret_cast(handle); + moduleFile = path; + return true; + } + } + return false; +} + +bool GetCallStack(PEXCEPTION_POINTERS ex, std::vector& addressList, DWORD frames = 10) +{ + STACKFRAME64 sf; + sf.AddrPC.Offset = ex->ContextRecord->Rip; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrStack.Offset = ex->ContextRecord->Rsp; + sf.AddrStack.Mode = AddrModeFlat; + sf.AddrFrame.Offset = ex->ContextRecord->Rbp; + sf.AddrFrame.Mode = AddrModeFlat; + CONTEXT ctx = *ex->ContextRecord; + addressList.clear(); + do + { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, &ctx, nullptr, nullptr, nullptr, nullptr)) + return false; + addressList.push_back(sf.AddrPC.Offset); + } while (sf.AddrReturn.Offset != 0 && --frames); + return true; +} + +LONG ExceptionHandler(PEXCEPTION_POINTERS ex) +{ + // return if the exception is not in the whitelist + if (std::ranges::find(g_exception_whitelist, ex->ExceptionRecord->ExceptionCode) == g_exception_whitelist.end()) + return EXCEPTION_CONTINUE_SEARCH; + + DWORD64 module_base; + std::filesystem::path module_path; + + // return if the exception did not happen in ffxiv_dx11.exe + if (!GetModuleFileAndBase(ex->ContextRecord->Rip, &module_base, module_path) || module_path.filename() != L"ffxiv_dx11.exe") + return EXCEPTION_CONTINUE_SEARCH; + + GetModuleFileAndBase(reinterpret_cast(&ExceptionHandler), &module_base, module_path); +#ifndef NDEBUG + std::wstring dmp_path = _wcsdup(module_path.replace_filename(L"dalamud_appcrashd.dmp").wstring().c_str()); +#else + std::wstring dmp_path = _wcsdup(module_path.replace_filename(L"dalamud_appcrash.dmp").wstring().c_str()); +#endif + std::wstring log_path = _wcsdup(module_path.replace_filename(L"dalamud_appcrash.log").wstring().c_str()); + + std::wofstream log; + log.open(log_path, std::ios::app); + + std::wstring time_stamp; + + std::time_t t = std::time(nullptr); + std::tm tm{}; + localtime_s(&tm, &t); + + log << L"[" << std::put_time(&tm, L"%d/%m/%Y %H:%M:%S") << L"] Exception " << std::uppercase << std::hex << ex->ExceptionRecord->ExceptionCode << " at "; + if (GetModuleFileAndBase(ex->ContextRecord->Rip, &module_base, module_path)) + log << module_path.filename() << "+" << std::uppercase << std::hex << ex->ContextRecord->Rip - module_base << std::endl; + else log << std::uppercase << std::hex << ex->ContextRecord->Rip << std::endl; + + if (std::vector call_stack; GetCallStack(ex, call_stack, -1)) + { + log << L"Call Stack:" << std::endl; + for (auto& addr : call_stack) + { + if (GetModuleFileAndBase(addr, &module_base, module_path)) + log << L" " << module_path.filename().c_str() << "+" << std::uppercase << std::hex << addr - module_base << std::endl; + else log << L" " << std::uppercase << std::hex << addr << std::endl; + } + } + + MINIDUMP_EXCEPTION_INFORMATION ex_info; + ex_info.ClientPointers = true; + ex_info.ExceptionPointers = ex; + ex_info.ThreadId = GetCurrentThreadId(); + + auto file = CreateFileW(dmp_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpWithDataSegs, &ex_info, nullptr, nullptr); + CloseHandle(file); + + log << "Crash Dump: " << dmp_path << std::endl; + + log.close(); + + return EXCEPTION_CONTINUE_SEARCH; +} + +PVOID g_hVEH; // VEH Handle to remove the handler later in DLL_PROCESS_DETACH + +bool veh::add_handler() +{ + if (g_hVEH) + return false; + g_hVEH = AddVectoredExceptionHandler(0, ExceptionHandler); + return g_hVEH != nullptr; +} + +bool veh::remove_handler() +{ + if (g_hVEH && RemoveVectoredExceptionHandler(g_hVEH) != 0) + { + g_hVEH = nullptr; + return true; + } + return false; +} + +void* veh::get_handle() +{ + return g_hVEH; +} diff --git a/Dalamud.Boot/veh.h b/Dalamud.Boot/veh.h new file mode 100644 index 000000000..ae199d70c --- /dev/null +++ b/Dalamud.Boot/veh.h @@ -0,0 +1,8 @@ +#pragma once + +namespace veh +{ + bool add_handler(); + bool remove_handler(); + void* get_handle(); +}