Offer to restart game from VEH exception handler (#936)

This commit is contained in:
kizer 2022-07-27 04:14:48 +09:00 committed by GitHub
parent 98e421a227
commit 58ceb1dc87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1279 additions and 616 deletions

View file

@ -33,10 +33,12 @@
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LibraryPath>$(LibraryPath)</LibraryPath>
<UseDebugLibraries>true</UseDebugLibraries>
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)</LibraryPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(LibraryPath)</LibraryPath>
<UseDebugLibraries>false</UseDebugLibraries>
<LibraryPath>$(SolutionDir)bin\lib\$(Configuration)\libMinHook\;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64)</LibraryPath>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
@ -53,7 +55,7 @@
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableUAC>false</EnableUAC>
<AdditionalDependencies>dbghelp.lib;Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalDependencies>Version.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>..\lib\CoreCLR;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
</ItemDefinitionGroup>

View file

@ -8,6 +8,9 @@ struct DalamudStartInfo {
BeforeDalamudConstruct = 1 << 2,
};
friend void from_json(const nlohmann::json&, WaitMessageboxFlags&);
friend WaitMessageboxFlags operator &(WaitMessageboxFlags a, WaitMessageboxFlags b) {
return static_cast<WaitMessageboxFlags>(static_cast<int>(a) & static_cast<int>(b));
}
enum class DotNetOpenProcessHookMode : int {
ImportHooks = 0,

View file

@ -1,19 +1,18 @@
#pragma once
#include "windows.h"
#include <cinttypes>
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
struct exception_info
{
void* ExceptionPointers; // Cannot dereference!
DWORD ThreadId;
DWORD ProcessId;
BOOL DoFullDump;
wchar_t DumpPath[1000];
// For metrics
DWORD ExceptionCode;
long long Lifetime;
LPEXCEPTION_POINTERS pExceptionPointers;
EXCEPTION_POINTERS ExceptionPointers;
EXCEPTION_RECORD ExceptionRecord;
CONTEXT ContextRecord;
uint64_t nLifetime;
HANDLE hThreadHandle;
DWORD dwStackTraceLength;
};
constexpr wchar_t SHARED_INFO_FILE_NAME[] = L"DalamudCrashInfoShare";
constexpr wchar_t CRASHDUMP_EVENT_NAME[] = L"Global\\DalamudRequestWriteDump";

View file

@ -87,7 +87,7 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
logging::I("Dalamud.Boot Injectable, (c) 2021 XIVLauncher Contributors");
logging::I("Built at: " __DATE__ "@" __TIME__);
if (static_cast<int>(g_startInfo.BootWaitMessageBox) & static_cast<int>(DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize))
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None)
MessageBoxW(nullptr, L"Press OK to continue (BeforeInitialize)", L"Dalamud Boot", MB_OK);
if (minHookLoaded) {

View file

@ -11,15 +11,16 @@
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
// Windows Header Files
#include <windows.h>
#include <DbgHelp.h>
// Windows Header Files (1)
#include <Windows.h>
// Windows Header Files (2)
#include <Dbt.h>
#include <PathCch.h>
#include <Psapi.h>
#include <Shlobj.h>
#include <TlHelp32.h>
#include <Dbt.h>
#include <ShlObj.h>
#include <SubAuth.h>
#include <TlHelp32.h>
// MSVC Compiler Intrinsic
#include <intrin.h>
@ -33,11 +34,11 @@
#include <fstream>
#include <functional>
#include <iostream>
#include <mutex>
#include <ranges>
#include <set>
#include <span>
#include <string>
#include <mutex>
#include <type_traits>
// https://www.akenotsuki.com/misc/srell/en/
@ -50,8 +51,8 @@
#include "../lib/Nomade040-nmd/nmd_assembly.h"
// https://github.com/dotnet/coreclr
#include "../lib/CoreCLR/CoreCLR.h"
#include "../lib/CoreCLR/boot.h"
#include "../lib/CoreCLR/CoreCLR.h"
// https://github.com/nlohmann/json
#include "../lib/nlohmann-json/json.hpp"

View file

@ -520,3 +520,36 @@ void utils::wait_for_game_window() {
};
SendMessageW(game_window, WM_NULL, 0, 0);
}
std::wstring utils::escape_shell_arg(const std::wstring& arg) {
// https://docs.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way
std::wstring res;
if (!arg.empty() && arg.find_first_of(L" \t\n\v\"") == std::wstring::npos) {
res.append(arg);
} else {
res.push_back(L'"');
for (auto it = arg.begin(); ; ++it) {
size_t bsCount = 0;
while (it != arg.end() && *it == L'\\') {
++it;
++bsCount;
}
if (it == arg.end()) {
res.append(bsCount * 2, L'\\');
break;
} else if (*it == L'"') {
res.append(bsCount * 2 + 1, L'\\');
res.push_back(*it);
} else {
res.append(bsCount, L'\\');
res.push_back(*it);
}
}
res.push_back(L'"');
}
return res;
}

