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:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user