[CPU] MMIO: Arm64, load register writes + exception cleanup

This commit is contained in:
Triang3l
2022-07-06 21:02:59 +03:00
parent fd03d886e9
commit 326e718035
9 changed files with 496 additions and 105 deletions

View File

@@ -40,11 +40,14 @@ namespace xe {
// 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 & kArm64LoadStoreAnyMask) != kArm64LoadStoreAnyValue) {
if ((instruction & kArm64LoadLiteralFMask) == kArm64LoadLiteralFixed) {
return true;
}
if ((instruction & kArm64LoadStoreAnyFMask) != kArm64LoadStoreAnyFixed) {
return false;
}
if ((instruction & kArm64LoadStorePairAnyMask) ==
kArm64LoadStorePairAnyValue) {
if ((instruction & kArm64LoadStorePairAnyFMask) ==
kArm64LoadStorePairAnyFixed) {
is_store_out = !(instruction & kArm64LoadStorePairLoadBit);
return true;
}

View File

@@ -48,13 +48,19 @@ namespace xe {
// 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.
constexpr uint32_t kArm64LoadStoreAnyMask = UINT32_C(0x0A000000);
constexpr uint32_t kArm64LoadStoreAnyValue = UINT32_C(0x08000000);
constexpr uint32_t kArm64LoadStorePairAnyMask = UINT32_C(0x3A000000);
constexpr uint32_t kArm64LoadStorePairAnyValue = UINT32_C(0x28000000);
constexpr uint32_t kArm64LoadStorePairLoadBit = UINT32_C(1) << 22;
constexpr uint32_t kArm64LoadStoreMask = UINT32_C(0xC4C00000);
// `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),
@@ -82,6 +88,17 @@ enum class Arm64LoadStoreOp : uint32_t {
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 {
@@ -114,6 +131,14 @@ class Exception {
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.
@@ -139,6 +164,35 @@ class Exception {
#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_; }
@@ -150,6 +204,13 @@ class Exception {
private:
Code code_ = Code::kInvalidException;
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

@@ -16,6 +16,7 @@
#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 {
@@ -43,6 +44,8 @@ static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info,
#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]);
@@ -160,11 +163,61 @@ static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_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?
#if XE_ARCH_AMD64
mcontext.gregs[REG_RIP] = thread_context.rip;
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;
}

View File

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

@@ -27,10 +27,10 @@ static const char* kRegisterNames[] = {
"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", "q0", "q1", "q2", "q3",
"q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11", "q12", "q13",
"q14", "q15", "q16", "q17", "q18", "q19", "q20", "q21", "q22", "q23",
"q24", "q25", "q26", "q27", "q28", "q29", "q30", "q31",
"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
};
@@ -47,12 +47,12 @@ std::string HostThreadContext::GetStringFromValue(HostRegister reg,
case X64Register::kEflags:
return hex ? string_util::to_hex_string(eflags) : std::to_string(eflags);
default:
if (int(reg) >= int(X64Register::kRax) &&
int(reg) <= int(X64Register::kR15)) {
auto value = int_registers[int(reg) - int(X64Register::kRax)];
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 (int(reg) >= int(X64Register::kXmm0) &&
int(reg) <= int(X64Register::kXmm15)) {
} 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 {
@@ -73,12 +73,10 @@ std::string HostThreadContext::GetStringFromValue(HostRegister reg,
case Arm64Register::kFpcr:
return hex ? string_util::to_hex_string(fpcr) : std::to_string(fpcr);
default:
if (int(reg) >= int(Arm64Register::kX0) &&
int(reg) <= int(Arm64Register::kX30)) {
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 (int(reg) >= int(Arm64Register::kV0) &&
int(reg) <= int(Arm64Register::kV31)) {
} 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 {

View File

@@ -23,12 +23,17 @@
namespace xe {
// NOTE: The order of the registers in the enumerations must match the order in
// the string table in host_thread_context.cc.
// the string table in host_thread_context.cc, as well as remapping tables in
// exception handler implementations.
enum class X64Register {
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,
@@ -44,6 +49,8 @@ enum class X64Register {
kR13,
kR14,
kR15,
kIntRegisterLast = kR15,
kXmm0,
kXmm1,
kXmm2,
@@ -101,8 +108,7 @@ enum class Arm64Register {
kPstate,
kFpsr,
kFpcr,
// In assembly, the whole 128 bits of the Neon vector registers are accessible
// as Q# (quadword registers). VFP also uses these registers.
// The whole 128 bits of a Vn register are also known as Qn (quadword).
kV0,
kV1,
kV2,