/** ****************************************************************************** * 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. * ****************************************************************************** * * @modified Tom Clay, 2026 - Adapted for ReXGlue runtime * * @file exception_handler.h * @brief Unified exception handling, thread context, and register defs * * @remarks Flattened from arch/{arm64,amd64}/host_thread_context.h, * arch/host_thread_context.h, arch/{arm64,amd64}/exception_handler.h, * and arch/exception_handler.h. * Contains VIXL-derived ARM64 load/store decoding constants. */ #pragma once #include #include #include #include #include #include #include #if REX_ARCH_AMD64 #include #endif namespace rex::arch { //============================================================================= // ARM64 Register Definitions //============================================================================= // 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. 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, }; // ARM64 thread context structure members // Used within HostThreadContext class via #if REX_ARCH_ARM64 struct Arm64ThreadContextMembers { uint64_t x[31]; uint64_t sp; uint64_t pc; uint64_t pstate; uint32_t fpsr; uint32_t fpcr; vec128_t v[32]; }; //============================================================================= // AMD64 Register Definitions //============================================================================= #if REX_ARCH_AMD64 // 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. enum class X64Register { kRip, kEflags, kIntRegisterFirst, // The order matches the indices in the instruction encoding, as well as the // Windows CONTEXT structure. kRax = kIntRegisterFirst, kRcx, kRdx, kRbx, kRsp, kRbp, kRsi, kRdi, kR8, kR9, kR10, kR11, kR12, kR13, kR14, kR15, kIntRegisterLast = kR15, kXmm0, kXmm1, kXmm2, kXmm3, kXmm4, kXmm5, kXmm6, kXmm7, kXmm8, kXmm9, kXmm10, kXmm11, kXmm12, kXmm13, kXmm14, kXmm15, }; // x86-64 thread context structure members // Used within HostThreadContext class via #if REX_ARCH_AMD64 struct X64ThreadContextMembers { uint64_t rip; uint32_t eflags; union { struct { uint64_t rax; uint64_t rcx; uint64_t rdx; uint64_t rbx; uint64_t rsp; uint64_t rbp; uint64_t rsi; uint64_t rdi; uint64_t r8; uint64_t r9; uint64_t r10; uint64_t r11; uint64_t r12; uint64_t r13; uint64_t r14; uint64_t r15; }; uint64_t int_registers[16]; }; union { struct { vec128_t xmm0; vec128_t xmm1; vec128_t xmm2; vec128_t xmm3; vec128_t xmm4; vec128_t xmm5; vec128_t xmm6; vec128_t xmm7; vec128_t xmm8; vec128_t xmm9; vec128_t xmm10; vec128_t xmm11; vec128_t xmm12; vec128_t xmm13; vec128_t xmm14; vec128_t xmm15; }; vec128_t xmm_registers[16]; }; }; #endif // REX_ARCH_AMD64 //============================================================================= // Host Register Typedef //============================================================================= #if REX_ARCH_AMD64 using HostRegister = X64Register; #elif REX_ARCH_ARM64 using HostRegister = Arm64Register; #else enum class HostRegister {}; #endif // REX_ARCH //============================================================================= // Host Thread Context //============================================================================= class HostThreadContext { public: #if REX_ARCH_AMD64 uint64_t rip; uint32_t eflags; union { struct { uint64_t rax; uint64_t rcx; uint64_t rdx; uint64_t rbx; uint64_t rsp; uint64_t rbp; uint64_t rsi; uint64_t rdi; uint64_t r8; uint64_t r9; uint64_t r10; uint64_t r11; uint64_t r12; uint64_t r13; uint64_t r14; uint64_t r15; }; uint64_t int_registers[16]; }; union { struct { vec128_t xmm0; vec128_t xmm1; vec128_t xmm2; vec128_t xmm3; vec128_t xmm4; vec128_t xmm5; vec128_t xmm6; vec128_t xmm7; vec128_t xmm8; vec128_t xmm9; vec128_t xmm10; vec128_t xmm11; vec128_t xmm12; vec128_t xmm13; vec128_t xmm14; vec128_t xmm15; }; vec128_t xmm_registers[16]; }; #elif REX_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 // REX_ARCH static const char* GetRegisterName(HostRegister reg); std::string GetStringFromValue(HostRegister reg, bool hex) const; }; //============================================================================= // ARM64 Load/Store Decoding (VIXL-derived) //============================================================================= // 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); // Determines if an ARM64 instruction is a load, prefetch, or store operation. // Returns true if the instruction is one of these, with is_store_out set to // indicate whether it's a store operation. bool IsArm64LoadPrefetchStore(uint32_t instruction, bool& is_store_out); //============================================================================= // Exception Class //============================================================================= class Exception { public: enum class Code { kInvalidException = 0, kAccessViolation, kIllegalInstruction, }; enum class AccessViolationOperation { kUnknown, kRead, kWrite, }; void InitializeAccessViolation(HostThreadContext* thread_context, uint64_t fault_address, AccessViolationOperation operation) { code_ = Code::kAccessViolation; thread_context_ = thread_context; fault_address_ = fault_address; access_violation_operation_ = operation; } void InitializeIllegalInstruction(HostThreadContext* thread_context) { code_ = Code::kIllegalInstruction; thread_context_ = thread_context; } Code code() const { return code_; } // Returns the platform-specific thread context info. // 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_; } // Returns the program counter where the exception occurred. uint64_t pc() const { #if REX_ARCH_AMD64 return thread_context_->rip; #elif REX_ARCH_ARM64 return thread_context_->pc; #else assert_always(); return 0; #endif // REX_ARCH } // Sets the program counter where execution will resume. void set_resume_pc(uint64_t pc) { #if REX_ARCH_AMD64 thread_context_->rip = pc; #elif REX_ARCH_ARM64 thread_context_->pc = pc; #else assert_always(); #endif // REX_ARCH } #if REX_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 REX_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 // REX_ARCH // In case of AV, address that was read from/written to. uint64_t fault_address() const { return fault_address_; } // In case of AV, what kind of operation caused it. AccessViolationOperation access_violation_operation() const { return access_violation_operation_; } private: Code code_ = Code::kInvalidException; HostThreadContext* thread_context_ = nullptr; #if REX_ARCH_AMD64 uint16_t modified_int_registers_ = 0; uint16_t modified_xmm_registers_ = 0; #elif REX_ARCH_ARM64 uint32_t modified_x_registers_ = 0; uint32_t modified_v_registers_ = 0; #endif // REX_ARCH uint64_t fault_address_ = 0; AccessViolationOperation access_violation_operation_ = AccessViolationOperation::kUnknown; }; //============================================================================= // Exception Handler //============================================================================= class ExceptionHandler { public: typedef bool (*Handler)(Exception* ex, void* data); // Installs an exception handler. // Handlers are called in the order they are installed. static void Install(Handler fn, void* data); // Uninstalls a previously-installed exception handler. static void Uninstall(Handler fn, void* data); }; } // namespace rex::arch