diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 74a271f08..48f24465f 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -3,6 +3,7 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU set(SRCS break_points.cpp + crash_handler.cpp emu_window.cpp file_util.cpp framebuffer_layout.cpp @@ -34,6 +35,7 @@ set(HEADERS common_funcs.h common_paths.h common_types.h + crash_handler.h emu_window.h file_util.h framebuffer_layout.h diff --git a/src/common/crash_handler.cpp b/src/common/crash_handler.cpp new file mode 100644 index 000000000..aeebaeffc --- /dev/null +++ b/src/common/crash_handler.cpp @@ -0,0 +1,161 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include "common/crash_handler.h" +#include "common/scope_exit.h" + +#ifdef _WIN32 + +#pragma comment(lib, "dbghelp.lib") + +#include +// windows.h must be included first. +#include + +#include "common/string_util.h" + +namespace Common { + +static bool unhandled_exception_called; +static jmp_buf unhandled_exception_jmp_buf; +static std::vector unhandled_exception_stack_trace; + +static LONG WINAPI MyUnhandledExceptionFilter(_EXCEPTION_POINTERS*); +static void GetStackTrace(CONTEXT& c); + +void CrashHandler(std::function try_, + std::function catch_) { + unhandled_exception_called = false; + SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); + + if (setjmp(unhandled_exception_jmp_buf) != 0) { + catch_({unhandled_exception_stack_trace}); + return; + } + + try_(); +} + +/** + * 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(_EXCEPTION_POINTERS* ep) { + // Prevent re-entry. + if (unhandled_exception_called) + return EXCEPTION_CONTINUE_SEARCH; + + unhandled_exception_called = true; + GetStackTrace(*ep->ContextRecord); + + // Ensure we have a log of everything in the console first. + fprintf(stderr, "Unhandled Exception:\n"); + for (const auto& line : unhandled_exception_stack_trace) { + fprintf(stderr, "%s\n", line.c_str()); + } + fflush(stderr); + + longjmp(unhandled_exception_jmp_buf, 1); + + return EXCEPTION_CONTINUE_SEARCH; +} + +/** + * This function walks the stack of the current thread using StackWalk64. + * @param ctx The context record that contains the information on the stack of interest. + * @return A string containing a human-readable stack trace. + */ +static void GetStackTrace(CONTEXT& ctx) { + 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; // Offset of return_address from function entry point. + 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; // Offset of return_address from first instruction of line. + IMAGEHLP_LINE64 line = {}; + line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + SymGetLineFromAddr64(process, return_address, &line_displacement, &line); + + // Remove unnecessary path information before the "\\src\\" directory. + 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 + return Common::StringFromFormat("[%llx] %s+0x%llx (%s:%i)", return_address, + undecorated_name, symbol_displacement, file_name.c_str(), + line.LineNumber); + }; + + // NOTE: SymFunctionTableAccess64 doesn't work with the non-standard stack frames our JIT + // produces. Thus we elect to not use StackWalk64, but instead manually use the Rtl* functions. + + // Initialise symbols + if (SymInitialize(process, nullptr, TRUE) == FALSE) { + fprintf(stderr, "Failed to get symbols. Continuing anyway...\n"); + } + SymSetOptions(SymGetOptions() | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + SCOPE_EXIT({ SymCleanup(process); }); + + // Walk the stack + unhandled_exception_stack_trace = {}; + while (ctx.Rip != 0) { + unhandled_exception_stack_trace.emplace_back(get_symbol_info(ctx.Rip)); + + DWORD64 image_base; + PRUNTIME_FUNCTION runtime_function = RtlLookupFunctionEntry(ctx.Rip, &image_base, nullptr); + + if (!runtime_function) { + // This is likely a leaf function. Adjust the stack appropriately. + if (!ctx.Rsp) { + unhandled_exception_stack_trace.emplace_back("Invalid rsp"); + return; + } + ctx.Rip = *(reinterpret_cast(ctx.Rsp)); + ctx.Rsp += 8; + continue; + } + + PVOID handler_data; + ULONG64 establisher_frame; + RtlVirtualUnwind(UNW_FLAG_NHANDLER, image_base, ctx.Rip, runtime_function, &ctx, + &handler_data, &establisher_frame, nullptr); + } +} + +} // namespace Common + +#else + +#error "Unimplemented" + +#endif diff --git a/src/common/crash_handler.h b/src/common/crash_handler.h new file mode 100644 index 000000000..6bd0a7979 --- /dev/null +++ b/src/common/crash_handler.h @@ -0,0 +1,17 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include + +namespace Common { + +struct CrashInformation { + std::vector stack_trace; +}; + +void CrashHandler(std::function try_, std::function catch_); + +} // namespace Common