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

Show a GUI alert when crashing on desktop platforms #61906

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
30 changes: 28 additions & 2 deletions platform/linuxbsd/crash_handler_linuxbsd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "core/string/print_string.h"
#include "core/version.h"
#include "main/main.h"
#include "servers/display_server.h"

#ifndef DEBUG_ENABLED
#undef CRASH_HANDLER_ENABLED
Expand Down Expand Up @@ -62,19 +63,26 @@ static void handle_crash(int sig) {
String _execpath = OS::get_singleton()->get_executable_path();

String msg;
String log_path;
const ProjectSettings *proj_settings = ProjectSettings::get_singleton();
if (proj_settings) {
msg = proj_settings->get("debug/settings/crash_handler/message");
msg = proj_settings->get_setting_with_override("debug/settings/crash_handler/message");
if (proj_settings->get_setting_with_override("debug/file_logging/enable_file_logging")) {
log_path = proj_settings->globalize_path(proj_settings->get_setting_with_override("debug/file_logging/log_path"));
}
}

// Tell MainLoop about the crash. This can be handled by users too in Node.
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
}

// Set window title while dumping the backtrace, as this can take 10+ seconds to finish.
DisplayServer::get_singleton()->window_set_title("ERROR: Program crashed, dumping backtrace...");

// Dump the backtrace to stderr with a message to the user
print_error("\n================================================================");
print_error(vformat("%s: Program crashed with signal %d", __FUNCTION__, sig));
print_error(vformat("%s: Program crashed with signal %d.", __FUNCTION__, sig));

// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
if (String(VERSION_HASH).is_empty()) {
Expand Down Expand Up @@ -141,6 +149,24 @@ static void handle_crash(int sig) {
print_error("-- END OF BACKTRACE --");
print_error("================================================================");

// Notify the user that backtrace dumping is finished.
DisplayServer::get_singleton()->window_set_title("ERROR: Program crashed, dumped backtrace");

// Show alert so the user is aware of the crash, even if the engine wasn't started with visible stdout/stderr.
// This must be done after printing the backtrace to prevent the process from being blocked too early (`OS::alert()` is blocking).
if (!log_path.is_empty()) {
// `fprintf()` is used instead of `print_error()`, as this printed line must not be present
// in the log file (it references the log file itself).
fprintf(stderr, "\nFind the log file for this session at:\n%s\n\n", log_path.utf8().get_data());

if (DisplayServer::get_singleton()->get_name() != "headless") {
OS::get_singleton()->alert(vformat("%s: Program crashed with signal %d.\n\nFind the log file for this session at:\n%s\n\nClicking OK will open this log file. Please include the this log file's contents in bug reports.", __FUNCTION__, sig, log_path), "Crash");
OS::get_singleton()->shell_open(log_path.utf8().get_data());
}
} else if (DisplayServer::get_singleton()->get_name() != "headless") {
OS::get_singleton()->alert(vformat("%s: Program crashed with signal %d.", __FUNCTION__, sig), "Crash");
}

// Abort to pass the error to the OS
abort();
}
Expand Down
42 changes: 40 additions & 2 deletions platform/macos/crash_handler_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "core/string/print_string.h"
#include "core/version.h"
#include "main/main.h"
#include "servers/display_server.h"

#include <string.h>
#include <unistd.h>
Expand Down Expand Up @@ -86,19 +87,26 @@ static void handle_crash(int sig) {
String _execpath = OS::get_singleton()->get_executable_path();

String msg;
String log_path;
const ProjectSettings *proj_settings = ProjectSettings::get_singleton();
if (proj_settings) {
msg = proj_settings->get("debug/settings/crash_handler/message");
msg = proj_settings->get_setting_with_override("debug/settings/crash_handler/message");
if (proj_settings->get_setting_with_override("debug/file_logging/enable_file_logging")) {
log_path = proj_settings->globalize_path(proj_settings->get_setting_with_override("debug/file_logging/log_path"));
}
}

// Tell MainLoop about the crash. This can be handled by users too in Node.
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
}

// Set window title while dumping the backtrace, as this can take 10+ seconds to finish.
DisplayServer::get_singleton()->window_set_title("ERROR: Program crashed, dumping backtrace...");

// Dump the backtrace to stderr with a message to the user
print_error("\n================================================================");
print_error(vformat("%s: Program crashed with signal %d", __FUNCTION__, sig));
print_error(vformat("%s: Program crashed with signal %d.", __FUNCTION__, sig));

// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
if (String(VERSION_HASH).is_empty()) {
Expand Down Expand Up @@ -172,6 +180,36 @@ static void handle_crash(int sig) {
print_error("-- END OF BACKTRACE --");
print_error("================================================================");

// Notify the user that backtrace dumping is finished.
DisplayServer::get_singleton()->window_set_title("ERROR: Program crashed, dumped backtrace");

// Show alert so the user is aware of the crash, even if the engine wasn't started with visible stdout/stderr.
// This must be done after printing the backtrace to prevent the process from being blocked too early (`OS::alert()` is blocking).
if (!log_path.is_empty()) {
// `fprintf()` is used instead of `print_error()`, as this printed line must not be present
// in the log file (it references the log file itself).
fprintf(stderr, "\nFind the log file for this session at:\n%s\n\n", log_path.utf8().get_data());

if (DisplayServer::get_singleton()->get_name() != "headless") {
// Use AppleScript in `OS::execute()` instead of `OS::alert()` so that it works even when the OS event loop is blocked by the crash handler.
// (this is only required on macOS).
List<String> args;
args.push_back("-e");
args.push_back(vformat("display alert \"Crash\" message \"%s: Program crashed with signal %d.\\n\\nFind the log file for this session at:\\n%s\\n\\nClicking OK will open this log file. Please include the this log file's contents in bug reports.\"", __FUNCTION__, sig, log_path.c_escape()));
OS::get_singleton()->execute("osascript", args);
args.clear();
args.push_back(log_path);
// Use `OS::execute()` instead of `OS::shell_open()` so that it works even when the OS event loop is blocked by the crash handler.
OS::get_singleton()->execute("open", args);
}
} else if (DisplayServer::get_singleton()->get_name() != "headless") {
// Use AppleScript in `OS::execute()` instead of `OS::alert()` so that it works even when the OS event loop is blocked by the crash handler.
List<String> args;
args.push_back("-e");
args.push_back(vformat("display alert \"Crash\" message \"%s: Program crashed with signal %d.\"", __FUNCTION__, sig));
OS::get_singleton()->execute("osascript", args);
}

// Abort to pass the error to the OS
abort();
}
Expand Down
30 changes: 28 additions & 2 deletions platform/windows/crash_handler_windows_seh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "core/string/print_string.h"
#include "core/version.h"
#include "main/main.h"
#include "servers/display_server.h"

#ifdef CRASH_HANDLER_EXCEPTION

Expand Down Expand Up @@ -128,18 +129,25 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
}

String msg;
String log_path;
const ProjectSettings *proj_settings = ProjectSettings::get_singleton();
if (proj_settings) {
msg = proj_settings->get("debug/settings/crash_handler/message");
msg = proj_settings->get_setting_with_override("debug/settings/crash_handler/message");
if (proj_settings->get_setting_with_override("debug/file_logging/enable_file_logging")) {
log_path = proj_settings->globalize_path(proj_settings->get_setting_with_override("debug/file_logging/log_path"));
}
}

// Tell MainLoop about the crash. This can be handled by users too in Node.
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_CRASH);
}

