From e383e2f1013d5570c7428965628ac46002af15e6 Mon Sep 17 00:00:00 2001 From: "Dr. Chat" Date: Wed, 25 Nov 2015 21:35:05 -0600 Subject: [PATCH] Processor breakpoint support --- src/xenia/cpu/backend/backend.h | 4 ++ src/xenia/cpu/backend/x64/x64_backend.cc | 91 ++++++++++++++++++++++++ src/xenia/cpu/backend/x64/x64_backend.h | 9 +++ src/xenia/cpu/breakpoint.cc | 37 ++++++++++ src/xenia/cpu/breakpoint.h | 49 +++++++++++++ src/xenia/cpu/processor.cc | 64 +++++++++++++++++ src/xenia/cpu/processor.h | 15 ++++ 7 files changed, 269 insertions(+) create mode 100644 src/xenia/cpu/breakpoint.cc create mode 100644 src/xenia/cpu/breakpoint.h diff --git a/src/xenia/cpu/backend/backend.h b/src/xenia/cpu/backend/backend.h index b9cf9fbf6..86389f2fe 100644 --- a/src/xenia/cpu/backend/backend.h +++ b/src/xenia/cpu/backend/backend.h @@ -16,6 +16,7 @@ namespace xe { namespace cpu { +class Breakpoint; class GuestFunction; class Module; class Processor; @@ -51,6 +52,9 @@ class Backend { virtual std::unique_ptr CreateGuestFunction( Module* module, uint32_t address) = 0; + virtual bool InstallBreakpoint(Breakpoint* bp) { return false; } + virtual bool UninstallBreakpoint(Breakpoint* bp) { return false; } + protected: Processor* processor_; MachineInfo machine_info_; diff --git a/src/xenia/cpu/backend/x64/x64_backend.cc b/src/xenia/cpu/backend/x64/x64_backend.cc index c5b2a239f..978c92e4e 100644 --- a/src/xenia/cpu/backend/x64/x64_backend.cc +++ b/src/xenia/cpu/backend/x64/x64_backend.cc @@ -9,13 +9,16 @@ #include "xenia/cpu/backend/x64/x64_backend.h" +#include "xenia/base/exception_handler.h" #include "xenia/cpu/backend/x64/x64_assembler.h" #include "xenia/cpu/backend/x64/x64_code_cache.h" #include "xenia/cpu/backend/x64/x64_emitter.h" #include "xenia/cpu/backend/x64/x64_function.h" #include "xenia/cpu/backend/x64/x64_sequences.h" #include "xenia/cpu/backend/x64/x64_stack_layout.h" +#include "xenia/cpu/breakpoint.h" #include "xenia/cpu/processor.h" +#include "xenia/cpu/stack_walker.h" DEFINE_bool( enable_haswell_instructions, true, @@ -99,6 +102,9 @@ bool X64Backend::Initialize() { // Allocate emitter constant data. emitter_data_ = X64Emitter::PlaceData(processor()->memory()); + // Setup exception callback + ExceptionHandler::Install(&ExceptionCallbackThunk, this); + return true; } @@ -116,6 +122,91 @@ std::unique_ptr X64Backend::CreateGuestFunction( return std::make_unique(module, address); } +bool X64Backend::InstallBreakpoint(Breakpoint* bp) { + auto functions = processor()->FindFunctionsWithAddress(bp->address()); + if (functions.empty()) { + // Go ahead and fail - let the caller handle this. + return false; + } + + for (auto function : functions) { + assert_true(function->is_guest()); + auto guest_function = reinterpret_cast(function); + auto code = guest_function->MapGuestAddressToMachineCode(bp->address()); + assert_not_zero(code); + + bp->set_backend_data( + xe::load_and_swap(reinterpret_cast(code + 0x0))); + xe::store_and_swap(reinterpret_cast(code + 0x0), 0x0F0C); + } + + return true; +} + +bool X64Backend::UninstallBreakpoint(Breakpoint* bp) { + auto functions = processor()->FindFunctionsWithAddress(bp->address()); + if (functions.empty()) { + // This should not happen. + assert_always(); + return false; + } + + for (auto function : functions) { + assert_true(function->is_guest()); + auto guest_function = reinterpret_cast(function); + auto code = guest_function->MapGuestAddressToMachineCode(bp->address()); + assert_not_zero(code); + + xe::store_and_swap(reinterpret_cast(code + 0x0), + uint16_t(bp->backend_data())); + bp->set_backend_data(0); + } + + return true; +} + +bool X64Backend::ExceptionCallbackThunk(Exception* ex, void* data) { + auto backend = reinterpret_cast(data); + return backend->ExceptionCallback(ex); +} + +bool X64Backend::ExceptionCallback(Exception* ex) { + if (ex->code() != Exception::Code::kIllegalInstruction) { + // Has nothing to do with breakpoints. Not ours. + return false; + } + + auto instruction_bytes = + xe::load_and_swap(reinterpret_cast(ex->pc())); + if (instruction_bytes != 0x0F0C) { + // Not a BP instruction - not ours. + return false; + } + + uint64_t host_pcs[64]; + cpu::StackFrame frames[64]; + size_t count = processor()->stack_walker()->CaptureStackTrace( + host_pcs, 0, xe::countof(host_pcs)); + processor()->stack_walker()->ResolveStack(host_pcs, frames, count); + if (count == 0) { + // Stack resolve failed. + return false; + } + + for (int i = 0; i < count; i++) { + if (frames[i].type != cpu::StackFrame::Type::kGuest) { + continue; + } + + if (processor()->BreakpointHit(frames[i].guest_pc, frames[i].host_pc)) { + return true; + } + } + + // No breakpoints found at this address. + return false; +} + X64ThunkEmitter::X64ThunkEmitter(X64Backend* backend, XbyakAllocator* allocator) : X64Emitter(backend, allocator) {} diff --git a/src/xenia/cpu/backend/x64/x64_backend.h b/src/xenia/cpu/backend/x64/x64_backend.h index 5e94d321b..e1aadd9e1 100644 --- a/src/xenia/cpu/backend/x64/x64_backend.h +++ b/src/xenia/cpu/backend/x64/x64_backend.h @@ -18,6 +18,9 @@ DECLARE_bool(enable_haswell_instructions); +namespace xe { +class Exception; +} // namespace xe namespace xe { namespace cpu { namespace backend { @@ -59,7 +62,13 @@ class X64Backend : public Backend { std::unique_ptr CreateGuestFunction(Module* module, uint32_t address) override; + bool InstallBreakpoint(Breakpoint* bp) override; + bool UninstallBreakpoint(Breakpoint* bp) override; + private: + static bool ExceptionCallbackThunk(Exception* ex, void* data); + bool ExceptionCallback(Exception* ex); + std::unique_ptr code_cache_; uint32_t emitter_data_; diff --git a/src/xenia/cpu/breakpoint.cc b/src/xenia/cpu/breakpoint.cc new file mode 100644 index 000000000..06382bc66 --- /dev/null +++ b/src/xenia/cpu/breakpoint.cc @@ -0,0 +1,37 @@ +/** + ****************************************************************************** + * 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/breakpoint.h" + +#include "xenia/cpu/backend/backend.h" + +namespace xe { +namespace cpu { + +Breakpoint::Breakpoint(Processor* processor, uint32_t address, + std::function hit_callback) + : processor_(processor), address_(address), hit_callback_(hit_callback) {} +Breakpoint::~Breakpoint() { assert_false(installed_); } + +bool Breakpoint::Install() { + assert_false(installed_); + + installed_ = processor_->InstallBreakpoint(this); + return installed_; +} + +bool Breakpoint::Uninstall() { + assert_true(installed_); + + installed_ = !processor_->UninstallBreakpoint(this); + return !installed_; +} + +} // namespace cpu +} // namespace xe \ No newline at end of file diff --git a/src/xenia/cpu/breakpoint.h b/src/xenia/cpu/breakpoint.h new file mode 100644 index 000000000..d3d65be5b --- /dev/null +++ b/src/xenia/cpu/breakpoint.h @@ -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_CPU_BREAKPOINT_H_ +#define XENIA_CPU_BREAKPOINT_H_ + +#include "xenia/cpu/processor.h" + +namespace xe { +namespace cpu { + +class Breakpoint { + public: + Breakpoint(Processor* processor, uint32_t address, + std::function hit_callback); + ~Breakpoint(); + + uint32_t address() const { return address_; } + bool installed() const { return installed_; } + + bool Install(); + bool Uninstall(); + void Hit(uint64_t host_pc) { hit_callback_(address_, host_pc); } + + // CPU backend data. Implementation specific - DO NOT TOUCH THIS! + uint64_t backend_data() const { return backend_data_; } + void set_backend_data(uint64_t backend_data) { backend_data_ = backend_data; } + + private: + Processor* processor_ = nullptr; + + bool installed_ = false; + uint32_t address_ = 0; + std::function hit_callback_; + + // Opaque backend data. Don't touch this. + uint64_t backend_data_ = 0; +}; + +} // namespace cpu +} // namespace xe + +#endif // XENIA_CPU_BREAKPOINT_H_ \ No newline at end of file diff --git a/src/xenia/cpu/processor.cc b/src/xenia/cpu/processor.cc index 25230bcd8..7b1b36a92 100644 --- a/src/xenia/cpu/processor.cc +++ b/src/xenia/cpu/processor.cc @@ -17,6 +17,8 @@ #include "xenia/base/logging.h" #include "xenia/base/memory.h" #include "xenia/base/profiling.h" +#include "xenia/base/threading.h" +#include "xenia/cpu/breakpoint.h" #include "xenia/cpu/cpu_flags.h" #include "xenia/cpu/export_resolver.h" #include "xenia/cpu/module.h" @@ -25,6 +27,7 @@ #include "xenia/cpu/thread_state.h" #include "xenia/cpu/xex_module.h" #include "xenia/debug/debugger.h" +#include "xenia/kernel/xthread.h" // TODO(benvanik): based on compiler support #include "xenia/cpu/backend/x64/x64_backend.h" @@ -381,5 +384,66 @@ void Processor::LowerIrql(Irql old_value) { reinterpret_cast(&irql_)); } +bool Processor::InstallBreakpoint(Breakpoint* bp) { + std::lock_guard lock(breakpoint_lock_); + + if (FindBreakpoint(bp->address())) { + return false; + } + + // We need to register the breakpoint before installing it with the backend + // in-case a thread hits it while we're here. + breakpoints_.push_back(bp); + if (!backend_->InstallBreakpoint(bp)) { + breakpoints_.pop_back(); + return false; + } + + return true; +} + +bool Processor::UninstallBreakpoint(Breakpoint* bp) { + std::lock_guard lock(breakpoint_lock_); + + if (!backend_->UninstallBreakpoint(bp)) { + return false; + } + + for (auto it = breakpoints_.begin(); it != breakpoints_.end(); it++) { + if ((*it)->address() == bp->address()) { + breakpoints_.erase(it); + return true; + } + } + + return false; +} + +bool Processor::BreakpointHit(uint32_t address, uint64_t host_pc) { + auto bp = FindBreakpoint(address); + if (bp) { + bp->Hit(host_pc); + + // TODO: Remove dependency on XThread for suspending ourselves + kernel::XThread::GetCurrentThread()->Suspend(); + + return true; + } + + return false; +} + +Breakpoint* Processor::FindBreakpoint(uint32_t address) { + std::lock_guard lock(breakpoint_lock_); + + for (auto it = breakpoints_.begin(); it != breakpoints_.end(); it++) { + if ((*it)->address() == address) { + return *it; + } + } + + return nullptr; +} + } // namespace cpu } // namespace xe diff --git a/src/xenia/cpu/processor.h b/src/xenia/cpu/processor.h index c4ba2f83e..427fd5ac0 100644 --- a/src/xenia/cpu/processor.h +++ b/src/xenia/cpu/processor.h @@ -25,6 +25,8 @@ #include "xenia/memory.h" namespace xe { +class Emulator; + namespace debug { class Debugger; } // namespace debug @@ -33,6 +35,7 @@ class Debugger; namespace xe { namespace cpu { +class Breakpoint; class StackWalker; class ThreadState; class XexModule; @@ -89,7 +92,16 @@ class Processor { Irql RaiseIrql(Irql new_value); void LowerIrql(Irql old_value); + bool InstallBreakpoint(Breakpoint* bp); + bool UninstallBreakpoint(Breakpoint* bp); + bool BreakpointHit(uint32_t address, uint64_t host_pc); + Breakpoint* FindBreakpoint(uint32_t address); + std::vector breakpoints() const { return breakpoints_; } + private: + static bool ExceptionCallbackThunk(Exception* ex, void* data); + bool ExceptionCallback(Exception* ex); + bool DemandFunction(Function* function); Memory* memory_ = nullptr; @@ -108,6 +120,9 @@ class Processor { Module* builtin_module_ = nullptr; uint32_t next_builtin_address_ = 0xFFFF0000u; + std::mutex breakpoint_lock_; + std::vector breakpoints_; + Irql irql_; };