diff --git a/src/xenia/base/string_util.h b/src/xenia/base/string_util.h new file mode 100644 index 000000000..c4dc7ea1d --- /dev/null +++ b/src/xenia/base/string_util.h @@ -0,0 +1,165 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +#ifndef XENIA_BASE_STRING_UTIL_H_ +#define XENIA_BASE_STRING_UTIL_H_ + +#include +#include +#include +#include + +#include "xenia/base/platform.h" +#include "xenia/base/vec128.h" + +namespace xe { +namespace string_util { + +inline std::string to_hex_string(uint32_t value) { + char buffer[21]; + std::snprintf(buffer, sizeof(buffer), "%08" PRIX32, value); + return std::string(buffer); +} + +inline std::string to_hex_string(uint64_t value) { + char buffer[21]; + std::snprintf(buffer, sizeof(buffer), "%016" PRIX64, value); + return std::string(buffer); +} + +inline std::string to_hex_string(double value) { + union { + uint64_t ui; + double dbl; + } v; + v.dbl = value; + return to_hex_string(v.ui); +} + +inline std::string to_hex_string(const vec128_t& value) { + char buffer[128]; + std::snprintf(buffer, sizeof(buffer), "[%.8X, %.8X, %.8X, %.8X]", + value.u32[0], value.u32[1], value.u32[2], value.u32[3]); + return std::string(buffer); +} + +inline std::string to_hex_string(const __m128& value) { + char buffer[128]; + std::snprintf(buffer, sizeof(buffer), "[%.8X, %.8X, %.8X, %.8X]", + value.m128_u32[0], value.m128_u32[1], value.m128_u32[2], + value.m128_u32[3]); + return std::string(buffer); +} + +template +inline T from_string(const char* value); + +template <> +inline uint64_t from_string(const char* value) { + if (std::strchr(value, 'h') != nullptr) { + return std::strtoull(value, nullptr, 16); + } else { + return std::strtoull(value, nullptr, 0); + } +} + +template <> +inline double from_string(const char* value) { + if (std::strstr(value, "0x") == value || std::strchr(value, 'h') != nullptr) { + union { + uint64_t ui; + double dbl; + } v; + v.ui = from_string(value); + return v.dbl; + } + return std::strtod(value, nullptr); +} + +template <> +inline vec128_t from_string(const char* value) { + vec128_t v; + char* p = const_cast(value); + bool hex_mode = false; + if (*p == '[') { + hex_mode = true; + ++p; + } else if (*p == '(') { + hex_mode = false; + ++p; + } else { + // Assume hex? + hex_mode = true; + ++p; + } + if (hex_mode) { + v.i32[0] = std::strtoul(p, &p, 16); + while (*p == ' ' || *p == ',') ++p; + v.i32[1] = std::strtoul(p, &p, 16); + while (*p == ' ' || *p == ',') ++p; + v.i32[2] = std::strtoul(p, &p, 16); + while (*p == ' ' || *p == ',') ++p; + v.i32[3] = std::strtoul(p, &p, 16); + } else { + v.f32[0] = std::strtof(p, &p); + while (*p == ' ' || *p == ',') ++p; + v.f32[1] = std::strtof(p, &p); + while (*p == ' ' || *p == ',') ++p; + v.f32[2] = std::strtof(p, &p); + while (*p == ' ' || *p == ',') ++p; + v.f32[3] = std::strtof(p, &p); + } + return v; +} + +template <> +inline __m128 from_string<__m128>(const char* value) { + __m128 v; + char* p = const_cast(value); + bool hex_mode = false; + if (*p == '[') { + hex_mode = true; + ++p; + } else if (*p == '(') { + hex_mode = false; + ++p; + } else { + // Assume hex? + hex_mode = true; + ++p; + } + if (hex_mode) { + v.m128_u32[0] = std::strtoul(p, &p, 16); + while (*p == ' ' || *p == ',') ++p; + v.m128_u32[1] = std::strtoul(p, &p, 16); + while (*p == ' ' || *p == ',') ++p; + v.m128_u32[2] = std::strtoul(p, &p, 16); + while (*p == ' ' || *p == ',') ++p; + v.m128_u32[3] = std::strtoul(p, &p, 16); + } else { + v.m128_f32[0] = std::strtof(p, &p); + while (*p == ' ' || *p == ',') ++p; + v.m128_f32[1] = std::strtof(p, &p); + while (*p == ' ' || *p == ',') ++p; + v.m128_f32[2] = std::strtof(p, &p); + while (*p == ' ' || *p == ',') ++p; + v.m128_f32[3] = std::strtof(p, &p); + } + return v; +} + +template +inline T from_string(const std::string& value) { + return from_string(value.c_str()); +} + +} // namespace string_util +} // namespace xe + +#endif // XENIA_BASE_STRING_UTIL_H_ diff --git a/src/xenia/base/vec128.h b/src/xenia/base/vec128.h index 5a691253f..0d5e985eb 100644 --- a/src/xenia/base/vec128.h +++ b/src/xenia/base/vec128.h @@ -11,6 +11,7 @@ #define XENIA_BASE_VEC128_H_ #include +#include #include "xenia/base/math.h" #include "xenia/base/platform.h" @@ -194,6 +195,13 @@ static inline vec128_t vec128b(uint8_t x0, uint8_t x1, uint8_t x2, uint8_t x3, return v; } +inline std::string to_string(const vec128_t& value) { + char buffer[128]; + std::snprintf(buffer, sizeof(buffer), "(%g, %g, %g, %g)", value.x, value.y, + value.z, value.w); + return std::string(buffer); +} + } // namespace xe #endif // XENIA_BASE_VEC128_H_ diff --git a/src/xenia/cpu/compiler/passes/context_promotion_pass.cc b/src/xenia/cpu/compiler/passes/context_promotion_pass.cc index 429fb3942..d51f1341b 100644 --- a/src/xenia/cpu/compiler/passes/context_promotion_pass.cc +++ b/src/xenia/cpu/compiler/passes/context_promotion_pass.cc @@ -15,6 +15,8 @@ #include "xenia/cpu/processor.h" #include "xenia/profiling.h" +DECLARE_bool(debug); + DEFINE_bool(store_all_context_values, false, "Don't strip dead context stores to aid in debugging."); @@ -74,7 +76,9 @@ bool ContextPromotionPass::Run(HIRBuilder* builder) { } // Remove all dead stores. - if (!FLAGS_store_all_context_values) { + // This will break debugging as we can't recover this information when + // trying to extract stack traces/register values, so we don't do that. + if (!FLAGS_debug && !FLAGS_store_all_context_values) { block = builder->first_block(); while (block) { RemoveDeadStoresBlock(block); diff --git a/src/xenia/cpu/frontend/ppc_context.cc b/src/xenia/cpu/frontend/ppc_context.cc index c5912d141..d826c4b9f 100644 --- a/src/xenia/cpu/frontend/ppc_context.cc +++ b/src/xenia/cpu/frontend/ppc_context.cc @@ -12,82 +12,140 @@ #include #include +#include "xenia/base/assert.h" +#include "xenia/base/string_util.h" + namespace xe { namespace cpu { namespace frontend { -uint64_t ParseInt64(const char* value) { - return std::strtoull(value, nullptr, 0); -} - -double ParseFloat64(const char* value) { - if (strstr(value, "0x") == value) { - union { - uint64_t ui; - double dbl; - } v; - v.ui = ParseInt64(value); - return v.dbl; +std::string PPCContext::GetRegisterName(PPCRegister reg) { + switch (reg) { + case PPCRegister::kLR: + return "lr"; + case PPCRegister::kCTR: + return "ctr"; + case PPCRegister::kXER: + return "xer"; + case PPCRegister::kFPSCR: + return "fpscr"; + case PPCRegister::kVSCR: + return "vscr"; + case PPCRegister::kCR: + return "cr"; + default: + if (static_cast(reg) >= static_cast(PPCRegister::kR0) && + static_cast(reg) <= static_cast(PPCRegister::kR31)) { + return std::string("r") + + std::to_string(static_cast(reg) - + static_cast(PPCRegister::kR0)); + } else if (static_cast(reg) >= static_cast(PPCRegister::kFR0) && + static_cast(reg) <= + static_cast(PPCRegister::kFR31)) { + return std::string("fr") + + std::to_string(static_cast(reg) - + static_cast(PPCRegister::kFR0)); + } else if (static_cast(reg) >= static_cast(PPCRegister::kVR0) && + static_cast(reg) <= + static_cast(PPCRegister::kVR128)) { + return std::string("vr") + + std::to_string(static_cast(reg) - + static_cast(PPCRegister::kVR0)); + } else { + assert_unhandled_case(reg); + return "?"; + } } - return std::strtod(value, nullptr); } -vec128_t ParseVec128(const char* value) { - vec128_t v; - char* p = const_cast(value); - if (*p == '[') ++p; - v.i32[0] = std::strtoul(p, &p, 16); - while (*p == ' ' || *p == ',') ++p; - v.i32[1] = std::strtoul(p, &p, 16); - while (*p == ' ' || *p == ',') ++p; - v.i32[2] = std::strtoul(p, &p, 16); - while (*p == ' ' || *p == ',') ++p; - v.i32[3] = std::strtoul(p, &p, 16); - return v; +std::string PPCContext::GetStringFromValue(PPCRegister reg) const { + switch (reg) { + case PPCRegister::kLR: + return string_util::to_hex_string(lr); + case PPCRegister::kCTR: + return string_util::to_hex_string(ctr); + case PPCRegister::kXER: + // return Int64ToHex(xer_ca); + return "?"; + case PPCRegister::kFPSCR: + // return Int64ToHex(fpscr); + return "?"; + case PPCRegister::kVSCR: + // return Int64ToHex(vscr_sat); + return "?"; + case PPCRegister::kCR: + // return Int64ToHex(cr); + return "?"; + default: + if (static_cast(reg) >= static_cast(PPCRegister::kR0) && + static_cast(reg) <= static_cast(PPCRegister::kR31)) { + return string_util::to_hex_string( + r[static_cast(reg) - static_cast(PPCRegister::kR0)]); + } else if (static_cast(reg) >= static_cast(PPCRegister::kFR0) && + static_cast(reg) <= + static_cast(PPCRegister::kFR31)) { + return string_util::to_hex_string( + f[static_cast(reg) - static_cast(PPCRegister::kFR0)]); + } else if (static_cast(reg) >= static_cast(PPCRegister::kVR0) && + static_cast(reg) <= + static_cast(PPCRegister::kVR128)) { + return string_util::to_hex_string( + v[static_cast(reg) - static_cast(PPCRegister::kVR0)]); + } else { + assert_unhandled_case(reg); + return ""; + } + } +} + +void PPCContext::SetValueFromString(PPCRegister reg, std::string value) { + // TODO(benvanik): set value from string to replace SetRegFromString? + assert_always(false); } void PPCContext::SetRegFromString(const char* name, const char* value) { int n; if (sscanf(name, "r%d", &n) == 1) { - this->r[n] = ParseInt64(value); + this->r[n] = string_util::from_string(value); } else if (sscanf(name, "f%d", &n) == 1) { - this->f[n] = ParseFloat64(value); + this->f[n] = string_util::from_string(value); } else if (sscanf(name, "v%d", &n) == 1) { - this->v[n] = ParseVec128(value); + this->v[n] = string_util::from_string(value); } else { printf("Unrecognized register name: %s\n", name); } } bool PPCContext::CompareRegWithString(const char* name, const char* value, - char* out_value, size_t out_value_size) { + char* out_value, + size_t out_value_size) const { int n; if (sscanf(name, "r%d", &n) == 1) { - uint64_t expected = ParseInt64(value); + uint64_t expected = string_util::from_string(value); if (this->r[n] != expected) { - snprintf(out_value, out_value_size, "%016" PRIX64, this->r[n]); + std::snprintf(out_value, out_value_size, "%016" PRIX64, this->r[n]); return false; } return true; } else if (sscanf(name, "f%d", &n) == 1) { - double expected = ParseFloat64(value); + double expected = string_util::from_string(value); // TODO(benvanik): epsilon if (this->f[n] != expected) { - snprintf(out_value, out_value_size, "%f", this->f[n]); + std::snprintf(out_value, out_value_size, "%f", this->f[n]); return false; } return true; } else if (sscanf(name, "v%d", &n) == 1) { - vec128_t expected = ParseVec128(value); + vec128_t expected = string_util::from_string(value); if (this->v[n] != expected) { - snprintf(out_value, out_value_size, "[%.8X, %.8X, %.8X, %.8X]", - this->v[n].i32[0], this->v[n].i32[1], this->v[n].i32[2], - this->v[n].i32[3]); + std::snprintf(out_value, out_value_size, "[%.8X, %.8X, %.8X, %.8X]", + this->v[n].i32[0], this->v[n].i32[1], this->v[n].i32[2], + this->v[n].i32[3]); return false; } return true; } else { - printf("Unrecognized register name: %s\n", name); + assert_always("Unrecognized register name: %s\n", name); return false; } } diff --git a/src/xenia/cpu/frontend/ppc_context.h b/src/xenia/cpu/frontend/ppc_context.h index e2a18d913..2165487a3 100644 --- a/src/xenia/cpu/frontend/ppc_context.h +++ b/src/xenia/cpu/frontend/ppc_context.h @@ -11,6 +11,7 @@ #define XENIA_CPU_FRONTEND_PPC_CONTEXT_H_ #include +#include #include "xenia/base/vec128.h" @@ -40,6 +41,209 @@ namespace frontend { // 100: invalid // 128-256: VR +enum class PPCRegister { + kR0 = 0, + kR1, + kR2, + kR3, + kR4, + kR5, + kR6, + kR7, + kR8, + kR9, + kR10, + kR11, + kR12, + kR13, + kR14, + kR15, + kR16, + kR17, + kR18, + kR19, + kR20, + kR21, + kR22, + kR23, + kR24, + kR25, + kR26, + kR27, + kR28, + kR29, + kR30, + kR31, + kFR0 = 32, + kFR1, + kFR2, + kFR3, + kFR4, + kFR5, + kFR6, + kFR7, + kFR8, + kFR9, + kFR10, + kFR11, + kFR12, + kFR13, + kFR14, + kFR15, + kFR16, + kFR17, + kFR18, + kFR19, + kFR20, + kFR21, + kFR22, + kFR23, + kFR24, + kFR25, + kFR26, + kFR27, + kFR28, + kFR29, + kFR30, + kFR31, + kVR0 = 64, + kVR1, + kVR2, + kVR3, + kVR4, + kVR5, + kVR6, + kVR7, + kVR8, + kVR9, + kVR10, + kVR11, + kVR12, + kVR13, + kVR14, + kVR15, + kVR16, + kVR17, + kVR18, + kVR19, + kVR20, + kVR21, + kVR22, + kVR23, + kVR24, + kVR25, + kVR26, + kVR27, + kVR28, + kVR29, + kVR30, + kVR31, + kVR32, + kVR33, + kVR34, + kVR35, + kVR36, + kVR37, + kVR38, + kVR39, + kVR40, + kVR41, + kVR42, + kVR43, + kVR44, + kVR45, + kVR46, + kVR47, + kVR48, + kVR49, + kVR50, + kVR51, + kVR52, + kVR53, + kVR54, + kVR55, + kVR56, + kVR57, + kVR58, + kVR59, + kVR60, + kVR61, + kVR62, + kVR63, + kVR64, + kVR65, + kVR66, + kVR67, + kVR68, + kVR69, + kVR70, + kVR71, + kVR72, + kVR73, + kVR74, + kVR75, + kVR76, + kVR77, + kVR78, + kVR79, + kVR80, + kVR81, + kVR82, + kVR83, + kVR84, + kVR85, + kVR86, + kVR87, + kVR88, + kVR89, + kVR90, + kVR91, + kVR92, + kVR93, + kVR94, + kVR95, + kVR96, + kVR97, + kVR98, + kVR99, + kVR100, + kVR101, + kVR102, + kVR103, + kVR104, + kVR105, + kVR106, + kVR107, + kVR108, + kVR109, + kVR110, + kVR111, + kVR112, + kVR113, + kVR114, + kVR115, + kVR116, + kVR117, + kVR118, + kVR119, + kVR120, + kVR121, + kVR122, + kVR123, + kVR124, + kVR125, + kVR126, + kVR127, + kVR128, + + kLR, + kCTR, + kXER, + kFPSCR, + kVSCR, + kCR, +}; + #pragma pack(push, 8) typedef struct alignas(64) PPCContext_s { // Must be stored at 0x0 for now. @@ -218,9 +422,13 @@ typedef struct alignas(64) PPCContext_s { // Keep the struct padded out to 64b total. uint8_t _padding[8]; + static std::string GetRegisterName(PPCRegister reg); + std::string GetStringFromValue(PPCRegister reg) const; + void SetValueFromString(PPCRegister reg, std::string value); + void SetRegFromString(const char* name, const char* value); bool CompareRegWithString(const char* name, const char* value, - char* out_value, size_t out_value_size); + char* out_value, size_t out_value_size) const; } PPCContext; #pragma pack(pop) static_assert(sizeof(PPCContext) % 64 == 0, "64b padded"); diff --git a/src/xenia/cpu/stack_walker.h b/src/xenia/cpu/stack_walker.h index 061dd4e5d..f5286e085 100644 --- a/src/xenia/cpu/stack_walker.h +++ b/src/xenia/cpu/stack_walker.h @@ -14,6 +14,7 @@ #include #include "xenia/cpu/function.h" +#include "xenia/cpu/x64_context.h" namespace xe { namespace cpu { @@ -81,6 +82,7 @@ class StackWalker { virtual size_t CaptureStackTrace(void* thread_handle, uint64_t* frame_host_pcs, size_t frame_offset, size_t frame_count, + X64Context* out_host_context, uint64_t* out_stack_hash = nullptr) = 0; // Resolves symbol information for the given stack frames. diff --git a/src/xenia/cpu/stack_walker_win.cc b/src/xenia/cpu/stack_walker_win.cc index a9fab2048..86b3e7fc8 100644 --- a/src/xenia/cpu/stack_walker_win.cc +++ b/src/xenia/cpu/stack_walker_win.cc @@ -152,7 +152,10 @@ class Win32StackWalker : public StackWalker { size_t CaptureStackTrace(void* thread_handle, uint64_t* frame_host_pcs, size_t frame_offset, size_t frame_count, + X64Context* 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 // Query context. Thread must be suspended. // Need at least CONTEXT_CONTROL (for rip and rsp) and CONTEXT_INTEGER (for // rbp). @@ -163,6 +166,15 @@ class Win32StackWalker : public StackWalker { return 0; } + if (out_host_context) { + out_host_context->rip = thread_context.Rip; + out_host_context->eflags = thread_context.EFlags; + std::memcpy(&out_host_context->int_registers.values, &thread_context.Rax, + sizeof(out_host_context->int_registers.values)); + std::memcpy(&out_host_context->xmm_registers.values, &thread_context.Xmm0, + sizeof(out_host_context->xmm_registers.values)); + } + // Setup the frame for walking. STACKFRAME64 stack_frame = {0}; stack_frame.AddrPC.Mode = AddrModeFlat; diff --git a/src/xenia/cpu/x64_context.cc b/src/xenia/cpu/x64_context.cc new file mode 100644 index 000000000..0016115da --- /dev/null +++ b/src/xenia/cpu/x64_context.cc @@ -0,0 +1,63 @@ +/** + ****************************************************************************** + * 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/cpu/x64_context.h" + +#include "xenia/base/assert.h" +#include "xenia/base/string_util.h" + +namespace xe { +namespace cpu { + +// 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(reg)]; +} + +std::string X64Context::GetStringFromValue(X64Register reg) const { + switch (reg) { + case X64Register::kRip: + return string_util::to_hex_string(rip); + case X64Register::kEflags: + return string_util::to_hex_string(eflags); + default: + if (static_cast(reg) >= static_cast(X64Register::kRax) && + static_cast(reg) <= static_cast(X64Register::kR15)) { + return string_util::to_hex_string( + int_registers.values[static_cast(reg) - + static_cast(X64Register::kRax)]); + } else if (static_cast(reg) >= + static_cast(X64Register::kXmm0) && + static_cast(reg) <= + static_cast(X64Register::kXmm15)) { + return string_util::to_hex_string( + xmm_registers.values[static_cast(reg) - + static_cast(X64Register::kXmm0)]); + } else { + assert_unhandled_case(reg); + return ""; + } + } +} + +void X64Context::SetValueFromString(X64Register reg, std::string value) { + // TODO(benvanik): set value from string. + assert_always(false); +} + +} // namespace cpu +} // namespace xe diff --git a/src/xenia/cpu/x64_context.h b/src/xenia/cpu/x64_context.h new file mode 100644 index 000000000..a545f8179 --- /dev/null +++ b/src/xenia/cpu/x64_context.h @@ -0,0 +1,113 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +#ifndef XENIA_CPU_X64_CONTEXT_H_ +#define XENIA_CPU_X64_CONTEXT_H_ + +#include +#include + +namespace xe { +namespace cpu { + +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, + kRcx, + kRdx, + kRbx, + kRsp, + kRbp, + kRsi, + kRdi, + kR8, + kR9, + kR10, + kR11, + kR12, + kR13, + kR14, + kR15, + kXmm0, + kXmm1, + kXmm2, + kXmm3, + kXmm4, + kXmm5, + kXmm6, + kXmm7, + kXmm8, + kXmm9, + kXmm10, + kXmm11, + kXmm12, + kXmm13, + kXmm14, + kXmm15, +}; + +struct X64Context { + // TODO(benvanik): x64 registers. + 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 values[16]; + } int_registers; + union { + struct { + __m128 xmm0; + __m128 xmm1; + __m128 xmm2; + __m128 xmm3; + __m128 xmm4; + __m128 xmm5; + __m128 xmm6; + __m128 xmm7; + __m128 xmm8; + __m128 xmm9; + __m128 xmm10; + __m128 xmm11; + __m128 xmm12; + __m128 xmm13; + __m128 xmm14; + __m128 xmm15; + }; + __m128 values[16]; + } xmm_registers; + + static const char* GetRegisterName(X64Register reg); + std::string GetStringFromValue(X64Register reg) const; + void SetValueFromString(X64Register reg, std::string value); +}; + +} // namespace cpu +} // namespace xe + +#endif // XENIA_CPU_X64_CONTEXT_H_ diff --git a/src/xenia/debug/debug_client.cc b/src/xenia/debug/debug_client.cc index fe6daa2ac..ff4230697 100644 --- a/src/xenia/debug/debug_client.cc +++ b/src/xenia/debug/debug_client.cc @@ -174,14 +174,14 @@ bool DebugClient::ProcessPacket(proto::PacketReader* packet_reader, auto entries = packet_reader->ReadArray(body->count); listener_->OnThreadsUpdated(std::move(entries)); } break; - case PacketType::kThreadCallStacksResponse: { - auto body = packet_reader->Read(); + case PacketType::kThreadStatesResponse: { + auto body = packet_reader->Read(); for (size_t i = 0; i < body->count; ++i) { - auto entry = packet_reader->Read(); + auto entry = packet_reader->Read(); auto frames = packet_reader->ReadArray(entry->frame_count); - listener_->OnThreadCallStackUpdated(entry->thread_handle, - std::move(frames)); + listener_->OnThreadStateUpdated(entry->thread_handle, entry, + std::move(frames)); } } break; default: { @@ -261,7 +261,7 @@ void DebugClient::BeginUpdateAllState() { packet_writer_.Begin(PacketType::kThreadListRequest); packet_writer_.End(); // Request the full call stacks for all threads. - packet_writer_.Begin(PacketType::kThreadCallStacksRequest); + packet_writer_.Begin(PacketType::kThreadStatesRequest); packet_writer_.End(); Flush(); diff --git a/src/xenia/debug/debug_client.h b/src/xenia/debug/debug_client.h index 318a4e560..c749da8a4 100644 --- a/src/xenia/debug/debug_client.h +++ b/src/xenia/debug/debug_client.h @@ -33,6 +33,7 @@ namespace debug { using proto::ModuleListEntry; using proto::ThreadCallStackFrame; using proto::ThreadListEntry; +using proto::ThreadStateEntry; enum class ExecutionState { kRunning, @@ -52,8 +53,8 @@ class DebugClientListener { std::vector entries) = 0; virtual void OnThreadsUpdated( std::vector entries) = 0; - virtual void OnThreadCallStackUpdated( - uint32_t thread_handle, + virtual void OnThreadStateUpdated( + uint32_t thread_handle, const ThreadStateEntry* entry, std::vector frames) = 0; }; diff --git a/src/xenia/debug/debug_server.cc b/src/xenia/debug/debug_server.cc index 49ebd7c99..4811d789e 100644 --- a/src/xenia/debug/debug_server.cc +++ b/src/xenia/debug/debug_server.cc @@ -285,9 +285,9 @@ bool DebugServer::ProcessPacket(const proto::Packet* packet) { } packet_writer_.End(); } break; - case PacketType::kThreadCallStacksRequest: { - packet_writer_.Begin(PacketType::kThreadCallStacksResponse); - auto body = packet_writer_.Append(); + case PacketType::kThreadStatesRequest: { + packet_writer_.Begin(PacketType::kThreadStatesResponse); + auto body = packet_writer_.Append(); auto stack_walker = emulator->processor()->stack_walker(); auto threads = emulator->kernel_state()->object_table()->GetObjectsByType( @@ -296,13 +296,29 @@ bool DebugServer::ProcessPacket(const proto::Packet* packet) { uint64_t frame_host_pcs[64]; cpu::StackFrame frames[64]; for (auto& thread : threads) { + auto thread_entry_body = packet_writer_.Append(); + thread_entry_body->thread_handle = thread->handle(); + + // Grab PPC context. + // Note that this is only up to date if --store_all_context_values is + // enabled (or --debug). + if (thread->is_guest_thread()) { + std::memcpy(&thread_entry_body->guest_context, + thread->thread_state()->context(), + sizeof(thread_entry_body->guest_context)); + } else { + std::memset(&thread_entry_body->guest_context, 0, + sizeof(thread_entry_body->guest_context)); + } + + // Grab stack trace and X64 context then resolve all symbols. uint64_t hash; size_t count = stack_walker->CaptureStackTrace( thread->GetWaitHandle()->native_handle(), frame_host_pcs, 0, 64, - &hash); + &thread_entry_body->host_context, &hash); stack_walker->ResolveStack(frame_host_pcs, frames, count); - auto thread_entry_body = packet_writer_.Append(); - thread_entry_body->thread_handle = thread->handle(); + + // Populate stack frames with additional information. thread_entry_body->frame_count = uint32_t(count); for (size_t i = 0; i < count; ++i) { auto& frame = frames[i]; diff --git a/src/xenia/debug/debugger.cc b/src/xenia/debug/debugger.cc index 3e9667ba4..a3bdd6a9e 100644 --- a/src/xenia/debug/debugger.cc +++ b/src/xenia/debug/debugger.cc @@ -161,7 +161,8 @@ void Debugger::DumpThreadStacks() { uint64_t frame_host_pcs[64]; uint64_t hash; size_t count = stack_walker->CaptureStackTrace( - thread->GetWaitHandle()->native_handle(), frame_host_pcs, 0, 64, &hash); + thread->GetWaitHandle()->native_handle(), frame_host_pcs, 0, 64, + nullptr, &hash); cpu::StackFrame frames[64]; stack_walker->ResolveStack(frame_host_pcs, frames, count); for (size_t i = 0; i < count; ++i) { diff --git a/src/xenia/debug/proto/xdp_protocol.h b/src/xenia/debug/proto/xdp_protocol.h index a5dc15a98..60ec37c2c 100644 --- a/src/xenia/debug/proto/xdp_protocol.h +++ b/src/xenia/debug/proto/xdp_protocol.h @@ -12,6 +12,10 @@ #include +#include "xenia/base/vec128.h" +#include "xenia/cpu/frontend/ppc_context.h" +#include "xenia/cpu/stack_walker.h" + namespace xe { namespace debug { namespace proto { @@ -28,8 +32,8 @@ enum class PacketType : uint16_t { kThreadListRequest = 30, kThreadListResponse = 31, - kThreadCallStacksRequest = 32, - kThreadCallStacksResponse = 33, + kThreadStatesRequest = 32, + kThreadStatesResponse = 33, }; using request_id_t = uint16_t; @@ -168,17 +172,19 @@ struct ThreadListEntry { uint32_t tls_address; }; -struct ThreadCallStacksRequest { - static const PacketType type = PacketType::kThreadCallStacksRequest; +struct ThreadStatesRequest { + static const PacketType type = PacketType::kThreadStatesRequest; }; -struct ThreadCallStacksResponse { - static const PacketType type = PacketType::kThreadCallStacksResponse; +struct ThreadStatesResponse { + static const PacketType type = PacketType::kThreadStatesResponse; uint32_t count; - // ThreadCallStackEntry[count] + // ThreadStateEntry[count] }; -struct ThreadCallStackEntry { +struct ThreadStateEntry { uint32_t thread_handle; + cpu::frontend::PPCContext guest_context; + cpu::X64Context host_context; uint32_t frame_count; // ThreadCallStackFrame[frame_count] }; diff --git a/src/xenia/debug/ui/model/system.cc b/src/xenia/debug/ui/model/system.cc index a3ec9628f..a3b96e249 100644 --- a/src/xenia/debug/ui/model/system.cc +++ b/src/xenia/debug/ui/model/system.cc @@ -112,12 +112,13 @@ void System::OnThreadsUpdated(std::vector entries) { on_threads_updated(); } -void System::OnThreadCallStackUpdated( - uint32_t thread_handle, std::vector frames) { +void System::OnThreadStateUpdated( + uint32_t thread_handle, const ThreadStateEntry* entry, + std::vector frames) { auto thread = threads_by_handle_[thread_handle]; if (thread != nullptr) { - thread->UpdateCallStack(std::move(frames)); - on_thread_call_stack_updated(thread); + thread->UpdateState(entry, std::move(frames)); + on_thread_state_updated(thread); } } diff --git a/src/xenia/debug/ui/model/system.h b/src/xenia/debug/ui/model/system.h index 2aa718378..c48e1c3d0 100644 --- a/src/xenia/debug/ui/model/system.h +++ b/src/xenia/debug/ui/model/system.h @@ -46,7 +46,7 @@ class System : public DebugClientListener { Delegate on_execution_state_changed; Delegate on_modules_updated; Delegate on_threads_updated; - Delegate on_thread_call_stack_updated; + Delegate on_thread_state_updated; private: void OnExecutionStateChanged(ExecutionState execution_state) override; @@ -54,8 +54,8 @@ class System : public DebugClientListener { std::vector entries) override; void OnThreadsUpdated( std::vector entries) override; - void OnThreadCallStackUpdated( - uint32_t thread_handle, + void OnThreadStateUpdated( + uint32_t thread_handle, const ThreadStateEntry* entry, std::vector frames) override; xe::ui::Loop* loop_ = nullptr; diff --git a/src/xenia/debug/ui/model/thread.cc b/src/xenia/debug/ui/model/thread.cc index 7bd24d2d9..b0ba6c944 100644 --- a/src/xenia/debug/ui/model/thread.cc +++ b/src/xenia/debug/ui/model/thread.cc @@ -16,6 +16,12 @@ namespace debug { namespace ui { namespace model { +Thread::Thread(System* system) : system_(system) { + state_ = memory::AlignedAlloc(64); +} + +Thread::~Thread() { memory::AlignedFree(state_); } + std::string Thread::to_string() { std::string value = entry_.name; if (is_host_thread()) { @@ -28,8 +34,10 @@ void Thread::Update(const proto::ThreadListEntry* entry) { std::memcpy(&entry_, entry, sizeof(entry_)); } -void Thread::UpdateCallStack( +void Thread::UpdateState( + const proto::ThreadStateEntry* entry, std::vector frames) { + std::memcpy(state_, entry, sizeof(*state_)); call_stack_.resize(frames.size()); for (size_t i = 0; i < frames.size(); ++i) { std::memcpy(call_stack_.data() + i, frames[i], sizeof(Frame)); diff --git a/src/xenia/debug/ui/model/thread.h b/src/xenia/debug/ui/model/thread.h index af1a24c85..cda801a6d 100644 --- a/src/xenia/debug/ui/model/thread.h +++ b/src/xenia/debug/ui/model/thread.h @@ -27,27 +27,37 @@ class Thread { public: using Frame = proto::ThreadCallStackFrame; - Thread(System* system) : system_(system) {} + Thread(System* system); + ~Thread(); bool is_dead() const { return is_dead_; } void set_dead(bool is_dead) { is_dead_ = is_dead; } + const proto::ThreadListEntry* entry() const { return &entry_; } + const proto::ThreadStateEntry* state() const { return state_; } + uint32_t thread_handle() const { return entry_.thread_handle; } uint32_t thread_id() const { return entry_.thread_id; } bool is_host_thread() const { return entry_.is_host_thread; } std::string name() const { return entry_.name; } - const proto::ThreadListEntry* entry() const { return &entry_; } + + const cpu::frontend::PPCContext* guest_context() const { + return &state_->guest_context; + } + const cpu::X64Context* host_context() const { return &state_->host_context; } const std::vector& call_stack() const { return call_stack_; } std::string to_string(); void Update(const proto::ThreadListEntry* entry); - void UpdateCallStack(std::vector frames); + void UpdateState(const proto::ThreadStateEntry* entry, + std::vector frames); private: System* system_ = nullptr; bool is_dead_ = false; proto::ThreadListEntry entry_ = {0}; + proto::ThreadStateEntry* state_ = nullptr; std::vector call_stack_; }; diff --git a/src/xenia/debug/ui/views/cpu/call_stack_control.cc b/src/xenia/debug/ui/views/cpu/call_stack_control.cc index 12c466f92..807cf3cd6 100644 --- a/src/xenia/debug/ui/views/cpu/call_stack_control.cc +++ b/src/xenia/debug/ui/views/cpu/call_stack_control.cc @@ -116,12 +116,11 @@ el::Element* CallStackControl::BuildUI() { void CallStackControl::Setup(DebugClient* client) { client_ = client; - system()->on_thread_call_stack_updated.AddListener( - [this](model::Thread* thread) { - if (thread == thread_) { - InvalidateCallStack(); - } - }); + system()->on_thread_state_updated.AddListener([this](model::Thread* thread) { + if (thread == thread_) { + InvalidateCallStack(); + } + }); } void CallStackControl::set_thread(model::Thread* thread) { diff --git a/src/xenia/debug/ui/views/cpu/cpu_view.cc b/src/xenia/debug/ui/views/cpu/cpu_view.cc index 091d80b64..51b5d86c7 100644 --- a/src/xenia/debug/ui/views/cpu/cpu_view.cc +++ b/src/xenia/debug/ui/views/cpu/cpu_view.cc @@ -17,7 +17,12 @@ namespace ui { namespace views { namespace cpu { -CpuView::CpuView() : View("CPU") {} +CpuView::CpuView() + : View("CPU"), + gr_registers_control_(RegisterSet::kGeneral), + fr_registers_control_(RegisterSet::kFloat), + vr_registers_control_(RegisterSet::kVector), + host_registers_control_(RegisterSet::kHost) {} CpuView::~CpuView() = default; @@ -72,9 +77,14 @@ el::Element* CpuView::BuildUI() { auto source_registers_node = TabContainerNode() .gravity(Gravity::kAll) - .tab(ButtonNode("GPR"), CloneNode(register_list_node)) - .tab(ButtonNode("FPR"), CloneNode(register_list_node)) - .tab(ButtonNode("VMX"), CloneNode(register_list_node)); + .tab(ButtonNode("GR"), LabelNode("") + .id("gr_registers_placeholder")) + .tab(ButtonNode("FR"), LabelNode("") + .id("fr_registers_placeholder")) + .tab(ButtonNode("VR"), LabelNode("") + .id("vr_registers_placeholder")) + .tab(ButtonNode("X64"), LabelNode("") + .id("host_registers_placeholder")); auto source_tools_node = TabContainerNode() @@ -110,7 +120,7 @@ el::Element* CpuView::BuildUI() { .gravity(Gravity::kAll) .axis(Axis::kY) .fixed_pane(FixedPane::kSecond) - .value(180) + .value(240) .pane(source_code_node) .pane(source_registers_node)) .pane(source_tools_node))); @@ -135,10 +145,30 @@ el::Element* CpuView::BuildUI() { root_element_.set_layout_distribution(LayoutDistribution::kAvailable); root_element_.LoadNodeTree(node); + el::Label* gr_registers_placeholder; + el::Label* fr_registers_placeholder; + el::Label* vr_registers_placeholder; + el::Label* host_registers_placeholder; el::Label* call_stack_placeholder; root_element_.GetElementsById({ + {TBIDC("gr_registers_placeholder"), &gr_registers_placeholder}, + {TBIDC("fr_registers_placeholder"), &fr_registers_placeholder}, + {TBIDC("vr_registers_placeholder"), &vr_registers_placeholder}, + {TBIDC("host_registers_placeholder"), &host_registers_placeholder}, {TBIDC("call_stack_placeholder"), &call_stack_placeholder}, }); + gr_registers_placeholder->parent()->ReplaceChild( + gr_registers_placeholder, gr_registers_control_.BuildUI()); + gr_registers_control_.Setup(client_); + fr_registers_placeholder->parent()->ReplaceChild( + fr_registers_placeholder, fr_registers_control_.BuildUI()); + fr_registers_control_.Setup(client_); + vr_registers_placeholder->parent()->ReplaceChild( + vr_registers_placeholder, vr_registers_control_.BuildUI()); + vr_registers_control_.Setup(client_); + host_registers_placeholder->parent()->ReplaceChild( + host_registers_placeholder, host_registers_control_.BuildUI()); + host_registers_control_.Setup(client_); call_stack_placeholder->parent()->ReplaceChild(call_stack_placeholder, call_stack_control_.BuildUI()); call_stack_control_.Setup(client_); @@ -244,6 +274,10 @@ void CpuView::UpdateThreadList() { void CpuView::SwitchCurrentThread(model::Thread* thread) { current_thread_ = thread; + gr_registers_control_.set_thread(thread); + fr_registers_control_.set_thread(thread); + vr_registers_control_.set_thread(thread); + host_registers_control_.set_thread(thread); call_stack_control_.set_thread(thread); } diff --git a/src/xenia/debug/ui/views/cpu/cpu_view.h b/src/xenia/debug/ui/views/cpu/cpu_view.h index 5fab34b65..2805be9a1 100644 --- a/src/xenia/debug/ui/views/cpu/cpu_view.h +++ b/src/xenia/debug/ui/views/cpu/cpu_view.h @@ -15,6 +15,7 @@ #include "xenia/debug/ui/view.h" #include "xenia/debug/ui/views/cpu/call_stack_control.h" +#include "xenia/debug/ui/views/cpu/register_list_control.h" namespace xe { namespace debug { @@ -42,6 +43,10 @@ class CpuView : public View { // TODO(benvanik): better state machine. model::Thread* current_thread_ = nullptr; + RegisterListControl gr_registers_control_; + RegisterListControl fr_registers_control_; + RegisterListControl vr_registers_control_; + RegisterListControl host_registers_control_; CallStackControl call_stack_control_; }; diff --git a/src/xenia/debug/ui/views/cpu/register_list_control.cc b/src/xenia/debug/ui/views/cpu/register_list_control.cc new file mode 100644 index 000000000..2bc863c1e --- /dev/null +++ b/src/xenia/debug/ui/views/cpu/register_list_control.cc @@ -0,0 +1,324 @@ +/** + ****************************************************************************** + * 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 +#include + +#include "el/animation_manager.h" +#include "xenia/base/string_buffer.h" +#include "xenia/base/string_util.h" +#include "xenia/cpu/x64_context.h" +#include "xenia/debug/ui/views/cpu/register_list_control.h" + +namespace xe { +namespace debug { +namespace ui { +namespace views { +namespace cpu { + +using xe::cpu::frontend::PPCRegister; +using xe::cpu::X64Register; + +enum class RegisterType { + kControl, + kInteger, + kFloat, + kVector, +}; + +class RegisterItem : public el::GenericStringItem { + public: + const char* name() { return name_.c_str(); } + + virtual void set_thread(model::Thread* thread) { thread_ = thread; } + + protected: + RegisterItem(RegisterSet set, RegisterType type, int ordinal) + : GenericStringItem(""), set_(set), type_(type), ordinal_(ordinal) { + tag.set_integer(ordinal); + } + + RegisterSet set_; + RegisterType type_; + int ordinal_; + model::Thread* thread_ = nullptr; + std::string name_; +}; + +class GuestRegisterItem : public RegisterItem { + public: + GuestRegisterItem(RegisterSet set, RegisterType type, PPCRegister reg) + : RegisterItem(set, type, static_cast(reg)), reg_(reg) { + name_ = xe::cpu::frontend::PPCContext::GetRegisterName(reg_); + } + + void set_thread(model::Thread* thread) override { + RegisterItem::set_thread(thread); + if (thread_) { + str = thread_->guest_context()->GetStringFromValue(reg_); + } else { + str = "unknown"; + } + } + + private: + PPCRegister reg_; +}; + +class HostRegisterItem : public RegisterItem { + public: + HostRegisterItem(RegisterSet set, RegisterType type, X64Register reg) + : RegisterItem(set, type, static_cast(reg)), reg_(reg) { + name_ = xe::cpu::X64Context::GetRegisterName(reg_); + } + + void set_thread(model::Thread* thread) override { + RegisterItem::set_thread(thread); + if (thread_) { + str = thread_->host_context()->GetStringFromValue(reg_); + } else { + str = "unknown"; + } + } + + private: + X64Register reg_; +}; + +class RegisterItemElement : public el::LayoutBox { + public: + RegisterItemElement(RegisterItem* item, RegisterItemSource* source, + el::ListItemObserver* source_viewer, size_t index) + : item_(item), + source_(source), + source_viewer_(source_viewer), + index_(index) { + set_background_skin(TBIDC("ListItem")); + set_axis(el::Axis::kY); + set_gravity(el::Gravity::kAll); + set_layout_position(el::LayoutPosition::kLeftTop); + set_layout_distribution(el::LayoutDistribution::kAvailable); + set_layout_distribution_position(el::LayoutDistributionPosition::kLeftTop); + set_layout_size(el::LayoutSize::kAvailable); + set_paint_overflow_fadeout(false); + + using namespace el::dsl; // NOLINT(build/namespaces) + el::AnimationBlocker animation_blocker; + + auto node = LayoutBoxNode() + .axis(Axis::kX) + .gravity(Gravity::kLeft) + .distribution(LayoutDistribution::kAvailable) + .child(LabelNode(item_->name()) + .ignore_input(true) + .width(45_px) + .gravity(Gravity::kLeft)) + .child(TextBoxNode(item_->str.c_str()) + .gravity(Gravity::kLeftRight)); + content_root()->LoadNodeTree(node); + } + + bool OnEvent(const el::Event& ev) override { + return el::LayoutBox::OnEvent(ev); + } + + private: + RegisterItem* item_ = nullptr; + RegisterItemSource* source_ = nullptr; + el::ListItemObserver* source_viewer_ = nullptr; + size_t index_ = 0; +}; + +class RegisterItemSource : public el::ListItemSourceList { + public: + el::Element* CreateItemElement(size_t index, + el::ListItemObserver* viewer) override { + return new RegisterItemElement(at(index), this, viewer, index); + } +}; + +void DefineRegisterItem(RegisterItemSource* item_source, RegisterSet set, + RegisterType type, PPCRegister reg) { + auto item = std::make_unique(set, type, reg); + item->tag.set_integer(static_cast(reg)); + item_source->push_back(std::move(item)); +} +void DefineRegisterItem(RegisterItemSource* item_source, RegisterSet set, + RegisterType type, X64Register reg) { + auto item = std::make_unique(set, type, reg); + item->tag.set_integer(static_cast(reg)); + item_source->push_back(std::move(item)); +} + +RegisterListControl::RegisterListControl(RegisterSet set) + : set_(set), item_source_(new RegisterItemSource()) { + switch (set_) { + case RegisterSet::kGeneral: { + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + PPCRegister::kLR); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + PPCRegister::kCTR); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl, + PPCRegister::kXER); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl, + PPCRegister::kCR); + for (size_t i = 0; i < 32; ++i) { + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + static_cast( + static_cast(PPCRegister::kR0) + i)); + } + } break; + case RegisterSet::kFloat: { + DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl, + PPCRegister::kFPSCR); + for (size_t i = 0; i < 32; ++i) { + DefineRegisterItem(item_source_.get(), set_, RegisterType::kFloat, + static_cast( + static_cast(PPCRegister::kFR0) + i)); + } + } break; + case RegisterSet::kVector: { + DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl, + PPCRegister::kVSCR); + for (size_t i = 0; i < 128; ++i) { + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + static_cast( + static_cast(PPCRegister::kVR0) + i)); + } + } break; + case RegisterSet::kHost: { + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kRip); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl, + X64Register::kEflags); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kRax); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kRcx); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kRdx); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kRbx); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kRsp); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kRbp); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kRsi); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kRdi); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kR8); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kR9); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kR10); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kR11); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kR12); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kR13); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kR14); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, + X64Register::kR15); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm0); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm1); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm2); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm3); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm4); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm5); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm6); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm7); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm8); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm9); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm10); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm11); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm12); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm13); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm14); + DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, + X64Register::kXmm15); + } break; + } +} + +RegisterListControl::~RegisterListControl() = default; + +el::Element* RegisterListControl::BuildUI() { + using namespace el::dsl; // NOLINT(build/namespaces) + el::AnimationBlocker animation_blocker; + + auto node = + LayoutBoxNode() + .gravity(Gravity::kAll) + .distribution(LayoutDistribution::kAvailable) + .child(ListBoxNode().id("register_listbox").gravity(Gravity::kAll)); + + root_element_.set_gravity(Gravity::kAll); + root_element_.set_layout_distribution(LayoutDistribution::kAvailable); + root_element_.LoadNodeTree(node); + + auto register_listbox = + root_element_.GetElementById(TBIDC("register_listbox")); + register_listbox->set_source(item_source_.get()); + register_listbox->scroll_container()->set_scroll_mode( + el::ScrollMode::kAutoXAutoY); + + handler_ = std::make_unique(&root_element_); + + return &root_element_; +} + +void RegisterListControl::Setup(DebugClient* client) { + client_ = client; + + system()->on_thread_state_updated.AddListener([this](model::Thread* thread) { + if (thread == thread_) { + InvalidateRegisters(); + } + }); +} + +void RegisterListControl::set_thread(model::Thread* thread) { + thread_ = thread; + InvalidateRegisters(); +} + +void RegisterListControl::InvalidateRegisters() { + for (size_t i = 0; i < item_source_->size(); ++i) { + item_source_->at(i)->set_thread(thread_); + } + + auto register_listbox = + root_element_.GetElementById(TBIDC("register_listbox")); + register_listbox->InvalidateList(); +} + +} // namespace cpu +} // namespace views +} // namespace ui +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/ui/views/cpu/register_list_control.h b/src/xenia/debug/ui/views/cpu/register_list_control.h new file mode 100644 index 000000000..8e574c962 --- /dev/null +++ b/src/xenia/debug/ui/views/cpu/register_list_control.h @@ -0,0 +1,59 @@ +/** + ****************************************************************************** + * 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. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_UI_VIEWS_CPU_REGISTER_LIST_CONTROL_H_ +#define XENIA_DEBUG_UI_VIEWS_CPU_REGISTER_LIST_CONTROL_H_ + +#include +#include + +#include "xenia/debug/ui/control.h" + +namespace xe { +namespace debug { +namespace ui { +namespace views { +namespace cpu { + +class RegisterItemSource; + +enum class RegisterSet { + kGeneral, + kFloat, + kVector, + kHost, +}; + +class RegisterListControl : public Control { + public: + RegisterListControl(RegisterSet set); + ~RegisterListControl() override; + + el::Element* BuildUI() override; + + void Setup(xe::debug::DebugClient* client) override; + + model::Thread* thread() const { return thread_; } + void set_thread(model::Thread* thread); + + private: + void InvalidateRegisters(); + + RegisterSet set_; + model::Thread* thread_ = nullptr; + std::unique_ptr item_source_; +}; + +} // namespace cpu +} // namespace views +} // namespace ui +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_UI_VIEWS_CPU_REGISTER_LIST_CONTROL_H_