Report CLR errors through DalamudCrashHandler/VEH by hooking ReportEventW

This commit is contained in:
goaaats 2025-12-06 15:07:09 +01:00
parent ddc743aae1
commit 9c2d2b7c1d
3 changed files with 71 additions and 8 deletions

View file

@ -331,6 +331,47 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
logging::I("VEH was disabled manually"); logging::I("VEH was disabled manually");
} }
// ============================== CLR Reporting =================================== //
// This is pretty horrible - CLR just doesn't provide a way for us to handle these events, and the API for it
// was pushed back to .NET 11, so we have to hook ReportEventW and catch them ourselves for now.
// Ideally all of this will go away once they get to it.
static std::shared_ptr<hooks::global_import_hook<decltype(ReportEventW)>> s_hook;
s_hook = std::make_shared<hooks::global_import_hook<decltype(ReportEventW)>>("advapi32.dll!ReportEventW (global import, hook_clr_report_event)", L"advapi32.dll", "ReportEventW");
s_hook->set_detour([hook = s_hook.get()]( HANDLE hEventLog,
WORD wType,
WORD wCategory,
DWORD dwEventID,
PSID lpUserSid,
WORD wNumStrings,
DWORD dwDataSize,
LPCWSTR* lpStrings,
LPVOID lpRawData)->BOOL {
// Check for CLR Error Event IDs
if (dwEventID != 1026 && // ERT_UnhandledException: The process was terminated due to an unhandled exception
dwEventID != 1025 && // ERT_ManagedFailFast: The application requested process termination through System.Environment.FailFast
dwEventID != 1023 && // ERT_UnmanagedFailFast: The process was terminated due to an internal error in the .NET Runtime
dwEventID != 1027 && // ERT_StackOverflow: The process was terminated due to a stack overflow
dwEventID != 1028) // ERT_CodeContractFailed: The application encountered a bug. A managed code contract (precondition, postcondition, object invariant, or assert) failed
{
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
}
if (wNumStrings == 0 || lpStrings == nullptr) {
logging::W("ReportEventW called with no strings.");
return hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
}
// In most cases, DalamudCrashHandler will kill us now, so call original here to make sure we still write to the event log.
const BOOL original_ret = hook->call_original(hEventLog, wType, wCategory, dwEventID, lpUserSid, wNumStrings, dwDataSize, lpStrings, lpRawData);
const std::wstring error_details(lpStrings[0]);
veh::raise_external_event(error_details);
return original_ret;
});
// ============================== Dalamud ==================================== // // ============================== Dalamud ==================================== //
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint)) if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint))

View file

