2013-12-30 04:54:17 +01:00
|
|
|
/**
|
|
|
|
|
******************************************************************************
|
|
|
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
|
|
|
******************************************************************************
|
|
|
|
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
|
|
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
|
|
|
******************************************************************************
|
|
|
|
|
*/
|
|
|
|
|
|
2015-03-24 15:46:18 +01:00
|
|
|
#include "xenia/cpu/backend/x64/x64_backend.h"
|
2013-12-30 04:54:17 +01:00
|
|
|
|
2015-11-26 04:35:05 +01:00
|
|
|
#include "xenia/base/exception_handler.h"
|
2015-03-24 15:46:18 +01:00
|
|
|
#include "xenia/cpu/backend/x64/x64_assembler.h"
|
|
|
|
|
#include "xenia/cpu/backend/x64/x64_code_cache.h"
|
2015-08-06 06:50:02 +02:00
|
|
|
#include "xenia/cpu/backend/x64/x64_emitter.h"
|
|
|
|
|
#include "xenia/cpu/backend/x64/x64_function.h"
|
2015-03-24 15:46:18 +01:00
|
|
|
#include "xenia/cpu/backend/x64/x64_sequences.h"
|
2015-06-28 19:43:10 +02:00
|
|
|
#include "xenia/cpu/backend/x64/x64_stack_layout.h"
|
2015-11-26 04:35:05 +01:00
|
|
|
#include "xenia/cpu/breakpoint.h"
|
2015-06-16 03:47:53 +02:00
|
|
|
#include "xenia/cpu/processor.h"
|
2015-11-26 04:35:05 +01:00
|
|
|
#include "xenia/cpu/stack_walker.h"
|
2015-06-17 05:05:55 +02:00
|
|
|
|
|
|
|
|
DEFINE_bool(
|
|
|
|
|
enable_haswell_instructions, true,
|
|
|
|
|
"Uses the AVX2/FMA/etc instructions on Haswell processors, if available.");
|
2013-12-30 04:54:17 +01:00
|
|
|
|
2015-03-24 15:46:18 +01:00
|
|
|
namespace xe {
|
|
|
|
|
namespace cpu {
|
2014-07-11 05:20:00 +02:00
|
|
|
namespace backend {
|
|
|
|
|
namespace x64 {
|
2013-12-30 04:54:17 +01:00
|
|
|
|
2015-06-28 19:43:10 +02:00
|
|
|
class X64ThunkEmitter : public X64Emitter {
|
|
|
|
|
public:
|
|
|
|
|
X64ThunkEmitter(X64Backend* backend, XbyakAllocator* allocator);
|
|
|
|
|
~X64ThunkEmitter() override;
|
|
|
|
|
HostToGuestThunk EmitHostToGuestThunk();
|
|
|
|
|
GuestToHostThunk EmitGuestToHostThunk();
|
|
|
|
|
ResolveFunctionThunk EmitResolveFunctionThunk();
|
|
|
|
|
};
|
|
|
|
|
|
2015-05-04 07:28:25 +02:00
|
|
|
X64Backend::X64Backend(Processor* processor)
|
2015-06-16 03:47:53 +02:00
|
|
|
: Backend(processor), code_cache_(nullptr), emitter_data_(0) {}
|
2013-12-30 04:54:17 +01:00
|
|
|
|
2015-06-16 03:47:53 +02:00
|
|
|
X64Backend::~X64Backend() {
|
|
|
|
|
if (emitter_data_) {
|
|
|
|
|
processor()->memory()->SystemHeapFree(emitter_data_);
|
|
|
|
|
emitter_data_ = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2013-12-30 04:54:17 +01:00
|
|
|
|
2015-05-06 02:21:08 +02:00
|
|
|
bool X64Backend::Initialize() {
|
|
|
|
|
if (!Backend::Initialize()) {
|
|
|
|
|
return false;
|
2013-12-30 04:54:17 +01:00
|
|
|
}
|
|
|
|
|
|
2014-05-27 05:28:21 +02:00
|
|
|
RegisterSequences();
|
|
|
|
|
|
2015-06-17 05:05:55 +02:00
|
|
|
// Need movbe to do advanced LOAD/STORE tricks.
|
|
|
|
|
if (FLAGS_enable_haswell_instructions) {
|
|
|
|
|
Xbyak::util::Cpu cpu;
|
|
|
|
|
machine_info_.supports_extended_load_store =
|
|
|
|
|
cpu.has(Xbyak::util::Cpu::tMOVBE);
|
|
|
|
|
} else {
|
|
|
|
|
machine_info_.supports_extended_load_store = false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-18 23:45:49 +02:00
|
|
|
auto& gprs = machine_info_.register_sets[0];
|
|
|
|
|
gprs.id = 0;
|
|
|
|
|
std::strcpy(gprs.name, "gpr");
|
|
|
|
|
gprs.types = MachineInfo::RegisterSet::INT_TYPES;
|
|
|
|
|
gprs.count = X64Emitter::GPR_COUNT;
|
|
|
|
|
|
|
|
|
|
auto& xmms = machine_info_.register_sets[1];
|
|
|
|
|
xmms.id = 1;
|
|
|
|
|
std::strcpy(xmms.name, "xmm");
|
|
|
|
|
xmms.types = MachineInfo::RegisterSet::FLOAT_TYPES |
|
|
|
|
|
MachineInfo::RegisterSet::VEC_TYPES;
|
|
|
|
|
xmms.count = X64Emitter::XMM_COUNT;
|
2014-02-09 07:00:21 +01:00
|
|
|
|
2015-07-19 19:33:00 +02:00
|
|
|
code_cache_ = X64CodeCache::Create();
|
|
|
|
|
Backend::code_cache_ = code_cache_.get();
|
2015-05-06 02:21:08 +02:00
|
|
|
if (!code_cache_->Initialize()) {
|
|
|
|
|
return false;
|
2014-01-03 05:41:13 +01:00
|
|
|
}
|
|
|
|
|
|
2014-07-14 07:28:00 +02:00
|
|
|
// Generate thunks used to transition between jitted code and host code.
|
2015-06-28 19:43:10 +02:00
|
|
|
XbyakAllocator allocator;
|
|
|
|
|
X64ThunkEmitter thunk_emitter(this, &allocator);
|
|
|
|
|
host_to_guest_thunk_ = thunk_emitter.EmitHostToGuestThunk();
|
|
|
|
|
guest_to_host_thunk_ = thunk_emitter.EmitGuestToHostThunk();
|
|
|
|
|
resolve_function_thunk_ = thunk_emitter.EmitResolveFunctionThunk();
|
2015-05-21 09:12:28 +02:00
|
|
|
|
|
|
|
|
// Set the code cache to use the ResolveFunction thunk for default
|
|
|
|
|
// indirections.
|
|
|
|
|
assert_zero(uint64_t(resolve_function_thunk_) & 0xFFFFFFFF00000000ull);
|
|
|
|
|
code_cache_->set_indirection_default(
|
|
|
|
|
uint32_t(uint64_t(resolve_function_thunk_)));
|
|
|
|
|
|
|
|
|
|
// Allocate some special indirections.
|
|
|
|
|
code_cache_->CommitExecutableRange(0x9FFF0000, 0x9FFFFFFF);
|
2014-02-01 07:16:05 +01:00
|
|
|
|
2015-06-16 03:47:53 +02:00
|
|
|
// Allocate emitter constant data.
|
|
|
|
|
emitter_data_ = X64Emitter::PlaceData(processor()->memory());
|
|
|
|
|
|
2015-11-26 04:35:05 +01:00
|
|
|
// Setup exception callback
|
|
|
|
|
ExceptionHandler::Install(&ExceptionCallbackThunk, this);
|
|
|
|
|
|
2015-05-06 02:21:08 +02:00
|
|
|
return true;
|
2013-12-30 04:54:17 +01:00
|
|
|
}
|
|
|
|
|
|
2015-05-21 04:23:46 +02:00
|
|
|
void X64Backend::CommitExecutableRange(uint32_t guest_low,
|
|
|
|
|
uint32_t guest_high) {
|
|
|
|
|
code_cache_->CommitExecutableRange(guest_low, guest_high);
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-14 07:28:00 +02:00
|
|
|
std::unique_ptr<Assembler> X64Backend::CreateAssembler() {
|
|
|
|
|
return std::make_unique<X64Assembler>(this);
|
|
|
|
|
}
|
2014-07-11 05:20:00 +02:00
|
|
|
|
2015-08-06 06:50:02 +02:00
|
|
|
std::unique_ptr<GuestFunction> X64Backend::CreateGuestFunction(
|
|
|
|
|
Module* module, uint32_t address) {
|
|
|
|
|
return std::make_unique<X64Function>(module, address);
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-26 04:35:05 +01:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-28 19:43:10 +02:00
|
|
|
X64ThunkEmitter::X64ThunkEmitter(X64Backend* backend, XbyakAllocator* allocator)
|
|
|
|
|
: X64Emitter(backend, allocator) {}
|
|
|
|
|
|
|
|
|
|
X64ThunkEmitter::~X64ThunkEmitter() {}
|
|
|
|
|
|
|
|
|
|
HostToGuestThunk X64ThunkEmitter::EmitHostToGuestThunk() {
|
|
|
|
|
// rcx = target
|
|
|
|
|
// rdx = arg0
|
|
|
|
|
// r8 = arg1
|
|
|
|
|
|
|
|
|
|
const size_t stack_size = StackLayout::THUNK_STACK_SIZE;
|
|
|
|
|
// rsp + 0 = return address
|
|
|
|
|
mov(qword[rsp + 8 * 3], r8);
|
|
|
|
|
mov(qword[rsp + 8 * 2], rdx);
|
|
|
|
|
mov(qword[rsp + 8 * 1], rcx);
|
|
|
|
|
sub(rsp, stack_size);
|
|
|
|
|
|
|
|
|
|
mov(qword[rsp + 48], rbx);
|
|
|
|
|
mov(qword[rsp + 56], rcx);
|
|
|
|
|
mov(qword[rsp + 64], rbp);
|
|
|
|
|
mov(qword[rsp + 72], rsi);
|
|
|
|
|
mov(qword[rsp + 80], rdi);
|
|
|
|
|
mov(qword[rsp + 88], r12);
|
|
|
|
|
mov(qword[rsp + 96], r13);
|
|
|
|
|
mov(qword[rsp + 104], r14);
|
|
|
|
|
mov(qword[rsp + 112], r15);
|
|
|
|
|
|
|
|
|
|
/*movaps(ptr[rsp + 128], xmm6);
|
|
|
|
|
movaps(ptr[rsp + 144], xmm7);
|
|
|
|
|
movaps(ptr[rsp + 160], xmm8);
|
|
|
|
|
movaps(ptr[rsp + 176], xmm9);
|
|
|
|
|
movaps(ptr[rsp + 192], xmm10);
|
|
|
|
|
movaps(ptr[rsp + 208], xmm11);
|
|
|
|
|
movaps(ptr[rsp + 224], xmm12);
|
|
|
|
|
movaps(ptr[rsp + 240], xmm13);
|
|
|
|
|
movaps(ptr[rsp + 256], xmm14);
|
|
|
|
|
movaps(ptr[rsp + 272], xmm15);*/
|
|
|
|
|
|
|
|
|
|
mov(rax, rcx);
|
|
|
|
|
mov(rcx, rdx);
|
|
|
|
|
mov(rdx, r8);
|
|
|
|
|
call(rax);
|
|
|
|
|
|
|
|
|
|
/*movaps(xmm6, ptr[rsp + 128]);
|
|
|
|
|
movaps(xmm7, ptr[rsp + 144]);
|
|
|
|
|
movaps(xmm8, ptr[rsp + 160]);
|
|
|
|
|
movaps(xmm9, ptr[rsp + 176]);
|
|
|
|
|
movaps(xmm10, ptr[rsp + 192]);
|
|
|
|
|
movaps(xmm11, ptr[rsp + 208]);
|
|
|
|
|
movaps(xmm12, ptr[rsp + 224]);
|
|
|
|
|
movaps(xmm13, ptr[rsp + 240]);
|
|
|
|
|
movaps(xmm14, ptr[rsp + 256]);
|
|
|
|
|
movaps(xmm15, ptr[rsp + 272]);*/
|
|
|
|
|
|
|
|
|
|
mov(rbx, qword[rsp + 48]);
|
|
|
|
|
mov(rcx, qword[rsp + 56]);
|
|
|
|
|
mov(rbp, qword[rsp + 64]);
|
|
|
|
|
mov(rsi, qword[rsp + 72]);
|
|
|
|
|
mov(rdi, qword[rsp + 80]);
|
|
|
|
|
mov(r12, qword[rsp + 88]);
|
|
|
|
|
mov(r13, qword[rsp + 96]);
|
|
|
|
|
mov(r14, qword[rsp + 104]);
|
|
|
|
|
mov(r15, qword[rsp + 112]);
|
|
|
|
|
|
|
|
|
|
add(rsp, stack_size);
|
|
|
|
|
mov(rcx, qword[rsp + 8 * 1]);
|
|
|
|
|
mov(rdx, qword[rsp + 8 * 2]);
|
|
|
|
|
mov(r8, qword[rsp + 8 * 3]);
|
|
|
|
|
ret();
|
|
|
|
|
|
2015-07-29 09:15:52 +02:00
|
|
|
void* fn = Emplace(stack_size);
|
2015-06-28 19:43:10 +02:00
|
|
|
return (HostToGuestThunk)fn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GuestToHostThunk X64ThunkEmitter::EmitGuestToHostThunk() {
|
|
|
|
|
// rcx = context
|
|
|
|
|
// rdx = target function
|
|
|
|
|
// r8 = arg0
|
|
|
|
|
// r9 = arg1
|
|
|
|
|
// r10 = arg2
|
|
|
|
|
|
|
|
|
|
const size_t stack_size = StackLayout::THUNK_STACK_SIZE;
|
|
|
|
|
// rsp + 0 = return address
|
|
|
|
|
mov(qword[rsp + 8 * 2], rdx);
|
|
|
|
|
mov(qword[rsp + 8 * 1], rcx);
|
|
|
|
|
sub(rsp, stack_size);
|
|
|
|
|
|
|
|
|
|
mov(qword[rsp + 48], rbx);
|
|
|
|
|
mov(qword[rsp + 56], rcx);
|
|
|
|
|
mov(qword[rsp + 64], rbp);
|
|
|
|
|
mov(qword[rsp + 72], rsi);
|
|
|
|
|
mov(qword[rsp + 80], rdi);
|
|
|
|
|
mov(qword[rsp + 88], r12);
|
|
|
|
|
mov(qword[rsp + 96], r13);
|
|
|
|
|
mov(qword[rsp + 104], r14);
|
|
|
|
|
mov(qword[rsp + 112], r15);
|
|
|
|
|
|
|
|
|
|
// TODO(benvanik): save things? XMM0-5?
|
|
|
|
|
|
|
|
|
|
mov(rax, rdx);
|
|
|
|
|
mov(rdx, r8);
|
|
|
|
|
mov(r8, r9);
|
|
|
|
|
mov(r9, r10);
|
|
|
|
|
call(rax);
|
|
|
|
|
|
|
|
|
|
mov(rbx, qword[rsp + 48]);
|
|
|
|
|
mov(rcx, qword[rsp + 56]);
|
|
|
|
|
mov(rbp, qword[rsp + 64]);
|
|
|
|
|
mov(rsi, qword[rsp + 72]);
|
|
|
|
|
mov(rdi, qword[rsp + 80]);
|
|
|
|
|
mov(r12, qword[rsp + 88]);
|
|
|
|
|
mov(r13, qword[rsp + 96]);
|
|
|
|
|
mov(r14, qword[rsp + 104]);
|
|
|
|
|
mov(r15, qword[rsp + 112]);
|
|
|
|
|
|
|
|
|
|
add(rsp, stack_size);
|
|
|
|
|
mov(rcx, qword[rsp + 8 * 1]);
|
|
|
|
|
mov(rdx, qword[rsp + 8 * 2]);
|
|
|
|
|
ret();
|
|
|
|
|
|
2015-07-29 09:15:52 +02:00
|
|
|
void* fn = Emplace(stack_size);
|
2015-06-28 19:43:10 +02:00
|
|
|
return (HostToGuestThunk)fn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// X64Emitter handles actually resolving functions.
|
|
|
|
|
extern "C" uint64_t ResolveFunction(void* raw_context, uint32_t target_address);
|
|
|
|
|
|
|
|
|
|
ResolveFunctionThunk X64ThunkEmitter::EmitResolveFunctionThunk() {
|
|
|
|
|
// ebx = target PPC address
|
|
|
|
|
// rcx = context
|
|
|
|
|
|
|
|
|
|
const size_t stack_size = StackLayout::THUNK_STACK_SIZE;
|
|
|
|
|
// rsp + 0 = return address
|
|
|
|
|
mov(qword[rsp + 8 * 2], rdx);
|
|
|
|
|
mov(qword[rsp + 8 * 1], rcx);
|
|
|
|
|
sub(rsp, stack_size);
|
|
|
|
|
|
|
|
|
|
mov(qword[rsp + 48], rbx);
|
|
|
|
|
mov(qword[rsp + 56], rcx);
|
|
|
|
|
mov(qword[rsp + 64], rbp);
|
|
|
|
|
mov(qword[rsp + 72], rsi);
|
|
|
|
|
mov(qword[rsp + 80], rdi);
|
|
|
|
|
mov(qword[rsp + 88], r12);
|
|
|
|
|
mov(qword[rsp + 96], r13);
|
|
|
|
|
mov(qword[rsp + 104], r14);
|
|
|
|
|
mov(qword[rsp + 112], r15);
|
|
|
|
|
|
|
|
|
|
mov(rdx, rbx);
|
|
|
|
|
mov(rax, uint64_t(&ResolveFunction));
|
|
|
|
|
call(rax);
|
|
|
|
|
|
|
|
|
|
mov(rbx, qword[rsp + 48]);
|
|
|
|
|
mov(rcx, qword[rsp + 56]);
|
|
|
|
|
mov(rbp, qword[rsp + 64]);
|
|
|
|
|
mov(rsi, qword[rsp + 72]);
|
|
|
|
|
mov(rdi, qword[rsp + 80]);
|
|
|
|
|
mov(r12, qword[rsp + 88]);
|
|
|
|
|
mov(r13, qword[rsp + 96]);
|
|
|
|
|
mov(r14, qword[rsp + 104]);
|
|
|
|
|
mov(r15, qword[rsp + 112]);
|
|
|
|
|
|
|
|
|
|
add(rsp, stack_size);
|
|
|
|
|
mov(rcx, qword[rsp + 8 * 1]);
|
|
|
|
|
mov(rdx, qword[rsp + 8 * 2]);
|
|
|
|
|
jmp(rax);
|
|
|
|
|
|
2015-07-29 09:15:52 +02:00
|
|
|
void* fn = Emplace(stack_size);
|
2015-06-28 19:43:10 +02:00
|
|
|
return (ResolveFunctionThunk)fn;
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-11 05:20:00 +02:00
|
|
|
} // namespace x64
|
|
|
|
|
} // namespace backend
|
2015-03-24 15:46:18 +01:00
|
|
|
} // namespace cpu
|
|
|
|
|
} // namespace xe
|