Skip to content

Commit 8ff4662

Browse files
authoredApr 21, 2024··
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`.
1 parent b85914c commit 8ff4662

File tree

8 files changed

+101
-13
lines changed

8 files changed

+101
-13
lines changed
 

‎Dalamud.Boot/DalamudStartInfo.cpp

+16-1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ void from_json(const nlohmann::json& json, DalamudStartInfo::LoadMethod& value)
8282
}
8383
}
8484

85+
void from_json(const nlohmann::json& json, DalamudStartInfo::UnhandledExceptionHandlingMode& value) {
86+
if (json.is_number_integer()) {
87+
value = static_cast<DalamudStartInfo::UnhandledExceptionHandlingMode>(json.get<int>());
88+
89+
} else if (json.is_string()) {
90+
const auto langstr = unicode::convert<std::string>(json.get<std::string>(), &unicode::lower);
91+
if (langstr == "default")
92+
value = DalamudStartInfo::UnhandledExceptionHandlingMode::Default;
93+
else if (langstr == "stalldebug")
94+
value = DalamudStartInfo::UnhandledExceptionHandlingMode::StallDebug;
95+
else if (langstr == "none")
96+
value = DalamudStartInfo::UnhandledExceptionHandlingMode::None;
97+
}
98+
}
99+
85100
void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
86101
if (!json.is_object())
87102
return;
@@ -121,7 +136,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
121136
}
122137

123138
config.CrashHandlerShow = json.value("CrashHandlerShow", config.CrashHandlerShow);
124-
config.NoExceptionHandlers = json.value("NoExceptionHandlers", config.NoExceptionHandlers);
139+
config.UnhandledException = json.value("UnhandledException", config.UnhandledException);
125140
}
126141

127142
void DalamudStartInfo::from_envvars() {

‎Dalamud.Boot/DalamudStartInfo.h

+8-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,13 @@ struct DalamudStartInfo {
3232
};
3333
friend void from_json(const nlohmann::json&, LoadMethod&);
3434

35+
enum class UnhandledExceptionHandlingMode : int {
36+
Default,
37+
StallDebug,
38+
None,
39+
};
40+
friend void from_json(const nlohmann::json&, UnhandledExceptionHandlingMode&);
41+
3542
LoadMethod DalamudLoadMethod = LoadMethod::Entrypoint;
3643
std::string WorkingDirectory;
3744
std::string ConfigurationPath;
@@ -59,7 +66,7 @@ struct DalamudStartInfo {
5966
std::set<std::string> BootUnhookDlls{};
6067

6168
bool CrashHandlerShow = false;
62-
bool NoExceptionHandlers = false;
69+
UnhandledExceptionHandlingMode UnhandledException = UnhandledExceptionHandlingMode::Default;
6370

6471
friend void from_json(const nlohmann::json&, DalamudStartInfo&);
6572
void from_envvars();

‎Dalamud.Boot/dllmain.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ HRESULT WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
133133
// ============================== VEH ======================================== //
134134

135135
logging::I("Initializing VEH...");
136-
if (g_startInfo.NoExceptionHandlers) {
136+
if (g_startInfo.UnhandledException == DalamudStartInfo::UnhandledExceptionHandlingMode::None) {
137137
logging::W("=> Exception handlers are disabled from DalamudStartInfo.");
138138
} else if (g_startInfo.BootVehEnabled) {
139139
if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory))

‎Dalamud.Boot/veh.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,17 @@ static void append_injector_launch_args(std::vector<std::wstring>& args)
136136
args.emplace_back(L"--msgbox2");
137137
if ((g_startInfo.BootWaitMessageBox & DalamudStartInfo::WaitMessageboxFlags::BeforeDalamudConstruct) != DalamudStartInfo::WaitMessageboxFlags::None)
138138
args.emplace_back(L"--msgbox3");
139+
switch (g_startInfo.UnhandledException) {
140+
case DalamudStartInfo::UnhandledExceptionHandlingMode::Default:
141+
args.emplace_back(L"--unhandled-exception=default");
142+
break;
143+
case DalamudStartInfo::UnhandledExceptionHandlingMode::StallDebug:
144+
args.emplace_back(L"--unhandled-exception=stalldebug");
145+
break;
146+
case DalamudStartInfo::UnhandledExceptionHandlingMode::None:
147+
args.emplace_back(L"--unhandled-exception=none");
148+
break;
149+
}
139150

140151
args.emplace_back(L"--");
141152

@@ -148,6 +159,13 @@ static void append_injector_launch_args(std::vector<std::wstring>& args)
148159

149160
LONG exception_handler(EXCEPTION_POINTERS* ex)
150161
{
162+
if (g_startInfo.UnhandledException == DalamudStartInfo::UnhandledExceptionHandlingMode::StallDebug) {
163+
while (!IsDebuggerPresent())
164+
Sleep(100);
165+
166+
return EXCEPTION_CONTINUE_SEARCH;
167+
}
168+
151169
// block any other exceptions hitting the handler while the messagebox is open
152170
const auto lock = std::lock_guard(g_exception_handler_mutex);
153171

‎Dalamud.Common/DalamudStartInfo.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public DalamudStartInfo()
145145
public bool CrashHandlerShow { get; set; }
146146

147147
/// <summary>
148-
/// Gets or sets a value indicating whether to disable all kinds of global exception handlers.
148+
/// Gets or sets a value indicating how to deal with unhandled exceptions.
149149
/// </summary>
150-
public bool NoExceptionHandlers { get; set; }
150+
public UnhandledExceptionHandlingMode UnhandledException { get; set; }
151151
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace Dalamud.Common;
2+
3+
/// <summary>Enum describing what to do on unhandled exceptions.</summary>
4+
public enum UnhandledExceptionHandlingMode
5+
{
6+
/// <summary>Always show Dalamud Crash Handler on crash, except for some exceptions.</summary>
7+
/// <remarks>See `vectored_exception_handler` in `veh.cpp`.</remarks>
8+
Default,
9+
10+
/// <summary>Waits for debugger if none is attached, and pass the exception to the next handler.</summary>
11+
/// <remarks>See `exception_handler` in `veh.cpp`.</remarks>
12+
StallDebug,
13+
14+
/// <summary>Do not register an exception handler.</summary>
15+
None,
16+
}

‎Dalamud.Injector/EntryPoint.cs

+14-3
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ public static int Main(int argc, IntPtr argvPtr)
9999
args.Remove("--no-plugin");
100100
args.Remove("--no-3rd-plugin");
101101
args.Remove("--crash-handler-console");
102-
args.Remove("--no-exception-handlers");
103102

104103
var mainCommand = args[1].ToLowerInvariant();
105104
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
@@ -277,6 +276,7 @@ private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(Dalam
277276
var logName = startInfo.LogName;
278277
var logPath = startInfo.LogPath;
279278
var languageStr = startInfo.Language.ToString().ToLowerInvariant();
279+
var unhandledExceptionStr = startInfo.UnhandledException.ToString().ToLowerInvariant();
280280
var troubleshootingData = "{\"empty\": true, \"description\": \"No troubleshooting data supplied.\"}";
281281

282282
for (var i = 2; i < args.Count; i++)
@@ -317,6 +317,10 @@ private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(Dalam
317317
{
318318
logPath = args[i][key.Length..];
319319
}
320+
else if (args[i].StartsWith(key = "--unhandled-exception="))
321+
{
322+
unhandledExceptionStr = args[i][key.Length..];
323+
}
320324
else
321325
{
322326
continue;
@@ -416,7 +420,14 @@ private static DalamudStartInfo ExtractAndInitializeStartInfoFromArguments(Dalam
416420
startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-3rd-plugin");
417421
// startInfo.BootUnhookDlls = new List<string>() { "kernel32.dll", "ntdll.dll", "user32.dll" };
418422
startInfo.CrashHandlerShow = args.Contains("--crash-handler-console");
419-
startInfo.NoExceptionHandlers = args.Contains("--no-exception-handlers");
423+
startInfo.UnhandledException =
424+
Enum.TryParse<UnhandledExceptionHandlingMode>(
425+
unhandledExceptionStr,
426+
true,
427+
out var parsedUnhandledException)
428+
? parsedUnhandledException
429+
: throw new CommandLineException(
430+
$"\"{unhandledExceptionStr}\" is not a valid unhandled exception handling mode.");
420431

421432
return startInfo;
422433
}
@@ -458,7 +469,7 @@ private static int ProcessHelpCommand(List<string> args, string? particularComma
458469
Console.WriteLine("Verbose logging:\t[-v]");
459470
Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]");
460471
Console.WriteLine("Enable ETW:\t[--etw]");
461-
Console.WriteLine("Enable VEH:\t[--veh], [--veh-full], [--no-exception-handlers]");
472+
Console.WriteLine("Enable VEH:\t[--veh], [--veh-full], [--unhandled-exception=default|stalldebug|none]");
462473
Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]");
463474
Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]");
464475
Console.WriteLine("Logging:\t[--logname=<logfile suffix>] [--logpath=<log base directory>]");

‎Dalamud/EntryPoint.cs

+26-5
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,16 @@ private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEv
149149
LogLevelSwitch.MinimumLevel = configuration.LogLevel;
150150

151151
// Log any unhandled exception.
152-
if (!info.NoExceptionHandlers)
153-
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
152+
switch (info.UnhandledException)
153+
{
154+
case UnhandledExceptionHandlingMode.Default:
155+
AppDomain.CurrentDomain.UnhandledException += OnUnhandledExceptionDefault;
156+
break;
157+
case UnhandledExceptionHandlingMode.StallDebug:
158+
AppDomain.CurrentDomain.UnhandledException += OnUnhandledExceptionStallDebug;
159+
break;
160+
}
161+
154162
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
155163

156164
var unloadFailed = false;
@@ -199,8 +207,15 @@ private static void RunThread(DalamudStartInfo info, IntPtr mainThreadContinueEv
199207
finally
200208
{
201209
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
202-
if (!info.NoExceptionHandlers)
203-
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
210+
switch (info.UnhandledException)
211+
{
212+
case UnhandledExceptionHandlingMode.Default:
213+
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledExceptionDefault;
214+
break;
215+
case UnhandledExceptionHandlingMode.StallDebug:
216+
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledExceptionStallDebug;
217+
break;
218+
}
204219

205220
Log.Information("Session has ended.");
206221
Log.CloseAndFlush();
@@ -248,7 +263,7 @@ private static void InitSymbolHandler(DalamudStartInfo info)
248263
}
249264
}
250265

251-
private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs args)
266+
private static void OnUnhandledExceptionDefault(object sender, UnhandledExceptionEventArgs args)
252267
{
253268
switch (args.ExceptionObject)
254269
{
@@ -308,6 +323,12 @@ private static void OnUnhandledException(object sender, UnhandledExceptionEventA
308323
}
309324
}
310325

326+
private static void OnUnhandledExceptionStallDebug(object sender, UnhandledExceptionEventArgs args)
327+
{
328+
while (!Debugger.IsAttached)
329+
Thread.Sleep(100);
330+
}
331+
311332
private static void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs args)
312333
{
313334
if (!args.Observed)

0 commit comments

Comments
 (0)
Please sign in to comment.