// Set window title while dumping the backtrace, as this can take 10+ seconds to finish.
DisplayServer::get_singleton()->window_set_title("ERROR: Program crashed, dumping backtrace...");

print_error("\n================================================================");
print_error(vformat("%s: Program crashed", __FUNCTION__));
print_error(vformat("%s: Program crashed.", __FUNCTION__));

// Print the engine version just before, so that people are reminded to include the version in backtrace reports.
if (String(VERSION_HASH).is_empty()) {
Expand Down Expand Up @@ -223,6 +231,24 @@ DWORD CrashHandlerException(EXCEPTION_POINTERS *ep) {
print_error("-- END OF BACKTRACE --");
print_error("================================================================");

// Notify the user that backtrace dumping is finished.
DisplayServer::get_singleton()->window_set_title("ERROR: Program crashed, dumped backtrace");

// Show alert so the user is aware of the crash, even if the engine wasn't started with visible stdout/stderr.
// This must be done after printing the backtrace to prevent the process from being blocked too early (`OS::alert()` is blocking).
if (!log_path.is_empty()) {
// `fprintf()` is used instead of `print_error()`, as this printed line must not be present
// in the log file (it references the log file itself).
fprintf(stderr, "\nFind the log file for this session at:\n%s\n\n", log_path.utf8().get_data());

if (DisplayServer::get_singleton()->get_name() != "headless") {
OS::get_singleton()->alert(vformat("%s: Program crashed.\n\nFind the log file for this session at:\n%s\n\nClicking OK will open this log file. Please include the this log file's contents in bug reports.", __FUNCTION__, log_path), "Crash");
OS::get_singleton()->shell_open(log_path.utf8().get_data());
}
} else if (DisplayServer::get_singleton()->get_name() != "headless") {
OS::get_singleton()->alert(vformat("%s: Program crashed.", __FUNCTION__), "Crash");
}

SymCleanup(process);

// Pass the exception to the OS
Expand Down
Loading