Merge branch 'master' of https://github.com/xenia-project/xenia into canary_experimental

This commit is contained in:
Gliniak
2022-07-10 10:50:39 +02:00
49 changed files with 2378 additions and 1159 deletions

View File

@@ -0,0 +1,88 @@
/**
******************************************************************************
* 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/base/exception_handler.h"
namespace xe {
// Based on VIXL Instruction::IsLoad and IsStore.
// https://github.com/Linaro/vixl/blob/d48909dd0ac62197edb75d26ed50927e4384a199/src/aarch64/instructions-aarch64.cc#L484
//
// Copyright 2015, VIXL authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of ARM Limited nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
bool IsArm64LoadPrefetchStore(uint32_t instruction, bool& is_store_out) {
if ((instruction & kArm64LoadLiteralFMask) == kArm64LoadLiteralFixed) {
return true;
}
if ((instruction & kArm64LoadStoreAnyFMask) != kArm64LoadStoreAnyFixed) {
return false;
}
if ((instruction & kArm64LoadStorePairAnyFMask) ==
kArm64LoadStorePairAnyFixed) {
is_store_out = !(instruction & kArm64LoadStorePairLoadBit);
return true;
}
switch (Arm64LoadStoreOp(instruction & kArm64LoadStoreMask)) {
case Arm64LoadStoreOp::kLDRB_w:
case Arm64LoadStoreOp::kLDRH_w:
case Arm64LoadStoreOp::kLDR_w:
case Arm64LoadStoreOp::kLDR_x:
case Arm64LoadStoreOp::kLDRSB_x:
case Arm64LoadStoreOp::kLDRSH_x:
case Arm64LoadStoreOp::kLDRSW_x:
case Arm64LoadStoreOp::kLDRSB_w:
case Arm64LoadStoreOp::kLDRSH_w:
case Arm64LoadStoreOp::kLDR_b:
case Arm64LoadStoreOp::kLDR_h:
case Arm64LoadStoreOp::kLDR_s:
case Arm64LoadStoreOp::kLDR_d:
case Arm64LoadStoreOp::kLDR_q:
case Arm64LoadStoreOp::kPRFM:
is_store_out = false;
return true;
case Arm64LoadStoreOp::kSTRB_w:
case Arm64LoadStoreOp::kSTRH_w:
case Arm64LoadStoreOp::kSTR_w:
case Arm64LoadStoreOp::kSTR_x:
case Arm64LoadStoreOp::kSTR_b:
case Arm64LoadStoreOp::kSTR_h:
case Arm64LoadStoreOp::kSTR_s:
case Arm64LoadStoreOp::kSTR_d:
case Arm64LoadStoreOp::kSTR_q:
is_store_out = true;
return true;
default:
return false;
}
}
} // namespace xe

View File

@@ -2,7 +2,7 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
@@ -10,14 +10,97 @@
#ifndef XENIA_BASE_EXCEPTION_HANDLER_H_
#define XENIA_BASE_EXCEPTION_HANDLER_H_
#include <cstdint>
#include <functional>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/x64_context.h"
#include "xenia/base/host_thread_context.h"
namespace xe {
// AArch64 load and store decoding based on VIXL.
// https://github.com/Linaro/vixl/blob/ae5957cd66517b3f31dbf37e9bf39db6594abfe3/src/aarch64/constants-aarch64.h
//
// Copyright 2015, VIXL authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of ARM Limited nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// `Instruction address + literal offset` loads.
// This includes PRFM_lit.
constexpr uint32_t kArm64LoadLiteralFMask = UINT32_C(0x3B000000);
constexpr uint32_t kArm64LoadLiteralFixed = UINT32_C(0x18000000);
constexpr uint32_t kArm64LoadStoreAnyFMask = UINT32_C(0x0A000000);
constexpr uint32_t kArm64LoadStoreAnyFixed = UINT32_C(0x08000000);
constexpr uint32_t kArm64LoadStorePairAnyFMask = UINT32_C(0x3A000000);
constexpr uint32_t kArm64LoadStorePairAnyFixed = UINT32_C(0x28000000);
constexpr uint32_t kArm64LoadStorePairLoadBit = UINT32_C(1) << 22;
constexpr uint32_t kArm64LoadStoreMask = UINT32_C(0xC4C00000);
enum class Arm64LoadStoreOp : uint32_t {
kSTRB_w = UINT32_C(0x00000000),
kSTRH_w = UINT32_C(0x40000000),
kSTR_w = UINT32_C(0x80000000),
kSTR_x = UINT32_C(0xC0000000),
kLDRB_w = UINT32_C(0x00400000),
kLDRH_w = UINT32_C(0x40400000),
kLDR_w = UINT32_C(0x80400000),
kLDR_x = UINT32_C(0xC0400000),
kLDRSB_x = UINT32_C(0x00800000),
kLDRSH_x = UINT32_C(0x40800000),
kLDRSW_x = UINT32_C(0x80800000),
kLDRSB_w = UINT32_C(0x00C00000),
kLDRSH_w = UINT32_C(0x40C00000),
kSTR_b = UINT32_C(0x04000000),
kSTR_h = UINT32_C(0x44000000),
kSTR_s = UINT32_C(0x84000000),
kSTR_d = UINT32_C(0xC4000000),
kSTR_q = UINT32_C(0x04800000),
kLDR_b = UINT32_C(0x04400000),
kLDR_h = UINT32_C(0x44400000),
kLDR_s = UINT32_C(0x84400000),
kLDR_d = UINT32_C(0xC4400000),
kLDR_q = UINT32_C(0x04C00000),
kPRFM = UINT32_C(0xC0800000),
};
constexpr uint32_t kArm64LoadStoreOffsetFMask = UINT32_C(0x3B200C00);
enum class Arm64LoadStoreOffsetFixed : uint32_t {
kUnscaledOffset = UINT32_C(0x38000000),
kPostIndex = UINT32_C(0x38000400),
kPreIndex = UINT32_C(0x38000C00),
kRegisterOffset = UINT32_C(0x38200800),
};
constexpr uint32_t kArm64LoadStoreUnsignedOffsetFMask = UINT32_C(0x3B000000);
constexpr uint32_t kArm64LoadStoreUnsignedOffsetFixed = UINT32_C(0x39000000);
bool IsArm64LoadPrefetchStore(uint32_t instruction, bool& is_store_out);
class Exception {
public:
enum class Code {
@@ -32,7 +115,7 @@ class Exception {
kWrite,
};
void InitializeAccessViolation(X64Context* thread_context,
void InitializeAccessViolation(HostThreadContext* thread_context,
uint64_t fault_address,
AccessViolationOperation operation) {
code_ = Code::kAccessViolation;
@@ -40,7 +123,7 @@ class Exception {
fault_address_ = fault_address;
access_violation_operation_ = operation;
}
void InitializeIllegalInstruction(X64Context* thread_context) {
void InitializeIllegalInstruction(HostThreadContext* thread_context) {
code_ = Code::kIllegalInstruction;
thread_context_ = thread_context;
}
@@ -48,24 +131,67 @@ class Exception {
Code code() const { return code_; }
// Returns the platform-specific thread context info.
X64Context* thread_context() const { return thread_context_; }
// Note that certain registers must be modified through Modify* proxy
// functions rather than directly:
// x86-64:
// - General-purpose registers (r##, r8-r15).
// - XMM registers.
// AArch64:
// - General-purpose registers (Xn), including FP and LR.
// - SIMD and floating-point registers (Vn).
HostThreadContext* thread_context() const { return thread_context_; }
#if XE_ARCH_AMD64
// Returns the program counter where the exception occurred.
// RIP on x64.
uint64_t pc() const { return thread_context_->rip; }
// Sets the program counter where execution will resume.
void set_resume_pc(uint64_t pc) { thread_context_->rip = pc; }
#else
// Returns the program counter where the exception occurred.
// RIP on x64.
uint64_t pc() const {
#if XE_ARCH_AMD64
return thread_context_->rip;
#elif XE_ARCH_ARM64
return thread_context_->pc;
#else
assert_always();
return 0;
#endif // XE_ARCH
}
// Sets the program counter where execution will resume.
void set_resume_pc(uint64_t pc) { assert_always(); }
#endif
void set_resume_pc(uint64_t pc) {
#if XE_ARCH_AMD64
thread_context_->rip = pc;
#elif XE_ARCH_ARM64
thread_context_->pc = pc;
#else
assert_always();
#endif // XE_ARCH
}
#if XE_ARCH_AMD64
// The index is relative to X64Register::kIntRegisterFirst.
uint64_t& ModifyIntRegister(uint32_t index) {
assert_true(index <= 15);
modified_int_registers_ |= UINT16_C(1) << index;
return thread_context_->int_registers[index];
}
uint16_t modified_int_registers() const { return modified_int_registers_; }
vec128_t& ModifyXmmRegister(uint32_t index) {
assert_true(index <= 15);
modified_xmm_registers_ |= UINT16_C(1) << index;
return thread_context_->xmm_registers[index];
}
uint16_t modified_xmm_registers() const { return modified_xmm_registers_; }
#elif XE_ARCH_ARM64
uint64_t& ModifyXRegister(uint32_t index) {
assert_true(index <= 30);
modified_x_registers_ |= UINT32_C(1) << index;
return thread_context_->x[index];
}
uint32_t modified_x_registers() const { return modified_x_registers_; }
vec128_t& ModifyVRegister(uint32_t index) {
assert_true(index <= 31);
modified_v_registers_ |= UINT32_C(1) << index;
return thread_context_->v[index];
}
uint32_t modified_v_registers() const { return modified_v_registers_; }
#endif // XE_ARCH
// In case of AV, address that was read from/written to.
uint64_t fault_address() const { return fault_address_; }
@@ -77,7 +203,14 @@ class Exception {
private:
Code code_ = Code::kInvalidException;
X64Context* thread_context_ = nullptr;
HostThreadContext* thread_context_ = nullptr;
#if XE_ARCH_AMD64
uint16_t modified_int_registers_ = 0;
uint16_t modified_xmm_registers_ = 0;
#elif XE_ARCH_ARM64
uint32_t modified_x_registers_ = 0;
uint32_t modified_v_registers_ = 0;
#endif // XE_ARCH
uint64_t fault_address_ = 0;
AccessViolationOperation access_violation_operation_ =
AccessViolationOperation::kUnknown;

View File

@@ -1,35 +0,0 @@
/**
******************************************************************************
* 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/base/exception_handler.h"
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/base/platform_linux.h"
namespace xe {
// This can be as large as needed, but isn't often needed.
// As we will be sometimes firing many exceptions we want to avoid having to
// scan the table too much or invoke many custom handlers.
constexpr size_t kMaxHandlerCount = 8;
// All custom handlers, left-aligned and null terminated.
// Executed in order.
std::pair<ExceptionHandler::Handler, void*> handlers_[kMaxHandlerCount];
void ExceptionHandler::Install(Handler fn, void* data) {
// TODO(dougvj) stub
}
void ExceptionHandler::Uninstall(Handler fn, void* data) {
// TODO(dougvj) stub
}
} // namespace xe

View File

@@ -2,17 +2,285 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2017 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/base/exception_handler.h"
#include <signal.h>
#include <ucontext.h>
#include <cstdint>
#include "xenia/base/assert.h"
#include "xenia/base/host_thread_context.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/platform.h"
namespace xe {
// TODO(DrChat): Exception handling on linux.
void ExceptionHandler::Install(Handler fn, void* data) {}
void ExceptionHandler::Uninstall(Handler fn, void* data) {}
bool signal_handlers_installed_ = false;
struct sigaction original_sigill_handler_;
struct sigaction original_sigsegv_handler_;
} // namespace xe
// This can be as large as needed, but isn't often needed.
// As we will be sometimes firing many exceptions we want to avoid having to
// scan the table too much or invoke many custom handlers.
constexpr size_t kMaxHandlerCount = 8;
// All custom handlers, left-aligned and null terminated.
// Executed in order.
std::pair<ExceptionHandler::Handler, void*> handlers_[kMaxHandlerCount];
static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info,
void* signal_context) {
mcontext_t& mcontext =
reinterpret_cast<ucontext_t*>(signal_context)->uc_mcontext;
HostThreadContext thread_context;
#if XE_ARCH_AMD64
thread_context.rip = uint64_t(mcontext.gregs[REG_RIP]);
thread_context.eflags = uint32_t(mcontext.gregs[REG_EFL]);
// The REG_ order may be different than the register indices in the
// instruction encoding.
thread_context.rax = uint64_t(mcontext.gregs[REG_RAX]);
thread_context.rcx = uint64_t(mcontext.gregs[REG_RCX]);
thread_context.rdx = uint64_t(mcontext.gregs[REG_RDX]);
thread_context.rbx = uint64_t(mcontext.gregs[REG_RBX]);
thread_context.rsp = uint64_t(mcontext.gregs[REG_RSP]);
thread_context.rbp = uint64_t(mcontext.gregs[REG_RBP]);
thread_context.rsi = uint64_t(mcontext.gregs[REG_RSI]);
thread_context.rdi = uint64_t(mcontext.gregs[REG_RDI]);
thread_context.r8 = uint64_t(mcontext.gregs[REG_R8]);
thread_context.r9 = uint64_t(mcontext.gregs[REG_R9]);
thread_context.r10 = uint64_t(mcontext.gregs[REG_R10]);
thread_context.r11 = uint64_t(mcontext.gregs[REG_R11]);
thread_context.r12 = uint64_t(mcontext.gregs[REG_R12]);
thread_context.r13 = uint64_t(mcontext.gregs[REG_R13]);
thread_context.r14 = uint64_t(mcontext.gregs[REG_R14]);
thread_context.r15 = uint64_t(mcontext.gregs[REG_R15]);
std::memcpy(thread_context.xmm_registers, mcontext.fpregs->_xmm,
sizeof(thread_context.xmm_registers));
#elif XE_ARCH_ARM64
std::memcpy(thread_context.x, mcontext.regs, sizeof(thread_context.x));
thread_context.sp = mcontext.sp;
thread_context.pc = mcontext.pc;
thread_context.pstate = mcontext.pstate;
struct fpsimd_context* mcontext_fpsimd = nullptr;
struct esr_context* mcontext_esr = nullptr;
for (struct _aarch64_ctx* mcontext_extension =
reinterpret_cast<struct _aarch64_ctx*>(mcontext.__reserved);
mcontext_extension->magic;
mcontext_extension = reinterpret_cast<struct _aarch64_ctx*>(
reinterpret_cast<uint8_t*>(mcontext_extension) +
mcontext_extension->size)) {
switch (mcontext_extension->magic) {
case FPSIMD_MAGIC:
mcontext_fpsimd =
reinterpret_cast<struct fpsimd_context*>(mcontext_extension);
break;
case ESR_MAGIC:
mcontext_esr =
reinterpret_cast<struct esr_context*>(mcontext_extension);
break;
default:
break;
}
}
assert_not_null(mcontext_fpsimd);
if (mcontext_fpsimd) {
thread_context.fpsr = mcontext_fpsimd->fpsr;
thread_context.fpcr = mcontext_fpsimd->fpcr;
std::memcpy(thread_context.v, mcontext_fpsimd->vregs,
sizeof(thread_context.v));
}
#endif // XE_ARCH
Exception ex;
switch (signal_number) {
case SIGILL:
ex.InitializeIllegalInstruction(&thread_context);
break;
case SIGSEGV: {
Exception::AccessViolationOperation access_violation_operation;
#if XE_ARCH_AMD64
// x86_pf_error_code::X86_PF_WRITE
constexpr uint64_t kX86PageFaultErrorCodeWrite = UINT64_C(1) << 1;
access_violation_operation =
(uint64_t(mcontext.gregs[REG_ERR]) & kX86PageFaultErrorCodeWrite)
? Exception::AccessViolationOperation::kWrite
: Exception::AccessViolationOperation::kRead;
#elif XE_ARCH_ARM64
// For a Data Abort (EC - ESR_EL1 bits 31:26 - 0b100100 from a lower
// Exception Level, 0b100101 without a change in the Exception Level),
// bit 6 is 0 for reading from a memory location, 1 for writing to a
// memory location.
if (mcontext_esr && ((mcontext_esr->esr >> 26) & 0b111110) == 0b100100) {
access_violation_operation =
(mcontext_esr->esr & (UINT64_C(1) << 6))
? Exception::AccessViolationOperation::kWrite
: Exception::AccessViolationOperation::kRead;
} else {
// Determine the memory access direction based on which instruction has
// requested it.
// esr_context may be unavailable on certain hosts (for instance, on
// Android, it was added only in NDK r16 - which is the first NDK
// version to support the Android API level 27, while NDK r15 doesn't
// have esr_context in its API 26 sigcontext.h).
// On AArch64 (unlike on AArch32), the program counter is the address of
// the currently executing instruction.
bool instruction_is_store;
if (IsArm64LoadPrefetchStore(
*reinterpret_cast<const uint32_t*>(mcontext.pc),
instruction_is_store)) {
access_violation_operation =
instruction_is_store ? Exception::AccessViolationOperation::kWrite
: Exception::AccessViolationOperation::kRead;
} else {
assert_always(
"No ESR in the exception thread context, or it's not a Data "
"Abort, and the faulting instruction is not a known load, "
"prefetch or store instruction");
access_violation_operation =
Exception::AccessViolationOperation::kUnknown;
}
}
#else
access_violation_operation =
Exception::AccessViolationOperation::kUnknown;
#endif // XE_ARCH
ex.InitializeAccessViolation(
&thread_context, reinterpret_cast<uint64_t>(signal_info->si_addr),
access_violation_operation);
} break;
default:
assert_unhandled_case(signal_number);
}
for (size_t i = 0; i < xe::countof(handlers_) && handlers_[i].first; ++i) {
if (handlers_[i].first(&ex, handlers_[i].second)) {
// Exception handled.
#if XE_ARCH_AMD64
mcontext.gregs[REG_RIP] = greg_t(thread_context.rip);
mcontext.gregs[REG_EFL] = greg_t(thread_context.eflags);
uint32_t modified_register_index;
// The order must match the order in X64Register.
static const size_t kIntRegisterMap[] = {
REG_RAX, REG_RCX, REG_RDX, REG_RBX, REG_RSP, REG_RBP,
REG_RSI, REG_RDI, REG_R8, REG_R9, REG_R10, REG_R11,
REG_R12, REG_R13, REG_R14, REG_R15,
};
uint16_t modified_int_registers_remaining = ex.modified_int_registers();
while (xe::bit_scan_forward(modified_int_registers_remaining,
&modified_register_index)) {
modified_int_registers_remaining &=
~(UINT16_C(1) << modified_register_index);
mcontext.gregs[kIntRegisterMap[modified_register_index]] =
thread_context.int_registers[modified_register_index];
}
uint16_t modified_xmm_registers_remaining = ex.modified_xmm_registers();
while (xe::bit_scan_forward(modified_xmm_registers_remaining,
&modified_register_index)) {
modified_xmm_registers_remaining &=
~(UINT16_C(1) << modified_register_index);
std::memcpy(&mcontext.fpregs->_xmm[modified_register_index],
&thread_context.xmm_registers[modified_register_index],
sizeof(vec128_t));
}
#elif XE_ARCH_ARM64
uint32_t modified_register_index;
uint32_t modified_x_registers_remaining = ex.modified_x_registers();
while (xe::bit_scan_forward(modified_x_registers_remaining,
&modified_register_index)) {
modified_x_registers_remaining &=
~(UINT32_C(1) << modified_register_index);
mcontext.regs[modified_register_index] =
thread_context.x[modified_register_index];
}
mcontext.sp = thread_context.sp;
mcontext.pc = thread_context.pc;
mcontext.pstate = thread_context.pstate;
if (mcontext_fpsimd) {
mcontext_fpsimd->fpsr = thread_context.fpsr;
mcontext_fpsimd->fpcr = thread_context.fpcr;
uint32_t modified_v_registers_remaining = ex.modified_v_registers();
while (xe::bit_scan_forward(modified_v_registers_remaining,
&modified_register_index)) {
modified_v_registers_remaining &=
~(UINT32_C(1) << modified_register_index);
std::memcpy(&mcontext_fpsimd->vregs[modified_register_index],
&thread_context.v[modified_register_index],
sizeof(vec128_t));
mcontext.regs[modified_register_index] =
thread_context.x[modified_register_index];
}
}
#endif // XE_ARCH
return;
}
}
}
void ExceptionHandler::Install(Handler fn, void* data) {
if (!signal_handlers_installed_) {
struct sigaction signal_handler;
std::memset(&signal_handler, 0, sizeof(signal_handler));
signal_handler.sa_sigaction = ExceptionHandlerCallback;
signal_handler.sa_flags = SA_SIGINFO;
if (sigaction(SIGILL, &signal_handler, &original_sigill_handler_) != 0) {
assert_always("Failed to install new SIGILL handler");
}
if (sigaction(SIGSEGV, &signal_handler, &original_sigsegv_handler_) != 0) {
assert_always("Failed to install new SIGSEGV handler");
}
signal_handlers_installed_ = true;
}
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
if (!handlers_[i].first) {
handlers_[i].first = fn;
handlers_[i].second = data;
return;
}
}
assert_always("Too many exception handlers installed");
}
void ExceptionHandler::Uninstall(Handler fn, void* data) {
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
if (handlers_[i].first == fn && handlers_[i].second == data) {
for (; i < xe::countof(handlers_) - 1; ++i) {
handlers_[i] = handlers_[i + 1];
}
handlers_[i].first = nullptr;
handlers_[i].second = nullptr;
break;
}
}
bool has_any = false;
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
if (handlers_[i].first) {
has_any = true;
break;
}
}
if (!has_any) {
if (signal_handlers_installed_) {
if (sigaction(SIGILL, &original_sigill_handler_, NULL) != 0) {
assert_always("Failed to restore original SIGILL handler");
}
if (sigaction(SIGSEGV, &original_sigsegv_handler_, NULL) != 0) {
assert_always("Failed to restore original SIGSEGV handler");
}
signal_handlers_installed_ = false;
}
}
}
} // namespace xe

View File

@@ -35,8 +35,7 @@ LONG CALLBACK ExceptionHandlerCallback(PEXCEPTION_POINTERS ex_info) {
return EXCEPTION_CONTINUE_SEARCH;
}
// TODO(benvanik): avoid this by mapping X64Context virtual?
X64Context thread_context;
HostThreadContext thread_context;
thread_context.rip = ex_info->ContextRecord->Rip;
thread_context.eflags = ex_info->ContextRecord->EFlags;
std::memcpy(thread_context.int_registers, &ex_info->ContextRecord->Rax,
@@ -79,8 +78,26 @@ LONG CALLBACK ExceptionHandlerCallback(PEXCEPTION_POINTERS ex_info) {
for (size_t i = 0; i < xe::countof(handlers_) && handlers_[i].first; ++i) {
if (handlers_[i].first(&ex, handlers_[i].second)) {
// Exception handled.
// TODO(benvanik): update all thread state? Dirty flags?
ex_info->ContextRecord->Rip = thread_context.rip;
ex_info->ContextRecord->EFlags = thread_context.eflags;
uint32_t modified_register_index;
uint16_t modified_int_registers_remaining = ex.modified_int_registers();
while (xe::bit_scan_forward(modified_int_registers_remaining,
&modified_register_index)) {
modified_int_registers_remaining &=
~(UINT16_C(1) << modified_register_index);
(&ex_info->ContextRecord->Rax)[modified_register_index] =
thread_context.int_registers[modified_register_index];
}
uint16_t modified_xmm_registers_remaining = ex.modified_xmm_registers();
while (xe::bit_scan_forward(modified_xmm_registers_remaining,
&modified_register_index)) {
modified_xmm_registers_remaining &=
~(UINT16_C(1) << modified_register_index);
std::memcpy(&ex_info->ContextRecord->Xmm0 + modified_register_index,
&thread_context.xmm_registers[modified_register_index],
sizeof(vec128_t));
}
return EXCEPTION_CONTINUE_EXECUTION;
}
}

View File

@@ -0,0 +1,95 @@
/**
******************************************************************************
* 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/base/host_thread_context.h"
#include "xenia/base/assert.h"
#include "xenia/base/platform.h"
#include "xenia/base/string_util.h"
namespace xe {
// NOTE: this order matches 1:1 with the HostRegister enums.
static const char* kRegisterNames[] = {
#if XE_ARCH_AMD64
"rip", "eflags", "rax", "rcx", "rdx", "rbx", "rsp",
"rbp", "rsi", "rdi", "r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15", "xmm0", "xmm1", "xmm2",
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9",
"xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
#elif XE_ARCH_ARM64
"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7", "x8", "x9",
"x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", "x18", "x19",
"x20", "x21", "x22", "x23", "x24", "x25", "x26", "x27", "x28", "x29",
"x30", "sp", "pc", "pstate", "fpsr", "fpcr", "v0", "v1", "v2", "v3",
"v4", "v5", "v6", "v7", "v8", "v9", "v10", "v11", "v12", "v13",
"v14", "v15", "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23",
"v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31",
#endif // XE_ARCH
};
const char* HostThreadContext::GetRegisterName(HostRegister reg) {
return kRegisterNames[int(reg)];
}
std::string HostThreadContext::GetStringFromValue(HostRegister reg,
bool hex) const {
#if XE_ARCH_AMD64
switch (reg) {
case X64Register::kRip:
return hex ? string_util::to_hex_string(rip) : std::to_string(rip);
case X64Register::kEflags:
return hex ? string_util::to_hex_string(eflags) : std::to_string(eflags);
default:
if (reg >= X64Register::kIntRegisterFirst &&
reg <= X64Register::kIntRegisterLast) {
auto value =
int_registers[int(reg) - int(X64Register::kIntRegisterFirst)];
return hex ? string_util::to_hex_string(value) : std::to_string(value);
} else if (reg >= X64Register::kXmm0 && reg <= X64Register::kXmm15) {
auto value = xmm_registers[int(reg) - int(X64Register::kXmm0)];
return hex ? string_util::to_hex_string(value) : xe::to_string(value);
} else {
assert_unhandled_case(reg);
return std::string();
}
}
#elif XE_ARCH_ARM64
switch (reg) {
case Arm64Register::kSp:
return hex ? string_util::to_hex_string(sp) : std::to_string(sp);
case Arm64Register::kPc:
return hex ? string_util::to_hex_string(pc) : std::to_string(pc);
case Arm64Register::kPstate:
return hex ? string_util::to_hex_string(pstate) : std::to_string(pstate);
case Arm64Register::kFpsr:
return hex ? string_util::to_hex_string(fpsr) : std::to_string(fpsr);
case Arm64Register::kFpcr:
return hex ? string_util::to_hex_string(fpcr) : std::to_string(fpcr);
default:
if (reg >= Arm64Register::kX0 && reg <= Arm64Register::kX30) {
auto value = x[int(reg) - int(Arm64Register::kX0)];
return hex ? string_util::to_hex_string(value) : std::to_string(value);
} else if (reg >= Arm64Register::kV0 && reg <= Arm64Register::kV31) {
auto value = v[int(reg) - int(Arm64Register::kV0)];
return hex ? string_util::to_hex_string(value) : xe::to_string(value);
} else {
assert_unhandled_case(reg);
return std::string();
}
}
#else
assert_always(
"HostThreadContext::GetStringFromValue not implemented for the target "
"CPU architecture");
return std::string();
#endif // XE_ARCH
}
} // namespace xe

View File

@@ -2,13 +2,13 @@
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_BASE_X64_CONTEXT_H_
#define XENIA_BASE_X64_CONTEXT_H_
#ifndef XENIA_BASE_HOST_THREAD_CONTEXT_H_
#define XENIA_BASE_HOST_THREAD_CONTEXT_H_
#include <cstdint>
#include <string>
@@ -22,15 +22,18 @@
namespace xe {
class X64Context;
// NOTE: The order of the registers in the enumerations must match the order in
// the string table in host_thread_context.cc, as well as remapping tables in
// exception handler implementations.
#if XE_ARCH_AMD64
enum class X64Register {
// NOTE: this order matches 1:1 with the order in the X64Context.
// NOTE: this order matches 1:1 with a string table in the x64_context.cc.
kRip,
kEflags,
kRax,
kIntRegisterFirst,
// The order matches the indices in the instruction encoding, as well as the
// Windows CONTEXT structure.
kRax = kIntRegisterFirst,
kRcx,
kRdx,
kRbx,
@@ -46,6 +49,8 @@ enum class X64Register {
kR13,
kR14,
kR15,
kIntRegisterLast = kR15,
kXmm0,
kXmm1,
kXmm2,
@@ -64,8 +69,91 @@ enum class X64Register {
kXmm15,
};
class X64Context {
enum class Arm64Register {
kX0,
kX1,
kX2,
kX3,
kX4,
kX5,
kX6,
kX7,
kX8,
kX9,
kX10,
kX11,
kX12,
kX13,
kX14,
kX15,
kX16,
kX17,
kX18,
kX19,
kX20,
kX21,
kX22,
kX23,
kX24,
kX25,
kX26,
kX27,
kX28,
// FP (frame pointer).
kX29,
// LR (link register).
kX30,
kSp,
kPc,
kPstate,
kFpsr,
kFpcr,
// The whole 128 bits of a Vn register are also known as Qn (quadword).
kV0,
kV1,
kV2,
kV3,
kV4,
kV5,
kV6,
kV7,
kV8,
kV9,
kV10,
kV11,
kV12,
kV13,
kV14,
kV15,
kV16,
kV17,
kV18,
kV19,
kV20,
kV21,
kV22,
kV23,
kV24,
kV25,
kV26,
kV27,
kV28,
kV29,
kV30,
kV31,
};
#if XE_ARCH_AMD64
using HostRegister = X64Register;
#elif XE_ARCH_ARM64
using HostRegister = Arm64Register;
#else
enum class HostRegister {};
#endif // XE_ARCH
class HostThreadContext {
public:
#if XE_ARCH_AMD64
uint64_t rip;
uint32_t eflags;
union {
@@ -89,7 +177,6 @@ class X64Context {
};
uint64_t int_registers[16];
};
union {
struct {
vec128_t xmm0;
@@ -111,12 +198,19 @@ class X64Context {
};
vec128_t xmm_registers[16];
};
#elif XE_ARCH_ARM64
uint64_t x[31];
uint64_t sp;
uint64_t pc;
uint64_t pstate;
uint32_t fpsr;
uint32_t fpcr;
vec128_t v[32];
#endif // XE_ARCH
static const char* GetRegisterName(X64Register reg);
std::string GetStringFromValue(X64Register reg, bool hex) const;
void SetValueFromString(X64Register reg, std::string value, bool hex);
static const char* GetRegisterName(HostRegister reg);
std::string GetStringFromValue(HostRegister reg, bool hex) const;
};
#endif // XE_ARCH_AMD64
} // namespace xe

View File

@@ -1,67 +0,0 @@
/**
******************************************************************************
* 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/base/x64_context.h"
#include "xenia/base/assert.h"
#include "xenia/base/platform.h"
#include "xenia/base/string_util.h"
namespace xe {
#if XE_ARCH_AMD64
// NOTE: this order matches 1:1 with the X64Register enum.
static const char* kRegisterNames[] = {
"rip", "eflags", "rax", "rcx", "rdx", "rbx", "rsp",
"rbp", "rsi", "rdi", "r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15", "xmm0", "xmm1", "xmm2",
"xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9",
"xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
};
const char* X64Context::GetRegisterName(X64Register reg) {
return kRegisterNames[static_cast<int>(reg)];
}
std::string X64Context::GetStringFromValue(X64Register reg, bool hex) const {
switch (reg) {
case X64Register::kRip:
return hex ? string_util::to_hex_string(rip) : std::to_string(rip);
case X64Register::kEflags:
return hex ? string_util::to_hex_string(eflags) : std::to_string(eflags);
default:
if (static_cast<int>(reg) >= static_cast<int>(X64Register::kRax) &&
static_cast<int>(reg) <= static_cast<int>(X64Register::kR15)) {
auto value = int_registers[static_cast<int>(reg) -
static_cast<int>(X64Register::kRax)];
return hex ? string_util::to_hex_string(value) : std::to_string(value);
} else if (static_cast<int>(reg) >=
static_cast<int>(X64Register::kXmm0) &&
static_cast<int>(reg) <=
static_cast<int>(X64Register::kXmm15)) {
auto value = xmm_registers[static_cast<int>(reg) -
static_cast<int>(X64Register::kXmm0)];
return hex ? string_util::to_hex_string(value) : xe::to_string(value);
} else {
assert_unhandled_case(reg);
return "";
}
}
}
void X64Context::SetValueFromString(X64Register reg, std::string value,
bool hex) {
// TODO(benvanik): set value from string.
assert_always(false);
}
#endif // XE_ARCH_AMD64
} // namespace xe