Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

boot: implement --unhandled-exception=stalldebug #1690

Merged
merged 1 commit into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
boot: implement --unhandled-exception=stalldebug
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`.
  • Loading branch information
Soreepeong committed Apr 21, 2024
commit b53373d8d3041ece3173f1d5c58aaa5ddc8a553c
17 changes: 16 additions & 1 deletion Dalamud.Boot/DalamudStartInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
if (!json.is_object())
return;
Expand Down Expand Up @@ -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() {
Expand Down
9 changes: 8 additions & 1 deletion Dalamud.Boot/DalamudStartInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,7 +66,7 @@ struct DalamudStartInfo {
std::set<std::string> BootUnhookDlls{};

bool CrashHandlerShow = false;
bool NoExceptionHandlers = false;
UnhandledExceptionHandlingMode UnhandledException = UnhandledExceptionHandlingMode::Default;

friend void from_json(const nlohmann::json&, DalamudStartInfo&);
void from_envvars();
Expand Down
2 changes: 1 addition & 1 deletion Dalamud.Boot/dllmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
18 changes: 18 additions & 0 deletions Dalamud.Boot/veh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,17 @@ static void append_injector_launch_args(std::vector<std::wstring>& 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"--");

Expand All @@ -148,6 +159,13 @@ static void append_injector_launch_args(std::vector<std::wstring>& 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);

Expand Down
4 changes: 2 additions & 2 deletions Dalamud.Common/DalamudStartInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ public DalamudStartInfo()
public bool CrashHandlerShow { get; set; }

/// <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>
public bool NoExceptionHandlers { get; set; }
public UnhandledExceptionHandlingMode UnhandledException { get; set; }
}
16 changes: 16 additions & 0 deletions Dalamud.Common/UnhandledExceptionHandlingMode.cs
Original file line number Diff line number Diff line change
@@ -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,
}
17 changes: 14 additions & 3 deletions Dalamud.Injector/EntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ public static int Main(int argc, IntPtr argvPtr)
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)
Expand Down Expand Up @@ -277,6 +276,7 @@ private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(Dalam
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++)
Expand Down Expand Up @@ -317,6 +317,10 @@ private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(Dalam
{
logPath = args[i][key.Length..];
}
else if (args[i].StartsWith(key = "--unhandled-exception="))
{
unhandledExceptionStr = args[i][key.Length..];
}
else
{
continue;
Expand Down Expand Up @@ -416,7 +420,14 @@ private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(Dalam
startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-3rd-plugin");
// startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" };
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;
}
Expand Down Expand Up @@ -458,7 +469,7 @@ private static int ProcessHelpCommand(List<string> args, string? particularComma
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=<logfile suffix>] [--logpath=<log base directory>]");
Expand Down
31 changes: 26 additions & 5 deletions Dalamud/EntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,16 @@ private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEv
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;
Expand Down Expand Up @@ -199,8 +207,15 @@ private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEv
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();
Expand Down Expand Up @@ -248,7 +263,7 @@ private static void InitSymbolHandler(DalamudStartInfo info)
}
}

private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
private static void OnUnhandledExceptionDefault(object sender, UnhandledExceptionEventArgs args)
{
switch (args.ExceptionObject)
{
Expand Down Expand Up @@ -308,6 +323,12 @@ private static void OnUnhandledException(object sender, UnhandledExceptionEventA
}
}

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)
Expand Down
Loading