A new debugger.

Lots of bugs/rough edges/etc - issues will be filed.
Old-style debugging still works (just use --emit_source_annotations to get
the helpful movs back and --break_on_instruction will still fire).
This commit is contained in:
Ben Vanik
2015-09-20 21:31:05 -07:00
parent 8046c19898
commit 5d033f9cb3
90 changed files with 4183 additions and 4651 deletions

View File

@@ -38,6 +38,8 @@ DEFINE_bool(enable_debugprint_log, false,
"Log debugprint traps to the active debugger");
DEFINE_bool(ignore_undefined_externs, true,
"Don't exit when an undefined extern is called.");
DEFINE_bool(emit_source_annotations, false,
"Add extra movs and nops to make disassembly easier to read.");
namespace xe {
namespace cpu {
@@ -239,7 +241,7 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t* out_stack_size) {
add(rsp, (uint32_t)stack_size);
ret();
if (FLAGS_debug) {
if (FLAGS_emit_source_annotations) {
nop();
nop();
nop();
@@ -254,9 +256,9 @@ void X64Emitter::MarkSourceOffset(const Instr* i) {
auto entry = source_map_arena_.Alloc<SourceMapEntry>();
entry->source_offset = static_cast<uint32_t>(i->src1.offset);
entry->hir_offset = uint32_t(i->block->ordinal << 16) | i->ordinal;
entry->code_offset = static_cast<uint32_t>(getSize() + 1);
entry->code_offset = static_cast<uint32_t>(getSize());
if (FLAGS_debug) {
if (FLAGS_emit_source_annotations) {
nop();
nop();
mov(eax, entry->source_offset);

View File

@@ -15,50 +15,84 @@
namespace xe {
namespace cpu {
ExportResolver::Table::Table(const char* module_name,
const std::vector<Export*>* exports_by_ordinal)
: exports_by_ordinal_(exports_by_ordinal) {
auto dot_pos = std::strrchr(module_name, '.');
if (dot_pos != nullptr) {
std::strncpy(module_name_, module_name,
static_cast<size_t>(dot_pos - module_name));
} else {
std::strncpy(module_name_, module_name, xe::countof(module_name_) - 1);
}
exports_by_name_.reserve(exports_by_ordinal_->size());
for (size_t i = 0; i < exports_by_ordinal_->size(); ++i) {
auto export = exports_by_ordinal_->at(i);
if (export) {
exports_by_name_.push_back(export);
}
}
std::sort(
exports_by_name_.begin(), exports_by_name_.end(),
[](Export* a, Export* b) { return std::strcmp(a->name, b->name) <= 0; });
}
ExportResolver::ExportResolver() = default;
ExportResolver::~ExportResolver() = default;
void ExportResolver::RegisterTable(
const std::string& library_name,
const std::vector<xe::cpu::Export*>* exports) {
tables_.emplace_back(library_name, exports);
const char* module_name, const std::vector<xe::cpu::Export*>* exports) {
tables_.emplace_back(module_name, exports);
all_exports_by_name_.reserve(all_exports_by_name_.size() + exports->size());
for (size_t i = 0; i < exports->size(); ++i) {
auto export = exports->at(i);
if (export) {
all_exports_by_name_.push_back(export);
}
}
std::sort(
all_exports_by_name_.begin(), all_exports_by_name_.end(),
[](Export* a, Export* b) { return std::strcmp(a->name, b->name) <= 0; });
}
Export* ExportResolver::GetExportByOrdinal(const std::string& library_name,
Export* ExportResolver::GetExportByOrdinal(const char* module_name,
uint16_t ordinal) {
for (const auto& table : tables_) {
if (table.name == library_name || table.simple_name == library_name) {
if (ordinal > table.exports->size()) {
if (std::strncmp(module_name, table.module_name(),
std::strlen(table.module_name())) == 0) {
if (ordinal > table.exports_by_ordinal().size()) {
return nullptr;
}
return table.exports->at(ordinal);
return table.exports_by_ordinal().at(ordinal);
}
}
return nullptr;
}
void ExportResolver::SetVariableMapping(const std::string& library_name,
void ExportResolver::SetVariableMapping(const char* module_name,
uint16_t ordinal, uint32_t value) {
auto export_entry = GetExportByOrdinal(library_name, ordinal);
auto export_entry = GetExportByOrdinal(module_name, ordinal);
assert_not_null(export_entry);
export_entry->tags |= ExportTag::kImplemented;
export_entry->variable_ptr = value;
}
void ExportResolver::SetFunctionMapping(const std::string& library_name,
void ExportResolver::SetFunctionMapping(const char* module_name,
uint16_t ordinal,
xe_kernel_export_shim_fn shim) {
auto export_entry = GetExportByOrdinal(library_name, ordinal);
auto export_entry = GetExportByOrdinal(module_name, ordinal);
assert_not_null(export_entry);
export_entry->tags |= ExportTag::kImplemented;
export_entry->function_data.shim = shim;
}
void ExportResolver::SetFunctionMapping(const std::string& library_name,
void ExportResolver::SetFunctionMapping(const char* module_name,
uint16_t ordinal,
ExportTrampoline trampoline) {
auto export_entry = GetExportByOrdinal(library_name, ordinal);
auto export_entry = GetExportByOrdinal(module_name, ordinal);
assert_not_null(export_entry);
export_entry->tags |= ExportTag::kImplemented;
export_entry->function_data.trampoline = trampoline;

View File

@@ -99,37 +99,46 @@ class Export {
class ExportResolver {
public:
class Table {
public:
Table(const char* module_name, const std::vector<Export*>* exports);
const char* module_name() const { return module_name_; }
const std::vector<Export*>& exports_by_ordinal() const {
return *exports_by_ordinal_;
}
const std::vector<Export*>& exports_by_name() const {
return exports_by_name_;
}
private:
char module_name_[32] = {0};
const std::vector<Export*>* exports_by_ordinal_ = nullptr;
std::vector<Export*> exports_by_name_;
};
ExportResolver();
~ExportResolver();
void RegisterTable(const std::string& library_name,
void RegisterTable(const char* module_name,
const std::vector<Export*>* exports);
const std::vector<Table>& tables() const { return tables_; }
const std::vector<Export*>& all_exports_by_name() const {
return all_exports_by_name_;
}
Export* GetExportByOrdinal(const std::string& library_name, uint16_t ordinal);
Export* GetExportByOrdinal(const char* module_name, uint16_t ordinal);
void SetVariableMapping(const std::string& library_name, uint16_t ordinal,
void SetVariableMapping(const char* module_name, uint16_t ordinal,
uint32_t value);
void SetFunctionMapping(const std::string& library_name, uint16_t ordinal,
void SetFunctionMapping(const char* module_name, uint16_t ordinal,
xe_kernel_export_shim_fn shim);
void SetFunctionMapping(const std::string& library_name, uint16_t ordinal,
void SetFunctionMapping(const char* module_name, uint16_t ordinal,
ExportTrampoline trampoline);
private:
struct ExportTable {
std::string name;
std::string simple_name; // without extension
const std::vector<Export*>* exports;
ExportTable(const std::string& name, const std::vector<Export*>* exports)
: name(name), exports(exports) {
auto dot_pos = name.find_last_of('.');
if (dot_pos != std::string::npos) {
simple_name = name.substr(0, dot_pos);
} else {
simple_name = name;
}
}
};
std::vector<ExportTable> tables_;
std::vector<Table> tables_;
std::vector<Export*> all_exports_by_name_;
};
} // namespace cpu

View File

@@ -246,7 +246,7 @@ enum class PPCRegister {
};
#pragma pack(push, 8)
typedef struct alignas(64) PPCContext_s {
typedef struct PPCContext_s {
// Must be stored at 0x0 for now.
// TODO(benvanik): find a nice way to describe this to the JIT.
ThreadState* thread_state;

View File

@@ -16,8 +16,6 @@
namespace xe {
namespace cpu {
using xe::debug::Breakpoint;
Function::Function(Module* module, uint32_t address)
: Symbol(Symbol::Type::kFunction, module, address) {}
@@ -100,6 +98,18 @@ const SourceMapEntry* GuestFunction::LookupCodeOffset(uint32_t offset) const {
return source_map_.empty() ? nullptr : &source_map_[0];
}
uintptr_t GuestFunction::MapSourceToCode(uint32_t source_address) const {
auto entry = LookupSourceOffset(source_address - address());
return entry ? entry->code_offset
: reinterpret_cast<uintptr_t>(machine_code());
}
uint32_t GuestFunction::MapCodeToSource(uintptr_t host_address) const {
auto entry = LookupCodeOffset(static_cast<uint32_t>(
host_address - reinterpret_cast<uintptr_t>(machine_code())));
return entry ? entry->source_offset : address();
}
bool GuestFunction::Call(ThreadState* thread_state, uint32_t return_address) {
// SCOPE_profile_cpu_f("cpu");

View File

@@ -17,7 +17,6 @@
#include "xenia/cpu/frontend/ppc_context.h"
#include "xenia/cpu/symbol.h"
#include "xenia/cpu/thread_state.h"
#include "xenia/debug/breakpoint.h"
#include "xenia/debug/function_trace_data.h"
namespace xe {
@@ -111,6 +110,9 @@ class GuestFunction : public Function {
const SourceMapEntry* LookupHIROffset(uint32_t offset) const;
const SourceMapEntry* LookupCodeOffset(uint32_t offset) const;
uintptr_t MapSourceToCode(uint32_t source_address) const;
uint32_t MapCodeToSource(uintptr_t host_address) const;
bool Call(ThreadState* thread_state, uint32_t return_address) override;
protected:

View File

@@ -11,6 +11,7 @@
#include "xenia/base/assert.h"
#include "xenia/base/byte_order.h"
#include "xenia/base/exception_handler.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/memory.h"
@@ -20,33 +21,28 @@ namespace cpu {
MMIOHandler* MMIOHandler::global_handler_ = nullptr;
// Implemented in the platform cc file.
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* virtual_membase,
uint8_t* physical_membase);
std::unique_ptr<MMIOHandler> MMIOHandler::Install(uint8_t* virtual_membase,
uint8_t* physical_membase,
uint8_t* memory_end) {
uint8_t* membase_end) {
// There can be only one handler at a time.
assert_null(global_handler_);
if (global_handler_) {
return nullptr;
}
// Create the platform-specific handler.
auto handler = CreateMMIOHandler(virtual_membase, physical_membase);
auto handler = std::unique_ptr<MMIOHandler>(
new MMIOHandler(virtual_membase, physical_membase, membase_end));
// Platform-specific initialization for the handler.
if (!handler->Initialize()) {
return nullptr;
}
// Install the exception handler directed at the MMIOHandler.
ExceptionHandler::Install(ExceptionCallbackThunk, handler.get());
handler->memory_end_ = memory_end;
global_handler_ = handler.get();
return handler;
}
MMIOHandler::~MMIOHandler() {
ExceptionHandler::Uninstall(ExceptionCallbackThunk, this);
assert_true(global_handler_ == this);
global_handler_ = nullptr;
}
@@ -166,7 +162,8 @@ void MMIOHandler::CancelWriteWatch(uintptr_t watch_handle) {
delete entry;
}
bool MMIOHandler::CheckWriteWatch(void* thread_state, uint64_t fault_address) {
bool MMIOHandler::CheckWriteWatch(X64Context* thread_context,
uint64_t fault_address) {
uint32_t physical_address = uint32_t(fault_address);
if (physical_address > 0x1FFFFFFF) {
physical_address &= 0x1FFFFFFF;
@@ -364,10 +361,16 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
return true;
}
bool MMIOHandler::HandleAccessFault(void* thread_state,
uint64_t fault_address) {
if (fault_address < uint64_t(virtual_membase_) ||
fault_address > uint64_t(memory_end_)) {
bool MMIOHandler::ExceptionCallbackThunk(Exception* ex, void* data) {
return reinterpret_cast<MMIOHandler*>(data)->ExceptionCallback(ex);
}
bool MMIOHandler::ExceptionCallback(Exception* ex) {
if (ex->code() != Exception::Code::kAccessViolation) {
return false;
}
if (ex->fault_address() < uint64_t(virtual_membase_) ||
ex->fault_address() > uint64_t(memory_end_)) {
// Quick kill anything outside our mapping.
return false;
}
@@ -375,9 +378,10 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
// Access violations are pretty rare, so we can do a linear search here.
// Only check if in the virtual range, as we only support virtual ranges.
const MMIORange* range = nullptr;
if (fault_address < uint64_t(physical_membase_)) {
if (ex->fault_address() < uint64_t(physical_membase_)) {
for (const auto& test_range : mapped_ranges_) {
if ((uint32_t(fault_address) & test_range.mask) == test_range.address) {
if ((static_cast<uint32_t>(ex->fault_address()) & test_range.mask) ==
test_range.address) {
// Address is within the range of this mapping.
range = &test_range;
break;
@@ -387,10 +391,10 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
if (!range) {
// Access is not found within any range, so fail and let the caller handle
// it (likely by aborting).
return CheckWriteWatch(thread_state, fault_address);
return CheckWriteWatch(ex->thread_context(), ex->fault_address());
}
auto rip = GetThreadStateRip(thread_state);
auto rip = ex->pc();
auto p = reinterpret_cast<const uint8_t*>(rip);
DecodedMov mov = {0};
bool decoded = TryDecodeMov(p, &mov);
@@ -404,8 +408,8 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
// Load of a memory value - read from range, swap, and store in the
// register.
uint32_t value = range->read(nullptr, range->callback_context,
fault_address & 0xFFFFFFFF);
uint64_t* reg_ptr = GetThreadStateRegPtr(thread_state, mov.value_reg);
static_cast<uint32_t>(ex->fault_address()));
uint64_t* reg_ptr = &ex->thread_context()->int_registers[mov.value_reg];
if (!mov.byte_swap) {
// We swap only if it's not a movbe, as otherwise we are swapping twice.
value = xe::byte_swap(value);
@@ -417,19 +421,19 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
if (mov.is_constant) {
value = uint32_t(mov.constant);
} else {
uint64_t* reg_ptr = GetThreadStateRegPtr(thread_state, mov.value_reg);
uint64_t* reg_ptr = &ex->thread_context()->int_registers[mov.value_reg];
value = static_cast<uint32_t>(*reg_ptr);
if (!mov.byte_swap) {
// We swap only if it's not a movbe, as otherwise we are swapping twice.
value = xe::byte_swap(static_cast<uint32_t>(value));
}
}
range->write(nullptr, range->callback_context, fault_address & 0xFFFFFFFF,
value);
range->write(nullptr, range->callback_context,
static_cast<uint32_t>(ex->fault_address()), value);
}
// Advance RIP to the next instruction so that we resume properly.
SetThreadStateRip(thread_state, rip + mov.length);
ex->set_resume_pc(rip + mov.length);
return true;
}

View File

@@ -16,6 +16,11 @@
#include "xenia/base/mutex.h"
namespace xe {
class Exception;
class X64Context;
} // namespace xe
namespace xe {
namespace cpu {
@@ -59,9 +64,6 @@ class MMIOHandler {
void* callback_context, void* callback_data);
void CancelWriteWatch(uintptr_t watch_handle);
public:
bool HandleAccessFault(void* thread_state, uint64_t fault_address);
protected:
struct WriteWatchEntry {
uint32_t address;
@@ -71,19 +73,17 @@ class MMIOHandler {
void* callback_data;
};
MMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase)
MMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase,
uint8_t* membase_end)
: virtual_membase_(virtual_membase),
physical_membase_(physical_membase) {}
physical_membase_(physical_membase),
memory_end_(membase_end) {}
virtual bool Initialize() = 0;
static bool ExceptionCallbackThunk(Exception* ex, void* data);
bool ExceptionCallback(Exception* ex);
void ClearWriteWatch(WriteWatchEntry* entry);
bool CheckWriteWatch(void* thread_state, uint64_t fault_address);
virtual uint64_t GetThreadStateRip(void* thread_state_ptr) = 0;
virtual void SetThreadStateRip(void* thread_state_ptr, uint64_t rip) = 0;
virtual uint64_t* GetThreadStateRegPtr(void* thread_state_ptr,
int32_t be_reg_index) = 0;
bool CheckWriteWatch(X64Context* thread_context, uint64_t fault_address);
uint8_t* virtual_membase_;
uint8_t* physical_membase_;

View File

@@ -1,237 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/mmio_handler.h"
#include <mach/mach.h>
#include <signal.h>
#include <thread>
#include "xenia/base/logging.h"
// Mach internal function, not defined in any header.
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/exc_server.html
extern "C" boolean_t exc_server(mach_msg_header_t* request_msg,
mach_msg_header_t* reply_msg);
// Exported for the kernel to call back into.
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/catch_exception_raise.html
extern "C" kern_return_t catch_exception_raise(
mach_port_t exception_port, mach_port_t thread, mach_port_t task,
exception_type_t exception, exception_data_t code,
mach_msg_type_number_t code_count);
namespace xe {
namespace cpu {
class MachMMIOHandler : public MMIOHandler {
public:
explicit MachMMIOHandler(uint8_t* mapping_base);
~MachMMIOHandler() override;
protected:
bool Initialize() override;
uint64_t GetThreadStateRip(void* thread_state_ptr) override;
void SetThreadStateRip(void* thread_state_ptr, uint64_t rip) override;
uint64_t* GetThreadStateRegPtr(void* thread_state_ptr,
int32_t be_reg_index) override;
private:
void ThreadEntry();
// Worker thread processing exceptions.
std::unique_ptr<std::thread> thread_;
// Port listening for exceptions on the worker thread.
mach_port_t listen_port_;
};
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* mapping_base) {
return std::make_unique<MachMMIOHandler>(mapping_base);
}
MachMMIOHandler::MachMMIOHandler(uint8_t* mapping_base)
: MMIOHandler(mapping_base), listen_port_(0) {}
bool MachMMIOHandler::Initialize() {
// Allocates the port that listens for exceptions.
// This will be freed in the dtor.
if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
&listen_port_) != KERN_SUCCESS) {
XELOGE("Unable to allocate listen port");
return false;
}
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_port_insert_right.html
if (mach_port_insert_right(mach_task_self(), listen_port_, listen_port_,
MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS) {
XELOGE("Unable to insert listen port right");
return false;
}
// Sets our exception filter so that any BAD_ACCESS exceptions go to it.
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/task_set_exception_ports.html
if (task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS,
listen_port_, EXCEPTION_DEFAULT,
MACHINE_THREAD_STATE) != KERN_SUCCESS) {
XELOGE("Unable to set exception port");
return false;
}
// Spin up the worker thread.
std::unique_ptr<std::thread> thread(
new std::thread([this]() { ThreadEntry(); }));
thread->detach();
thread_ = std::move(thread);
return true;
}
MachMMIOHandler::~MachMMIOHandler() {
task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, 0,
EXCEPTION_DEFAULT, 0);
mach_port_deallocate(mach_task_self(), listen_port_);
}
void MachMMIOHandler::ThreadEntry() {
while (true) {
struct {
mach_msg_header_t head;
mach_msg_body_t msgh_body;
char data[1024];
} msg;
struct {
mach_msg_header_t head;
char data[1024];
} reply;
// Wait for a message on the exception port.
mach_msg_return_t ret =
mach_msg(&msg.head, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(msg),
listen_port_, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (ret != MACH_MSG_SUCCESS) {
XELOGE("mach_msg receive failed with %d %s", ret, mach_error_string(ret));
xe::debugging::Break();
break;
}
// Call exc_server, which will dispatch the catch_exception_raise.
exc_server(&msg.head, &reply.head);
// Send the reply.
if (mach_msg(&reply.head, MACH_SEND_MSG, reply.head.msgh_size, 0,
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL) != MACH_MSG_SUCCESS) {
XELOGE("mach_msg reply send failed");
xe::debugging::Break();
break;
}
}
}
// Kills the app when a bad access exception is unhandled.
void FailBadAccess() {
raise(SIGSEGV);
abort();
}
kern_return_t CatchExceptionRaise(mach_port_t thread) {
auto state_count = x86_EXCEPTION_STATE64_COUNT;
x86_exception_state64_t exc_state;
if (thread_get_state(thread, x86_EXCEPTION_STATE64,
reinterpret_cast<thread_state_t>(&exc_state),
&state_count) != KERN_SUCCESS) {
XELOGE("thread_get_state failed to get exception state");
return KERN_FAILURE;
}
state_count = x86_THREAD_STATE64_COUNT;
x86_thread_state64_t thread_state;
if (thread_get_state(thread, x86_THREAD_STATE64,
reinterpret_cast<thread_state_t>(&thread_state),
&state_count) != KERN_SUCCESS) {
XELOGE("thread_get_state failed to get thread state");
return KERN_FAILURE;
}
auto fault_address = exc_state.__faultvaddr;
auto mmio_handler =
static_cast<MachMMIOHandler*>(MMIOHandler::global_handler());
bool handled = mmio_handler->HandleAccessFault(&thread_state, fault_address);
if (!handled) {
// Unhandled - raise to the system.
XELOGE("MMIO unhandled bad access for %llx, bubbling", fault_address);
// TODO(benvanik): manipulate stack so that we can rip = break_handler or
// something and have the stack trace be valid.
xe::debugging::Break();
// When the thread resumes, kill it.
thread_state.__rip = reinterpret_cast<uint64_t>(FailBadAccess);
// Mach docs say we can return this to continue searching for handlers, but
// OSX doesn't seem to have it.
// return MIG_DESTROY_REQUEST;
}
// Set the thread state - as we've likely changed it.
if (thread_set_state(thread, x86_THREAD_STATE64,
reinterpret_cast<thread_state_t>(&thread_state),
state_count) != KERN_SUCCESS) {
XELOGE("thread_set_state failed to set thread state for continue");
return KERN_FAILURE;
}
return KERN_SUCCESS;
}
uint64_t MachMMIOHandler::GetThreadStateRip(void* thread_state_ptr) {
auto thread_state = reinterpret_cast<x86_thread_state64_t*>(thread_state_ptr);
return thread_state->__rip;
}
void MachMMIOHandler::SetThreadStateRip(void* thread_state_ptr, uint64_t rip) {
auto thread_state = reinterpret_cast<x86_thread_state64_t*>(thread_state_ptr);
thread_state->__rip = rip;
}
uint64_t* MachMMIOHandler::GetThreadStateRegPtr(void* thread_state_ptr,
int32_t be_reg_index) {
// Map from BeaEngine register order to x86_thread_state64 order.
static const uint32_t mapping[] = {
0, // REG0 / RAX -> 0
2, // REG1 / RCX -> 2
3, // REG2 / RDX -> 3
1, // REG3 / RBX -> 1
7, // REG4 / RSP -> 7
6, // REG5 / RBP -> 6
5, // REG6 / RSI -> 5
4, // REG7 / RDI -> 4
8, // REG8 / R8 -> 8
9, // REG9 / R9 -> 9
10, // REG10 / R10 -> 10
11, // REG11 / R11 -> 11
12, // REG12 / R12 -> 12
13, // REG13 / R13 -> 13
14, // REG14 / R14 -> 14
15, // REG15 / R15 -> 15
};
auto thread_state = reinterpret_cast<x86_thread_state64_t*>(thread_state_ptr);
return &thread_state->__rax + mapping[be_reg_index];
}
} // namespace cpu
} // namespace xe
// Exported and called by exc_server.
extern "C" kern_return_t catch_exception_raise(
mach_port_t exception_port, mach_port_t thread, mach_port_t task,
exception_type_t exception, exception_data_t code,
mach_msg_type_number_t code_count) {
// We get/set the states manually instead of using catch_exception_raise_state
// variant because that truncates everything to 32bit.
return xe::cpu::CatchExceptionRaise(thread);
}

View File

@@ -1,110 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/mmio_handler.h"
#include "xenia/base/platform_win.h"
#include "xenia/profiling.h"
namespace xe {
void CrashDump();
} // namespace xe
namespace xe {
namespace cpu {
LONG CALLBACK MMIOExceptionHandler(PEXCEPTION_POINTERS ex_info);
class WinMMIOHandler : public MMIOHandler {
public:
WinMMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase)
: MMIOHandler(virtual_membase, physical_membase) {}
~WinMMIOHandler() override;
protected:
bool Initialize() override;
uint64_t GetThreadStateRip(void* thread_state_ptr) override;
void SetThreadStateRip(void* thread_state_ptr, uint64_t rip) override;
uint64_t* GetThreadStateRegPtr(void* thread_state_ptr,
int32_t reg_index) override;
};
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* virtual_membase,
uint8_t* physical_membase) {
return std::make_unique<WinMMIOHandler>(virtual_membase, physical_membase);
}
bool WinMMIOHandler::Initialize() {
// If there is a debugger attached the normal exception handler will not
// fire and we must instead add the continue handler.
AddVectoredExceptionHandler(1, MMIOExceptionHandler);
if (IsDebuggerPresent()) {
// TODO(benvanik): is this really required?
// AddVectoredContinueHandler(1, MMIOExceptionHandler);
}
return true;
}
WinMMIOHandler::~WinMMIOHandler() {
// Remove exception handlers.
RemoveVectoredExceptionHandler(MMIOExceptionHandler);
RemoveVectoredContinueHandler(MMIOExceptionHandler);
}
// Handles potential accesses to mmio. We look for access violations to
// addresses in our range and call into the registered handlers, if any.
// If there are none, we continue.
LONG CALLBACK MMIOExceptionHandler(PEXCEPTION_POINTERS ex_info) {
// Fast path for SetThreadName.
if (ex_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
return EXCEPTION_CONTINUE_SEARCH;
}
// Disabled because this can cause odd issues during handling.
// SCOPE_profile_cpu_i("cpu", "MMIOExceptionHandler");
// http://msdn.microsoft.com/en-us/library/ms679331(v=vs.85).aspx
// http://msdn.microsoft.com/en-us/library/aa363082(v=vs.85).aspx
auto code = ex_info->ExceptionRecord->ExceptionCode;
if (code == STATUS_ACCESS_VIOLATION) {
auto fault_address = ex_info->ExceptionRecord->ExceptionInformation[1];
if (MMIOHandler::global_handler()->HandleAccessFault(ex_info->ContextRecord,
fault_address)) {
// Handled successfully - RIP has been updated and we can continue.
return EXCEPTION_CONTINUE_EXECUTION;
} else {
// Failed to handle; continue search for a handler (and die if no other
// handler is found).
xe::CrashDump();
return EXCEPTION_CONTINUE_SEARCH;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
uint64_t WinMMIOHandler::GetThreadStateRip(void* thread_state_ptr) {
auto context = reinterpret_cast<LPCONTEXT>(thread_state_ptr);
return context->Rip;
}
void WinMMIOHandler::SetThreadStateRip(void* thread_state_ptr, uint64_t rip) {
auto context = reinterpret_cast<LPCONTEXT>(thread_state_ptr);
context->Rip = rip;
}
uint64_t* WinMMIOHandler::GetThreadStateRegPtr(void* thread_state_ptr,
int32_t reg_index) {
auto context = reinterpret_cast<LPCONTEXT>(thread_state_ptr);
// Register indices line up with the CONTEXT structure format.
return &context->Rax + reg_index;
}
} // namespace cpu
} // namespace xe

View File

@@ -13,8 +13,8 @@
#include <memory>
#include <string>
#include "xenia/base/x64_context.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/x64_context.h"
namespace xe {
namespace cpu {
@@ -82,6 +82,7 @@ class StackWalker {
virtual size_t CaptureStackTrace(void* thread_handle,
uint64_t* frame_host_pcs,
size_t frame_offset, size_t frame_count,
const X64Context* in_host_context,
X64Context* out_host_context,
uint64_t* out_stack_hash = nullptr) = 0;

View File

@@ -152,6 +152,7 @@ class Win32StackWalker : public StackWalker {
size_t CaptureStackTrace(void* thread_handle, uint64_t* frame_host_pcs,
size_t frame_offset, size_t frame_count,
const X64Context* in_host_context,
X64Context* out_host_context,
uint64_t* out_stack_hash) override {
// TODO(benvanik): use xstate?
@@ -160,19 +161,33 @@ class Win32StackWalker : public StackWalker {
// Need at least CONTEXT_CONTROL (for rip and rsp) and CONTEXT_INTEGER (for
// rbp).
CONTEXT thread_context;
thread_context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(thread_handle, &thread_context)) {
XELOGE("Unable to read thread context for stack walk");
return 0;
if (!in_host_context) {
// If not given a context we need to ask for it.
// This gets the state of the thread exactly where it was when suspended.
thread_context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(thread_handle, &thread_context)) {
XELOGE("Unable to read thread context for stack walk");
return 0;
}
} else {
// Copy thread context local. We will be modifying it during stack
// walking, so we don't want to mess with the incoming copy.
thread_context.Rip = in_host_context->rip;
thread_context.EFlags = in_host_context->eflags;
std::memcpy(&thread_context.Rax, in_host_context->int_registers,
sizeof(in_host_context->int_registers));
std::memcpy(&thread_context.Xmm0, in_host_context->xmm_registers,
sizeof(in_host_context->xmm_registers));
}
if (out_host_context) {
// Write out the captured thread context if the caller asked for it.
out_host_context->rip = thread_context.Rip;
out_host_context->eflags = thread_context.EFlags;
std::memcpy(&out_host_context->int_registers.values, &thread_context.Rax,
sizeof(out_host_context->int_registers.values));
std::memcpy(&out_host_context->xmm_registers.values, &thread_context.Xmm0,
sizeof(out_host_context->xmm_registers.values));
std::memcpy(out_host_context->int_registers, &thread_context.Rax,
sizeof(out_host_context->int_registers));
std::memcpy(out_host_context->xmm_registers, &thread_context.Xmm0,
sizeof(out_host_context->xmm_registers));
}
// Setup the frame for walking.

View File

@@ -1,63 +0,0 @@
/**
******************************************************************************
* 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/cpu/x64_context.h"
#include "xenia/base/assert.h"
#include "xenia/base/string_util.h"
namespace xe {
namespace cpu {
// NOTE: this order matches 1:1 with the X64Register enum.
static const char* kRegisterNames[] = {
"rip", "eflags", "rax", "rcx", "rdx", "rbx", "rsp",
"rbp", "rsi", "rdi", "r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15", "xmm0", "xmm1", "xmm2",
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9",
"xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
};
const char* X64Context::GetRegisterName(X64Register reg) {
return kRegisterNames[static_cast<int>(reg)];
}
std::string X64Context::GetStringFromValue(X64Register reg) const {
switch (reg) {
case X64Register::kRip:
return string_util::to_hex_string(rip);
case X64Register::kEflags:
return string_util::to_hex_string(eflags);
default:
if (static_cast<int>(reg) >= static_cast<int>(X64Register::kRax) &&
static_cast<int>(reg) <= static_cast<int>(X64Register::kR15)) {
return string_util::to_hex_string(
int_registers.values[static_cast<int>(reg) -
static_cast<int>(X64Register::kRax)]);
} else if (static_cast<int>(reg) >=
static_cast<int>(X64Register::kXmm0) &&
static_cast<int>(reg) <=
static_cast<int>(X64Register::kXmm15)) {
return string_util::to_hex_string(
xmm_registers.values[static_cast<int>(reg) -
static_cast<int>(X64Register::kXmm0)]);
} else {
assert_unhandled_case(reg);
return "";
}
}
}
void X64Context::SetValueFromString(X64Register reg, std::string value) {
// TODO(benvanik): set value from string.
assert_always(false);
}
} // namespace cpu
} // namespace xe

View File

@@ -1,113 +0,0 @@
/**
******************************************************************************
* 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_CPU_X64_CONTEXT_H_
#define XENIA_CPU_X64_CONTEXT_H_
#include <cstdint>
#include <string>
namespace xe {
namespace cpu {
enum class X64Register {
// NOTE: this order matches 1:1 with the order in the X64Context.
// NOTE: this order matches 1:1 with a string table in the x64_context.cc.
kRip,
kEflags,
kRax,
kRcx,
kRdx,
kRbx,
kRsp,
kRbp,
kRsi,
kRdi,
kR8,
kR9,
kR10,
kR11,
kR12,
kR13,
kR14,
kR15,
kXmm0,
kXmm1,
kXmm2,
kXmm3,
kXmm4,
kXmm5,
kXmm6,
kXmm7,
kXmm8,
kXmm9,
kXmm10,
kXmm11,
kXmm12,
kXmm13,
kXmm14,
kXmm15,
};
struct X64Context {
// TODO(benvanik): x64 registers.
uint64_t rip;
uint32_t eflags;
union {
struct {
uint64_t rax;
uint64_t rcx;
uint64_t rdx;
uint64_t rbx;
uint64_t rsp;
uint64_t rbp;
uint64_t rsi;
uint64_t rdi;
uint64_t r8;
uint64_t r9;
uint64_t r10;
uint64_t r11;
uint64_t r12;
uint64_t r13;
uint64_t r14;
uint64_t r15;
};
uint64_t values[16];
} int_registers;
union {
struct {
__m128 xmm0;
__m128 xmm1;
__m128 xmm2;
__m128 xmm3;
__m128 xmm4;
__m128 xmm5;
__m128 xmm6;
__m128 xmm7;
__m128 xmm8;
__m128 xmm9;
__m128 xmm10;
__m128 xmm11;
__m128 xmm12;
__m128 xmm13;
__m128 xmm14;
__m128 xmm15;
};
__m128 values[16];
} xmm_registers;
static const char* GetRegisterName(X64Register reg);
std::string GetStringFromValue(X64Register reg) const;
void SetValueFromString(X64Register reg, std::string value);
};
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_X64_CONTEXT_H_