diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj b/Dalamud.Boot/Dalamud.Boot.vcxproj index 3733fe388..77783e269 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj @@ -162,6 +162,7 @@ + @@ -182,4 +183,4 @@ - + \ No newline at end of file diff --git a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters index 27483eeed..8b4483684 100644 --- a/Dalamud.Boot/Dalamud.Boot.vcxproj.filters +++ b/Dalamud.Boot/Dalamud.Boot.vcxproj.filters @@ -139,6 +139,7 @@ MinHook + @@ -146,4 +147,4 @@ - + \ No newline at end of file diff --git a/Dalamud.Boot/crashhandler_shared.h b/Dalamud.Boot/crashhandler_shared.h new file mode 100644 index 000000000..0c1649cc4 --- /dev/null +++ b/Dalamud.Boot/crashhandler_shared.h @@ -0,0 +1,15 @@ +#pragma once + +#include "windows.h" + +struct exception_info +{ + void* ExceptionPointers; // Cannot dereference! + DWORD ThreadId; + DWORD ProcessId; + BOOL DoFullDump; + wchar_t DumpPath[1000]; +}; + +constexpr wchar_t SHARED_INFO_FILE_NAME[] = L"DalamudCrashInfoShare"; +constexpr wchar_t CRASHDUMP_EVENT_NAME[] = L"Global\\DalamudRequestWriteDump"; diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index 5a2665f09..d5660e976 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -136,7 +136,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { if (utils::is_running_on_linux()) { logging::I("=> VEH was disabled, running on linux"); } else if (g_startInfo.BootVehEnabled) { - if (veh::add_handler(g_startInfo.BootVehFull)) + if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory)) logging::I("=> Done!"); else logging::I("=> Failed!"); diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index 1ab0e5c9b..b29c38b07 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -9,6 +9,8 @@ #include "logging.h" #include "utils.h" +#include "crashhandler_shared.h" + #pragma comment(lib, "comctl32.lib") #if defined _M_IX86 @@ -24,6 +26,9 @@ PVOID g_veh_handle = nullptr; bool g_veh_do_full_dump = false; +exception_info* g_crashhandler_shared_info; +HANDLE g_crashhandler_event; + bool is_whitelist_exception(const DWORD code) { switch (code) @@ -292,14 +297,19 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) ex_info.ClientPointers = false; ex_info.ExceptionPointers = ex; ex_info.ThreadId = GetCurrentThreadId(); - - auto miniDumpType = MiniDumpWithDataSegs; - if (g_veh_do_full_dump) - miniDumpType = MiniDumpWithFullMemory; - HANDLE file = CreateFileW(dmp_path.c_str(), GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); - //MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, miniDumpType, &ex_info, nullptr, nullptr); - CloseHandle(file); + if (g_crashhandler_shared_info && g_crashhandler_event) + { + memset(g_crashhandler_shared_info, 0, sizeof(exception_info)); + + wcsncpy_s(g_crashhandler_shared_info->DumpPath, dmp_path.c_str(), 1000); + g_crashhandler_shared_info->ThreadId = GetThreadId(GetCurrentThread()); + g_crashhandler_shared_info->ProcessId = GetCurrentProcessId(); + g_crashhandler_shared_info->ExceptionPointers = ex; + g_crashhandler_shared_info->DoFullDump = g_veh_do_full_dump; + + SetEvent(g_crashhandler_event); + } std::wstring message; void* fn; @@ -384,7 +394,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex) return EXCEPTION_CONTINUE_SEARCH; } -bool veh::add_handler(bool doFullDump) +bool veh::add_handler(bool doFullDump, std::string workingDirectory) { if (g_veh_handle) return false; @@ -393,6 +403,58 @@ bool veh::add_handler(bool doFullDump) g_veh_do_full_dump = doFullDump; + auto file_mapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(exception_info), SHARED_INFO_FILE_NAME); + if (!file_mapping) { + std::cout << "Could not map info share file.\n"; + g_crashhandler_shared_info = nullptr; + } + else + { + g_crashhandler_shared_info = (exception_info*)MapViewOfFile(file_mapping, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(exception_info)); + if (!g_crashhandler_shared_info) { + std::cout << "Could not map view of info share file.\n"; + } + } + + g_crashhandler_event = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + CRASHDUMP_EVENT_NAME // object name + ); + + if (!g_crashhandler_event) + { + std::cout << "Couldn't acquire event handle\n"; + } + + auto handler_path = std::filesystem::path(workingDirectory) / "DalamudCrashHandler.exe"; + + // additional information + STARTUPINFO si; + PROCESS_INFORMATION pi; + + // set the size of the structures + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + CreateProcess( handler_path.c_str(), // the path + NULL, // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + 0, // No creation flags + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses) + ); + + // Close process and thread handles. + CloseHandle( pi.hProcess ); + CloseHandle( pi.hThread ); + return g_veh_handle != nullptr; } diff --git a/Dalamud.Boot/veh.h b/Dalamud.Boot/veh.h index bf0c549f3..7820d6982 100644 --- a/Dalamud.Boot/veh.h +++ b/Dalamud.Boot/veh.h @@ -2,6 +2,6 @@ namespace veh { - bool add_handler(bool doFullDump); + bool add_handler(bool doFullDump, std::string workingDirectory); bool remove_handler(); } diff --git a/Dalamud.sln b/Dalamud.sln index 546e31dfe..a9cdf123d 100644 --- a/Dalamud.sln +++ b/Dalamud.sln @@ -34,6 +34,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs", "lib\F EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FFXIVClientStructs.Generators", "lib\FFXIVClientStructs\FFXIVClientStructs.Generators\FFXIVClientStructs.Generators.csproj", "{05AB2F46-268B-4915-806F-DDF813E2D59D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DalamudCrashHandler", "DalamudCrashHandler\DalamudCrashHandler.vcxproj", "{317A264C-920B-44A1-8A34-F3A6827B0705}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -186,6 +188,18 @@ Global {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x64.Build.0 = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.ActiveCfg = Release|Any CPU {05AB2F46-268B-4915-806F-DDF813E2D59D}.Release|x86.Build.0 = Release|Any CPU + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.ActiveCfg = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|Any CPU.Build.0 = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.ActiveCfg = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x64.Build.0 = Debug|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.ActiveCfg = Debug|Win32 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Debug|x86.Build.0 = Debug|Win32 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.ActiveCfg = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|Any CPU.Build.0 = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.ActiveCfg = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x64.Build.0 = Release|x64 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.ActiveCfg = Release|Win32 + {317A264C-920B-44A1-8A34-F3A6827B0705}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp new file mode 100644 index 000000000..e7bc79055 --- /dev/null +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include + +#include "../Dalamud.Boot/crashhandler_shared.h" + +DWORD WINAPI MyThreadFunction(LPVOID lpParam) +{ + while (true) + { + PROCESSENTRY32 entry; + entry.dwSize = sizeof(PROCESSENTRY32); + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + + if (Process32First(snapshot, &entry) == TRUE) + { + bool had_xiv = false; + + while (Process32Next(snapshot, &entry) == TRUE) + { + // Exit if there's another crash handler + // TODO(goat): We should make this more robust and ensure that there is one per PID + if (_wcsicmp(entry.szExeFile, L"DalamudCrashHandler.exe") == 0 && + entry.th32ProcessID != GetCurrentProcessId()) + { + ExitProcess(0); + break; + } + + if (_wcsicmp(entry.szExeFile, L"ffxiv_dx11.exe") == 0) + { + had_xiv = true; + } + } + + if (!had_xiv) + { + ExitProcess(0); + break; + } + } + + CloseHandle(snapshot); + + Sleep(1000); + } +} + +int main() +{ + CreateThread( + NULL, // default security attributes + 0, // use default stack size + MyThreadFunction, // thread function name + NULL, // argument to thread function + 0, // use default creation flags + NULL); // returns the thread identifier + + auto file_mapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(exception_info), SHARED_INFO_FILE_NAME); + if (!file_mapping) { + std::cout << "Could not map info share file.\n"; + return -2; + } + + auto file_ptr = MapViewOfFile(file_mapping, FILE_MAP_READ, 0, 0, sizeof(exception_info)); + if (!file_ptr) { + std::cout << "Could not map view of info share file.\n"; + return -3; + } + + std::cout << "Waiting for crash...\n"; + + auto crash_event = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + CRASHDUMP_EVENT_NAME // object name + ); + + if (!crash_event) + { + std::cout << "Couldn't acquire event handle\n"; + return -1; + } + + auto wait_result = WaitForSingleObject(crash_event, INFINITE); + std::cout << "Crash triggered, writing dump!\n"; + + auto info_share = (exception_info*)file_ptr; + + if (!info_share->ExceptionPointers) + { + std::cout << "info_share->ExceptionPointers was nullptr\n"; + return -4; + } + + MINIDUMP_EXCEPTION_INFORMATION mdmp_info; + mdmp_info.ClientPointers = true; + mdmp_info.ExceptionPointers = (PEXCEPTION_POINTERS)info_share->ExceptionPointers; + mdmp_info.ThreadId = info_share->ThreadId; + + + std::cout << "Dump for " << info_share->ProcessId << std::endl; + HANDLE file = CreateFileW(info_share->DumpPath, GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); + if (!file) + { + auto hr = GetLastError(); + std::cout << "Failed to open dump file: " << std::hex << hr << std::endl; + return -6; + } + + auto process = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION, FALSE, info_share->ProcessId); + if (!process) + { + auto hr = GetLastError(); + std::cout << "Failed to open " << info_share->ProcessId << ": " << std::hex << hr << std::endl; + return -6; + } + + auto success = MiniDumpWriteDump(process, info_share->ProcessId, file, MiniDumpWithFullMemory, &mdmp_info, NULL, NULL); + if (!success) + { + auto hr = GetLastError(); + std::cout << "Failed: " << std::hex << hr << std::endl; + } + + // TODO(goat): Technically, we should have another event or a semaphore to block xiv while dumping... + + CloseHandle(file); + CloseHandle(process); + CloseHandle(file_mapping); + + return 0; +} diff --git a/DalamudCrashHandler/DalamudCrashHandler.rc b/DalamudCrashHandler/DalamudCrashHandler.rc new file mode 100644 index 000000000..daa41a282 --- /dev/null +++ b/DalamudCrashHandler/DalamudCrashHandler.rc @@ -0,0 +1,71 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United Kingdom) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON1 ICON "dalamud.ico" + +#endif // English (United Kingdom) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj b/DalamudCrashHandler/DalamudCrashHandler.vcxproj new file mode 100644 index 000000000..b56628f6a --- /dev/null +++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj @@ -0,0 +1,93 @@ + + + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {317a264c-920b-44a1-8a34-f3a6827b0705} + DalamudCrashHandler + 10.0 + ..\bin\$(Configuration)\ + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Windows + true + Dbghelp.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + mainCRTStartup + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DalamudCrashHandler/DalamudCrashHandler.vcxproj.filters b/DalamudCrashHandler/DalamudCrashHandler.vcxproj.filters new file mode 100644 index 000000000..890f66520 --- /dev/null +++ b/DalamudCrashHandler/DalamudCrashHandler.vcxproj.filters @@ -0,0 +1,40 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + Header Files + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/DalamudCrashHandler/dalamud.ico b/DalamudCrashHandler/dalamud.ico new file mode 100644 index 000000000..1cd63765d Binary files /dev/null and b/DalamudCrashHandler/dalamud.ico differ diff --git a/DalamudCrashHandler/resource.h b/DalamudCrashHandler/resource.h new file mode 100644 index 000000000..cdeb6ea3c --- /dev/null +++ b/DalamudCrashHandler/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by DalamudCrashHandler.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/build/DalamudBuild.cs b/build/DalamudBuild.cs index 01d6d3038..318af7851 100644 --- a/build/DalamudBuild.cs +++ b/build/DalamudBuild.cs @@ -31,6 +31,9 @@ public class DalamudBuild : NukeBuild AbsolutePath DalamudBootProjectDir => RootDirectory / "Dalamud.Boot"; AbsolutePath DalamudBootProjectFile => DalamudBootProjectDir / "Dalamud.Boot.vcxproj"; + + AbsolutePath DalamudCrashHandlerProjectDir => RootDirectory / "DalamudCrashHandler"; + AbsolutePath DalamudCrashHandlerProjectFile => DalamudBootProjectDir / "DalamudCrashHandler.vcxproj"; AbsolutePath InjectorProjectDir => RootDirectory / "Dalamud.Injector"; AbsolutePath InjectorProjectFile => InjectorProjectDir / "Dalamud.Injector.csproj"; @@ -71,6 +74,14 @@ public class DalamudBuild : NukeBuild .SetTargetPath(DalamudBootProjectFile) .SetConfiguration(Configuration)); }); + + Target CompileDalamudCrashHandler => _ => _ + .Executes(() => + { + MSBuildTasks.MSBuild(s => s + .SetTargetPath(DalamudCrashHandlerProjectFile) + .SetConfiguration(Configuration)); + }); Target CompileInjector => _ => _ .DependsOn(Restore) @@ -117,6 +128,11 @@ public class DalamudBuild : NukeBuild .SetProjectFile(DalamudBootProjectFile) .SetConfiguration(Configuration) .SetTargets("Clean")); + + MSBuildTasks.MSBuild(s => s + .SetProjectFile(DalamudCrashHandlerProjectFile) + .SetConfiguration(Configuration) + .SetTargets("Clean")); DotNetTasks.DotNetClean(s => s .SetProject(InjectorProjectFile)