Capturing guest/host context and showing registers in debugger.
This commit is contained in:
parent
ab04175aad
commit
3c50b6739a
|
@ -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 <cinttypes>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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 <typename T>
|
||||||
|
inline T from_string(const char* value);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline uint64_t from_string<uint64_t>(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<double>(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<uint64_t>(value);
|
||||||
|
return v.dbl;
|
||||||
|
}
|
||||||
|
return std::strtod(value, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline vec128_t from_string<vec128_t>(const char* value) {
|
||||||
|
vec128_t v;
|
||||||
|
char* p = const_cast<char*>(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<char*>(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 <typename T>
|
||||||
|
inline T from_string(const std::string& value) {
|
||||||
|
return from_string<T>(value.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace string_util
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_BASE_STRING_UTIL_H_
|
|
@ -11,6 +11,7 @@
|
||||||
#define XENIA_BASE_VEC128_H_
|
#define XENIA_BASE_VEC128_H_
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/platform.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;
|
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
|
} // namespace xe
|
||||||
|
|
||||||
#endif // XENIA_BASE_VEC128_H_
|
#endif // XENIA_BASE_VEC128_H_
|
||||||
|
|
|
@ -15,6 +15,8 @@
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/cpu/processor.h"
|
||||||
#include "xenia/profiling.h"
|
#include "xenia/profiling.h"
|
||||||
|
|
||||||
|
DECLARE_bool(debug);
|
||||||
|
|
||||||
DEFINE_bool(store_all_context_values, false,
|
DEFINE_bool(store_all_context_values, false,
|
||||||
"Don't strip dead context stores to aid in debugging.");
|
"Don't strip dead context stores to aid in debugging.");
|
||||||
|
|
||||||
|
@ -74,7 +76,9 @@ bool ContextPromotionPass::Run(HIRBuilder* builder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove all dead stores.
|
// 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();
|
block = builder->first_block();
|
||||||
while (block) {
|
while (block) {
|
||||||
RemoveDeadStoresBlock(block);
|
RemoveDeadStoresBlock(block);
|
||||||
|
|
|
@ -12,82 +12,140 @@
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/string_util.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
namespace frontend {
|
namespace frontend {
|
||||||
|
|
||||||
uint64_t ParseInt64(const char* value) {
|
std::string PPCContext::GetRegisterName(PPCRegister reg) {
|
||||||
return std::strtoull(value, nullptr, 0);
|
switch (reg) {
|
||||||
}
|
case PPCRegister::kLR:
|
||||||
|
return "lr";
|
||||||
double ParseFloat64(const char* value) {
|
case PPCRegister::kCTR:
|
||||||
if (strstr(value, "0x") == value) {
|
return "ctr";
|
||||||
union {
|
case PPCRegister::kXER:
|
||||||
uint64_t ui;
|
return "xer";
|
||||||
double dbl;
|
case PPCRegister::kFPSCR:
|
||||||
} v;
|
return "fpscr";
|
||||||
v.ui = ParseInt64(value);
|
case PPCRegister::kVSCR:
|
||||||
return v.dbl;
|
return "vscr";
|
||||||
|
case PPCRegister::kCR:
|
||||||
|
return "cr";
|
||||||
|
default:
|
||||||
|
if (static_cast<int>(reg) >= static_cast<int>(PPCRegister::kR0) &&
|
||||||
|
static_cast<int>(reg) <= static_cast<int>(PPCRegister::kR31)) {
|
||||||
|
return std::string("r") +
|
||||||
|
std::to_string(static_cast<int>(reg) -
|
||||||
|
static_cast<int>(PPCRegister::kR0));
|
||||||
|
} else if (static_cast<int>(reg) >= static_cast<int>(PPCRegister::kFR0) &&
|
||||||
|
static_cast<int>(reg) <=
|
||||||
|
static_cast<int>(PPCRegister::kFR31)) {
|
||||||
|
return std::string("fr") +
|
||||||
|
std::to_string(static_cast<int>(reg) -
|
||||||
|
static_cast<int>(PPCRegister::kFR0));
|
||||||
|
} else if (static_cast<int>(reg) >= static_cast<int>(PPCRegister::kVR0) &&
|
||||||
|
static_cast<int>(reg) <=
|
||||||
|
static_cast<int>(PPCRegister::kVR128)) {
|
||||||
|
return std::string("vr") +
|
||||||
|
std::to_string(static_cast<int>(reg) -
|
||||||
|
static_cast<int>(PPCRegister::kVR0));
|
||||||
|
} else {
|
||||||
|
assert_unhandled_case(reg);
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return std::strtod(value, nullptr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vec128_t ParseVec128(const char* value) {
|
std::string PPCContext::GetStringFromValue(PPCRegister reg) const {
|
||||||
vec128_t v;
|
switch (reg) {
|
||||||
char* p = const_cast<char*>(value);
|
case PPCRegister::kLR:
|
||||||
if (*p == '[') ++p;
|
return string_util::to_hex_string(lr);
|
||||||
v.i32[0] = std::strtoul(p, &p, 16);
|
case PPCRegister::kCTR:
|
||||||
while (*p == ' ' || *p == ',') ++p;
|
return string_util::to_hex_string(ctr);
|
||||||
v.i32[1] = std::strtoul(p, &p, 16);
|
case PPCRegister::kXER:
|
||||||
while (*p == ' ' || *p == ',') ++p;
|
// return Int64ToHex(xer_ca);
|
||||||
v.i32[2] = std::strtoul(p, &p, 16);
|
return "?";
|
||||||
while (*p == ' ' || *p == ',') ++p;
|
case PPCRegister::kFPSCR:
|
||||||
v.i32[3] = std::strtoul(p, &p, 16);
|
// return Int64ToHex(fpscr);
|
||||||
return v;
|
return "?";
|
||||||
|
case PPCRegister::kVSCR:
|
||||||
|
// return Int64ToHex(vscr_sat);
|
||||||
|
return "?";
|
||||||
|
case PPCRegister::kCR:
|
||||||
|
// return Int64ToHex(cr);
|
||||||
|
return "?";
|
||||||
|
default:
|
||||||
|
if (static_cast<int>(reg) >= static_cast<int>(PPCRegister::kR0) &&
|
||||||
|
static_cast<int>(reg) <= static_cast<int>(PPCRegister::kR31)) {
|
||||||
|
return string_util::to_hex_string(
|
||||||
|
r[static_cast<int>(reg) - static_cast<int>(PPCRegister::kR0)]);
|
||||||
|
} else if (static_cast<int>(reg) >= static_cast<int>(PPCRegister::kFR0) &&
|
||||||
|
static_cast<int>(reg) <=
|
||||||
|
static_cast<int>(PPCRegister::kFR31)) {
|
||||||
|
return string_util::to_hex_string(
|
||||||
|
f[static_cast<int>(reg) - static_cast<int>(PPCRegister::kFR0)]);
|
||||||
|
} else if (static_cast<int>(reg) >= static_cast<int>(PPCRegister::kVR0) &&
|
||||||
|
static_cast<int>(reg) <=
|
||||||
|
static_cast<int>(PPCRegister::kVR128)) {
|
||||||
|
return string_util::to_hex_string(
|
||||||
|
v[static_cast<int>(reg) - static_cast<int>(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) {
|
void PPCContext::SetRegFromString(const char* name, const char* value) {
|
||||||
int n;
|
int n;
|
||||||
if (sscanf(name, "r%d", &n) == 1) {
|
if (sscanf(name, "r%d", &n) == 1) {
|
||||||
this->r[n] = ParseInt64(value);
|
this->r[n] = string_util::from_string<uint64_t>(value);
|
||||||
} else if (sscanf(name, "f%d", &n) == 1) {
|
} else if (sscanf(name, "f%d", &n) == 1) {
|
||||||
this->f[n] = ParseFloat64(value);
|
this->f[n] = string_util::from_string<double>(value);
|
||||||
} else if (sscanf(name, "v%d", &n) == 1) {
|
} else if (sscanf(name, "v%d", &n) == 1) {
|
||||||
this->v[n] = ParseVec128(value);
|
this->v[n] = string_util::from_string<vec128_t>(value);
|
||||||
} else {
|
} else {
|
||||||
printf("Unrecognized register name: %s\n", name);
|
printf("Unrecognized register name: %s\n", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PPCContext::CompareRegWithString(const char* name, const char* value,
|
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;
|
int n;
|
||||||
if (sscanf(name, "r%d", &n) == 1) {
|
if (sscanf(name, "r%d", &n) == 1) {
|
||||||
uint64_t expected = ParseInt64(value);
|
uint64_t expected = string_util::from_string<uint64_t>(value);
|
||||||
if (this->r[n] != expected) {
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (sscanf(name, "f%d", &n) == 1) {
|
} else if (sscanf(name, "f%d", &n) == 1) {
|
||||||
double expected = ParseFloat64(value);
|
double expected = string_util::from_string<double>(value);
|
||||||
// TODO(benvanik): epsilon
|
// TODO(benvanik): epsilon
|
||||||
if (this->f[n] != expected) {
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else if (sscanf(name, "v%d", &n) == 1) {
|
} else if (sscanf(name, "v%d", &n) == 1) {
|
||||||
vec128_t expected = ParseVec128(value);
|
vec128_t expected = string_util::from_string<vec128_t>(value);
|
||||||
if (this->v[n] != expected) {
|
if (this->v[n] != expected) {
|
||||||
snprintf(out_value, out_value_size, "[%.8X, %.8X, %.8X, %.8X]",
|
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[0], this->v[n].i32[1], this->v[n].i32[2],
|
||||||
this->v[n].i32[3]);
|
this->v[n].i32[3]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
printf("Unrecognized register name: %s\n", name);
|
assert_always("Unrecognized register name: %s\n", name);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#define XENIA_CPU_FRONTEND_PPC_CONTEXT_H_
|
#define XENIA_CPU_FRONTEND_PPC_CONTEXT_H_
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "xenia/base/vec128.h"
|
#include "xenia/base/vec128.h"
|
||||||
|
|
||||||
|
@ -40,6 +41,209 @@ namespace frontend {
|
||||||
// 100: invalid
|
// 100: invalid
|
||||||
// 128-256: VR
|
// 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)
|
#pragma pack(push, 8)
|
||||||
typedef struct alignas(64) PPCContext_s {
|
typedef struct alignas(64) PPCContext_s {
|
||||||
// Must be stored at 0x0 for now.
|
// 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.
|
// Keep the struct padded out to 64b total.
|
||||||
uint8_t _padding[8];
|
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);
|
void SetRegFromString(const char* name, const char* value);
|
||||||
bool CompareRegWithString(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;
|
} PPCContext;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
static_assert(sizeof(PPCContext) % 64 == 0, "64b padded");
|
static_assert(sizeof(PPCContext) % 64 == 0, "64b padded");
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "xenia/cpu/function.h"
|
#include "xenia/cpu/function.h"
|
||||||
|
#include "xenia/cpu/x64_context.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
|
@ -81,6 +82,7 @@ class StackWalker {
|
||||||
virtual size_t CaptureStackTrace(void* thread_handle,
|
virtual size_t CaptureStackTrace(void* thread_handle,
|
||||||
uint64_t* frame_host_pcs,
|
uint64_t* frame_host_pcs,
|
||||||
size_t frame_offset, size_t frame_count,
|
size_t frame_offset, size_t frame_count,
|
||||||
|
X64Context* out_host_context,
|
||||||
uint64_t* out_stack_hash = nullptr) = 0;
|
uint64_t* out_stack_hash = nullptr) = 0;
|
||||||
|
|
||||||
// Resolves symbol information for the given stack frames.
|
// Resolves symbol information for the given stack frames.
|
||||||
|
|
|
@ -152,7 +152,10 @@ class Win32StackWalker : public StackWalker {
|
||||||
|
|
||||||
size_t CaptureStackTrace(void* thread_handle, uint64_t* frame_host_pcs,
|
size_t CaptureStackTrace(void* thread_handle, uint64_t* frame_host_pcs,
|
||||||
size_t frame_offset, size_t frame_count,
|
size_t frame_offset, size_t frame_count,
|
||||||
|
X64Context* out_host_context,
|
||||||
uint64_t* out_stack_hash) override {
|
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.
|
// Query context. Thread must be suspended.
|
||||||
// Need at least CONTEXT_CONTROL (for rip and rsp) and CONTEXT_INTEGER (for
|
// Need at least CONTEXT_CONTROL (for rip and rsp) and CONTEXT_INTEGER (for
|
||||||
// rbp).
|
// rbp).
|
||||||
|
@ -163,6 +166,15 @@ class Win32StackWalker : public StackWalker {
|
||||||
return 0;
|
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.
|
// Setup the frame for walking.
|
||||||
STACKFRAME64 stack_frame = {0};
|
STACKFRAME64 stack_frame = {0};
|
||||||
stack_frame.AddrPC.Mode = AddrModeFlat;
|
stack_frame.AddrPC.Mode = AddrModeFlat;
|
||||||
|
|
|
@ -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<int>(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<int>(reg) >= static_cast<int>(X64Register::kRax) &&
|
||||||
|
static_cast<int>(reg) <= static_cast<int>(X64Register::kR15)) {
|
||||||
|
return string_util::to_hex_string(
|
||||||
|
int_registers.values[static_cast<int>(reg) -
|
||||||
|
static_cast<int>(X64Register::kRax)]);
|
||||||
|
} else if (static_cast<int>(reg) >=
|
||||||
|
static_cast<int>(X64Register::kXmm0) &&
|
||||||
|
static_cast<int>(reg) <=
|
||||||
|
static_cast<int>(X64Register::kXmm15)) {
|
||||||
|
return string_util::to_hex_string(
|
||||||
|
xmm_registers.values[static_cast<int>(reg) -
|
||||||
|
static_cast<int>(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
|
|
@ -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 <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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_
|
|
@ -174,13 +174,13 @@ bool DebugClient::ProcessPacket(proto::PacketReader* packet_reader,
|
||||||
auto entries = packet_reader->ReadArray<ThreadListEntry>(body->count);
|
auto entries = packet_reader->ReadArray<ThreadListEntry>(body->count);
|
||||||
listener_->OnThreadsUpdated(std::move(entries));
|
listener_->OnThreadsUpdated(std::move(entries));
|
||||||
} break;
|
} break;
|
||||||
case PacketType::kThreadCallStacksResponse: {
|
case PacketType::kThreadStatesResponse: {
|
||||||
auto body = packet_reader->Read<ThreadCallStacksResponse>();
|
auto body = packet_reader->Read<ThreadStatesResponse>();
|
||||||
for (size_t i = 0; i < body->count; ++i) {
|
for (size_t i = 0; i < body->count; ++i) {
|
||||||
auto entry = packet_reader->Read<ThreadCallStackEntry>();
|
auto entry = packet_reader->Read<ThreadStateEntry>();
|
||||||
auto frames =
|
auto frames =
|
||||||
packet_reader->ReadArray<ThreadCallStackFrame>(entry->frame_count);
|
packet_reader->ReadArray<ThreadCallStackFrame>(entry->frame_count);
|
||||||
listener_->OnThreadCallStackUpdated(entry->thread_handle,
|
listener_->OnThreadStateUpdated(entry->thread_handle, entry,
|
||||||
std::move(frames));
|
std::move(frames));
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
@ -261,7 +261,7 @@ void DebugClient::BeginUpdateAllState() {
|
||||||
packet_writer_.Begin(PacketType::kThreadListRequest);
|
packet_writer_.Begin(PacketType::kThreadListRequest);
|
||||||
packet_writer_.End();
|
packet_writer_.End();
|
||||||
// Request the full call stacks for all threads.
|
// Request the full call stacks for all threads.
|
||||||
packet_writer_.Begin(PacketType::kThreadCallStacksRequest);
|
packet_writer_.Begin(PacketType::kThreadStatesRequest);
|
||||||
packet_writer_.End();
|
packet_writer_.End();
|
||||||
|
|
||||||
Flush();
|
Flush();
|
||||||
|
|
|
@ -33,6 +33,7 @@ namespace debug {
|
||||||
using proto::ModuleListEntry;
|
using proto::ModuleListEntry;
|
||||||
using proto::ThreadCallStackFrame;
|
using proto::ThreadCallStackFrame;
|
||||||
using proto::ThreadListEntry;
|
using proto::ThreadListEntry;
|
||||||
|
using proto::ThreadStateEntry;
|
||||||
|
|
||||||
enum class ExecutionState {
|
enum class ExecutionState {
|
||||||
kRunning,
|
kRunning,
|
||||||
|
@ -52,8 +53,8 @@ class DebugClientListener {
|
||||||
std::vector<const ModuleListEntry*> entries) = 0;
|
std::vector<const ModuleListEntry*> entries) = 0;
|
||||||
virtual void OnThreadsUpdated(
|
virtual void OnThreadsUpdated(
|
||||||
std::vector<const ThreadListEntry*> entries) = 0;
|
std::vector<const ThreadListEntry*> entries) = 0;
|
||||||
virtual void OnThreadCallStackUpdated(
|
virtual void OnThreadStateUpdated(
|
||||||
uint32_t thread_handle,
|
uint32_t thread_handle, const ThreadStateEntry* entry,
|
||||||
std::vector<const ThreadCallStackFrame*> frames) = 0;
|
std::vector<const ThreadCallStackFrame*> frames) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -285,9 +285,9 @@ bool DebugServer::ProcessPacket(const proto::Packet* packet) {
|
||||||
}
|
}
|
||||||
packet_writer_.End();
|
packet_writer_.End();
|
||||||
} break;
|
} break;
|
||||||
case PacketType::kThreadCallStacksRequest: {
|
case PacketType::kThreadStatesRequest: {
|
||||||
packet_writer_.Begin(PacketType::kThreadCallStacksResponse);
|
packet_writer_.Begin(PacketType::kThreadStatesResponse);
|
||||||
auto body = packet_writer_.Append<ThreadCallStacksResponse>();
|
auto body = packet_writer_.Append<ThreadStatesResponse>();
|
||||||
auto stack_walker = emulator->processor()->stack_walker();
|
auto stack_walker = emulator->processor()->stack_walker();
|
||||||
auto threads =
|
auto threads =
|
||||||
emulator->kernel_state()->object_table()->GetObjectsByType<XThread>(
|
emulator->kernel_state()->object_table()->GetObjectsByType<XThread>(
|
||||||
|
@ -296,13 +296,29 @@ bool DebugServer::ProcessPacket(const proto::Packet* packet) {
|
||||||
uint64_t frame_host_pcs[64];
|
uint64_t frame_host_pcs[64];
|
||||||
cpu::StackFrame frames[64];
|
cpu::StackFrame frames[64];
|
||||||
for (auto& thread : threads) {
|
for (auto& thread : threads) {
|
||||||
|
auto thread_entry_body = packet_writer_.Append<ThreadStateEntry>();
|
||||||
|
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;
|
uint64_t hash;
|
||||||
size_t count = stack_walker->CaptureStackTrace(
|
size_t count = stack_walker->CaptureStackTrace(
|
||||||
thread->GetWaitHandle()->native_handle(), frame_host_pcs, 0, 64,
|
thread->GetWaitHandle()->native_handle(), frame_host_pcs, 0, 64,
|
||||||
&hash);
|
&thread_entry_body->host_context, &hash);
|
||||||
stack_walker->ResolveStack(frame_host_pcs, frames, count);
|
stack_walker->ResolveStack(frame_host_pcs, frames, count);
|
||||||
auto thread_entry_body = packet_writer_.Append<ThreadCallStackEntry>();
|
|
||||||
thread_entry_body->thread_handle = thread->handle();
|
// Populate stack frames with additional information.
|
||||||
thread_entry_body->frame_count = uint32_t(count);
|
thread_entry_body->frame_count = uint32_t(count);
|
||||||
for (size_t i = 0; i < count; ++i) {
|
for (size_t i = 0; i < count; ++i) {
|
||||||
auto& frame = frames[i];
|
auto& frame = frames[i];
|
||||||
|
|
|
@ -161,7 +161,8 @@ void Debugger::DumpThreadStacks() {
|
||||||
uint64_t frame_host_pcs[64];
|
uint64_t frame_host_pcs[64];
|
||||||
uint64_t hash;
|
uint64_t hash;
|
||||||
size_t count = stack_walker->CaptureStackTrace(
|
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];
|
cpu::StackFrame frames[64];
|
||||||
stack_walker->ResolveStack(frame_host_pcs, frames, count);
|
stack_walker->ResolveStack(frame_host_pcs, frames, count);
|
||||||
for (size_t i = 0; i < count; ++i) {
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
|
|
@ -12,6 +12,10 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "xenia/base/vec128.h"
|
||||||
|
#include "xenia/cpu/frontend/ppc_context.h"
|
||||||
|
#include "xenia/cpu/stack_walker.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace debug {
|
namespace debug {
|
||||||
namespace proto {
|
namespace proto {
|
||||||
|
@ -28,8 +32,8 @@ enum class PacketType : uint16_t {
|
||||||
|
|
||||||
kThreadListRequest = 30,
|
kThreadListRequest = 30,
|
||||||
kThreadListResponse = 31,
|
kThreadListResponse = 31,
|
||||||
kThreadCallStacksRequest = 32,
|
kThreadStatesRequest = 32,
|
||||||
kThreadCallStacksResponse = 33,
|
kThreadStatesResponse = 33,
|
||||||
};
|
};
|
||||||
|
|
||||||
using request_id_t = uint16_t;
|
using request_id_t = uint16_t;
|
||||||
|
@ -168,17 +172,19 @@ struct ThreadListEntry {
|
||||||
uint32_t tls_address;
|
uint32_t tls_address;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ThreadCallStacksRequest {
|
struct ThreadStatesRequest {
|
||||||
static const PacketType type = PacketType::kThreadCallStacksRequest;
|
static const PacketType type = PacketType::kThreadStatesRequest;
|
||||||
};
|
};
|
||||||
struct ThreadCallStacksResponse {
|
struct ThreadStatesResponse {
|
||||||
static const PacketType type = PacketType::kThreadCallStacksResponse;
|
static const PacketType type = PacketType::kThreadStatesResponse;
|
||||||
|
|
||||||
uint32_t count;
|
uint32_t count;
|
||||||
// ThreadCallStackEntry[count]
|
// ThreadStateEntry[count]
|
||||||
};
|
};
|
||||||
struct ThreadCallStackEntry {
|
struct ThreadStateEntry {
|
||||||
uint32_t thread_handle;
|
uint32_t thread_handle;
|
||||||
|
cpu::frontend::PPCContext guest_context;
|
||||||
|
cpu::X64Context host_context;
|
||||||
uint32_t frame_count;
|
uint32_t frame_count;
|
||||||
// ThreadCallStackFrame[frame_count]
|
// ThreadCallStackFrame[frame_count]
|
||||||
};
|
};
|
||||||
|
|
|
@ -112,12 +112,13 @@ void System::OnThreadsUpdated(std::vector<const ThreadListEntry*> entries) {
|
||||||
on_threads_updated();
|
on_threads_updated();
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::OnThreadCallStackUpdated(
|
void System::OnThreadStateUpdated(
|
||||||
uint32_t thread_handle, std::vector<const ThreadCallStackFrame*> frames) {
|
uint32_t thread_handle, const ThreadStateEntry* entry,
|
||||||
|
std::vector<const ThreadCallStackFrame*> frames) {
|
||||||
auto thread = threads_by_handle_[thread_handle];
|
auto thread = threads_by_handle_[thread_handle];
|
||||||
if (thread != nullptr) {
|
if (thread != nullptr) {
|
||||||
thread->UpdateCallStack(std::move(frames));
|
thread->UpdateState(entry, std::move(frames));
|
||||||
on_thread_call_stack_updated(thread);
|
on_thread_state_updated(thread);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ class System : public DebugClientListener {
|
||||||
Delegate<void> on_execution_state_changed;
|
Delegate<void> on_execution_state_changed;
|
||||||
Delegate<void> on_modules_updated;
|
Delegate<void> on_modules_updated;
|
||||||
Delegate<void> on_threads_updated;
|
Delegate<void> on_threads_updated;
|
||||||
Delegate<Thread*> on_thread_call_stack_updated;
|
Delegate<Thread*> on_thread_state_updated;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void OnExecutionStateChanged(ExecutionState execution_state) override;
|
void OnExecutionStateChanged(ExecutionState execution_state) override;
|
||||||
|
@ -54,8 +54,8 @@ class System : public DebugClientListener {
|
||||||
std::vector<const proto::ModuleListEntry*> entries) override;
|
std::vector<const proto::ModuleListEntry*> entries) override;
|
||||||
void OnThreadsUpdated(
|
void OnThreadsUpdated(
|
||||||
std::vector<const proto::ThreadListEntry*> entries) override;
|
std::vector<const proto::ThreadListEntry*> entries) override;
|
||||||
void OnThreadCallStackUpdated(
|
void OnThreadStateUpdated(
|
||||||
uint32_t thread_handle,
|
uint32_t thread_handle, const ThreadStateEntry* entry,
|
||||||
std::vector<const ThreadCallStackFrame*> frames) override;
|
std::vector<const ThreadCallStackFrame*> frames) override;
|
||||||
|
|
||||||
xe::ui::Loop* loop_ = nullptr;
|
xe::ui::Loop* loop_ = nullptr;
|
||||||
|
|
|
@ -16,6 +16,12 @@ namespace debug {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
namespace model {
|
namespace model {
|
||||||
|
|
||||||
|
Thread::Thread(System* system) : system_(system) {
|
||||||
|
state_ = memory::AlignedAlloc<proto::ThreadStateEntry>(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread::~Thread() { memory::AlignedFree(state_); }
|
||||||
|
|
||||||
std::string Thread::to_string() {
|
std::string Thread::to_string() {
|
||||||
std::string value = entry_.name;
|
std::string value = entry_.name;
|
||||||
if (is_host_thread()) {
|
if (is_host_thread()) {
|
||||||
|
@ -28,8 +34,10 @@ void Thread::Update(const proto::ThreadListEntry* entry) {
|
||||||
std::memcpy(&entry_, entry, sizeof(entry_));
|
std::memcpy(&entry_, entry, sizeof(entry_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Thread::UpdateCallStack(
|
void Thread::UpdateState(
|
||||||
|
const proto::ThreadStateEntry* entry,
|
||||||
std::vector<const proto::ThreadCallStackFrame*> frames) {
|
std::vector<const proto::ThreadCallStackFrame*> frames) {
|
||||||
|
std::memcpy(state_, entry, sizeof(*state_));
|
||||||
call_stack_.resize(frames.size());
|
call_stack_.resize(frames.size());
|
||||||
for (size_t i = 0; i < frames.size(); ++i) {
|
for (size_t i = 0; i < frames.size(); ++i) {
|
||||||
std::memcpy(call_stack_.data() + i, frames[i], sizeof(Frame));
|
std::memcpy(call_stack_.data() + i, frames[i], sizeof(Frame));
|
||||||
|
|
|
@ -27,27 +27,37 @@ class Thread {
|
||||||
public:
|
public:
|
||||||
using Frame = proto::ThreadCallStackFrame;
|
using Frame = proto::ThreadCallStackFrame;
|
||||||
|
|
||||||
Thread(System* system) : system_(system) {}
|
Thread(System* system);
|
||||||
|
~Thread();
|
||||||
|
|
||||||
bool is_dead() const { return is_dead_; }
|
bool is_dead() const { return is_dead_; }
|
||||||
void set_dead(bool is_dead) { is_dead_ = 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_handle() const { return entry_.thread_handle; }
|
||||||
uint32_t thread_id() const { return entry_.thread_id; }
|
uint32_t thread_id() const { return entry_.thread_id; }
|
||||||
bool is_host_thread() const { return entry_.is_host_thread; }
|
bool is_host_thread() const { return entry_.is_host_thread; }
|
||||||
std::string name() const { return entry_.name; }
|
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<Frame>& call_stack() const { return call_stack_; }
|
const std::vector<Frame>& call_stack() const { return call_stack_; }
|
||||||
|
|
||||||
std::string to_string();
|
std::string to_string();
|
||||||
|
|
||||||
void Update(const proto::ThreadListEntry* entry);
|
void Update(const proto::ThreadListEntry* entry);
|
||||||
void UpdateCallStack(std::vector<const proto::ThreadCallStackFrame*> frames);
|
void UpdateState(const proto::ThreadStateEntry* entry,
|
||||||
|
std::vector<const proto::ThreadCallStackFrame*> frames);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
System* system_ = nullptr;
|
System* system_ = nullptr;
|
||||||
bool is_dead_ = false;
|
bool is_dead_ = false;
|
||||||
proto::ThreadListEntry entry_ = {0};
|
proto::ThreadListEntry entry_ = {0};
|
||||||
|
proto::ThreadStateEntry* state_ = nullptr;
|
||||||
std::vector<Frame> call_stack_;
|
std::vector<Frame> call_stack_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -116,8 +116,7 @@ el::Element* CallStackControl::BuildUI() {
|
||||||
void CallStackControl::Setup(DebugClient* client) {
|
void CallStackControl::Setup(DebugClient* client) {
|
||||||
client_ = client;
|
client_ = client;
|
||||||
|
|
||||||
system()->on_thread_call_stack_updated.AddListener(
|
system()->on_thread_state_updated.AddListener([this](model::Thread* thread) {
|
||||||
[this](model::Thread* thread) {
|
|
||||||
if (thread == thread_) {
|
if (thread == thread_) {
|
||||||
InvalidateCallStack();
|
InvalidateCallStack();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,12 @@ namespace ui {
|
||||||
namespace views {
|
namespace views {
|
||||||
namespace cpu {
|
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;
|
CpuView::~CpuView() = default;
|
||||||
|
|
||||||
|
@ -72,9 +77,14 @@ el::Element* CpuView::BuildUI() {
|
||||||
auto source_registers_node =
|
auto source_registers_node =
|
||||||
TabContainerNode()
|
TabContainerNode()
|
||||||
.gravity(Gravity::kAll)
|
.gravity(Gravity::kAll)
|
||||||
.tab(ButtonNode("GPR"), CloneNode(register_list_node))
|
.tab(ButtonNode("GR"), LabelNode("<register list control>")
|
||||||
.tab(ButtonNode("FPR"), CloneNode(register_list_node))
|
.id("gr_registers_placeholder"))
|
||||||
.tab(ButtonNode("VMX"), CloneNode(register_list_node));
|
.tab(ButtonNode("FR"), LabelNode("<register list control>")
|
||||||
|
.id("fr_registers_placeholder"))
|
||||||
|
.tab(ButtonNode("VR"), LabelNode("<register list control>")
|
||||||
|
.id("vr_registers_placeholder"))
|
||||||
|
.tab(ButtonNode("X64"), LabelNode("<register list control>")
|
||||||
|
.id("host_registers_placeholder"));
|
||||||
|
|
||||||
auto source_tools_node =
|
auto source_tools_node =
|
||||||
TabContainerNode()
|
TabContainerNode()
|
||||||
|
@ -110,7 +120,7 @@ el::Element* CpuView::BuildUI() {
|
||||||
.gravity(Gravity::kAll)
|
.gravity(Gravity::kAll)
|
||||||
.axis(Axis::kY)
|
.axis(Axis::kY)
|
||||||
.fixed_pane(FixedPane::kSecond)
|
.fixed_pane(FixedPane::kSecond)
|
||||||
.value(180)
|
.value(240)
|
||||||
.pane(source_code_node)
|
.pane(source_code_node)
|
||||||
.pane(source_registers_node))
|
.pane(source_registers_node))
|
||||||
.pane(source_tools_node)));
|
.pane(source_tools_node)));
|
||||||
|
@ -135,10 +145,30 @@ el::Element* CpuView::BuildUI() {
|
||||||
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
|
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
|
||||||
root_element_.LoadNodeTree(node);
|
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;
|
el::Label* call_stack_placeholder;
|
||||||
root_element_.GetElementsById({
|
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},
|
{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_placeholder->parent()->ReplaceChild(call_stack_placeholder,
|
||||||
call_stack_control_.BuildUI());
|
call_stack_control_.BuildUI());
|
||||||
call_stack_control_.Setup(client_);
|
call_stack_control_.Setup(client_);
|
||||||
|
@ -244,6 +274,10 @@ void CpuView::UpdateThreadList() {
|
||||||
|
|
||||||
void CpuView::SwitchCurrentThread(model::Thread* thread) {
|
void CpuView::SwitchCurrentThread(model::Thread* thread) {
|
||||||
current_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);
|
call_stack_control_.set_thread(thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
#include "xenia/debug/ui/view.h"
|
#include "xenia/debug/ui/view.h"
|
||||||
#include "xenia/debug/ui/views/cpu/call_stack_control.h"
|
#include "xenia/debug/ui/views/cpu/call_stack_control.h"
|
||||||
|
#include "xenia/debug/ui/views/cpu/register_list_control.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace debug {
|
namespace debug {
|
||||||
|
@ -42,6 +43,10 @@ class CpuView : public View {
|
||||||
// TODO(benvanik): better state machine.
|
// TODO(benvanik): better state machine.
|
||||||
model::Thread* current_thread_ = nullptr;
|
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_;
|
CallStackControl call_stack_control_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 <cinttypes>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#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<int>(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<int>(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<RegisterItem> {
|
||||||
|
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<GuestRegisterItem>(set, type, reg);
|
||||||
|
item->tag.set_integer(static_cast<int>(reg));
|
||||||
|
item_source->push_back(std::move(item));
|
||||||
|
}
|
||||||
|
void DefineRegisterItem(RegisterItemSource* item_source, RegisterSet set,
|
||||||
|
RegisterType type, X64Register reg) {
|
||||||
|
auto item = std::make_unique<HostRegisterItem>(set, type, reg);
|
||||||
|
item->tag.set_integer(static_cast<int>(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<PPCRegister>(
|
||||||
|
static_cast<size_t>(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<PPCRegister>(
|
||||||
|
static_cast<size_t>(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<PPCRegister>(
|
||||||
|
static_cast<size_t>(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<el::ListBox>(TBIDC("register_listbox"));
|
||||||
|
register_listbox->set_source(item_source_.get());
|
||||||
|
register_listbox->scroll_container()->set_scroll_mode(
|
||||||
|
el::ScrollMode::kAutoXAutoY);
|
||||||
|
|
||||||
|
handler_ = std::make_unique<el::EventHandler>(&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<el::ListBox>(TBIDC("register_listbox"));
|
||||||
|
register_listbox->InvalidateList();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cpu
|
||||||
|
} // namespace views
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
|
@ -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 <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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<RegisterItemSource> item_source_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace cpu
|
||||||
|
} // namespace views
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_DEBUG_UI_VIEWS_CPU_REGISTER_LIST_CONTROL_H_
|
Loading…
Reference in New Issue