Make the crash handler work on wine properly (#1636)

* add LoadMethod to DalamudStartInfo
* add to_wstring utility function
* append full injector launch args for VEH
* remove usage of std::chrono::zoned_time
* fix injector arguments in crash handler restart
* enable VEH on wine
* remove dead wine detection code
* write out tspack with std::fstream
* fix off-by-one error in get_window_string()
* remove usage of std::chrono when writing tspack
* do not deadlock on crashing DalamudCrashHandler
This commit is contained in:
marzent 2024-02-11 22:01:34 +01:00 committed by GitHub
parent 16bc6b86e5
commit 386e5f245c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 148 additions and 77 deletions

View file

@ -68,10 +68,25 @@ void from_json(const nlohmann::json& json, DalamudStartInfo::ClientLanguage& val
}
}
void from_json(const nlohmann::json& json, DalamudStartInfo::LoadMethod& value) {
if (json.is_number_integer()) {
value = static_cast<DalamudStartInfo::LoadMethod>(json.get<int>());
}
else if (json.is_string()) {
const auto langstr = unicode::convert<std::string>(json.get<std::string>(), &unicode::lower);
if (langstr == "entrypoint")
value = DalamudStartInfo::LoadMethod::Entrypoint;
else if (langstr == "inject")
value = DalamudStartInfo::LoadMethod::DllInject;
}
}
void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
if (!json.is_object())
return;
config.DalamudLoadMethod = json.value("LoadMethod", config.DalamudLoadMethod);
config.WorkingDirectory = json.value("WorkingDirectory", config.WorkingDirectory);
config.ConfigurationPath = json.value("ConfigurationPath", config.ConfigurationPath);
config.PluginDirectory = json.value("PluginDirectory", config.PluginDirectory);

View file

@ -26,6 +26,13 @@ struct DalamudStartInfo {
};
friend void from_json(const nlohmann::json&, ClientLanguage&);
enum class LoadMethod : int {
Entrypoint,
DllInject,
};
friend void from_json(const nlohmann::json&, LoadMethod&);
LoadMethod DalamudLoadMethod = LoadMethod::Entrypoint;
std::string WorkingDirectory;
std::string ConfigurationPath;
std::string PluginDirectory;

View file

@ -14,6 +14,7 @@ struct exception_info
CONTEXT ContextRecord;
uint64_t nLifetime;
HANDLE hThreadHandle;
HANDLE hEventHandle;
DWORD dwStackTraceLength;
DWORD dwTroubleshootingPackDataLength;
};

View file

@ -135,8 +135,6 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
logging::I("Initializing VEH...");
if (g_startInfo.NoExceptionHandlers) {
logging::W("=> Exception handlers are disabled from DalamudStartInfo.");
} else if (utils::is_running_on_wine()) {
logging::I("=> VEH was disabled, running on wine");
} else if (g_startInfo.BootVehEnabled) {
if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory))
logging::I("=> Done!");

View file

