Processor breakpoint support

This commit is contained in:
Dr. Chat 2015-11-25 21:35:05 -06:00 committed by Ben Vanik
parent 5019f1aa98
commit e383e2f101
7 changed files with 269 additions and 0 deletions

View File

@ -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_;

View File

@ -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) {}

View File

@ -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_;

View File

@ -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

View File

@ -0,0 +1,49 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_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_

View File

@ -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

View File

@ -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_;
};