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`.
This commit is contained in:
srkizer 2024-04-21 13:20:11 +09:00 committed by GitHub
parent b85914c54c
commit 8ff4662f1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 101 additions and 13 deletions

View file

@ -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<DalamudStartInfo::UnhandledExceptionHandlingMode>(json.get<int>());
} else if (json.is_string()) {
const auto langstr = unicode::convert<std::string>(json.get<std::string>(), &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) { void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
if (!json.is_object()) if (!json.is_object())
return; return;
@ -121,7 +136,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
} }
config.CrashHandlerShow = json.value("CrashHandlerShow", config.CrashHandlerShow); 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() { void DalamudStartInfo::from_envvars() {

View file

@ -32,6 +32,13 @@ struct DalamudStartInfo {
}; };
friend void from_json(const nlohmann::json&, LoadMethod&); 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; LoadMethod DalamudLoadMethod = LoadMethod::Entrypoint;
std::string WorkingDirectory; std::string WorkingDirectory;
std::string ConfigurationPath; std::string ConfigurationPath;
@ -59,7 +66,7 @@ struct DalamudStartInfo {
std::set<std::string> BootUnhookDlls{}; std::set<std::string> BootUnhookDlls{};
bool CrashHandlerShow = false; bool CrashHandlerShow = false;
bool NoExceptionHandlers = false; UnhandledExceptionHandlingMode UnhandledException = UnhandledExceptionHandlingMode::Default;
friend void from_json(const nlohmann::json&, DalamudStartInfo&); friend void from_json(const nlohmann::json&, DalamudStartInfo&);
void from_envvars(); void from_envvars();

View file

@ -133,7 +133,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
// ============================== VEH ======================================== // // ============================== VEH ======================================== //
logging::I("Initializing VEH..."); logging::I("Initializing VEH...");
if (g_startInfo.NoExceptionHandlers) { if (g_startInfo.UnhandledException == DalamudStartInfo::UnhandledExceptionHandlingMode::None) {
logging::W("=> Exception handlers are disabled from DalamudStartInfo."); logging::W("=> Exception handlers are disabled from DalamudStartInfo.");
} else if (g_startInfo.BootVehEnabled) { } else if (g_startInfo.BootVehEnabled) {
if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory)) if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory))

View file

@ -136,6 +136,17 @@ static void append_injector_launch_args(std::vector<std::wstring>& args)
args.emplace_back(L"--msgbox2"); args.emplace_back(L"--msgbox2");
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudConstruct) != DalamudStartInfo::WaitMessageboxFlags::None) if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudConstruct) != DalamudStartInfo::WaitMessageboxFlags::None)
args.emplace_back(L"--msgbox3"); 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"--"); args.emplace_back(L"--");
@ -148,6 +159,13 @@ static void append_injector_launch_args(std::vector<std::wstring>& args)
LONG exception_handler(EXCEPTION_POINTERS* ex) 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 // block any other exceptions hitting the handler while the messagebox is open
const auto lock = std::lock_guard(g_exception_handler_mutex); const auto lock = std::lock_guard(g_exception_handler_mutex);

View file

@ -145,7 +145,7 @@ public record DalamudStartInfo
public bool CrashHandlerShow { get; set; } public bool CrashHandlerShow { get; set; }
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
public bool NoExceptionHandlers { get; set; } public UnhandledExceptionHandlingMode UnhandledException { get; set; }
} }

View file

@ -0,0 +1,16 @@
namespace Dalamud.Common;
/// <summary>Enum describing what to do on unhandled exceptions.</summary>
public enum UnhandledExceptionHandlingMode
{
/// <summary>Always show Dalamud Crash Handler on crash, except for some exceptions.</summary>
/// <remarks>See `vectored_exception_handler` in `veh.cpp`.</remarks>
Default,
/// <summary>Waits for debugger if none is attached, and pass the exception to the next handler.</summary>
/// <remarks>See `exception_handler` in `veh.cpp`.</remarks>
StallDebug,
/// <summary>Do not register an exception handler.</summary>
None,
}

View file

@ -99,7 +99,6 @@ namespace Dalamud.Injector
args.Remove("--no-plugin"); args.Remove("--no-plugin");
args.Remove("--no-3rd-plugin"); args.Remove("--no-3rd-plugin");
args.Remove("--crash-handler-console"); args.Remove("--crash-handler-console");
args.Remove("--no-exception-handlers");
var mainCommand = args[1].ToLowerInvariant(); var mainCommand = args[1].ToLowerInvariant();
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand) if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
@ -277,6 +276,7 @@ namespace Dalamud.Injector
var logName = startInfo.LogName; var logName = startInfo.LogName;
var logPath = startInfo.LogPath; var logPath = startInfo.LogPath;
var languageStr = startInfo.Language.ToString().ToLowerInvariant(); var languageStr = startInfo.Language.ToString().ToLowerInvariant();
var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant();
var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}"; var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}";
for (var i = 2; i < args.Count; i++) for (var i = 2; i < args.Count; i++)
@ -317,6 +317,10 @@ namespace Dalamud.Injector
{ {
logPath = args[i][key.Length..]; logPath = args[i][key.Length..];
} }
else if (args[i].StartsWith(key = "--unhandled-exception="))
{
unhandledExceptionStr = args[i][key.Length..];
}
else else
{ {
continue; continue;
@ -416,7 +420,14 @@ namespace Dalamud.Injector
startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-3rd-plugin"); startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-3rd-plugin");
// startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" }; // startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" };
startInfo.CrashHandlerShow = args.Contains("--crash-handler-console"); startInfo.CrashHandlerShow = args.Contains("--crash-handler-console");
startInfo.NoExceptionHandlers = args.Contains("--no-exception-handlers"); startInfo.UnhandledException =
Enum.TryParse<UnhandledExceptionHandlingMode>(
unhandledExceptionStr,
true,
out var parsedUnhandledException)
? parsedUnhandledException
: throw new CommandLineException(
$"\"{unhandledExceptionStr}\" is not a valid unhandled exception handling mode.");
return startInfo; return startInfo;
} }
@ -458,7 +469,7 @@ namespace Dalamud.Injector
Console.WriteLine("Verbose logging:\t[-v]"); Console.WriteLine("Verbose logging:\t[-v]");
Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]"); Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]");
Console.WriteLine("Enable ETW:\t[--etw]"); 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("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]");
Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]"); Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]");
Console.WriteLine("Logging:\t[--logname=<logfile suffix>] [--logpath=<log base directory>]"); Console.WriteLine("Logging:\t[--logname=<logfile suffix>] [--logpath=<log base directory>]");

View file

@ -149,8 +149,16 @@ public sealed class EntryPoint
LogLevelSwitch.MinimumLevel = configuration.LogLevel; LogLevelSwitch.MinimumLevel = configuration.LogLevel;
// Log any unhandled exception. // Log any unhandled exception.
if (!info.NoExceptionHandlers) switch (info.UnhandledException)
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException; {
case UnhandledExceptionHandlingMode.Default:
AppDomain.CurrentDomain.UnhandledException += OnUnhandledExceptionDefault;
break;
case UnhandledExceptionHandlingMode.StallDebug:
AppDomain.CurrentDomain.UnhandledException += OnUnhandledExceptionStallDebug;
break;
}
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException; TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
var unloadFailed = false; var unloadFailed = false;
@ -199,8 +207,15 @@ public sealed class EntryPoint
finally finally
{ {
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException; TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
if (!info.NoExceptionHandlers) switch (info.UnhandledException)
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException; {
case UnhandledExceptionHandlingMode.Default:
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledExceptionDefault;
break;
case UnhandledExceptionHandlingMode.StallDebug:
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledExceptionStallDebug;
break;
}
Log.Information("Session has ended."); Log.Information("Session has ended.");
Log.CloseAndFlush(); 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) 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) private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
{ {
if (!args.Observed) if (!args.Observed)