mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-25 19:30:15 +00:00
crash_handler: Catch unhandled structured exceptions on Windows
This commit is contained in:
parent
c96acc1941
commit
710b70f555
@ -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
|
||||
|
161
src/common/crash_handler.cpp
Normal file
161
src/common/crash_handler.cpp
Normal file
@ -0,0 +1,161 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "common/crash_handler.h"
|
||||
#include "common/scope_exit.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#pragma comment(lib, "dbghelp.lib")
|
||||
|
||||
#include <windows.h>
|
||||
// windows.h must be included first.
|
||||
#include <dbghelp.h>
|
||||
|
||||
#include "common/string_util.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
static bool unhandled_exception_called;
|
||||
static jmp_buf unhandled_exception_jmp_buf;
|
||||
static std::vector<std::string> unhandled_exception_stack_trace;
|
||||
|
||||
static LONG WINAPI MyUnhandledExceptionFilter(_EXCEPTION_POINTERS*);
|
||||
static void GetStackTrace(CONTEXT& c);
|
||||
|
||||
void CrashHandler(std::function<void()> try_,
|
||||
std::function<void(const Common::CrashInformation&)> 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<IMAGEHLP_SYMBOL64*>(
|
||||
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<u64*>(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
|
17
src/common/crash_handler.h
Normal file
17
src/common/crash_handler.h
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
struct CrashInformation {
|
||||
std::vector<std::string> stack_trace;
|
||||
};
|
||||
|
||||
void CrashHandler(std::function<void()> try_, std::function<void(const CrashInformation&)> catch_);
|
||||
|
||||
} // namespace Common
|
Loading…
Reference in New Issue
Block a user