From 8ff4662f1fdbee659dd8d9f58d748ff7663bac8a Mon Sep 17 00:00:00 2001 From: srkizer Date: Sun, 21 Apr 2024 13:20:11 +0900 Subject: [PATCH] boot: implement --unhandled-exception=stalldebug (#1690) Using this option will stall the crashed thread until the debugger attaches, so that the newly attached debugger can be handed over the exception, enabling breaking at correct stack location. `--no-exception-handlers` injector option controlled whether Dalamud Crash Handler would intercept exceptions before. Those are now either `default` or `none` option for `--unhandled-exception`. --- Dalamud.Boot/DalamudStartInfo.cpp | 17 +++++++++- Dalamud.Boot/DalamudStartInfo.h | 9 +++++- Dalamud.Boot/dllmain.cpp | 2 +- Dalamud.Boot/veh.cpp | 18 +++++++++++ Dalamud.Common/DalamudStartInfo.cs | 4 +-- .../UnhandledExceptionHandlingMode.cs | 16 ++++++++++ Dalamud.Injector/EntryPoint.cs | 17 ++++++++-- Dalamud/EntryPoint.cs | 31 ++++++++++++++++--- 8 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 Dalamud.Common/UnhandledExceptionHandlingMode.cs diff --git a/Dalamud.Boot/DalamudStartInfo.cpp b/Dalamud.Boot/DalamudStartInfo.cpp index f5632a2ea..985332966 100644 --- a/Dalamud.Boot/DalamudStartInfo.cpp +++ b/Dalamud.Boot/DalamudStartInfo.cpp @@ -82,6 +82,21 @@ void from_json(const nlohmann::json& json, DalamudStartInfo::LoadMethod& value) } } +void from_json(const nlohmann::json& json, DalamudStartInfo::UnhandledExceptionHandlingMode& value) { + if (json.is_number_integer()) { + value = static_cast(json.get()); + + } else if (json.is_string()) { + const auto langstr = unicode::convert(json.get(), &unicode::lower); + if (langstr == "default") + value = DalamudStartInfo::UnhandledExceptionHandlingMode::Default; + else if (langstr == "stalldebug") + value = DalamudStartInfo::UnhandledExceptionHandlingMode::StallDebug; + else if (langstr == "none") + value = DalamudStartInfo::UnhandledExceptionHandlingMode::None; + } +} + void from_json(const nlohmann::json& json, DalamudStartInfo& config) { if (!json.is_object()) return; @@ -121,7 +136,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) { } config.CrashHandlerShow = json.value("CrashHandlerShow", config.CrashHandlerShow); - config.NoExceptionHandlers = json.value("NoExceptionHandlers", config.NoExceptionHandlers); + config.UnhandledException = json.value("UnhandledException", config.UnhandledException); } void DalamudStartInfo::from_envvars() { diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h index e6cc54ab0..cc31ba2c5 100644 --- a/Dalamud.Boot/DalamudStartInfo.h +++ b/Dalamud.Boot/DalamudStartInfo.h @@ -32,6 +32,13 @@ struct DalamudStartInfo { }; friend void from_json(const nlohmann::json&, LoadMethod&); + enum class UnhandledExceptionHandlingMode : int { + Default, + StallDebug, + None, + }; + friend void from_json(const nlohmann::json&, UnhandledExceptionHandlingMode&); + LoadMethod DalamudLoadMethod = LoadMethod::Entrypoint; std::string WorkingDirectory; std::string ConfigurationPath; @@ -59,7 +66,7 @@ struct DalamudStartInfo { std::set BootUnhookDlls{}; bool CrashHandlerShow = false; - bool NoExceptionHandlers = false; + UnhandledExceptionHandlingMode UnhandledException = UnhandledExceptionHandlingMode::Default; friend void from_json(const nlohmann::json&, DalamudStartInfo&); void from_envvars(); diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp index e6aa9c4ac..5c7c00b68 100644 --- a/Dalamud.Boot/dllmain.cpp +++ b/Dalamud.Boot/dllmain.cpp @@ -133,7 +133,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) { // ============================== VEH ======================================== // logging::I("Initializing VEH..."); - if (g_startInfo.NoExceptionHandlers) { + if (g_startInfo.UnhandledException == DalamudStartInfo::UnhandledExceptionHandlingMode::None) { logging::W("=> Exception handlers are disabled from DalamudStartInfo."); } else if (g_startInfo.BootVehEnabled) { if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory)) diff --git a/Dalamud.Boot/veh.cpp b/Dalamud.Boot/veh.cpp index 58234783a..6c951f93d 100644 --- a/Dalamud.Boot/veh.cpp +++ b/Dalamud.Boot/veh.cpp @@ -136,6 +136,17 @@ static void append_injector_launch_args(std::vector& args) args.emplace_back(L"--msgbox2"); if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudConstruct) != DalamudStartInfo::WaitMessageboxFlags::None) args.emplace_back(L"--msgbox3"); + switch (g_startInfo.UnhandledException) { + case DalamudStartInfo::UnhandledExceptionHandlingMode::Default: + args.emplace_back(L"--unhandled-exception=default"); + break; + case DalamudStartInfo::UnhandledExceptionHandlingMode::StallDebug: + args.emplace_back(L"--unhandled-exception=stalldebug"); + break; + case DalamudStartInfo::UnhandledExceptionHandlingMode::None: + args.emplace_back(L"--unhandled-exception=none"); + break; + } args.emplace_back(L"--"); @@ -148,6 +159,13 @@ static void append_injector_launch_args(std::vector& args) LONG exception_handler(EXCEPTION_POINTERS* ex) { + if (g_startInfo.UnhandledException == DalamudStartInfo::UnhandledExceptionHandlingMode::StallDebug) { + while (!IsDebuggerPresent()) + Sleep(100); + + return EXCEPTION_CONTINUE_SEARCH; + } + // block any other exceptions hitting the handler while the messagebox is open const auto lock = std::lock_guard(g_exception_handler_mutex); diff --git a/Dalamud.Common/DalamudStartInfo.cs b/Dalamud.Common/DalamudStartInfo.cs index a84d3b68f..c3cc33a12 100644 --- a/Dalamud.Common/DalamudStartInfo.cs +++ b/Dalamud.Common/DalamudStartInfo.cs @@ -145,7 +145,7 @@ public record DalamudStartInfo public bool CrashHandlerShow { get; set; } /// - /// Gets or sets a value indicating whether to disable all kinds of global exception handlers. + /// Gets or sets a value indicating how to deal with unhandled exceptions. /// - public bool NoExceptionHandlers { get; set; } + public UnhandledExceptionHandlingMode UnhandledException { get; set; } } diff --git a/Dalamud.Common/UnhandledExceptionHandlingMode.cs b/Dalamud.Common/UnhandledExceptionHandlingMode.cs new file mode 100644 index 000000000..a0dd0c102 --- /dev/null +++ b/Dalamud.Common/UnhandledExceptionHandlingMode.cs @@ -0,0 +1,16 @@ +namespace Dalamud.Common; + +/// Enum describing what to do on unhandled exceptions. +public enum UnhandledExceptionHandlingMode +{ + /// Always show Dalamud Crash Handler on crash, except for some exceptions. + /// See `vectored_exception_handler` in `veh.cpp`. + Default, + + /// Waits for debugger if none is attached, and pass the exception to the next handler. + /// See `exception_handler` in `veh.cpp`. + StallDebug, + + /// Do not register an exception handler. + None, +} diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs index 9085eae04..f22c2923e 100644 --- a/Dalamud.Injector/EntryPoint.cs +++ b/Dalamud.Injector/EntryPoint.cs @@ -99,7 +99,6 @@ namespace Dalamud.Injector args.Remove("--no-plugin"); args.Remove("--no-3rd-plugin"); args.Remove("--crash-handler-console"); - args.Remove("--no-exception-handlers"); var mainCommand = args[1].ToLowerInvariant(); if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand) @@ -277,6 +276,7 @@ namespace Dalamud.Injector var logName = startInfo.LogName; var logPath = startInfo.LogPath; var languageStr = startInfo.Language.ToString().ToLowerInvariant(); + var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant(); var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}"; for (var i = 2; i < args.Count; i++) @@ -317,6 +317,10 @@ namespace Dalamud.Injector { logPath = args[i][key.Length..]; } + else if (args[i].StartsWith(key = "--unhandled-exception=")) + { + unhandledExceptionStr = args[i][key.Length..]; + } else { continue; @@ -416,7 +420,14 @@ namespace Dalamud.Injector startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-3rd-plugin"); // startInfo.BootUnhookDlls = new List() { "kernel32.dll", "ntdll.dll", "user32.dll" }; startInfo.CrashHandlerShow = args.Contains("--crash-handler-console"); - startInfo.NoExceptionHandlers = args.Contains("--no-exception-handlers"); + startInfo.UnhandledException = + Enum.TryParse( + unhandledExceptionStr, + true, + out var parsedUnhandledException) + ? parsedUnhandledException + : throw new CommandLineException( + $"\"{unhandledExceptionStr}\" is not a valid unhandled exception handling mode."); return startInfo; } @@ -458,7 +469,7 @@ namespace Dalamud.Injector Console.WriteLine("Verbose logging:\t[-v]"); Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]"); Console.WriteLine("Enable ETW:\t[--etw]"); - Console.WriteLine("Enable VEH:\t[--veh], [--veh-full], [--no-exception-handlers]"); + Console.WriteLine("Enable VEH:\t[--veh], [--veh-full], [--unhandled-exception=default|stalldebug|none]"); Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]"); Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]"); Console.WriteLine("Logging:\t[--logname=] [--logpath=]"); diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs index 1ad3ad8a9..ecf159eab 100644 --- a/Dalamud/EntryPoint.cs +++ b/Dalamud/EntryPoint.cs @@ -149,8 +149,16 @@ public sealed class EntryPoint LogLevelSwitch.MinimumLevel = configuration.LogLevel; // Log any unhandled exception. - if (!info.NoExceptionHandlers) - AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; + switch (info.UnhandledException) + { + case UnhandledExceptionHandlingMode.Default: + AppDomain.CurrentDomain.UnhandledException += OnUnhandledExceptionDefault; + break; + case UnhandledExceptionHandlingMode.StallDebug: + AppDomain.CurrentDomain.UnhandledException += OnUnhandledExceptionStallDebug; + break; + } + TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; var unloadFailed = false; @@ -199,8 +207,15 @@ public sealed class EntryPoint finally { TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException; - if (!info.NoExceptionHandlers) - AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException; + switch (info.UnhandledException) + { + case UnhandledExceptionHandlingMode.Default: + AppDomain.CurrentDomain.UnhandledException -= OnUnhandledExceptionDefault; + break; + case UnhandledExceptionHandlingMode.StallDebug: + AppDomain.CurrentDomain.UnhandledException -= OnUnhandledExceptionStallDebug; + break; + } Log.Information("Session has ended."); Log.CloseAndFlush(); @@ -248,7 +263,7 @@ public sealed class EntryPoint } } - private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args) + private static void OnUnhandledExceptionDefault(object sender, UnhandledExceptionEventArgs args) { switch (args.ExceptionObject) { @@ -308,6 +323,12 @@ public sealed class EntryPoint } } + private static void OnUnhandledExceptionStallDebug(object sender, UnhandledExceptionEventArgs args) + { + while (!Debugger.IsAttached) + Thread.Sleep(100); + } + private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args) { if (!args.Observed)