Merge pull request #665 from pohky/veh

Move the VectoredExceptionHandler to Dalamud.Boot
This commit is contained in:
goaaats 2021-10-31 01:21:03 +02:00 committed by GitHub
commit f58a190b13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 190 additions and 390 deletions

View file

@ -85,6 +85,7 @@
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp" />
<ClCompile Include="..\lib\CoreCLR\pch.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="veh.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\boot.h" />
@ -94,6 +95,7 @@
<ClInclude Include="..\lib\CoreCLR\framework.h" />
<ClInclude Include="..\lib\CoreCLR\nethost\nethost.h" />
<ClInclude Include="..\lib\CoreCLR\pch.h" />
<ClInclude Include="veh.h" />
</ItemGroup>
<ItemGroup>
<Library Include="..\lib\CoreCLR\nethost\libnethost.lib" />
@ -103,4 +105,4 @@
<Delete Files="$(OutDir)$(TargetName).lib" />
<Delete Files="$(OutDir)$(TargetName).exp" />
</Target>
</Project>
</Project>

View file

@ -27,6 +27,9 @@
<ClCompile Include="..\lib\CoreCLR\CoreCLR.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="veh.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\lib\CoreCLR\core\coreclr_delegates.h">
@ -50,6 +53,9 @@
<ClInclude Include="..\lib\CoreCLR\boot.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="veh.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Library Include="..\lib\CoreCLR\nethost\nethost.lib">

View file

@ -5,6 +5,7 @@
#include <Windows.h>
#include "..\lib\CoreCLR\CoreCLR.h"
#include "..\lib\CoreCLR\boot.h"
#include "veh.h"
HMODULE g_hModule;
@ -44,6 +45,12 @@ DllExport DWORD WINAPI Initialize(LPVOID lpParam)
entrypoint_fn(lpParam);
printf("Done!\n");
// ============================== VEH ======================================== //
if (veh::add_handler())
printf("VEH Installed\n");
else printf("Failed to Install VEH\n");
// =========================================================================== //
#ifndef NDEBUG
@ -65,6 +72,8 @@ BOOL APIENTRY DllMain(const HMODULE hModule, const DWORD dwReason, LPVOID lpRese
g_hModule = hModule;
break;
case DLL_PROCESS_DETACH:
// remove the VEH on unload
veh::remove_handler();
break;
}
return TRUE;

163
Dalamud.Boot/veh.cpp Normal file
View file

@ -0,0 +1,163 @@
#define WIN32_LEAN_AND_MEAN
#include "veh.h"
#include <filesystem>
#include <fstream>
#include <Windows.h>
#include <DbgHelp.h>
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<LPCSTR>(address), &handle))
{
if (wchar_t path[1024]; GetModuleFileNameW(handle, path, sizeof path / 2) > 0)
{
*moduleBase = reinterpret_cast<DWORD64>(handle);
moduleFile = path;
return true;
}
}
return false;
}
bool GetCallStack(PEXCEPTION_POINTERS ex, std::vector<DWORD64>& 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<DWORD64>(&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<DWORD64> 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;
}

8
Dalamud.Boot/veh.h Normal file
View file

@ -0,0 +1,8 @@
#pragma once
namespace veh
{
bool add_handler();
bool remove_handler();
void* get_handle();
}

View file

@ -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<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)

View file

@ -1,303 +0,0 @@
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
}
}