Merge pull request #423 from DrChat/crash_pause

Pause Xenia if it crashes in guest code
This commit is contained in:
Ben Vanik 2015-09-11 15:19:32 -07:00
commit 02103ddde0
4 changed files with 181 additions and 0 deletions

View File

@ -0,0 +1,49 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BASE_EXCEPTION_HANDLER_H_
#define XENIA_BASE_EXCEPTION_HANDLER_H_
#include <functional>
#include <vector>
namespace xe {
class ExceptionHandler {
public:
struct Info {
enum {
kInvalidException = 0,
kAccessViolation,
} code = kInvalidException;
uint64_t pc = 0; // Program counter address. RIP on x64.
uint64_t fault_address =
0; // In case of AV, address that was read from/written to.
void* thread_context = nullptr; // Platform-specific thread context info.
};
typedef std::function<bool(Info* ex_info)> Handler;
// Static initialization. Only call this once!
static bool Initialize();
// Install an exception handler. Returns an ID which you can save to remove
// this later. This will install the exception handler in the last place.
// TODO: ID support!
static uint32_t Install(Handler fn);
static bool Remove(uint32_t id);
static const std::vector<Handler>& handlers() { return handlers_; }
private:
static std::vector<Handler> handlers_;
};
}; // namespace xe
#endif // XENIA_BASE_EXCEPTION_HANDLER_H_

View File

@ -0,0 +1,61 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/base/exception_handler.h"
#include "xenia/base/platform_win.h"
namespace xe {
std::vector<ExceptionHandler::Handler> ExceptionHandler::handlers_;
LONG CALLBACK ExceptionHandlerCallback(PEXCEPTION_POINTERS ex_info) {
// Visual Studio SetThreadName
if (ex_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
return EXCEPTION_CONTINUE_SEARCH;
}
auto code = ex_info->ExceptionRecord->ExceptionCode;
ExceptionHandler::Info info;
info.pc = ex_info->ContextRecord->Rip;
info.thread_context = ex_info->ContextRecord;
switch (code) {
case STATUS_ACCESS_VIOLATION:
info.code = ExceptionHandler::Info::kAccessViolation;
info.fault_address = ex_info->ExceptionRecord->ExceptionInformation[1];
break;
}
// Only call a handler if we support this type of exception.
if (info.code != ExceptionHandler::Info::kInvalidException) {
for (auto handler : ExceptionHandler::handlers()) {
if (handler(&info)) {
// Exception handled.
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
bool ExceptionHandler::Initialize() {
AddVectoredExceptionHandler(0, ExceptionHandlerCallback);
// TODO: Do we need a continue handler if a debugger is attached?
return true;
}
uint32_t ExceptionHandler::Install(std::function<bool(Info* ex_info)> fn) {
handlers_.push_back(fn);
// TODO: ID support!
return 0;
}
}

View File

@ -14,8 +14,11 @@
#include "xenia/apu/audio_system.h" #include "xenia/apu/audio_system.h"
#include "xenia/base/assert.h" #include "xenia/base/assert.h"
#include "xenia/base/clock.h" #include "xenia/base/clock.h"
#include "xenia/base/debugging.h"
#include "xenia/base/exception_handler.h"
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/base/string.h" #include "xenia/base/string.h"
#include "xenia/cpu/backend/code_cache.h"
#include "xenia/gpu/graphics_system.h" #include "xenia/gpu/graphics_system.h"
#include "xenia/hid/input_system.h" #include "xenia/hid/input_system.h"
#include "xenia/kernel/kernel_state.h" #include "xenia/kernel/kernel_state.h"
@ -27,6 +30,8 @@
#include "xenia/vfs/devices/stfs_container_device.h" #include "xenia/vfs/devices/stfs_container_device.h"
#include "xenia/vfs/virtual_file_system.h" #include "xenia/vfs/virtual_file_system.h"
#include "el/elements/message_form.h"
DEFINE_double(time_scalar, 1.0, DEFINE_double(time_scalar, 1.0,
"Scalar used to speed or slow time (1x, 2x, 1/2x, etc)."); "Scalar used to speed or slow time (1x, 2x, 1/2x, etc).");
@ -159,6 +164,11 @@ X_STATUS Emulator::Setup(ui::Window* display_window) {
Profiler::set_display(std::move(profiler_display)); Profiler::set_display(std::move(profiler_display));
}); });
// Install an exception handler.
ExceptionHandler::Initialize();
ExceptionHandler::Install(
std::bind(&Emulator::ExceptionCallback, this, std::placeholders::_1));
return result; return result;
} }
@ -262,6 +272,64 @@ X_STATUS Emulator::LaunchStfsContainer(std::wstring path) {
return CompleteLaunch(path, "game:\\default.xex"); return CompleteLaunch(path, "game:\\default.xex");
} }
bool Emulator::ExceptionCallback(ExceptionHandler::Info* info) {
// Check to see if the exception occurred in guest code.
auto code_cache = processor()->backend()->code_cache();
auto code_base = code_cache->base_address();
auto code_end = code_base + code_cache->total_size();
if (!debugger()->is_attached() && debugging::IsDebuggerAttached()) {
// If Xenia's debugger isn't attached but another one is, pass it to that
// debugger.
return false;
}
if (info->pc >= code_base && info->pc < code_end) {
using namespace xe::kernel;
auto global_lock = global_critical_region::AcquireDirect();
// Within range. Pause the emulator and eat the exception.
auto threads = kernel_state()->object_table()->GetObjectsByType<XThread>(
XObject::kTypeThread);
auto cur_thread = XThread::GetCurrentThread();
for (auto thread : threads) {
if (!thread->is_guest_thread()) {
// Don't pause host threads.
continue;
}
if (cur_thread == thread.get()) {
continue;
}
thread->Suspend(nullptr);
}
if (debugger()->is_attached()) {
// TODO: Send the exception to Xenia's debugger
}
// Display a dialog telling the user the guest has crashed.
display_window()->loop()->PostSynchronous([&]() {
auto msgbox = new el::elements::MessageForm(
display_window()->root_element(), "none");
msgbox->Show("Uh-oh!",
"The guest has crashed.\n\n"
"Xenia has now paused itself.");
});
// Now suspend ourself (we should be a guest thread).
cur_thread->Suspend(nullptr);
// We should not arrive here!
assert_always();
return false;
}
// Didn't occur in guest code. Let it pass.
return false;
}
X_STATUS Emulator::CompleteLaunch(const std::wstring& path, X_STATUS Emulator::CompleteLaunch(const std::wstring& path,
const std::string& module_path) { const std::string& module_path) {
// Allow xam to request module loads. // Allow xam to request module loads.

View File

@ -12,6 +12,7 @@
#include <string> #include <string>
#include "xenia/base/exception_handler.h"
#include "xenia/debug/debugger.h" #include "xenia/debug/debugger.h"
#include "xenia/kernel/kernel_state.h" #include "xenia/kernel/kernel_state.h"
#include "xenia/memory.h" #include "xenia/memory.h"
@ -75,6 +76,8 @@ class Emulator {
X_STATUS LaunchStfsContainer(std::wstring path); X_STATUS LaunchStfsContainer(std::wstring path);
private: private:
bool ExceptionCallback(ExceptionHandler::Info* info);
X_STATUS CompleteLaunch(const std::wstring& path, X_STATUS CompleteLaunch(const std::wstring& path,
const std::string& module_path); const std::string& module_path);