From 88cfb313ba91b01d7f848b1c4b04ec0d63abdce8 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Thu, 2 Jun 2016 17:18:34 +0100 Subject: [PATCH] citra-qt: Print stack trace --- src/citra_qt/CMakeLists.txt | 2 + src/citra_qt/crash_handler.cpp | 270 +++++++++++++++++++++++++++++++++ src/citra_qt/crash_handler.h | 9 ++ src/citra_qt/main.cpp | 3 + 4 files changed, 284 insertions(+) create mode 100644 src/citra_qt/crash_handler.cpp create mode 100644 src/citra_qt/crash_handler.h diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 0a5d4624b..4fd7ec505 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -24,6 +24,7 @@ set(SRCS configure_debug.cpp configure_dialog.cpp configure_general.cpp + crash_handler.cpp game_list.cpp hotkeys.cpp main.cpp @@ -56,6 +57,7 @@ set(HEADERS configure_debug.h configure_dialog.h configure_general.h + crash_handler.h game_list.h game_list_p.h hotkeys.h diff --git a/src/citra_qt/crash_handler.cpp b/src/citra_qt/crash_handler.cpp new file mode 100644 index 000000000..939615160 --- /dev/null +++ b/src/citra_qt/crash_handler.cpp @@ -0,0 +1,270 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/scm_rev.h" +#include "common/scope_exit.h" + +// Since we have no idea what the state of the application is at the point +// the crash handler is called, we're going to have to use reasonably +// low-level methods to display things to the user. +// +// An advisable strategy is to get things onto the console first before trying to do fancy GUI stuff. + +#ifdef _WIN32 + +#pragma comment(lib, "dbghelp.lib") + +#include // Must include this first. +#include +#include + +namespace CrashHandler { + +static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS*); +static std::string GetStackTrace(CONTEXT& c); + +void Register() { + SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); +} + +/** + * This function is called by the operating system when an unhandled exception occurs. + * This includes things like debug breakpoints when not connected to a debugger. + * @param ep The exception pointer containing exception information. + * @return See Microsoft's documentation on SetUnhandledExceptionFilter for possible return values. + */ +static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS* ep) { + std::string stack_trace = GetStackTrace(*ep->ContextRecord); + + std::string detail = std::string() + + "Version: " + Common::g_scm_branch + ":" + Common::g_scm_desc + "\n" + "Commit: " + Common::g_scm_rev + "\n" + "Stack Trace:\n" + stack_trace; + + // Ensure we have a log of everything in the console first. + fprintf(stderr, "Unhandled Exception:\n%s", detail.c_str()); + fflush(stderr); + + std::string title = "Citra: Caught Unhandled Exception"; + + std::string message = std::string() + + "Press Ctrl+C to copy text\n" + + "Please also take a copy of the console window\n" + + "\n" + + detail; + + // QMessageBox does not work and when it does it isn't happy. We need to use something lower-level. + MessageBoxA(nullptr, message.c_str(), title.c_str(), MB_ICONSTOP); + FatalAppExit(0, _T("Terminating application")); + + return EXCEPTION_CONTINUE_SEARCH; +} + +/** + * This function walks the stack of the current thread using StackWalk64. + * @param c The context record that contains the information on the stack of interest. + * @return A string containing a human-readable stack trace. + */ +static std::string GetStackTrace(CONTEXT& c) { + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + + // This function generates a single line of the stack trace. + // `return_address` is a return address found on the stack. + auto get_symbol_info = [&process](DWORD64 return_address) -> std::string { + constexpr size_t SYMBOL_NAME_SIZE = 512; // arbitrary value + + // Allocate space for symbol info. + IMAGEHLP_SYMBOL64* symbol = static_cast(calloc(sizeof(IMAGEHLP_SYMBOL64) + SYMBOL_NAME_SIZE * sizeof(char), 1)); + symbol->MaxNameLength = SYMBOL_NAME_SIZE; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + SCOPE_EXIT({ free(symbol); }); + + // Actually get symbol info. + DWORD64 symbol_displacement; // The number of bytes return_address is offset from the start of the function. + SymGetSymFromAddr64(process, return_address, &symbol_displacement, symbol); + + // Get undecorated name. + char undecorated_name[SYMBOL_NAME_SIZE + 1]; + UnDecorateSymbolName(symbol->Name, &undecorated_name[0], SYMBOL_NAME_SIZE, UNDNAME_COMPLETE); + + // Get source code line information. + DWORD line_displacement = 0; // The number of bytes return_address is offset from the first instruction of this line. + IMAGEHLP_LINE64 line = { 0 }; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + SymGetLineFromAddr64(process, return_address, &line_displacement, &line); + + // Remove unnecessary path information before the "\\src\\" directory.s + std::string file_name = "(null)"; + if (line.FileName) { + file_name = line.FileName; + size_t found = file_name.find("\\src\\"); + if (found != std::string::npos) + file_name = file_name.substr(found + 1); + } + + // Format string + std::string ret; + ret.resize(1024); + size_t ret_size = snprintf(&ret[0], ret.size(), "[%llx] %s+0x%llx (%s:%i)\n", return_address, undecorated_name, symbol_displacement, file_name.c_str(), line.LineNumber); + ret.resize(ret_size); + return ret; + }; + + // Initialise symbols + if (SymInitialize(process, NULL, TRUE) == FALSE) { + fprintf(stderr, "Failed to get symbols. Continuing anyway...\n"); + } + SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + SCOPE_EXIT({ SymCleanup(process); }); + + // Extract stack information from context + STACKFRAME64 s; + s.AddrPC.Offset = c.Rip; + s.AddrPC.Mode = AddrModeFlat; + s.AddrStack.Offset = c.Rsp; + s.AddrStack.Mode = AddrModeFlat; + s.AddrFrame.Offset = c.Rbp; + s.AddrFrame.Mode = AddrModeFlat; + + // Return value + std::string stack_trace; + + // Walk the stack + do { + if (!StackWalk64(IMAGE_FILE_MACHINE_AMD64, process, thread, &s, &c, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) { + stack_trace += "Last StackWalk64 failed\n"; + return stack_trace; + } + + if (s.AddrPC.Offset != 0) { + stack_trace += get_symbol_info(s.AddrPC.Offset); + } else { + stack_trace += "No Symbols: rip == 0\n"; + } + } while (s.AddrReturn.Offset != 0); + + return stack_trace; +} + +} // namespace CrashHandler + +#else + +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +#include +#include +#endif + +namespace CrashHandler { + +static void SignalHandler(int signal); +static const char* GetSignalName(int signal); +static std::string GetStackTrace(); + +#ifdef __APPLE__ +static void OSXMessageBox(std::string title, std::string message); +#endif + +void Register() { + // signal is supported on both OS X and Linux. + signal(SIGABRT, SignalHandler); + signal(SIGSEGV, SignalHandler); + signal(SIGILL, SignalHandler); + signal(SIGFPE, SignalHandler); + signal(SIGBUS, SignalHandler); + signal(SIGTRAP, SignalHandler); +} + +static void SignalHandler(int signal) { + // Just in case recursion is possible. + static size_t times_called = 0; + times_called++; + if (times_called > 1) { + return; + } + + fprintf(stderr, "Oops. Now in CrashHandler::SignalHandler:\n"); + fprintf(stderr, "Caught signal %i (%s)\n", signal, GetSignalName(signal)); + fprintf(stderr, "Git Branch: %s (%s)\n", Common::g_scm_branch, Common::g_scm_desc); + fprintf(stderr, "Git Commit: %s\n", Common::g_scm_rev); + fprintf(stderr, "Stack Trace:\n"); + + std::string stack_trace = GetStackTrace(); + +#ifdef __APPLE__ + std::string title = "Caught Signal " + std::to_string(signal) + " (" + GetSignalName(signal) + ")"; + + std::string message = std::string() + + "Version: " + Common::g_scm_branch + "-" + Common::g_scm_desc + "\n" + + "Commit: " + Common::g_scm_rev + "\n" + + "Stack Trace:\n" + stack_trace; + + OSXMessageBox(title, message); +#endif + + exit(-1); +} + +static const char* GetSignalName(int signal) { + switch (signal) { + case SIGABRT: + return "SIGABRT"; + case SIGSEGV: + return "SIGSEGV"; + case SIGILL: + return "SIGILL"; + case SIGFPE: + return "SIGFPE"; + case SIGBUS: + return "SIGBUS"; + case SIGTRAP: + return "SIGTRAP"; + } + + return "unknown signal"; +} + +static std::string GetStackTrace() { + constexpr size_t MAX_NUM_FRAMES = 64; + void* return_addresses[MAX_NUM_FRAMES + 1]; + + size_t num_frames = backtrace(return_addresses, MAX_NUM_FRAMES); + if (num_frames == 0) + return ""; + + char** symbols = backtrace_symbols(return_addresses, num_frames); + + std::string ret; + for (size_t i = 0; i < num_frames; i++) { + fprintf(stderr, "%s\n", symbols[i]); + ret += symbols[i]; + ret += "\n"; + } + return ret; +} + +#ifdef __APPLE__ +static void OSXMessageBox(std::string title, std::string message) { + // This implementation leaks memory, but at this point we don't really care. + id alert = objc_msgSend(objc_getClass("NSAlert"), sel_registerName("alloc")); + alert = objc_msgSend(alert, sel_registerName("init")); + objc_msgSend(alert, sel_getUid("setAlertStyle:"), 1); + objc_msgSend(alert, sel_getUid("setMessageText:"), CFSTR(message.c_str())); + objc_msgSend(alert, sel_getUid("setInformativeText:"), CFSTR(title.c_str())); + objc_msgSend(alert, sel_getUid("runModal")); +} +#endif + +} // namespace CrashHandler + +#endif diff --git a/src/citra_qt/crash_handler.h b/src/citra_qt/crash_handler.h new file mode 100644 index 000000000..a5a19f84c --- /dev/null +++ b/src/citra_qt/crash_handler.h @@ -0,0 +1,9 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +namespace CrashHandler { + +void Register(); + +} // namespace CrashHandler diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 6239160bc..fe4f7acc4 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -18,6 +18,7 @@ #include "citra_qt/bootmanager.h" #include "citra_qt/config.h" #include "citra_qt/configure_dialog.h" +#include "citra_qt/crash_handler.h" #include "citra_qt/game_list.h" #include "citra_qt/hotkeys.h" #include "citra_qt/main.h" @@ -561,6 +562,8 @@ void GMainWindow::closeEvent(QCloseEvent* event) { #endif int main(int argc, char* argv[]) { + CrashHandler::Register(); + Log::Filter log_filter(Log::Level::Info); Log::SetFilter(&log_filter);