Merge branch 'master' of https://github.com/xenia-project/xenia into canary_experimental
This commit is contained in:
36
src/xenia/cpu/backend/null_backend.cc
Normal file
36
src/xenia/cpu/backend/null_backend.cc
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/backend/null_backend.h"
|
||||
|
||||
#include "xenia/cpu/backend/assembler.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
|
||||
void NullBackend::CommitExecutableRange(uint32_t guest_low,
|
||||
uint32_t guest_high) {}
|
||||
|
||||
std::unique_ptr<Assembler> NullBackend::CreateAssembler() { return nullptr; }
|
||||
|
||||
std::unique_ptr<GuestFunction> NullBackend::CreateGuestFunction(
|
||||
Module* module, uint32_t address) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint64_t NullBackend::CalculateNextHostInstruction(ThreadDebugInfo* thread_info,
|
||||
uint64_t current_pc) {
|
||||
return current_pc;
|
||||
}
|
||||
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
36
src/xenia/cpu/backend/null_backend.h
Normal file
36
src/xenia/cpu/backend/null_backend.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_CPU_BACKEND_NULL_BACKEND_H_
|
||||
#define XENIA_CPU_BACKEND_NULL_BACKEND_H_
|
||||
|
||||
#include "xenia/cpu/backend/backend.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
|
||||
class NullBackend : public Backend {
|
||||
public:
|
||||
void CommitExecutableRange(uint32_t guest_low, uint32_t guest_high) override;
|
||||
|
||||
std::unique_ptr<Assembler> CreateAssembler() override;
|
||||
|
||||
std::unique_ptr<GuestFunction> CreateGuestFunction(Module* module,
|
||||
uint32_t address) override;
|
||||
|
||||
uint64_t CalculateNextHostInstruction(ThreadDebugInfo* thread_info,
|
||||
uint64_t current_pc) override;
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_BACKEND_NULL_BACKEND_H_
|
||||
@@ -163,7 +163,7 @@ std::unique_ptr<GuestFunction> X64Backend::CreateGuestFunction(
|
||||
return std::make_unique<X64Function>(module, address);
|
||||
}
|
||||
|
||||
uint64_t ReadCapstoneReg(X64Context* context, x86_reg reg) {
|
||||
uint64_t ReadCapstoneReg(HostThreadContext* context, x86_reg reg) {
|
||||
switch (reg) {
|
||||
case X86_REG_RAX:
|
||||
return context->rax;
|
||||
|
||||
@@ -27,8 +27,6 @@ namespace x64 {
|
||||
|
||||
class X64CodeCache;
|
||||
|
||||
#define XENIA_HAS_X64_BACKEND 1
|
||||
|
||||
typedef void* (*HostToGuestThunk)(void* target, void* arg0, void* arg1);
|
||||
typedef void* (*GuestToHostThunk)(void* target, void* arg0, void* arg1);
|
||||
typedef void (*ResolveFunctionThunk)();
|
||||
|
||||
@@ -1414,14 +1414,17 @@ void Value::DotProduct3(Value* other) {
|
||||
assert_true(this->type == VEC128_TYPE && other->type == VEC128_TYPE);
|
||||
switch (type) {
|
||||
case VEC128_TYPE: {
|
||||
alignas(16) float result[4];
|
||||
__m128 src1 = _mm_load_ps(constant.v128.f32);
|
||||
__m128 src2 = _mm_load_ps(other->constant.v128.f32);
|
||||
__m128 dest = _mm_dp_ps(src1, src2, 0b01110001);
|
||||
_mm_store_ps(result, dest);
|
||||
// TODO(rick): is this sane?
|
||||
type = FLOAT32_TYPE;
|
||||
constant.f32 = result[0];
|
||||
// Using x86 DPPS ordering for consistency with x86-64 code generation:
|
||||
// (X1 * X2 + Y1 * Y2) + (Z1 * Z2 + 0.0f)
|
||||
// (+ 0.0f for zero sign, as zero imm8[4:7] bits result in zero terms,
|
||||
// not in complete exclusion of them)
|
||||
// TODO(Triang3l): NaN on overflow.
|
||||
constant.f32 =
|
||||
(constant.v128.f32[0] * other->constant.v128.f32[0] +
|
||||
constant.v128.f32[1] * other->constant.v128.f32[1]) +
|
||||
(constant.v128.f32[2] * other->constant.v128.f32[2] + 0.0f);
|
||||
} break;
|
||||
default:
|
||||
assert_unhandled_case(type);
|
||||
@@ -1433,14 +1436,15 @@ void Value::DotProduct4(Value* other) {
|
||||
assert_true(this->type == VEC128_TYPE && other->type == VEC128_TYPE);
|
||||
switch (type) {
|
||||
case VEC128_TYPE: {
|
||||
alignas(16) float result[4];
|
||||
__m128 src1 = _mm_load_ps(constant.v128.f32);
|
||||
__m128 src2 = _mm_load_ps(other->constant.v128.f32);
|
||||
__m128 dest = _mm_dp_ps(src1, src2, 0b11110001);
|
||||
_mm_store_ps(result, dest);
|
||||
// TODO(rick): is this sane?
|
||||
type = FLOAT32_TYPE;
|
||||
constant.f32 = result[0];
|
||||
// Using x86 DPPS ordering for consistency with x86-64 code generation:
|
||||
// (X1 * X2 + Y1 * Y2) + (Z1 * Z2 + W1 * W2)
|
||||
// TODO(Triang3l): NaN on overflow.
|
||||
constant.f32 = (constant.v128.f32[0] * other->constant.v128.f32[0] +
|
||||
constant.v128.f32[1] * other->constant.v128.f32[1]) +
|
||||
(constant.v128.f32[2] * other->constant.v128.f32[2] +
|
||||
constant.v128.f32[3] * other->constant.v128.f32[3]);
|
||||
} break;
|
||||
default:
|
||||
assert_unhandled_case(type);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "xenia/base/exception_handler.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
@@ -114,28 +115,10 @@ bool MMIOHandler::CheckStore(uint32_t virtual_address, uint32_t value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct DecodedMov {
|
||||
size_t length;
|
||||
// Inidicates this is a load (or conversely a store).
|
||||
bool is_load;
|
||||
// Indicates the memory must be swapped.
|
||||
bool byte_swap;
|
||||
// Source (for store) or target (for load) register.
|
||||
// AX CX DX BX SP BP SI DI // REX.R=0
|
||||
// R8 R9 R10 R11 R12 R13 R14 R15 // REX.R=1
|
||||
uint32_t value_reg;
|
||||
// [base + (index * scale) + displacement]
|
||||
bool mem_has_base;
|
||||
uint8_t mem_base_reg;
|
||||
bool mem_has_index;
|
||||
uint8_t mem_index_reg;
|
||||
uint8_t mem_scale;
|
||||
int32_t mem_displacement;
|
||||
bool is_constant;
|
||||
int32_t constant;
|
||||
};
|
||||
|
||||
bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
|
||||
bool MMIOHandler::TryDecodeLoadStore(const uint8_t* p,
|
||||
DecodedLoadStore& decoded_out) {
|
||||
std::memset(&decoded_out, 0, sizeof(decoded_out));
|
||||
#if XE_ARCH_AMD64
|
||||
uint8_t i = 0; // Current byte decode index.
|
||||
uint8_t rex = 0;
|
||||
if ((p[i] & 0xF0) == 0x40) {
|
||||
@@ -148,8 +131,8 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
|
||||
// 44 0f 38 f1 a4 02 00 movbe DWORD PTR [rdx+rax*1+0x0],r12d
|
||||
// 42 0f 38 f1 8c 22 00 movbe DWORD PTR [rdx+r12*1+0x0],ecx
|
||||
// 0f 38 f1 8c 02 00 00 movbe DWORD PTR [rdx + rax * 1 + 0x0], ecx
|
||||
mov->is_load = false;
|
||||
mov->byte_swap = true;
|
||||
decoded_out.is_load = false;
|
||||
decoded_out.byte_swap = true;
|
||||
i += 3;
|
||||
} else if (p[i] == 0x0F && p[i + 1] == 0x38 && p[i + 2] == 0xF0) {
|
||||
// MOVBE r32, m32 (load)
|
||||
@@ -159,8 +142,8 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
|
||||
// 46 0f 38 f0 a4 22 00 movbe r12d,DWORD PTR [rdx+r12*1+0x0]
|
||||
// 0f 38 f0 8c 02 00 00 movbe ecx,DWORD PTR [rdx+rax*1+0x0]
|
||||
// 0F 38 F0 1C 02 movbe ebx,dword ptr [rdx+rax]
|
||||
mov->is_load = true;
|
||||
mov->byte_swap = true;
|
||||
decoded_out.is_load = true;
|
||||
decoded_out.byte_swap = true;
|
||||
i += 3;
|
||||
} else if (p[i] == 0x89) {
|
||||
// MOV m32, r32 (store)
|
||||
@@ -168,8 +151,8 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
|
||||
// 44 89 24 02 mov DWORD PTR[rdx + rax * 1], r12d
|
||||
// 42 89 0c 22 mov DWORD PTR[rdx + r12 * 1], ecx
|
||||
// 89 0c 02 mov DWORD PTR[rdx + rax * 1], ecx
|
||||
mov->is_load = false;
|
||||
mov->byte_swap = false;
|
||||
decoded_out.is_load = false;
|
||||
decoded_out.byte_swap = false;
|
||||
++i;
|
||||
} else if (p[i] == 0x8B) {
|
||||
// MOV r32, m32 (load)
|
||||
@@ -178,16 +161,16 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
|
||||
// 42 8b 0c 22 mov ecx, DWORD PTR[rdx + r12 * 1]
|
||||
// 46 8b 24 22 mov r12d, DWORD PTR[rdx + r12 * 1]
|
||||
// 8b 0c 02 mov ecx, DWORD PTR[rdx + rax * 1]
|
||||
mov->is_load = true;
|
||||
mov->byte_swap = false;
|
||||
decoded_out.is_load = true;
|
||||
decoded_out.byte_swap = false;
|
||||
++i;
|
||||
} else if (p[i] == 0xC7) {
|
||||
// MOV m32, simm32
|
||||
// https://web.archive.org/web/20161017042413/https://www.asmpedia.org/index.php?title=MOV
|
||||
// C7 04 02 02 00 00 00 mov dword ptr [rdx+rax],2
|
||||
mov->is_load = false;
|
||||
mov->byte_swap = false;
|
||||
mov->is_constant = true;
|
||||
decoded_out.is_load = false;
|
||||
decoded_out.byte_swap = false;
|
||||
decoded_out.is_constant = true;
|
||||
++i;
|
||||
} else {
|
||||
return false;
|
||||
@@ -204,13 +187,13 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
|
||||
uint8_t mod = (modrm & 0b11000000) >> 6;
|
||||
uint8_t reg = (modrm & 0b00111000) >> 3;
|
||||
uint8_t rm = (modrm & 0b00000111);
|
||||
mov->value_reg = reg + (rex_r ? 8 : 0);
|
||||
mov->mem_has_base = false;
|
||||
mov->mem_base_reg = 0;
|
||||
mov->mem_has_index = false;
|
||||
mov->mem_index_reg = 0;
|
||||
mov->mem_scale = 1;
|
||||
mov->mem_displacement = 0;
|
||||
decoded_out.value_reg = reg + (rex_r ? 8 : 0);
|
||||
decoded_out.mem_has_base = false;
|
||||
decoded_out.mem_base_reg = 0;
|
||||
decoded_out.mem_has_index = false;
|
||||
decoded_out.mem_index_reg = 0;
|
||||
decoded_out.mem_scale = 1;
|
||||
decoded_out.mem_displacement = 0;
|
||||
bool has_sib = false;
|
||||
switch (rm) {
|
||||
case 0b100: // SIB
|
||||
@@ -221,17 +204,17 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
|
||||
// RIP-relative not supported.
|
||||
return false;
|
||||
}
|
||||
mov->mem_has_base = true;
|
||||
mov->mem_base_reg = rm + (rex_b ? 8 : 0);
|
||||
decoded_out.mem_has_base = true;
|
||||
decoded_out.mem_base_reg = rm + (rex_b ? 8 : 0);
|
||||
break;
|
||||
default:
|
||||
mov->mem_has_base = true;
|
||||
mov->mem_base_reg = rm + (rex_b ? 8 : 0);
|
||||
decoded_out.mem_has_base = true;
|
||||
decoded_out.mem_base_reg = rm + (rex_b ? 8 : 0);
|
||||
break;
|
||||
}
|
||||
if (has_sib) {
|
||||
uint8_t sib = p[i++];
|
||||
mov->mem_scale = 1 << ((sib & 0b11000000) >> 8);
|
||||
decoded_out.mem_scale = 1 << ((sib & 0b11000000) >> 8);
|
||||
uint8_t sib_index = (sib & 0b00111000) >> 3;
|
||||
uint8_t sib_base = (sib & 0b00000111);
|
||||
switch (sib_index) {
|
||||
@@ -239,8 +222,9 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
|
||||
// No index.
|
||||
break;
|
||||
default:
|
||||
mov->mem_has_index = true;
|
||||
mov->mem_index_reg = sib_index + (rex_x ? 8 : 0);
|
||||
decoded_out.mem_has_index = true;
|
||||
decoded_out.mem_index_reg = sib_index + (rex_x ? 8 : 0);
|
||||
decoded_out.mem_index_size = sizeof(uint64_t);
|
||||
break;
|
||||
}
|
||||
switch (sib_base) {
|
||||
@@ -249,29 +233,162 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
|
||||
assert_zero(mod);
|
||||
return false;
|
||||
default:
|
||||
mov->mem_has_base = true;
|
||||
mov->mem_base_reg = sib_base + (rex_b ? 8 : 0);
|
||||
decoded_out.mem_has_base = true;
|
||||
decoded_out.mem_base_reg = sib_base + (rex_b ? 8 : 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
switch (mod) {
|
||||
case 0b00: {
|
||||
mov->mem_displacement += 0;
|
||||
decoded_out.mem_displacement += 0;
|
||||
} break;
|
||||
case 0b01: {
|
||||
mov->mem_displacement += int8_t(p[i++]);
|
||||
decoded_out.mem_displacement += int8_t(p[i++]);
|
||||
} break;
|
||||
case 0b10: {
|
||||
mov->mem_displacement += xe::load<int32_t>(p + i);
|
||||
decoded_out.mem_displacement += xe::load<int32_t>(p + i);
|
||||
i += 4;
|
||||
} break;
|
||||
}
|
||||
if (mov->is_constant) {
|
||||
mov->constant = xe::load<int32_t>(p + i);
|
||||
if (decoded_out.is_constant) {
|
||||
decoded_out.constant = xe::load<int32_t>(p + i);
|
||||
i += 4;
|
||||
}
|
||||
mov->length = i;
|
||||
decoded_out.length = i;
|
||||
return true;
|
||||
|
||||
#elif XE_ARCH_ARM64
|
||||
decoded_out.length = sizeof(uint32_t);
|
||||
uint32_t instruction = *reinterpret_cast<const uint32_t*>(p);
|
||||
|
||||
// Literal loading (PC-relative) is not handled.
|
||||
|
||||
if ((instruction & kArm64LoadStoreAnyFMask) != kArm64LoadStoreAnyFixed) {
|
||||
// Not a load or a store instruction.
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((instruction & kArm64LoadStorePairAnyFMask) ==
|
||||
kArm64LoadStorePairAnyFixed) {
|
||||
// Handling MMIO only for single 32-bit values, not for pairs.
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t value_reg_base;
|
||||
switch (Arm64LoadStoreOp(instruction & kArm64LoadStoreMask)) {
|
||||
case Arm64LoadStoreOp::kSTR_w:
|
||||
decoded_out.is_load = false;
|
||||
value_reg_base = DecodedLoadStore::kArm64ValueRegX0;
|
||||
break;
|
||||
case Arm64LoadStoreOp::kLDR_w:
|
||||
decoded_out.is_load = true;
|
||||
value_reg_base = DecodedLoadStore::kArm64ValueRegX0;
|
||||
break;
|
||||
case Arm64LoadStoreOp::kSTR_s:
|
||||
decoded_out.is_load = false;
|
||||
value_reg_base = DecodedLoadStore::kArm64ValueRegV0;
|
||||
break;
|
||||
case Arm64LoadStoreOp::kLDR_s:
|
||||
decoded_out.is_load = true;
|
||||
value_reg_base = DecodedLoadStore::kArm64ValueRegV0;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// `Rt` field (load / store register).
|
||||
decoded_out.value_reg = value_reg_base + (instruction & 31);
|
||||
if (decoded_out.is_load &&
|
||||
decoded_out.value_reg == DecodedLoadStore::kArm64ValueRegZero) {
|
||||
// Zero constant rather than a register read.
|
||||
decoded_out.is_constant = true;
|
||||
decoded_out.constant = 0;
|
||||
}
|
||||
|
||||
decoded_out.mem_has_base = true;
|
||||
// The base is Xn (for 0...30) or SP (for 31).
|
||||
// `Rn` field (first source register).
|
||||
decoded_out.mem_base_reg = (instruction >> 5) & 31;
|
||||
|
||||
bool is_unsigned_offset =
|
||||
(instruction & kArm64LoadStoreUnsignedOffsetFMask) ==
|
||||
kArm64LoadStoreUnsignedOffsetFixed;
|
||||
if (is_unsigned_offset) {
|
||||
// LDR|STR Wt|St, [Xn|SP{, #pimm}]
|
||||
// pimm (positive immediate) is scaled by the size of the data (4 for
|
||||
// words).
|
||||
// `ImmLSUnsigned` field.
|
||||
uint32_t unsigned_offset = (instruction >> 10) & 4095;
|
||||
decoded_out.mem_displacement =
|
||||
ptrdiff_t(sizeof(uint32_t) * unsigned_offset);
|
||||
} else {
|
||||
Arm64LoadStoreOffsetFixed offset =
|
||||
Arm64LoadStoreOffsetFixed(instruction & kArm64LoadStoreOffsetFMask);
|
||||
// simm (signed immediate) is not scaled.
|
||||
// Only applicable to kUnscaledOffset, kPostIndex and kPreIndex.
|
||||
// `ImmLS` field.
|
||||
int32_t signed_offset = int32_t(instruction << (32 - (9 + 12))) >> (32 - 9);
|
||||
// For both post- and pre-indexing, the new address is written to the
|
||||
// register after the data register write, thus if Xt and Xn are the same,
|
||||
// the final value in the register will be the new address.
|
||||
// https://developer.arm.com/documentation/ddi0596/2020-12/Base-Instructions/LDR--immediate---Load-Register--immediate--
|
||||
switch (offset) {
|
||||
case Arm64LoadStoreOffsetFixed::kUnscaledOffset: {
|
||||
// LDUR|STUR Wt|St, [Xn|SP{, #simm}]
|
||||
decoded_out.mem_displacement = signed_offset;
|
||||
} break;
|
||||
case Arm64LoadStoreOffsetFixed::kPostIndex: {
|
||||
// LDR|STR Wt|St, [Xn|SP], #simm
|
||||
decoded_out.mem_base_writeback = true;
|
||||
decoded_out.mem_base_writeback_offset = signed_offset;
|
||||
} break;
|
||||
case Arm64LoadStoreOffsetFixed::kPreIndex: {
|
||||
// LDR|STR Wt|St, [Xn|SP, #simm]!
|
||||
decoded_out.mem_base_writeback = true;
|
||||
decoded_out.mem_base_writeback_offset = signed_offset;
|
||||
decoded_out.mem_displacement = signed_offset;
|
||||
} break;
|
||||
case Arm64LoadStoreOffsetFixed::kRegisterOffset: {
|
||||
// LDR|STR Wt|St, [Xn|SP, (Wm|Xm){, extend {amount}}]
|
||||
// `Rm` field.
|
||||
decoded_out.mem_index_reg = (instruction >> 16) & 31;
|
||||
if (decoded_out.mem_index_reg != DecodedLoadStore::kArm64RegZero) {
|
||||
decoded_out.mem_has_index = true;
|
||||
// Allowed extend types in the `option` field are UXTW (0b010), LSL
|
||||
// (0b011 - identical to UXTX), SXTW (0b110), SXTX (0b111).
|
||||
// The shift (0 or 2 for 32-bit LDR/STR) can be applied regardless of
|
||||
// the extend type ("LSL" is just a term for assembly readability,
|
||||
// internally it's treated simply as UXTX).
|
||||
// If bit 0 of the `option` field is 0 (UXTW, SXTW), the index
|
||||
// register is treated as 32-bit (Wm) extended to 64-bit. If it's 1
|
||||
// (LSL aka UXTX, SXTX), the index register is treated as 64-bit (Xm).
|
||||
// `ExtendMode` (`option`) field.
|
||||
uint32_t extend_mode = (instruction >> 13) & 0b111;
|
||||
if (!(extend_mode & 0b010)) {
|
||||
// Sub-word index - undefined.
|
||||
return false;
|
||||
}
|
||||
decoded_out.mem_index_size =
|
||||
(extend_mode & 0b001) ? sizeof(uint64_t) : sizeof(uint32_t);
|
||||
decoded_out.mem_index_sign_extend = (extend_mode & 0b100) != 0;
|
||||
// Shift is either 0 or log2(sizeof(load or store size)).
|
||||
// Supporting MMIO only for 4-byte words.
|
||||
// `ImmShiftLS` field.
|
||||
decoded_out.mem_scale =
|
||||
(instruction & (UINT32_C(1) << 12)) ? sizeof(uint32_t) : 1;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
#else
|
||||
#error TryDecodeLoadStore not implemented for the target CPU architecture.
|
||||
return false;
|
||||
#endif // XE_ARCH
|
||||
}
|
||||
|
||||
bool MMIOHandler::ExceptionCallbackThunk(Exception* ex, void* data) {
|
||||
@@ -300,11 +417,13 @@ bool MMIOHandler::ExceptionCallback(Exception* ex) {
|
||||
// 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;
|
||||
uint32_t fault_guest_virtual_address = 0;
|
||||
if (ex->fault_address() < uint64_t(physical_membase_)) {
|
||||
uint32_t fault_virtual_address = host_to_guest_virtual_(
|
||||
fault_guest_virtual_address = host_to_guest_virtual_(
|
||||
host_to_guest_virtual_context_, fault_host_address);
|
||||
for (const auto& test_range : mapped_ranges_) {
|
||||
if ((fault_virtual_address & test_range.mask) == test_range.address) {
|
||||
if ((fault_guest_virtual_address & test_range.mask) ==
|
||||
test_range.address) {
|
||||
// Address is within the range of this mapping.
|
||||
range = &test_range;
|
||||
break;
|
||||
@@ -336,44 +455,114 @@ bool MMIOHandler::ExceptionCallback(Exception* ex) {
|
||||
|
||||
auto rip = ex->pc();
|
||||
auto p = reinterpret_cast<const uint8_t*>(rip);
|
||||
DecodedMov mov = {0};
|
||||
bool decoded = TryDecodeMov(p, &mov);
|
||||
if (!decoded) {
|
||||
XELOGE("Unable to decode MMIO mov at {}", p);
|
||||
DecodedLoadStore decoded_load_store;
|
||||
if (!TryDecodeLoadStore(p, decoded_load_store)) {
|
||||
XELOGE("Unable to decode MMIO load or store instruction at {}", p);
|
||||
assert_always("Unknown MMIO instruction type");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mov.is_load) {
|
||||
HostThreadContext& thread_context = *ex->thread_context();
|
||||
|
||||
#if XE_ARCH_ARM64
|
||||
// Preserve the base address with the pre- or the post-index offset to write
|
||||
// it after writing the result (since the base address register and the
|
||||
// register to load to may be the same, in which case it should receive the
|
||||
// original base address with the offset).
|
||||
uintptr_t mem_base_writeback_address = 0;
|
||||
if (decoded_load_store.mem_has_base &&
|
||||
decoded_load_store.mem_base_writeback) {
|
||||
if (decoded_load_store.mem_base_reg ==
|
||||
DecodedLoadStore::kArm64MemBaseRegSp) {
|
||||
mem_base_writeback_address = thread_context.sp;
|
||||
} else {
|
||||
assert_true(decoded_load_store.mem_base_reg <= 30);
|
||||
mem_base_writeback_address =
|
||||
thread_context.x[decoded_load_store.mem_base_reg];
|
||||
}
|
||||
mem_base_writeback_address += decoded_load_store.mem_base_writeback_offset;
|
||||
}
|
||||
#endif // XE_ARCH_ARM64
|
||||
|
||||
uint8_t value_reg = decoded_load_store.value_reg;
|
||||
if (decoded_load_store.is_load) {
|
||||
// Load of a memory value - read from range, swap, and store in the
|
||||
// register.
|
||||
uint32_t value = range->read(nullptr, range->callback_context,
|
||||
static_cast<uint32_t>(ex->fault_address()));
|
||||
uint64_t* reg_ptr = &ex->thread_context()->int_registers[mov.value_reg];
|
||||
if (!mov.byte_swap) {
|
||||
fault_guest_virtual_address);
|
||||
if (!decoded_load_store.byte_swap) {
|
||||
// We swap only if it's not a movbe, as otherwise we are swapping twice.
|
||||
value = xe::byte_swap(value);
|
||||
}
|
||||
*reg_ptr = value;
|
||||
#if XE_ARCH_AMD64
|
||||
ex->ModifyIntRegister(value_reg) = value;
|
||||
#elif XE_ARCH_ARM64
|
||||
if (value_reg >= DecodedLoadStore::kArm64ValueRegX0 &&
|
||||
value_reg <= (DecodedLoadStore::kArm64ValueRegX0 + 30)) {
|
||||
ex->ModifyXRegister(value_reg - DecodedLoadStore::kArm64ValueRegX0) =
|
||||
value;
|
||||
} else if (value_reg >= DecodedLoadStore::kArm64ValueRegV0 &&
|
||||
value_reg <= (DecodedLoadStore::kArm64ValueRegV0 + 31)) {
|
||||
ex->ModifyVRegister(value_reg - DecodedLoadStore::kArm64ValueRegV0)
|
||||
.u32[0] = value;
|
||||
} else {
|
||||
assert_true(value_reg == DecodedLoadStore::kArm64ValueRegZero);
|
||||
// Register write is ignored for X31.
|
||||
}
|
||||
#else
|
||||
#error Register value writing not implemented for the target CPU architecture.
|
||||
#endif // XE_ARCH
|
||||
} else {
|
||||
// Store of a register value - read register, swap, write to range.
|
||||
int32_t value;
|
||||
if (mov.is_constant) {
|
||||
value = uint32_t(mov.constant);
|
||||
uint32_t value;
|
||||
if (decoded_load_store.is_constant) {
|
||||
value = uint32_t(decoded_load_store.constant);
|
||||
} else {
|
||||
uint64_t* reg_ptr = &ex->thread_context()->int_registers[mov.value_reg];
|
||||
value = static_cast<uint32_t>(*reg_ptr);
|
||||
if (!mov.byte_swap) {
|
||||
#if XE_ARCH_AMD64
|
||||
value = uint32_t(thread_context.int_registers[value_reg]);
|
||||
#elif XE_ARCH_ARM64
|
||||
if (value_reg >= DecodedLoadStore::kArm64ValueRegX0 &&
|
||||
value_reg <= (DecodedLoadStore::kArm64ValueRegX0 + 30)) {
|
||||
value = uint32_t(
|
||||
thread_context.x[value_reg - DecodedLoadStore::kArm64ValueRegX0]);
|
||||
} else if (value_reg >= DecodedLoadStore::kArm64ValueRegV0 &&
|
||||
value_reg <= (DecodedLoadStore::kArm64ValueRegV0 + 31)) {
|
||||
value = thread_context.v[value_reg - DecodedLoadStore::kArm64ValueRegV0]
|
||||
.u32[0];
|
||||
} else {
|
||||
assert_true(value_reg == DecodedLoadStore::kArm64ValueRegZero);
|
||||
value = 0;
|
||||
}
|
||||
#else
|
||||
#error Register value reading not implemented for the target CPU architecture.
|
||||
#endif // XE_ARCH
|
||||
if (!decoded_load_store.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));
|
||||
value = xe::byte_swap(value);
|
||||
}
|
||||
}
|
||||
range->write(nullptr, range->callback_context,
|
||||
static_cast<uint32_t>(ex->fault_address()), value);
|
||||
range->write(nullptr, range->callback_context, fault_guest_virtual_address,
|
||||
value);
|
||||
}
|
||||
|
||||
#if XE_ARCH_ARM64
|
||||
// Write the base address with the pre- or the post-index offset, overwriting
|
||||
// the register to load to if it's the same.
|
||||
if (decoded_load_store.mem_has_base &&
|
||||
decoded_load_store.mem_base_writeback) {
|
||||
if (decoded_load_store.mem_base_reg ==
|
||||
DecodedLoadStore::kArm64MemBaseRegSp) {
|
||||
thread_context.sp = mem_base_writeback_address;
|
||||
} else {
|
||||
assert_true(decoded_load_store.mem_base_reg <= 30);
|
||||
ex->ModifyXRegister(decoded_load_store.mem_base_reg) =
|
||||
mem_base_writeback_address;
|
||||
}
|
||||
}
|
||||
#endif // XE_ARCH_ARM64
|
||||
|
||||
// Advance RIP to the next instruction so that we resume properly.
|
||||
ex->set_resume_pc(rip + mov.length);
|
||||
ex->set_resume_pc(rip + decoded_load_store.length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/mutex.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
namespace xe {
|
||||
class Exception;
|
||||
class X64Context;
|
||||
class HostThreadContext;
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
@@ -93,6 +94,61 @@ class MMIOHandler {
|
||||
static MMIOHandler* global_handler_;
|
||||
|
||||
xe::global_critical_region global_critical_region_;
|
||||
|
||||
private:
|
||||
struct DecodedLoadStore {
|
||||
// Matches the Xn/Wn register number for 0 reads and ignored writes in many
|
||||
// usage cases.
|
||||
static constexpr uint8_t kArm64RegZero = 31;
|
||||
|
||||
// Matches the actual register number encoding for an SP base in AArch64
|
||||
// load and store instructions.
|
||||
static constexpr uint8_t kArm64MemBaseRegSp = kArm64RegZero;
|
||||
|
||||
static constexpr uint8_t kArm64ValueRegX0 = 0;
|
||||
static constexpr uint8_t kArm64ValueRegZero =
|
||||
kArm64ValueRegX0 + kArm64RegZero;
|
||||
static constexpr uint8_t kArm64ValueRegV0 = 32;
|
||||
|
||||
size_t length;
|
||||
// Inidicates this is a load (or conversely a store).
|
||||
bool is_load;
|
||||
// Indicates the memory must be swapped.
|
||||
bool byte_swap;
|
||||
// Source (for store) or target (for load) register.
|
||||
// For x86-64:
|
||||
// AX CX DX BX SP BP SI DI // REX.R=0
|
||||
// R8 R9 R10 R11 R12 R13 R14 R15 // REX.R=1
|
||||
// For AArch64:
|
||||
// - kArm64ValueRegX0 + [0...30]: Xn (Wn for 32 bits - upper 32 bits of Xn
|
||||
// are zeroed on Wn write).
|
||||
// - kArm64ValueRegZero: Zero constant for register read, ignored register
|
||||
// write (though memory must still be accessed - a MMIO load may have side
|
||||
// effects even if the result is discarded).
|
||||
// - kArm64ValueRegV0 + [0...31]: Vn (Sn for 32 bits).
|
||||
uint8_t value_reg;
|
||||
// [base + (index * scale) + displacement]
|
||||
bool mem_has_base;
|
||||
// On AArch64, if mem_base_reg is kArm64MemBaseRegSp, the base register is
|
||||
// SP, not Xn.
|
||||
uint8_t mem_base_reg;
|
||||
// For AArch64 pre- and post-indexing. In case of a load, the base register
|
||||
// is written back after the loaded data is written to the register,
|
||||
// overwriting the value register if it's the same.
|
||||
bool mem_base_writeback;
|
||||
int32_t mem_base_writeback_offset;
|
||||
bool mem_has_index;
|
||||
uint8_t mem_index_reg;
|
||||
uint8_t mem_index_size;
|
||||
bool mem_index_sign_extend;
|
||||
uint8_t mem_scale;
|
||||
ptrdiff_t mem_displacement;
|
||||
bool is_constant;
|
||||
int32_t constant;
|
||||
};
|
||||
|
||||
static bool TryDecodeLoadStore(const uint8_t* p,
|
||||
DecodedLoadStore& decoded_out);
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
|
||||
@@ -15,13 +15,16 @@
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string_buffer.h"
|
||||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||
#include "xenia/cpu/cpu_flags.h"
|
||||
#include "xenia/cpu/ppc/ppc_context.h"
|
||||
#include "xenia/cpu/ppc/ppc_frontend.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/cpu/raw_module.h"
|
||||
|
||||
#if XE_ARCH_AMD64
|
||||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||
#endif // XE_ARCH
|
||||
|
||||
#if XE_COMPILER_MSVC
|
||||
#include "xenia/base/platform_win.h"
|
||||
#endif // XE_COMPILER_MSVC
|
||||
@@ -196,17 +199,17 @@ class TestRunner {
|
||||
|
||||
std::unique_ptr<xe::cpu::backend::Backend> backend;
|
||||
if (!backend) {
|
||||
#if defined(XENIA_HAS_X64_BACKEND) && XENIA_HAS_X64_BACKEND
|
||||
#if XE_ARCH_AMD64
|
||||
if (cvars::cpu == "x64") {
|
||||
backend.reset(new xe::cpu::backend::x64::X64Backend());
|
||||
}
|
||||
#endif // XENIA_HAS_X64_BACKEND
|
||||
#endif // XE_ARCH
|
||||
if (cvars::cpu == "any") {
|
||||
#if defined(XENIA_HAS_X64_BACKEND) && XENIA_HAS_X64_BACKEND
|
||||
if (!backend) {
|
||||
#if XE_ARCH_AMD64
|
||||
backend.reset(new xe::cpu::backend::x64::X64Backend());
|
||||
#endif // XE_ARCH
|
||||
}
|
||||
#endif // XENIA_HAS_X64_BACKEND
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ project("xenia-cpu-ppc-tests")
|
||||
"fmt",
|
||||
"mspack",
|
||||
"xenia-core",
|
||||
"xenia-cpu-backend-x64",
|
||||
"xenia-cpu",
|
||||
"xenia-base",
|
||||
})
|
||||
@@ -24,6 +23,10 @@ project("xenia-cpu-ppc-tests")
|
||||
})
|
||||
filter("files:*.s")
|
||||
flags({"ExcludeFromBuild"})
|
||||
filter("architecture:x86_64")
|
||||
links({
|
||||
"xenia-cpu-backend-x64",
|
||||
})
|
||||
filter("platforms:Windows")
|
||||
debugdir(project_root)
|
||||
debugargs({
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "xenia/base/literals.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/profiling.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/cpu/breakpoint.h"
|
||||
@@ -133,7 +134,11 @@ bool Processor::Setup(std::unique_ptr<backend::Backend> backend) {
|
||||
// Stack walker is used when profiling, debugging, and dumping.
|
||||
// Note that creation may fail, in which case we'll have to disable those
|
||||
// features.
|
||||
stack_walker_ = StackWalker::Create(backend_->code_cache());
|
||||
// The code cache may be unavailable in case of a "null" backend.
|
||||
cpu::backend::CodeCache* code_cache = backend_->code_cache();
|
||||
if (code_cache) {
|
||||
stack_walker_ = StackWalker::Create(code_cache);
|
||||
}
|
||||
if (!stack_walker_) {
|
||||
// TODO(benvanik): disable features.
|
||||
if (cvars::debug) {
|
||||
@@ -698,7 +703,13 @@ bool Processor::OnThreadBreakpointHit(Exception* ex) {
|
||||
|
||||
// Apply thread context changes.
|
||||
// TODO(benvanik): apply to all threads?
|
||||
#if XE_ARCH_AMD64
|
||||
ex->set_resume_pc(thread_info->host_context.rip);
|
||||
#elif XE_ARCH_ARM64
|
||||
ex->set_resume_pc(thread_info->host_context.pc);
|
||||
#else
|
||||
#error Instruction pointer not specified for the target CPU architecture.
|
||||
#endif // XE_ARCH
|
||||
|
||||
// Resume execution.
|
||||
return true;
|
||||
@@ -828,8 +839,8 @@ bool Processor::ResumeAllThreads() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void Processor::UpdateThreadExecutionStates(uint32_t override_thread_id,
|
||||
X64Context* override_context) {
|
||||
void Processor::UpdateThreadExecutionStates(
|
||||
uint32_t override_thread_id, HostThreadContext* override_context) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
uint64_t frame_host_pcs[64];
|
||||
xe::cpu::StackFrame cpu_frames[64];
|
||||
@@ -851,7 +862,7 @@ void Processor::UpdateThreadExecutionStates(uint32_t override_thread_id,
|
||||
|
||||
// Grab stack trace and X64 context then resolve all symbols.
|
||||
uint64_t hash;
|
||||
X64Context* in_host_context = nullptr;
|
||||
HostThreadContext* in_host_context = nullptr;
|
||||
if (override_thread_id == thread_info->thread_id) {
|
||||
// If we were passed an override context we use that. Otherwise, ask the
|
||||
// stack walker for a new context.
|
||||
|
||||
@@ -215,8 +215,9 @@ class Processor {
|
||||
// Updates all cached thread execution info (state, call stacks, etc).
|
||||
// The given override thread handle and context will be used in place of
|
||||
// sampled values for that thread.
|
||||
void UpdateThreadExecutionStates(uint32_t override_handle = 0,
|
||||
X64Context* override_context = nullptr);
|
||||
void UpdateThreadExecutionStates(
|
||||
uint32_t override_handle = 0,
|
||||
HostThreadContext* override_context = nullptr);
|
||||
|
||||
// Suspends all breakpoints, uninstalling them as required.
|
||||
// No breakpoints will be triggered until they are resumed.
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/base/x64_context.h"
|
||||
#include "xenia/base/host_thread_context.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
|
||||
namespace xe {
|
||||
@@ -83,8 +83,8 @@ 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,
|
||||
const HostThreadContext* in_host_context,
|
||||
HostThreadContext* out_host_context,
|
||||
uint64_t* out_stack_hash = nullptr) = 0;
|
||||
|
||||
// Resolves symbol information for the given stack frames.
|
||||
|
||||
@@ -153,8 +153,8 @@ 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,
|
||||
const HostThreadContext* in_host_context,
|
||||
HostThreadContext* out_host_context,
|
||||
uint64_t* out_stack_hash) override {
|
||||
// TODO(benvanik): use xstate?
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/hh134240(v=vs.85).aspx
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/x64_context.h"
|
||||
#include "xenia/base/host_thread_context.h"
|
||||
#include "xenia/cpu/thread.h"
|
||||
#include "xenia/cpu/thread_state.h"
|
||||
|
||||
@@ -70,10 +70,10 @@ struct ThreadDebugInfo {
|
||||
// Last-sampled PPC context.
|
||||
// This is updated whenever the debugger stops.
|
||||
ppc::PPCContext guest_context;
|
||||
// Last-sampled host x64 context.
|
||||
// Last-sampled host context.
|
||||
// This is updated whenever the debugger stops and must be used instead of any
|
||||
// value taken from the StackWalker as it properly respects exception stacks.
|
||||
X64Context host_context;
|
||||
HostThreadContext host_context;
|
||||
|
||||
// A single frame in a call stack.
|
||||
struct Frame {
|
||||
|
||||
Reference in New Issue
Block a user