View file

@ -260,4 +260,6 @@ namespace utils {
HWND try_find_game_window();
void wait_for_game_window();
std::wstring escape_shell_arg(const std::wstring& arg);
}

View file

@ -1,7 +1,5 @@
#include "pch.h"
#include "resource.h"
#include "veh.h"
#include <shellapi.h>
@ -10,6 +8,7 @@
#include "utils.h"
#include "crashhandler_shared.h"
#include "DalamudStartInfo.h"
#pragma comment(lib, "comctl32.lib")
@ -25,9 +24,8 @@
PVOID g_veh_handle = nullptr;
bool g_veh_do_full_dump = false;
exception_info* g_crashhandler_shared_info;
HANDLE g_crashhandler_event;
HANDLE g_crashhandler_process = nullptr;
HANDLE g_crashhandler_pipe_write = nullptr;
std::chrono::time_point<std::chrono::system_clock> g_time_start;
@ -73,7 +71,6 @@ bool is_whitelist_exception(const DWORD code)
}
}
bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std::filesystem::path& module_file)
{
HMODULE handle;
@ -91,7 +88,6 @@ bool get_module_file_and_base(const DWORD64 address, DWORD64& module_base, std::
return false;
}
bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
{
DWORD64 module_base;
@ -100,369 +96,223 @@ bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
return false;
}
bool get_sym_from_addr(const DWORD64 address, DWORD64& displacement, std::wstring& symbol_name)
static void append_injector_launch_args(std::vector<std::wstring>& args)
{
union {
char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]{};
SYMBOL_INFOW symbol;
};
symbol.SizeOfStruct = sizeof(SYMBOL_INFO);
symbol.MaxNameLen = MAX_SYM_NAME;
args.emplace_back(L"-g");
args.emplace_back(utils::loaded_module::current_process().path().wstring());
if (g_startInfo.BootShowConsole)
args.emplace_back(L"--console");
if (g_startInfo.BootEnableEtw)
args.emplace_back(L"--etw");
if (g_startInfo.BootVehEnabled)
args.emplace_back(L"--veh");
if (g_startInfo.BootVehFull)
args.emplace_back(L"--veh-full");
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeInitialize) != DalamudStartInfo::WaitMessageboxFlags::None)
args.emplace_back(L"--msgbox1");
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudEntrypoint) != DalamudStartInfo::WaitMessageboxFlags::None)
args.emplace_back(L"--msgbox2");
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudConstruct) != DalamudStartInfo::WaitMessageboxFlags::None)
args.emplace_back(L"--msgbox3");
if (SymFromAddrW(GetCurrentProcess(), address, &displacement, &symbol) && symbol.Name[0])
{
symbol_name = symbol.Name;
return true;
args.emplace_back(L"--");
if (int nArgs; LPWSTR * szArgList = CommandLineToArgvW(GetCommandLineW(), &nArgs)) {
for (auto i = 1; i < nArgs; i++)
args.emplace_back(szArgList[i]);
LocalFree(szArgList);
}
return false;
}
std::wstring to_address_string(const DWORD64 address, const bool try_ptrderef = true)
{
DWORD64 module_base;
std::filesystem::path module_path;
bool is_mod_addr = get_module_file_and_base(address, module_base, module_path);
DWORD64 value = 0;
if(try_ptrderef && address > 0x10000 && address < 0x7FFFFFFE0000)
{
ReadProcessMemory(GetCurrentProcess(), reinterpret_cast<void*>(address), &value, sizeof value, nullptr);
}
std::wstring addr_str = is_mod_addr ?
std::format(L"{}+{:X}", module_path.filename().c_str(), address - module_base) :
std::format(L"{:X}", address);
DWORD64 displacement;
if (std::wstring symbol; get_sym_from_addr(address, displacement, symbol))
return std::format(L"{}\t({})", addr_str, displacement != 0 ? std::format(L"{}+0x{:X}", symbol, displacement) : std::format(L"{}", symbol));
return value != 0 ? std::format(L"{} [{}]", addr_str, to_address_string(value, false)) : addr_str;
}
void print_exception_info_extended(const EXCEPTION_POINTERS* ex, std::wostringstream& log)
{
CONTEXT ctx = *ex->ContextRecord;
log << L"\nRegisters\n{";
log << std::format(L"\n RAX:\t{}", to_address_string(ctx.Rax));
log << std::format(L"\n RBX:\t{}", to_address_string(ctx.Rbx));
log << std::format(L"\n RCX:\t{}", to_address_string(ctx.Rcx));
log << std::format(L"\n RDX:\t{}", to_address_string(ctx.Rdx));
log << std::format(L"\n R8:\t{}", to_address_string(ctx.R8));
log << std::format(L"\n R9:\t{}", to_address_string(ctx.R9));
log << std::format(L"\n R10:\t{}", to_address_string(ctx.R10));
log << std::format(L"\n R11:\t{}", to_address_string(ctx.R11));
log << std::format(L"\n R12:\t{}", to_address_string(ctx.R12));
log << std::format(L"\n R13:\t{}", to_address_string(ctx.R13));
log << std::format(L"\n R14:\t{}", to_address_string(ctx.R14));
log << std::format(L"\n R15:\t{}", to_address_string(ctx.R15));
log << std::format(L"\n RSI:\t{}", to_address_string(ctx.Rsi));
log << std::format(L"\n RDI:\t{}", to_address_string(ctx.Rdi));
log << std::format(L"\n RBP:\t{}", to_address_string(ctx.Rbp));
log << std::format(L"\n RSP:\t{}", to_address_string(ctx.Rsp));
log << std::format(L"\n RIP:\t{}", to_address_string(ctx.Rip));
log << L"\n}" << std::endl;
if(0x10000 < ctx.Rsp && ctx.Rsp < 0x7FFFFFFE0000)
{
log << L"\nStack\n{";
for(DWORD64 i = 0; i < 16; i++)
log << std::format(L"\n [RSP+{:X}]\t{}", i * 8, to_address_string(*reinterpret_cast<DWORD64*>(ctx.Rsp + i * 8ull)));
log << L"\n}\n";
}
log << L"\nModules\n{";
if(HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, GetCurrentProcessId()); snap != INVALID_HANDLE_VALUE)
{
MODULEENTRY32 mod;
mod.dwSize = sizeof MODULEENTRY32;
if(Module32First(snap, &mod))
{
do
{
log << std::format(L"\n {:08X}\t{}", reinterpret_cast<DWORD64>(mod.modBaseAddr), mod.szExePath);
}
while (Module32Next(snap, &mod));
}
CloseHandle(snap);
}
log << L"\n}\n";
}
void print_exception_info(const EXCEPTION_POINTERS* ex, std::wostringstream& log)
{
size_t rec_index = 0;
for (auto rec = ex->ExceptionRecord; rec; rec = rec->ExceptionRecord)
{
log << std::format(L"\nException Info #{}\n", ++rec_index);
log << std::format(L"Address: {:X}\n", rec->ExceptionCode);
log << std::format(L"Flags: {:X}\n", rec->ExceptionFlags);
log << std::format(L"Address: {:X}\n", reinterpret_cast<size_t>(rec->ExceptionAddress));
if (!rec->NumberParameters)
continue;
log << L"Parameters: ";
for (DWORD i = 0; i < rec->NumberParameters; ++i)
{
if (i != 0)
log << L", ";
log << std::format(L"{:X}", rec->ExceptionInformation[i]);
}
}
log << L"\nCall Stack\n{";
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;
int frame_index = 0;
log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false));
do
{
if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, GetCurrentProcess(), GetCurrentThread(), &sf, &ctx, nullptr, SymFunctionTableAccess64, SymGetModuleBase64, nullptr))
break;
log << std::format(L"\n [{}]\t{}", frame_index++, to_address_string(sf.AddrPC.Offset, false));
} while (sf.AddrReturn.Offset != 0 && sf.AddrPC.Offset != sf.AddrReturn.Offset);
log << L"\n}\n";
}
HRESULT CALLBACK TaskDialogCallbackProc(HWND hwnd,
UINT uNotification,
WPARAM wParam,
LPARAM lParam,
LONG_PTR dwRefData)
{
HRESULT hr = S_OK;
switch (uNotification)
{
case TDN_CREATED:
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
break;
}
return hr;
}
LONG exception_handler(EXCEPTION_POINTERS* ex)
{
static std::recursive_mutex s_exception_handler_mutex;
if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode))
return EXCEPTION_CONTINUE_SEARCH;
if (ex->ExceptionRecord->ExceptionCode == 0x12345678)
{
// pass
}
else
{
if (!is_whitelist_exception(ex->ExceptionRecord->ExceptionCode))
return EXCEPTION_CONTINUE_SEARCH;
if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) &&
!is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip))
return EXCEPTION_CONTINUE_SEARCH;
if (!is_ffxiv_address(L"ffxiv_dx11.exe", ex->ContextRecord->Rip) &&
!is_ffxiv_address(L"cimgui.dll", ex->ContextRecord->Rip))
return EXCEPTION_CONTINUE_SEARCH;
}
// block any other exceptions hitting the veh while the messagebox is open
const auto lock = std::lock_guard(s_exception_handler_mutex);
const auto module_path = utils::loaded_module(g_hModule).path().parent_path();
#ifndef NDEBUG
const auto dmp_path = (module_path / L"dalamud_appcrashd.dmp").wstring();
#else
const auto dmp_path = (module_path / L"dalamud_appcrash.dmp").wstring();
#endif
const auto log_path = (module_path / L"dalamud_appcrash.log").wstring();
exception_info exinfo{};
exinfo.pExceptionPointers = ex;
exinfo.ExceptionPointers = *ex;
exinfo.ContextRecord = *ex->ContextRecord;
exinfo.ExceptionRecord = ex->ExceptionRecord ? *ex->ExceptionRecord : EXCEPTION_RECORD{};
const auto time_now = std::chrono::system_clock::now();
auto lifetime = std::chrono::duration_cast<std::chrono::seconds>(
time_now.time_since_epoch()).count()
- std::chrono::duration_cast<std::chrono::seconds>(
g_time_start.time_since_epoch()).count();
exinfo.nLifetime = lifetime;
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), g_crashhandler_process, &exinfo.hThreadHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
std::wostringstream log;
log << std::format(L"Unhandled native exception occurred at {}", to_address_string(ex->ContextRecord->Rip, false)) << std::endl;
log << std::format(L"Code: {:X}", ex->ExceptionRecord->ExceptionCode) << std::endl;
log << std::format(L"Dump at: {}", dmp_path) << std::endl;
log << L"Time: " << std::chrono::zoned_time{ std::chrono::current_zone(), std::chrono::system_clock::now() } << std::endl;
SymRefreshModuleList(GetCurrentProcess());
print_exception_info(ex, log);
auto window_log_str = log.str();
print_exception_info_extended(ex, log);
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;
g_crashhandler_shared_info->ExceptionCode = ex->ExceptionRecord->ExceptionCode;
const auto time_now = std::chrono::system_clock::now();
auto lifetime = std::chrono::duration_cast<std::chrono::seconds>(
time_now.time_since_epoch()).count()
- std::chrono::duration_cast<std::chrono::seconds>(
g_time_start.time_since_epoch()).count();
g_crashhandler_shared_info->Lifetime = lifetime;
SetEvent(g_crashhandler_event);
}
std::wstring message;
void* fn;
if (const auto err = static_cast<DWORD>(g_clr->get_function_pointer(
std::wstring stackTrace;
if (void* fn; const auto err = static_cast<DWORD>(g_clr->get_function_pointer(
L"Dalamud.EntryPoint, Dalamud",
L"VehCallback",
L"Dalamud.EntryPoint+VehDelegate, Dalamud",
nullptr, nullptr, &fn)))
{
message = std::format(
L"An error within the game has occurred.\n\n"
L"This may be caused by a faulty plugin, a broken TexTools modification, any other third-party tool or simply a bug in the game.\n"
L"Please try \"Start Over\" or \"Download Index Backup\" in TexTools, an integrity check in the XIVLauncher settings, and disabling plugins you don't need.\n\n"
L"The log file is located at:\n"
L"{1}\n\n"
L"Press OK to exit the application.\n\nFailed to read stack trace: 0x{2:08x}",
dmp_path, log_path, err);
stackTrace = std::format(L"Failed to read stack trace: 0x{:08x}", err);
}
else
{
const auto pMessage = ((wchar_t*(__stdcall*)(const void*, const void*, const void*))fn)(dmp_path.c_str(), log_path.c_str(), log.str().c_str());
message = pMessage;
stackTrace = static_cast<wchar_t*(*)()>(fn)();
// Don't free it, as the program's going to be quit anyway
}
exinfo.dwStackTraceLength = static_cast<DWORD>(stackTrace.size());
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &exinfo, static_cast<DWORD>(sizeof exinfo), &written, nullptr) || sizeof exinfo != written)
return EXCEPTION_CONTINUE_SEARCH;
logging::E(std::format(L"Trapped in VEH handler: {}", message));
// show in another thread to prevent messagebox from pumping messages of current thread
std::thread([&]()
{
int nButtonPressed = 0;
TASKDIALOGCONFIG config = {0};
const TASKDIALOG_BUTTON buttons[] = {
{IDOK, L"Disable all plugins"},
{IDABORT, L"Open help page"},
};
config.cbSize = sizeof(config);
config.hInstance = g_hModule;
config.dwCommonButtons = TDCBF_CLOSE_BUTTON;
config.pszMainIcon = MAKEINTRESOURCE(IDI_ICON1);
//config.hMainIcon = dalamud_icon;
config.pszMainInstruction = L"An error occurred";
config.pszContent = message.c_str();
config.pButtons = buttons;
config.cButtons = ARRAYSIZE(buttons);
config.pszExpandedInformation = window_log_str.c_str();
config.pszWindowTitle = L"Dalamud Error";
config.nDefaultButton = IDCLOSE;
config.cxWidth = 300;
// Can't do this, xiv stops pumping messages here
//config.hwndParent = FindWindowA("FFXIVGAME", NULL);
config.pfCallback = TaskDialogCallbackProc;
TaskDialogIndirect(&config, &nButtonPressed, NULL, NULL);
switch (nButtonPressed)
{
case IDOK:
TCHAR szPath[MAX_PATH];
if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, szPath)))
{
auto appdata = std::filesystem::path(szPath);
auto safemode_file_path = ( appdata / "XIVLauncher" / ".dalamud_safemode" );
std::ofstream ofs(safemode_file_path);
ofs << "STAY SAFE!!!";
ofs.close();
}
break;
case IDABORT:
ShellExecute(0, 0, L"https://goatcorp.github.io/faq?utm_source=vectored", 0, 0 , SW_SHOW );
break;
case IDCANCEL:
break;
default:
break;
}
}).join();
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &stackTrace[0], static_cast<DWORD>(std::span(stackTrace).size_bytes()), &written, nullptr) || std::span(stackTrace).size_bytes() != written)
return EXCEPTION_CONTINUE_SEARCH;
SuspendThread(GetCurrentThread());
return EXCEPTION_CONTINUE_SEARCH;
}
bool veh::add_handler(bool doFullDump, std::string workingDirectory)
bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
{
if (g_veh_handle)
return false;
g_veh_handle = AddVectoredExceptionHandler(1, exception_handler);
SetUnhandledExceptionFilter(nullptr);
g_veh_do_full_dump = doFullDump;
g_time_start = std::chrono::system_clock::now();
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;
std::optional<std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)>> hWritePipe;
std::optional<std::unique_ptr<std::remove_pointer_t<HANDLE>, decltype(&CloseHandle)>> hReadPipeInheritable;
if (HANDLE hReadPipeRaw, hWritePipeRaw; CreatePipe(&hReadPipeRaw, &hWritePipeRaw, nullptr, 65536))
{
hWritePipe.emplace(hWritePipeRaw, &CloseHandle);
if (HANDLE hReadPipeInheritableRaw; DuplicateHandle(GetCurrentProcess(), hReadPipeRaw, GetCurrentProcess(), &hReadPipeInheritableRaw, 0, TRUE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE))
{
hReadPipeInheritable.emplace(hReadPipeInheritableRaw, &CloseHandle);
}
else
{
logging::W("Failed to launch DalamudCrashHandler.exe: DuplicateHandle(1) error 0x{:x}", GetLastError());
return false;
}
}
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";
logging::W("Failed to launch DalamudCrashHandler.exe: CreatePipe error 0x{:x}", GetLastError());
return false;
}
// additional information
STARTUPINFOEXW siex{};
PROCESS_INFORMATION pi{};
siex.StartupInfo.cb = sizeof siex;
siex.StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
#ifdef NDEBUG
siex.StartupInfo.wShowWindow = SW_HIDE;
#else
siex.StartupInfo.wShowWindow = SW_SHOW;
#endif
// set up list of handles to inherit to child process
std::vector<char> attributeListBuf;
if (SIZE_T size = 0; !InitializeProcThreadAttributeList(nullptr, 1, 0, &size))
{
if (const auto err = GetLastError(); err != ERROR_INSUFFICIENT_BUFFER)
{
logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(1) error 0x{:x}", err);
return false;
}
attributeListBuf.resize(size);
siex.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(&attributeListBuf[0]);
if (!InitializeProcThreadAttributeList(siex.lpAttributeList, 1, 0, &size))
{
logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(2) error 0x{:x}", GetLastError());
return false;
}
}
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)
else
{
std::cout << "Couldn't acquire event handle\n";
logging::W("Failed to launch DalamudCrashHandler.exe: InitializeProcThreadAttributeList(0) was supposed to fail");
return false;
}
std::unique_ptr<std::remove_pointer_t<LPPROC_THREAD_ATTRIBUTE_LIST>, decltype(&DeleteProcThreadAttributeList)> cleanAttributeList(siex.lpAttributeList, &DeleteProcThreadAttributeList);
std::vector<HANDLE> handles;
HANDLE hInheritableCurrentProcess;
if (!DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &hInheritableCurrentProcess, 0, TRUE, DUPLICATE_SAME_ACCESS))
{
logging::W("Failed to launch DalamudCrashHandler.exe: DuplicateHandle(2) error 0x{:x}", GetLastError());
return false;
}
handles.push_back(hInheritableCurrentProcess);
handles.push_back(hReadPipeInheritable->get());
std::vector<std::wstring> args;
std::wstring argstr;
args.emplace_back((std::filesystem::path(workingDirectory) / "DalamudCrashHandler.exe").wstring());
args.emplace_back(std::format(L"--process-handle={}", reinterpret_cast<size_t>(hInheritableCurrentProcess)));
args.emplace_back(std::format(L"--exception-info-pipe-read-handle={}", reinterpret_cast<size_t>(hReadPipeInheritable->get())));
args.emplace_back(std::format(L"--asset-directory={}", unicode::convert<std::wstring>(g_startInfo.AssetDirectory)));
args.emplace_back(std::format(L"--log-directory={}", g_startInfo.BootLogPath.empty()
? utils::loaded_module(g_hModule).path().parent_path().wstring()
: std::filesystem::path(unicode::convert<std::wstring>(g_startInfo.BootLogPath)).parent_path().wstring()));
args.emplace_back(L"--");
append_injector_launch_args(args);
for (const auto& arg : args)
{
argstr.append(utils::escape_shell_arg(arg));
argstr.push_back(L' ');
}
argstr.pop_back();
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());
return false;
}
auto handler_path = std::filesystem::path(workingDirectory) / "DalamudCrashHandler.exe";
// additional information
STARTUPINFO si;
PROCESS_INFORMATION pi;
if (!CreateProcessW(
args[0].c_str(), // The path
&argstr[0], // Command line
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
TRUE, // Set handle inheritance to FALSE
EXTENDED_STARTUPINFO_PRESENT, // lpStartupInfo actually points to a STARTUPINFOEX(W)
nullptr, // Use parent's environment block
nullptr, // Use parent's starting directory
&siex.StartupInfo, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
))
{
logging::W("Failed to launch DalamudCrashHandler.exe: CreateProcessW error 0x{:x}", GetLastError());
return false;
}
// set the size of the structures
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
CloseHandle(pi.hThread);
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;
g_crashhandler_process = pi.hProcess;
g_crashhandler_pipe_write = hWritePipe->release();
logging::I("Launched DalamudCrashHandler.exe: PID {}", pi.dwProcessId);
return true;
}
bool veh::remove_handler()

View file

@ -2,6 +2,6 @@
namespace veh
{
bool add_handler(bool doFullDump, std::string workingDirectory);
bool add_handler(bool doFullDump, const std::string& workingDirectory);
bool remove_handler();
}