mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-25 19:20: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
|
set(SRCS
|
||||||
break_points.cpp
|
break_points.cpp
|
||||||
|
crash_handler.cpp
|
||||||
emu_window.cpp
|
emu_window.cpp
|
||||||
file_util.cpp
|
file_util.cpp
|
||||||
framebuffer_layout.cpp
|
framebuffer_layout.cpp
|
||||||
@ -34,6 +35,7 @@ set(HEADERS
|
|||||||
common_funcs.h
|
common_funcs.h
|
||||||
common_paths.h
|
common_paths.h
|
||||||
common_types.h
|
common_types.h
|
||||||
|
crash_handler.h
|
||||||
emu_window.h
|
emu_window.h
|
||||||
file_util.h
|
file_util.h
|
||||||
framebuffer_layout.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