@ -578,21 +578,14 @@ std::vector<std::string> utils::get_env_list(const wchar_t* pcszName) {
return res;
}
bool utils::is_running_on_wine() {
if (get_env<bool>(L"XL_WINEONLINUX"))
return true;
HMODULE hntdll = GetModuleHandleW(L"ntdll.dll");
if (!hntdll)
return true;
if (GetProcAddress(hntdll, "wine_get_version"))
return true;
if (GetProcAddress(hntdll, "wine_get_host_version"))
return true;
if (GetProcAddress(hntdll, "wine_server_call"))
return true;
if (GetProcAddress(hntdll, "wine_unix_to_nt_file_name"))
return true;
return false;
std::wstring utils::to_wstring(const std::string& str) {
if (str.empty()) return std::wstring();
size_t convertedChars = 0;
size_t newStrSize = str.size() + 1;
std::wstring wstr(newStrSize, L'\0');
mbstowcs_s(&convertedChars, &wstr[0], newStrSize, str.c_str(), _TRUNCATE);
wstr.resize(convertedChars - 1);
return wstr;
}
std::filesystem::path utils::get_module_path(HMODULE hModule) {

View file

@ -264,7 +264,7 @@ namespace utils {
return get_env_list<T>(unicode::convert<std::wstring>(pcszName).c_str());
}
bool is_running_on_wine();
std::wstring to_wstring(const std::string& str);
std::filesystem::path get_module_path(HMODULE hModule);

View file

@ -26,6 +26,7 @@ PVOID g_veh_handle = nullptr;
bool g_veh_do_full_dump = false;
HANDLE g_crashhandler_process = nullptr;
HANDLE g_crashhandler_event = nullptr;
HANDLE g_crashhandler_pipe_write = nullptr;
std::recursive_mutex g_exception_handler_mutex;
@ -101,8 +102,21 @@ bool is_ffxiv_address(const wchar_t* module_name, const DWORD64 address)
static void append_injector_launch_args(std::vector<std::wstring>& args)
{
args.emplace_back(L"-g");
args.emplace_back(utils::loaded_module::current_process().path().wstring());
args.emplace_back(L"--game=\"" + utils::loaded_module::current_process().path().wstring() + L"\"");
switch (g_startInfo.DalamudLoadMethod) {
case DalamudStartInfo::LoadMethod::Entrypoint:
args.emplace_back(L"--mode=entrypoint");
break;
case DalamudStartInfo::LoadMethod::DllInject:
args.emplace_back(L"--mode=inject");
}
args.emplace_back(L"--logpath=\"" + utils::to_wstring(g_startInfo.BootLogPath) + L"\"");
args.emplace_back(L"--dalamud-working-directory=\"" + utils::to_wstring(g_startInfo.WorkingDirectory) + L"\"");
args.emplace_back(L"--dalamud-configuration-path=\"" + utils::to_wstring(g_startInfo.ConfigurationPath) + L"\"");
args.emplace_back(L"--dalamud-plugin-directory=\"" + utils::to_wstring(g_startInfo.PluginDirectory) + L"\"");
args.emplace_back(L"--dalamud-asset-directory=\"" + utils::to_wstring(g_startInfo.AssetDirectory) + L"\"");
args.emplace_back(L"--dalamud-client-language=" + std::to_wstring(static_cast<int>(g_startInfo.Language)));
args.emplace_back(L"--dalamud-delay-initialize=" + std::to_wstring(g_startInfo.DelayInitializeMs));
if (g_startInfo.BootShowConsole)
args.emplace_back(L"--console");
if (g_startInfo.BootEnableEtw)
@ -158,6 +172,7 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
g_time_start.time_since_epoch()).count();
exinfo.nLifetime = lifetime;
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), g_crashhandler_process, &exinfo.hThreadHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
DuplicateHandle(GetCurrentProcess(), g_crashhandler_event, g_crashhandler_process, &exinfo.hEventHandle, 0, TRUE, DUPLICATE_SAME_ACCESS);
std::wstring stackTrace;
if (void* fn; const auto err = static_cast<DWORD>(g_clr->get_function_pointer(
@ -185,7 +200,20 @@ LONG exception_handler(EXCEPTION_POINTERS* ex)
if (DWORD written; !WriteFile(g_crashhandler_pipe_write, &g_startInfo.TroubleshootingPackData[0], static_cast<DWORD>(std::span(g_startInfo.TroubleshootingPackData).size_bytes()), &written, nullptr) || std::span(g_startInfo.TroubleshootingPackData).size_bytes() != written)
return EXCEPTION_CONTINUE_SEARCH;
SuspendThread(GetCurrentThread());
HANDLE waitHandles[] = { g_crashhandler_process, g_crashhandler_event };
DWORD waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
switch (waitResult) {
case WAIT_OBJECT_0:
logging::E("DalamudCrashHandler.exe exited unexpectedly");
break;
case WAIT_OBJECT_0 + 1:
logging::I("Crashing thread was resumed");
break;
default:
logging::E("Unexpected WaitForMultipleObjects return code 0x{:x}", waitResult);
}
return EXCEPTION_CONTINUE_SEARCH;
}
@ -308,6 +336,12 @@ bool veh::add_handler(bool doFullDump, const std::string& workingDirectory)
return false;
}
if (!(g_crashhandler_event = CreateEventW(NULL, FALSE, FALSE, NULL)))
{
logging::W("Failed to create crash synchronization event: CreateEventW error 0x{:x}", GetLastError());
return false;
}
CloseHandle(pi.hThread);
g_crashhandler_process = pi.hProcess;