@ -11,6 +11,8 @@
#include "crashhandler_shared.h" #include "crashhandler_shared.h"
#include "DalamudStartInfo.h" #include "DalamudStartInfo.h"
#define CUSTOM_EXCEPTION_EXTERNAL_EVENT 0x12345679
#pragma comment(lib, "comctl32.lib") #pragma comment(lib, "comctl32.lib")
#if defined _M_IX86 #if defined _M_IX86
@ -31,6 +33,8 @@ HANDLE g_crashhandler_process = nullptr;
HANDLE g_crashhandler_event = nullptr; HANDLE g_crashhandler_event = nullptr;
HANDLE g_crashhandler_pipe_write = nullptr; HANDLE g_crashhandler_pipe_write = nullptr;
wchar_t g_external_event_info[16384] = L"";
std::recursive_mutex g_exception_handler_mutex; std::recursive_mutex g_exception_handler_mutex;
std::chrono::time_point<std::chrono::system_clock> g_time_start; std::chrono::time_point<std::chrono::system_clock> g_time_start;
@ -190,7 +194,11 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS); DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
std::wstring stackTrace; std::wstring stackTrace;
if (!g_clr) if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
{
stackTrace = std::wstring(g_external_event_info);
}
else if (!g_clr)
{ {
stackTrace = L"(no CLR stack trace available)"; stackTrace = L"(no CLR stack trace available)";
} }
@ -251,6 +259,12 @@ LONG WINAPI structured_exception_handler(EXCEPTION_POINTERS* ex)
LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex) LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
{ {
// special case for CLR exceptions, always trigger crash handler
if (ex->ExceptionRecord->ExceptionCode == CUSTOM_EXCEPTION_EXTERNAL_EVENT)
{
return exception_handler(ex);
}
if (ex->ExceptionRecord->ExceptionCode == 0x12345678) if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
{ {
// pass // pass
@ -268,7 +282,7 @@ LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ex)
if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) && if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) &&
!is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip)) !is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip))
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
return exception_handler(ex); return exception_handler(ex);
@ -297,7 +311,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
if (HANDLE hReadPipeRaw, hWritePipeRaw; CreatePipe(&hReadPipeRaw, &hWritePipeRaw, nullptr, 65536)) if (HANDLE hReadPipeRaw, hWritePipeRaw; CreatePipe(&hReadPipeRaw, &hWritePipeRaw, nullptr, 65536))
{ {
hWritePipe.emplace(hWritePipeRaw, &CloseHandle); hWritePipe.emplace(hWritePipeRaw, &CloseHandle);
if (HANDLE hReadPipeInheritableRaw; DuplicateHandle(GetCurrentProcess(), hReadPipeRaw, GetCurrentProcess(), &hReadPipeInheritableRaw, 0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) if (HANDLE hReadPipeInheritableRaw; DuplicateHandle(GetCurrentProcess(), hReadPipeRaw, GetCurrentProcess(), &hReadPipeInheritableRaw, 0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
{ {
hReadPipeInheritable.emplace(hReadPipeInheritableRaw, &CloseHandle); hReadPipeInheritable.emplace(hReadPipeInheritableRaw, &CloseHandle);
@ -315,9 +329,9 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
} }
// additional information // additional information
STARTUPINFOEXW siex{}; STARTUPINFOEXW siex{};
PROCESS_INFORMATION pi{}; PROCESS_INFORMATION pi{};
siex.StartupInfo.cb = sizeof siex; siex.StartupInfo.cb = sizeof siex;
siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW; siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
siex.StartupInfo.wShowWindow = g_startInfo.CrashHandlerShow ? SW_SHOW : SW_HIDE; siex.StartupInfo.wShowWindow = g_startInfo.CrashHandlerShow ? SW_SHOW : SW_HIDE;
@ -385,7 +399,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
argstr.push_back(L' '); argstr.push_back(L' ');
} }
argstr.pop_back(); argstr.pop_back();
if (!handles.empty() && !UpdateProcThreadAttribute(siex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handles[0], std::span(handles).size_bytes(), nullptr, nullptr)) if (!handles.empty() && !UpdateProcThreadAttribute(siex.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, &handles[0], std::span(handles).size_bytes(), nullptr, nullptr))
{ {
logging::W("Failed to launch DalamudCrashHandler.exe: UpdateProcThreadAttribute error 0x{:x}", GetLastError()); logging::W("Failed to launch DalamudCrashHandler.exe: UpdateProcThreadAttribute error 0x{:x}", GetLastError());
@ -400,7 +414,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
TRUE, // Set handle inheritance to FALSE TRUE, // Set handle inheritance to FALSE
EXTENDED_STARTUPINFO_PRESENT, // lpStartupInfo actually points to a STARTUPINFOEX(W) EXTENDED_STARTUPINFO_PRESENT, // lpStartupInfo actually points to a STARTUPINFOEX(W)
nullptr, // Use parent's environment block nullptr, // Use parent's environment block
nullptr, // Use parent's starting directory nullptr, // Use parent's starting directory
&siex.StartupInfo, // Pointer to STARTUPINFO structure &siex.StartupInfo, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses) &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
)) ))
@ -416,7 +430,7 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
} }
CloseHandle(pi.hThread); CloseHandle(pi.hThread);
g_crashhandler_process = pi.hProcess; g_crashhandler_process = pi.hProcess;
g_crashhandler_pipe_write = hWritePipe->release(); g_crashhandler_pipe_write = hWritePipe->release();
logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId); logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId);
@ -434,3 +448,10 @@ bool veh::remove_handler()
} }
return false; return false;
} }
void veh::raise_external_event(const std::wstring& errorMessage)
{
const auto info_size = std::min(errorMessage.size(), std::size(g_external_event_info) - 1);
wcsncpy_s(g_external_event_info, errorMessage.c_str(), info_size);
RaiseException(CUSTOM_EXCEPTION_EXTERNAL_EVENT, 0, 0, nullptr);
}

View file

@ -4,4 +4,5 @@ namespace veh
{ {
bool add_handler(bool doFullDump, const std::string& workingDirectory); bool add_handler(bool doFullDump, const std::string& workingDirectory);
bool remove_handler(); bool remove_handler();
void raise_external_event(const std::wstring& info);
} }