Processor breakpoint support
This commit is contained in:
parent
5019f1aa98
commit
e383e2f101
|
@ -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<GuestFunction> 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_;
|
||||
|
|
|
@ -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<GuestFunction> X64Backend::CreateGuestFunction(
|
|||
return std::make_unique<X64Function>(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<cpu::GuestFunction*>(function);
|
||||
auto code = guest_function->MapGuestAddressToMachineCode(bp->address());
|
||||
assert_not_zero(code);
|
||||
|
||||
bp->set_backend_data(
|
||||
xe::load_and_swap<uint16_t>(reinterpret_cast<void*>(code + 0x0)));
|
||||
xe::store_and_swap<uint16_t>(reinterpret_cast<void*>(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<cpu::GuestFunction*>(function);
|
||||
auto code = guest_function->MapGuestAddressToMachineCode(bp->address());
|
||||
assert_not_zero(code);
|
||||
|
||||
xe::store_and_swap<uint16_t>(reinterpret_cast<void*>(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<X64Backend*>(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<uint16_t>(reinterpret_cast<void*>(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) {}
|
||||
|
||||
|
|
|
@ -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<GuestFunction> 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<X64CodeCache> code_cache_;
|
||||
|
||||
uint32_t emitter_data_;
|
||||
|
|
|
@ -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<void(uint32_t, uint64_t)> 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
|
|
@ -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<void(uint32_t, uint64_t)> 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<void(uint32_t, uint64_t)> hit_callback_;
|
||||
|
||||
// Opaque backend data. Don't touch this.
|
||||
uint64_t backend_data_ = 0;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_BREAKPOINT_H_
|
|
@ -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<volatile uint32_t*>(&irql_));
|
||||
}
|
||||
|
||||
bool Processor::InstallBreakpoint(Breakpoint* bp) {
|
||||
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> lock(breakpoint_lock_);
|
||||
|
||||
for (auto it = breakpoints_.begin(); it != breakpoints_.end(); it++) {
|
||||
if ((*it)->address() == address) {
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
|
|
@ -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<Breakpoint*> 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<Breakpoint*> breakpoints_;
|
||||
|
||||
Irql irql_;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue