A new debugger.

Lots of bugs/rough edges/etc - issues will be filed.
Old-style debugging still works (just use --emit_source_annotations to get
the helpful movs back and --break_on_instruction will still fire).
This commit is contained in:
Ben Vanik 2015-09-20 21:31:05 -07:00
parent 8046c19898
commit 5d033f9cb3
90 changed files with 4183 additions and 4651 deletions

View File

@ -28,6 +28,10 @@ the file to run in the 'Command Arguments' field (or use `--flagfile=flags.txt`)
By default logs are written to a file with the name of the executable. You can
override this with `--log_file=log.txt`.
If running under Visual Studio and you want to look at the JIT'ed code
(available around 0xA0000000) you should pass `--emit_source_annotations` to
get helpful spacers/movs in the disassembly.
### Linux
Linux support is extremely experimental and incomplete.

View File

@ -9,6 +9,7 @@
#include "xenia/app/emulator_window.h"
#include "third_party/elemental-forms/src/el/elements.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
@ -105,6 +106,10 @@ bool EmulatorWindow::Initialize() {
}
} break;
case 0x13: { // VK_PAUSE
CpuBreakIntoDebugger();
} break;
case 0x70: { // VK_F1
ShowHelpWebsite();
} break;
@ -147,6 +152,12 @@ bool EmulatorWindow::Initialize() {
L"&Pause/Resume Profiler", L"`",
[]() { Profiler::TogglePause(); }));
}
cpu_menu->AddChild(MenuItem::Create(MenuItem::Type::kSeparator));
{
cpu_menu->AddChild(MenuItem::Create(
MenuItem::Type::kString, L"&Break and Show Debugger", L"Pause/Break",
std::bind(&EmulatorWindow::CpuBreakIntoDebugger, this)));
}
main_menu->AddChild(std::move(cpu_menu));
// GPU menu.
@ -207,6 +218,25 @@ void EmulatorWindow::CpuTimeScalarSetDouble() {
UpdateTitle();
}
void EmulatorWindow::CpuBreakIntoDebugger() {
auto debugger = emulator()->debugger();
if (!debugger) {
auto message_form = new el::MessageForm(window_->root_element(),
TBIDC("debug_error_window"));
message_form->Show("Xenia Debugger",
"Xenia must be launched with the --debug flag in order "
"to enable debugging.");
return;
}
if (debugger->execution_state() == debug::ExecutionState::kRunning) {
// Currently running, so interrupt (and show the debugger).
debugger->Pause();
} else {
// Not running, so just bring the debugger into focus.
debugger->Show();
}
}
void EmulatorWindow::GpuTraceFrame() {
emulator()->graphics_system()->RequestFrameTrace();
}

View File

@ -43,6 +43,7 @@ class EmulatorWindow {
void CpuTimeScalarReset();
void CpuTimeScalarSetHalf();
void CpuTimeScalarSetDouble();
void CpuBreakIntoDebugger();
void GpuTraceFrame();
void GpuClearCaches();
void ToggleFullscreen();

View File

@ -10,6 +10,7 @@ project("xenia-app")
links({
"elemental-forms",
"gflags",
"imgui",
"xenia-apu",
"xenia-apu-nop",
"xenia-base",
@ -17,6 +18,7 @@ project("xenia-app")
"xenia-cpu",
"xenia-cpu-backend-x64",
"xenia-debug",
"xenia-debug-ui",
"xenia-gpu",
"xenia-gpu-gl4",
"xenia-hid-nop",

View File

@ -12,6 +12,7 @@
#include "xenia/app/emulator_window.h"
#include "xenia/base/logging.h"
#include "xenia/base/main.h"
#include "xenia/debug/ui/debug_window.h"
#include "xenia/emulator.h"
#include "xenia/profiling.h"
#include "xenia/ui/file_picker.h"
@ -39,6 +40,27 @@ int xenia_main(const std::vector<std::wstring>& args) {
return 1;
}
// Set a debug handler.
// This will respond to debugging requests so we can open the debug UI.
std::unique_ptr<xe::debug::ui::DebugWindow> debug_window;
if (emulator->debugger()) {
emulator->debugger()->set_debug_listener_request_handler([&](
xe::debug::Debugger* debugger) {
if (debug_window) {
return debug_window.get();
}
emulator_window->loop()->PostSynchronous([&]() {
debug_window = xe::debug::ui::DebugWindow::Create(
emulator.get(), emulator_window->loop());
debug_window->window()->on_closed.AddListener([&](xe::ui::UIEvent* e) {
emulator->debugger()->set_debug_listener(nullptr);
emulator_window->loop()->Post([&]() { debug_window.reset(); });
});
});
return debug_window.get();
});
}
// Grab path from the flag or unnamed argument.
std::wstring path;
if (!FLAGS_target.empty() || args.size() >= 2) {
@ -91,6 +113,7 @@ int xenia_main(const std::vector<std::wstring>& args) {
emulator->display_window()->loop()->AwaitQuit();
}
debug_window.reset();
emulator.reset();
emulator_window.reset();

View File

@ -13,37 +13,61 @@
#include <functional>
#include <vector>
#include "xenia/base/x64_context.h"
namespace xe {
class ExceptionHandler {
class Exception {
public:
struct Info {
enum {
kInvalidException = 0,
kAccessViolation,
} code = kInvalidException;
uint64_t pc = 0; // Program counter address. RIP on x64.
uint64_t fault_address =
0; // In case of AV, address that was read from/written to.
void* thread_context = nullptr; // Platform-specific thread context info.
enum class Code {
kInvalidException = 0,
kAccessViolation,
kIllegalInstruction,
};
typedef std::function<bool(Info* ex_info)> Handler;
// Static initialization. Only call this once!
static bool Initialize();
void InitializeAccessViolation(X64Context* thread_context,
uint64_t fault_address) {
code_ = Code::kAccessViolation;
thread_context_ = thread_context;
fault_address_ = fault_address;
}
void InitializeIllegalInstruction(X64Context* thread_context) {
code_ = Code::kIllegalInstruction;
thread_context_ = thread_context;
}
// Install an exception handler. Returns an ID which you can save to remove
// this later. This will install the exception handler in the last place.
// TODO: ID support!
static uint32_t Install(Handler fn);
static bool Remove(uint32_t id);
Code code() const { return code_; }
static const std::vector<Handler>& handlers() { return handlers_; }
// Returns the platform-specific thread context info.
X64Context* thread_context() const { return thread_context_; }
// Returns the program counter where the exception occurred.
// RIP on x64.
uint64_t pc() const { return thread_context_->rip; }
// Sets the program counter where execution will resume.
void set_resume_pc(uint64_t pc) { thread_context_->rip = pc; }
// In case of AV, address that was read from/written to.
uint64_t fault_address() const { return fault_address_; }
private:
static std::vector<Handler> handlers_;
Code code_ = Code::kInvalidException;
X64Context* thread_context_ = nullptr;
uint64_t fault_address_ = 0;
};
}; // namespace xe
#endif // XENIA_BASE_EXCEPTION_HANDLER_H_
class ExceptionHandler {
public:
typedef bool (*Handler)(Exception* ex, void* data);
// Installs an exception handler.
// Handlers are called in the order they are installed.
static void Install(Handler fn, void* data);
// Uninstalls a previously-installed exception handler.
static void Uninstall(Handler fn, void* data);
};
} // namespace xe
#endif // XENIA_BASE_EXCEPTION_HANDLER_H_

View File

@ -9,53 +9,118 @@
#include "xenia/base/exception_handler.h"
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/base/platform_win.h"
namespace xe {
std::vector<ExceptionHandler::Handler> ExceptionHandler::handlers_;
// Handle of the added VectoredExceptionHandler.
void* veh_handle_ = nullptr;
// Handle of the added VectoredContinueHandler.
void* vch_handle_ = nullptr;
// This can be as large as needed, but isn't often needed.
// As we will be sometimes firing many exceptions we want to avoid having to
// scan the table too much or invoke many custom handlers.
constexpr size_t kMaxHandlerCount = 8;
// All custom handlers, left-aligned and null terminated.
// Executed in order.
std::pair<ExceptionHandler::Handler, void*> handlers_[kMaxHandlerCount];
LONG CALLBACK ExceptionHandlerCallback(PEXCEPTION_POINTERS ex_info) {
// Visual Studio SetThreadName
// Visual Studio SetThreadName.
if (ex_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
return EXCEPTION_CONTINUE_SEARCH;
}
auto code = ex_info->ExceptionRecord->ExceptionCode;
ExceptionHandler::Info info;
info.pc = ex_info->ContextRecord->Rip;
info.thread_context = ex_info->ContextRecord;
// TODO(benvanik): avoid this by mapping X64Context virtual?
X64Context thread_context;
thread_context.rip = ex_info->ContextRecord->Rip;
thread_context.eflags = ex_info->ContextRecord->EFlags;
std::memcpy(thread_context.int_registers, &ex_info->ContextRecord->Rax,
sizeof(thread_context.int_registers));
std::memcpy(thread_context.xmm_registers, &ex_info->ContextRecord->Xmm0,
sizeof(thread_context.xmm_registers));
switch (code) {
case STATUS_ACCESS_VIOLATION:
info.code = ExceptionHandler::Info::kAccessViolation;
info.fault_address = ex_info->ExceptionRecord->ExceptionInformation[1];
// http://msdn.microsoft.com/en-us/library/ms679331(v=vs.85).aspx
// http://msdn.microsoft.com/en-us/library/aa363082(v=vs.85).aspx
Exception ex;
switch (ex_info->ExceptionRecord->ExceptionCode) {
case STATUS_ILLEGAL_INSTRUCTION:
ex.InitializeIllegalInstruction(&thread_context);
break;
case STATUS_ACCESS_VIOLATION:
ex.InitializeAccessViolation(
&thread_context, ex_info->ExceptionRecord->ExceptionInformation[1]);
break;
default:
// Unknown/unhandled type.
return EXCEPTION_CONTINUE_SEARCH;
}
// Only call a handler if we support this type of exception.
if (info.code != ExceptionHandler::Info::kInvalidException) {
for (auto handler : ExceptionHandler::handlers()) {
if (handler(&info)) {
// Exception handled.
return EXCEPTION_CONTINUE_EXECUTION;
}
for (size_t i = 0; i < xe::countof(handlers_) && handlers_[i].first; ++i) {
if (handlers_[i].first(&ex, handlers_[i].second)) {
// Exception handled.
// TODO(benvanik): update all thread state? Dirty flags?
ex_info->ContextRecord->Rip = thread_context.rip;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
bool ExceptionHandler::Initialize() {
AddVectoredExceptionHandler(0, ExceptionHandlerCallback);
void ExceptionHandler::Install(Handler fn, void* data) {
if (!veh_handle_) {
veh_handle_ = AddVectoredExceptionHandler(1, ExceptionHandlerCallback);
// TODO: Do we need a continue handler if a debugger is attached?
return true;
if (IsDebuggerPresent()) {
// TODO(benvanik): do we need a continue handler if a debugger is
// attached?
// vch_handle_ = AddVectoredContinueHandler(1, ExceptionHandlerCallback);
}
}
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
if (!handlers_[i].first) {
handlers_[i].first = fn;
handlers_[i].second = data;
return;
}
}
assert_always("Too many exception handlers installed");
}
uint32_t ExceptionHandler::Install(std::function<bool(Info* ex_info)> fn) {
handlers_.push_back(fn);
void ExceptionHandler::Uninstall(Handler fn, void* data) {
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
if (handlers_[i].first == fn && handlers_[i].second == data) {
for (; i < xe::countof(handlers_) - 1; ++i) {
handlers_[i] = handlers_[i + 1];
}
handlers_[i].first = nullptr;
handlers_[i].second = nullptr;
break;
}
}
// TODO: ID support!
return 0;
bool has_any = false;
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
if (handlers_[i].first) {
has_any = true;
break;
}
}
if (!has_any) {
if (veh_handle_) {
RemoveVectoredExceptionHandler(veh_handle_);
veh_handle_ = nullptr;
}
if (vch_handle_) {
RemoveVectoredContinueHandler(vch_handle_);
vch_handle_ = nullptr;
}
}
}
}
} // namespace xe

View File

@ -179,6 +179,12 @@ inline uint64_t rotate_left(uint64_t v, uint8_t sh) {
}
#endif // XE_PLATFORM_WIN32
template <typename T>
T clamp(T value, T min_value, T max_value) {
const T t = value < min_value ? min_value : value;
return t > max_value ? max_value : t;
}
// Utilities for SSE values.
template <int N>
float m128_f32(const __m128& v) {

View File

@ -16,6 +16,7 @@
#include <codecvt>
#endif // XE_PLATFORM_LINUX
#include <cctype>
#include <cstring>
#include <locale>
@ -25,7 +26,7 @@ std::string to_string(const std::wstring& source) {
#if NO_CODECVT
return std::string(source.begin(), source.end());
#else
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t> > converter;
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.to_bytes(source);
#endif // XE_PLATFORM_LINUX
}
@ -34,7 +35,7 @@ std::wstring to_wstring(const std::string& source) {
#if NO_CODECVT
return std::wstring(source.begin(), source.end());
#else
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t> > converter;
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.from_bytes(source);
#endif // XE_PLATFORM_LINUX
}
@ -227,4 +228,40 @@ std::wstring find_base_path(const std::wstring& path, wchar_t sep) {
}
}
int fuzzy_match(const std::string& pattern, const char* value) {
// https://github.com/mattyork/fuzzy/blob/master/lib/fuzzy.js
// TODO(benvanik): look at https://github.com/atom/fuzzaldrin/tree/master/src
// This does not weight complete substrings or prefixes right, which
// kind of sucks.
size_t pattern_index = 0;
size_t value_length = std::strlen(value);
int total_score = 0;
int local_score = 0;
for (size_t i = 0; i < value_length; ++i) {
if (std::tolower(value[i]) == std::tolower(pattern[pattern_index])) {
++pattern_index;
local_score += 1 + local_score;
} else {
local_score = 0;
}
total_score += local_score;
}
return total_score;
}
std::vector<std::pair<size_t, int>> fuzzy_filter(const std::string& pattern,
const void* const* entries,
size_t entry_count,
size_t string_offset) {
std::vector<std::pair<size_t, int>> results;
results.reserve(entry_count);
for (size_t i = 0; i < entry_count; ++i) {
auto entry_value =
reinterpret_cast<const char*>(entries[i]) + string_offset;
int score = fuzzy_match(pattern, entry_value);
results.emplace_back(i, score);
}
return results;
}
} // namespace xe

View File

@ -12,6 +12,7 @@
#include <cstdio>
#include <string>
#include <utility>
#include <vector>
#include "xenia/base/platform.h"
@ -56,6 +57,27 @@ std::string find_base_path(const std::string& path,
std::wstring find_base_path(const std::wstring& path,
wchar_t sep = xe::kPathSeparator);
// Tests a match against a case-insensitive fuzzy filter.
// Returns the score of the match or 0 if none.
int fuzzy_match(const std::string& pattern, const char* value);
// Applies a case-insensitive fuzzy filter to the given entries and ranks
// results.
// Entries is a list of pointers to opaque structs, each of which contains a
// char* string at the given offset.
// Returns an unsorted list of {original index, score}.
std::vector<std::pair<size_t, int>> fuzzy_filter(const std::string& pattern,
const void* const* entries,
size_t entry_count,
size_t string_offset);
template <typename T>
std::vector<std::pair<size_t, int>> fuzzy_filter(const std::string& pattern,
const std::vector<T>& entries,
size_t string_offset) {
return fuzzy_filter(pattern, reinterpret_cast<void* const*>(entries.data()),
entries.size(), string_offset);
}
} // namespace xe
#endif // XENIA_BASE_STRING_H_

View File

@ -19,6 +19,7 @@ namespace xe {
StringBuffer::StringBuffer(size_t initial_capacity) {
buffer_capacity_ = std::max(initial_capacity, static_cast<size_t>(16 * 1024));
buffer_ = reinterpret_cast<char*>(malloc(buffer_capacity_));
buffer_[0] = 0;
}
StringBuffer::~StringBuffer() {

View File

@ -33,6 +33,15 @@ inline std::string to_hex_string(uint64_t value) {
return std::string(buffer);
}
inline std::string to_hex_string(float value) {
union {
uint32_t ui;
float flt;
} v;
v.flt = value;
return to_hex_string(v.ui);
}
inline std::string to_hex_string(double value) {
union {
uint64_t ui;
@ -57,12 +66,46 @@ inline std::string to_hex_string(const __m128& value) {
return std::string(buffer);
}
inline std::string to_string(const __m128& value) {
char buffer[128];
std::snprintf(buffer, sizeof(buffer), "(%F, %F, %F, %F)", value.m128_f32[0],
value.m128_f32[1], value.m128_f32[2], value.m128_f32[3]);
return std::string(buffer);
}
template <typename T>
inline T from_string(const char* value);
inline T from_string(const char* value, bool force_hex = false);
template <>
inline uint64_t from_string<uint64_t>(const char* value) {
if (std::strchr(value, 'h') != nullptr) {
inline int32_t from_string<int32_t>(const char* value, bool force_hex) {
if (force_hex || std::strchr(value, 'h') != nullptr) {
return std::strtol(value, nullptr, 16);
} else {
return std::strtol(value, nullptr, 0);
}
}
template <>
inline uint32_t from_string<uint32_t>(const char* value, bool force_hex) {
if (force_hex || std::strchr(value, 'h') != nullptr) {
return std::strtoul(value, nullptr, 16);
} else {
return std::strtoul(value, nullptr, 0);
}
}
template <>
inline int64_t from_string<int64_t>(const char* value, bool force_hex) {
if (force_hex || std::strchr(value, 'h') != nullptr) {
return std::strtoll(value, nullptr, 16);
} else {
return std::strtoll(value, nullptr, 0);
}
}
template <>
inline uint64_t from_string<uint64_t>(const char* value, bool force_hex) {
if (force_hex || std::strchr(value, 'h') != nullptr) {
return std::strtoull(value, nullptr, 16);
} else {
return std::strtoull(value, nullptr, 0);
@ -70,23 +113,38 @@ inline uint64_t from_string<uint64_t>(const char* value) {
}
template <>
inline double from_string<double>(const char* value) {
if (std::strstr(value, "0x") == value || std::strchr(value, 'h') != nullptr) {
inline float from_string<float>(const char* value, bool force_hex) {
if (force_hex || std::strstr(value, "0x") == value ||
std::strchr(value, 'h') != nullptr) {
union {
uint32_t ui;
float flt;
} v;
v.ui = from_string<uint32_t>(value, force_hex);
return v.flt;
}
return std::strtof(value, nullptr);
}
template <>
inline double from_string<double>(const char* value, bool force_hex) {
if (force_hex || std::strstr(value, "0x") == value ||
std::strchr(value, 'h') != nullptr) {
union {
uint64_t ui;
double dbl;
} v;
v.ui = from_string<uint64_t>(value);
v.ui = from_string<uint64_t>(value, force_hex);
return v.dbl;
}
return std::strtod(value, nullptr);
}
template <>
inline vec128_t from_string<vec128_t>(const char* value) {
inline vec128_t from_string<vec128_t>(const char* value, bool force_hex) {
vec128_t v;
char* p = const_cast<char*>(value);
bool hex_mode = false;
bool hex_mode = force_hex;
if (*p == '[') {
hex_mode = true;
++p;
@ -119,10 +177,10 @@ inline vec128_t from_string<vec128_t>(const char* value) {
}
template <>
inline __m128 from_string<__m128>(const char* value) {
inline __m128 from_string<__m128>(const char* value, bool force_hex) {
__m128 v;
char* p = const_cast<char*>(value);
bool hex_mode = false;
bool hex_mode = force_hex;
if (*p == '[') {
hex_mode = true;
++p;
@ -155,8 +213,8 @@ inline __m128 from_string<__m128>(const char* value) {
}
template <typename T>
inline T from_string(const std::string& value) {
return from_string<T>(value.c_str());
inline T from_string(const std::string& value, bool force_hex = false) {
return from_string<T>(value.c_str(), force_hex);
}
} // namespace string_util

View File

@ -7,13 +7,12 @@
******************************************************************************
*/
#include "xenia/cpu/x64_context.h"
#include "xenia/base/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[] = {
@ -28,25 +27,26 @@ const char* X64Context::GetRegisterName(X64Register reg) {
return kRegisterNames[static_cast<int>(reg)];
}
std::string X64Context::GetStringFromValue(X64Register reg) const {
std::string X64Context::GetStringFromValue(X64Register reg, bool hex) const {
switch (reg) {
case X64Register::kRip:
return string_util::to_hex_string(rip);
return hex ? string_util::to_hex_string(rip) : std::to_string(rip);
case X64Register::kEflags:
return string_util::to_hex_string(eflags);
return hex ? string_util::to_hex_string(eflags) : std::to_string(eflags);
default:
if (static_cast<int>(reg) >= static_cast<int>(X64Register::kRax) &&
static_cast<int>(reg) <= static_cast<int>(X64Register::kR15)) {
return string_util::to_hex_string(
int_registers.values[static_cast<int>(reg) -
static_cast<int>(X64Register::kRax)]);
auto value = int_registers[static_cast<int>(reg) -
static_cast<int>(X64Register::kRax)];
return hex ? string_util::to_hex_string(value) : std::to_string(value);
} else if (static_cast<int>(reg) >=
static_cast<int>(X64Register::kXmm0) &&
static_cast<int>(reg) <=
static_cast<int>(X64Register::kXmm15)) {
return string_util::to_hex_string(
xmm_registers.values[static_cast<int>(reg) -
static_cast<int>(X64Register::kXmm0)]);
auto value = xmm_registers[static_cast<int>(reg) -
static_cast<int>(X64Register::kXmm0)];
return hex ? string_util::to_hex_string(value)
: string_util::to_string(value);
} else {
assert_unhandled_case(reg);
return "";
@ -54,10 +54,10 @@ std::string X64Context::GetStringFromValue(X64Register reg) const {
}
}
void X64Context::SetValueFromString(X64Register reg, std::string value) {
void X64Context::SetValueFromString(X64Register reg, std::string value,
bool hex) {
// TODO(benvanik): set value from string.
assert_always(false);
}
} // namespace cpu
} // namespace xe

View File

@ -7,14 +7,13 @@
******************************************************************************
*/
#ifndef XENIA_CPU_X64_CONTEXT_H_
#define XENIA_CPU_X64_CONTEXT_H_
#ifndef XENIA_BASE_X64_CONTEXT_H_
#define XENIA_BASE_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.
@ -55,8 +54,8 @@ enum class X64Register {
kXmm15,
};
struct X64Context {
// TODO(benvanik): x64 registers.
class X64Context {
public:
uint64_t rip;
uint32_t eflags;
union {
@ -78,8 +77,9 @@ struct X64Context {
uint64_t r14;
uint64_t r15;
};
uint64_t values[16];
} int_registers;
uint64_t int_registers[16];
};
union {
struct {
__m128 xmm0;
@ -99,15 +99,14 @@ struct X64Context {
__m128 xmm14;
__m128 xmm15;
};
__m128 values[16];
} xmm_registers;
__m128 xmm_registers[16];
};
static const char* GetRegisterName(X64Register reg);
std::string GetStringFromValue(X64Register reg) const;
void SetValueFromString(X64Register reg, std::string value);
std::string GetStringFromValue(X64Register reg, bool hex) const;
void SetValueFromString(X64Register reg, std::string value, bool hex);
};
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_X64_CONTEXT_H_
#endif // XENIA_BASE_X64_CONTEXT_H_

View File

@ -38,6 +38,8 @@ DEFINE_bool(enable_debugprint_log, false,
"Log debugprint traps to the active debugger");
DEFINE_bool(ignore_undefined_externs, true,
"Don't exit when an undefined extern is called.");
DEFINE_bool(emit_source_annotations, false,
"Add extra movs and nops to make disassembly easier to read.");
namespace xe {
namespace cpu {
@ -239,7 +241,7 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t* out_stack_size) {
add(rsp, (uint32_t)stack_size);
ret();
if (FLAGS_debug) {
if (FLAGS_emit_source_annotations) {
nop();
nop();
nop();
@ -254,9 +256,9 @@ void X64Emitter::MarkSourceOffset(const Instr* i) {
auto entry = source_map_arena_.Alloc<SourceMapEntry>();
entry->source_offset = static_cast<uint32_t>(i->src1.offset);
entry->hir_offset = uint32_t(i->block->ordinal << 16) | i->ordinal;
entry->code_offset = static_cast<uint32_t>(getSize() + 1);
entry->code_offset = static_cast<uint32_t>(getSize());
if (FLAGS_debug) {
if (FLAGS_emit_source_annotations) {
nop();
nop();
mov(eax, entry->source_offset);

View File

@ -15,50 +15,84 @@
namespace xe {
namespace cpu {
ExportResolver::Table::Table(const char* module_name,
const std::vector<Export*>* exports_by_ordinal)
: exports_by_ordinal_(exports_by_ordinal) {
auto dot_pos = std::strrchr(module_name, '.');
if (dot_pos != nullptr) {
std::strncpy(module_name_, module_name,
static_cast<size_t>(dot_pos - module_name));
} else {
std::strncpy(module_name_, module_name, xe::countof(module_name_) - 1);
}
exports_by_name_.reserve(exports_by_ordinal_->size());
for (size_t i = 0; i < exports_by_ordinal_->size(); ++i) {
auto export = exports_by_ordinal_->at(i);
if (export) {
exports_by_name_.push_back(export);
}
}
std::sort(
exports_by_name_.begin(), exports_by_name_.end(),
[](Export* a, Export* b) { return std::strcmp(a->name, b->name) <= 0; });
}
ExportResolver::ExportResolver() = default;
ExportResolver::~ExportResolver() = default;
void ExportResolver::RegisterTable(
const std::string& library_name,
const std::vector<xe::cpu::Export*>* exports) {
tables_.emplace_back(library_name, exports);
const char* module_name, const std::vector<xe::cpu::Export*>* exports) {
tables_.emplace_back(module_name, exports);
all_exports_by_name_.reserve(all_exports_by_name_.size() + exports->size());
for (size_t i = 0; i < exports->size(); ++i) {
auto export = exports->at(i);
if (export) {
all_exports_by_name_.push_back(export);
}
}
std::sort(
all_exports_by_name_.begin(), all_exports_by_name_.end(),
[](Export* a, Export* b) { return std::strcmp(a->name, b->name) <= 0; });
}
Export* ExportResolver::GetExportByOrdinal(const std::string& library_name,
Export* ExportResolver::GetExportByOrdinal(const char* module_name,
uint16_t ordinal) {
for (const auto& table : tables_) {
if (table.name == library_name || table.simple_name == library_name) {
if (ordinal > table.exports->size()) {
if (std::strncmp(module_name, table.module_name(),
std::strlen(table.module_name())) == 0) {
if (ordinal > table.exports_by_ordinal().size()) {
return nullptr;
}
return table.exports->at(ordinal);
return table.exports_by_ordinal().at(ordinal);
}
}
return nullptr;
}
void ExportResolver::SetVariableMapping(const std::string& library_name,
void ExportResolver::SetVariableMapping(const char* module_name,
uint16_t ordinal, uint32_t value) {
auto export_entry = GetExportByOrdinal(library_name, ordinal);
auto export_entry = GetExportByOrdinal(module_name, ordinal);
assert_not_null(export_entry);
export_entry->tags |= ExportTag::kImplemented;
export_entry->variable_ptr = value;
}
void ExportResolver::SetFunctionMapping(const std::string& library_name,
void ExportResolver::SetFunctionMapping(const char* module_name,
uint16_t ordinal,
xe_kernel_export_shim_fn shim) {
auto export_entry = GetExportByOrdinal(library_name, ordinal);
auto export_entry = GetExportByOrdinal(module_name, ordinal);
assert_not_null(export_entry);
export_entry->tags |= ExportTag::kImplemented;
export_entry->function_data.shim = shim;
}
void ExportResolver::SetFunctionMapping(const std::string& library_name,
void ExportResolver::SetFunctionMapping(const char* module_name,
uint16_t ordinal,
ExportTrampoline trampoline) {
auto export_entry = GetExportByOrdinal(library_name, ordinal);
auto export_entry = GetExportByOrdinal(module_name, ordinal);
assert_not_null(export_entry);
export_entry->tags |= ExportTag::kImplemented;
export_entry->function_data.trampoline = trampoline;

View File

@ -99,37 +99,46 @@ class Export {
class ExportResolver {
public:
class Table {
public:
Table(const char* module_name, const std::vector<Export*>* exports);
const char* module_name() const { return module_name_; }
const std::vector<Export*>& exports_by_ordinal() const {
return *exports_by_ordinal_;
}
const std::vector<Export*>& exports_by_name() const {
return exports_by_name_;
}
private:
char module_name_[32] = {0};
const std::vector<Export*>* exports_by_ordinal_ = nullptr;
std::vector<Export*> exports_by_name_;
};
ExportResolver();
~ExportResolver();
void RegisterTable(const std::string& library_name,
void RegisterTable(const char* module_name,
const std::vector<Export*>* exports);
const std::vector<Table>& tables() const { return tables_; }
const std::vector<Export*>& all_exports_by_name() const {
return all_exports_by_name_;
}
Export* GetExportByOrdinal(const std::string& library_name, uint16_t ordinal);
Export* GetExportByOrdinal(const char* module_name, uint16_t ordinal);
void SetVariableMapping(const std::string& library_name, uint16_t ordinal,
void SetVariableMapping(const char* module_name, uint16_t ordinal,
uint32_t value);
void SetFunctionMapping(const std::string& library_name, uint16_t ordinal,
void SetFunctionMapping(const char* module_name, uint16_t ordinal,
xe_kernel_export_shim_fn shim);
void SetFunctionMapping(const std::string& library_name, uint16_t ordinal,
void SetFunctionMapping(const char* module_name, uint16_t ordinal,
ExportTrampoline trampoline);
private:
struct ExportTable {
std::string name;
std::string simple_name; // without extension
const std::vector<Export*>* exports;
ExportTable(const std::string& name, const std::vector<Export*>* exports)
: name(name), exports(exports) {
auto dot_pos = name.find_last_of('.');
if (dot_pos != std::string::npos) {
simple_name = name.substr(0, dot_pos);
} else {
simple_name = name;
}
}
};
std::vector<ExportTable> tables_;
std::vector<Table> tables_;
std::vector<Export*> all_exports_by_name_;
};
} // namespace cpu

View File

@ -246,7 +246,7 @@ enum class PPCRegister {
};
#pragma pack(push, 8)
typedef struct alignas(64) PPCContext_s {
typedef struct PPCContext_s {
// Must be stored at 0x0 for now.
// TODO(benvanik): find a nice way to describe this to the JIT.
ThreadState* thread_state;

View File

@ -16,8 +16,6 @@
namespace xe {
namespace cpu {
using xe::debug::Breakpoint;
Function::Function(Module* module, uint32_t address)
: Symbol(Symbol::Type::kFunction, module, address) {}
@ -100,6 +98,18 @@ const SourceMapEntry* GuestFunction::LookupCodeOffset(uint32_t offset) const {
return source_map_.empty() ? nullptr : &source_map_[0];
}
uintptr_t GuestFunction::MapSourceToCode(uint32_t source_address) const {
auto entry = LookupSourceOffset(source_address - address());
return entry ? entry->code_offset
: reinterpret_cast<uintptr_t>(machine_code());
}
uint32_t GuestFunction::MapCodeToSource(uintptr_t host_address) const {
auto entry = LookupCodeOffset(static_cast<uint32_t>(
host_address - reinterpret_cast<uintptr_t>(machine_code())));
return entry ? entry->source_offset : address();
}
bool GuestFunction::Call(ThreadState* thread_state, uint32_t return_address) {
// SCOPE_profile_cpu_f("cpu");

View File

@ -17,7 +17,6 @@
#include "xenia/cpu/frontend/ppc_context.h"
#include "xenia/cpu/symbol.h"
#include "xenia/cpu/thread_state.h"
#include "xenia/debug/breakpoint.h"
#include "xenia/debug/function_trace_data.h"
namespace xe {
@ -111,6 +110,9 @@ class GuestFunction : public Function {
const SourceMapEntry* LookupHIROffset(uint32_t offset) const;
const SourceMapEntry* LookupCodeOffset(uint32_t offset) const;
uintptr_t MapSourceToCode(uint32_t source_address) const;
uint32_t MapCodeToSource(uintptr_t host_address) const;
bool Call(ThreadState* thread_state, uint32_t return_address) override;
protected:

View File

@ -11,6 +11,7 @@
#include "xenia/base/assert.h"
#include "xenia/base/byte_order.h"
#include "xenia/base/exception_handler.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/memory.h"
@ -20,33 +21,28 @@ namespace cpu {
MMIOHandler* MMIOHandler::global_handler_ = nullptr;
// Implemented in the platform cc file.
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* virtual_membase,
uint8_t* physical_membase);
std::unique_ptr<MMIOHandler> MMIOHandler::Install(uint8_t* virtual_membase,
uint8_t* physical_membase,
uint8_t* memory_end) {
uint8_t* membase_end) {
// There can be only one handler at a time.
assert_null(global_handler_);
if (global_handler_) {
return nullptr;
}
// Create the platform-specific handler.
auto handler = CreateMMIOHandler(virtual_membase, physical_membase);
auto handler = std::unique_ptr<MMIOHandler>(
new MMIOHandler(virtual_membase, physical_membase, membase_end));
// Platform-specific initialization for the handler.
if (!handler->Initialize()) {
return nullptr;
}
// Install the exception handler directed at the MMIOHandler.
ExceptionHandler::Install(ExceptionCallbackThunk, handler.get());
handler->memory_end_ = memory_end;
global_handler_ = handler.get();
return handler;
}
MMIOHandler::~MMIOHandler() {
ExceptionHandler::Uninstall(ExceptionCallbackThunk, this);
assert_true(global_handler_ == this);
global_handler_ = nullptr;
}
@ -166,7 +162,8 @@ void MMIOHandler::CancelWriteWatch(uintptr_t watch_handle) {
delete entry;
}
bool MMIOHandler::CheckWriteWatch(void* thread_state, uint64_t fault_address) {
bool MMIOHandler::CheckWriteWatch(X64Context* thread_context,
uint64_t fault_address) {
uint32_t physical_address = uint32_t(fault_address);
if (physical_address > 0x1FFFFFFF) {
physical_address &= 0x1FFFFFFF;
@ -364,10 +361,16 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
return true;
}
bool MMIOHandler::HandleAccessFault(void* thread_state,
uint64_t fault_address) {
if (fault_address < uint64_t(virtual_membase_) ||
fault_address > uint64_t(memory_end_)) {
bool MMIOHandler::ExceptionCallbackThunk(Exception* ex, void* data) {
return reinterpret_cast<MMIOHandler*>(data)->ExceptionCallback(ex);
}
bool MMIOHandler::ExceptionCallback(Exception* ex) {
if (ex->code() != Exception::Code::kAccessViolation) {
return false;
}
if (ex->fault_address() < uint64_t(virtual_membase_) ||
ex->fault_address() > uint64_t(memory_end_)) {
// Quick kill anything outside our mapping.
return false;
}
@ -375,9 +378,10 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
// Access violations are pretty rare, so we can do a linear search here.
// Only check if in the virtual range, as we only support virtual ranges.
const MMIORange* range = nullptr;
if (fault_address < uint64_t(physical_membase_)) {
if (ex->fault_address() < uint64_t(physical_membase_)) {
for (const auto& test_range : mapped_ranges_) {
if ((uint32_t(fault_address) & test_range.mask) == test_range.address) {
if ((static_cast<uint32_t>(ex->fault_address()) & test_range.mask) ==
test_range.address) {
// Address is within the range of this mapping.
range = &test_range;
break;
@ -387,10 +391,10 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
if (!range) {
// Access is not found within any range, so fail and let the caller handle
// it (likely by aborting).
return CheckWriteWatch(thread_state, fault_address);
return CheckWriteWatch(ex->thread_context(), ex->fault_address());
}
auto rip = GetThreadStateRip(thread_state);
auto rip = ex->pc();
auto p = reinterpret_cast<const uint8_t*>(rip);
DecodedMov mov = {0};
bool decoded = TryDecodeMov(p, &mov);
@ -404,8 +408,8 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
// Load of a memory value - read from range, swap, and store in the
// register.
uint32_t value = range->read(nullptr, range->callback_context,
fault_address & 0xFFFFFFFF);
uint64_t* reg_ptr = GetThreadStateRegPtr(thread_state, mov.value_reg);
static_cast<uint32_t>(ex->fault_address()));
uint64_t* reg_ptr = &ex->thread_context()->int_registers[mov.value_reg];
if (!mov.byte_swap) {
// We swap only if it's not a movbe, as otherwise we are swapping twice.
value = xe::byte_swap(value);
@ -417,19 +421,19 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
if (mov.is_constant) {
value = uint32_t(mov.constant);
} else {
uint64_t* reg_ptr = GetThreadStateRegPtr(thread_state, mov.value_reg);
uint64_t* reg_ptr = &ex->thread_context()->int_registers[mov.value_reg];
value = static_cast<uint32_t>(*reg_ptr);
if (!mov.byte_swap) {
// We swap only if it's not a movbe, as otherwise we are swapping twice.
value = xe::byte_swap(static_cast<uint32_t>(value));
}
}
range->write(nullptr, range->callback_context, fault_address & 0xFFFFFFFF,
value);
range->write(nullptr, range->callback_context,
static_cast<uint32_t>(ex->fault_address()), value);
}
// Advance RIP to the next instruction so that we resume properly.
SetThreadStateRip(thread_state, rip + mov.length);
ex->set_resume_pc(rip + mov.length);
return true;
}

View File

@ -16,6 +16,11 @@
#include "xenia/base/mutex.h"
namespace xe {
class Exception;
class X64Context;
} // namespace xe
namespace xe {
namespace cpu {
@ -59,9 +64,6 @@ class MMIOHandler {
void* callback_context, void* callback_data);
void CancelWriteWatch(uintptr_t watch_handle);
public:
bool HandleAccessFault(void* thread_state, uint64_t fault_address);
protected:
struct WriteWatchEntry {
uint32_t address;
@ -71,19 +73,17 @@ class MMIOHandler {
void* callback_data;
};
MMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase)
MMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase,
uint8_t* membase_end)
: virtual_membase_(virtual_membase),
physical_membase_(physical_membase) {}
physical_membase_(physical_membase),
memory_end_(membase_end) {}
virtual bool Initialize() = 0;
static bool ExceptionCallbackThunk(Exception* ex, void* data);
bool ExceptionCallback(Exception* ex);
void ClearWriteWatch(WriteWatchEntry* entry);
bool CheckWriteWatch(void* thread_state, uint64_t fault_address);
virtual uint64_t GetThreadStateRip(void* thread_state_ptr) = 0;
virtual void SetThreadStateRip(void* thread_state_ptr, uint64_t rip) = 0;
virtual uint64_t* GetThreadStateRegPtr(void* thread_state_ptr,
int32_t be_reg_index) = 0;
bool CheckWriteWatch(X64Context* thread_context, uint64_t fault_address);
uint8_t* virtual_membase_;
uint8_t* physical_membase_;

View File

@ -1,237 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/mmio_handler.h"
#include <mach/mach.h>
#include <signal.h>
#include <thread>
#include "xenia/base/logging.h"
// Mach internal function, not defined in any header.
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/exc_server.html
extern "C" boolean_t exc_server(mach_msg_header_t* request_msg,
mach_msg_header_t* reply_msg);
// Exported for the kernel to call back into.
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/catch_exception_raise.html
extern "C" kern_return_t catch_exception_raise(
mach_port_t exception_port, mach_port_t thread, mach_port_t task,
exception_type_t exception, exception_data_t code,
mach_msg_type_number_t code_count);
namespace xe {
namespace cpu {
class MachMMIOHandler : public MMIOHandler {
public:
explicit MachMMIOHandler(uint8_t* mapping_base);
~MachMMIOHandler() override;
protected:
bool Initialize() override;
uint64_t GetThreadStateRip(void* thread_state_ptr) override;
void SetThreadStateRip(void* thread_state_ptr, uint64_t rip) override;
uint64_t* GetThreadStateRegPtr(void* thread_state_ptr,
int32_t be_reg_index) override;
private:
void ThreadEntry();
// Worker thread processing exceptions.
std::unique_ptr<std::thread> thread_;
// Port listening for exceptions on the worker thread.
mach_port_t listen_port_;
};
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* mapping_base) {
return std::make_unique<MachMMIOHandler>(mapping_base);
}
MachMMIOHandler::MachMMIOHandler(uint8_t* mapping_base)
: MMIOHandler(mapping_base), listen_port_(0) {}
bool MachMMIOHandler::Initialize() {
// Allocates the port that listens for exceptions.
// This will be freed in the dtor.
if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
&listen_port_) != KERN_SUCCESS) {
XELOGE("Unable to allocate listen port");
return false;
}
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_port_insert_right.html
if (mach_port_insert_right(mach_task_self(), listen_port_, listen_port_,
MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS) {
XELOGE("Unable to insert listen port right");
return false;
}
// Sets our exception filter so that any BAD_ACCESS exceptions go to it.
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/task_set_exception_ports.html
if (task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS,
listen_port_, EXCEPTION_DEFAULT,
MACHINE_THREAD_STATE) != KERN_SUCCESS) {
XELOGE("Unable to set exception port");
return false;
}
// Spin up the worker thread.
std::unique_ptr<std::thread> thread(
new std::thread([this]() { ThreadEntry(); }));
thread->detach();
thread_ = std::move(thread);
return true;
}
MachMMIOHandler::~MachMMIOHandler() {
task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, 0,
EXCEPTION_DEFAULT, 0);
mach_port_deallocate(mach_task_self(), listen_port_);
}
void MachMMIOHandler::ThreadEntry() {
while (true) {
struct {
mach_msg_header_t head;
mach_msg_body_t msgh_body;
char data[1024];
} msg;
struct {
mach_msg_header_t head;
char data[1024];
} reply;
// Wait for a message on the exception port.
mach_msg_return_t ret =
mach_msg(&msg.head, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(msg),
listen_port_, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if (ret != MACH_MSG_SUCCESS) {
XELOGE("mach_msg receive failed with %d %s", ret, mach_error_string(ret));
xe::debugging::Break();
break;
}
// Call exc_server, which will dispatch the catch_exception_raise.
exc_server(&msg.head, &reply.head);
// Send the reply.
if (mach_msg(&reply.head, MACH_SEND_MSG, reply.head.msgh_size, 0,
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL) != MACH_MSG_SUCCESS) {
XELOGE("mach_msg reply send failed");
xe::debugging::Break();
break;
}
}
}
// Kills the app when a bad access exception is unhandled.
void FailBadAccess() {
raise(SIGSEGV);
abort();
}
kern_return_t CatchExceptionRaise(mach_port_t thread) {
auto state_count = x86_EXCEPTION_STATE64_COUNT;
x86_exception_state64_t exc_state;
if (thread_get_state(thread, x86_EXCEPTION_STATE64,
reinterpret_cast<thread_state_t>(&exc_state),
&state_count) != KERN_SUCCESS) {
XELOGE("thread_get_state failed to get exception state");
return KERN_FAILURE;
}
state_count = x86_THREAD_STATE64_COUNT;
x86_thread_state64_t thread_state;
if (thread_get_state(thread, x86_THREAD_STATE64,
reinterpret_cast<thread_state_t>(&thread_state),
&state_count) != KERN_SUCCESS) {
XELOGE("thread_get_state failed to get thread state");
return KERN_FAILURE;
}
auto fault_address = exc_state.__faultvaddr;
auto mmio_handler =
static_cast<MachMMIOHandler*>(MMIOHandler::global_handler());
bool handled = mmio_handler->HandleAccessFault(&thread_state, fault_address);
if (!handled) {
// Unhandled - raise to the system.
XELOGE("MMIO unhandled bad access for %llx, bubbling", fault_address);
// TODO(benvanik): manipulate stack so that we can rip = break_handler or
// something and have the stack trace be valid.
xe::debugging::Break();
// When the thread resumes, kill it.
thread_state.__rip = reinterpret_cast<uint64_t>(FailBadAccess);
// Mach docs say we can return this to continue searching for handlers, but
// OSX doesn't seem to have it.
// return MIG_DESTROY_REQUEST;
}
// Set the thread state - as we've likely changed it.
if (thread_set_state(thread, x86_THREAD_STATE64,
reinterpret_cast<thread_state_t>(&thread_state),
state_count) != KERN_SUCCESS) {
XELOGE("thread_set_state failed to set thread state for continue");
return KERN_FAILURE;
}
return KERN_SUCCESS;
}
uint64_t MachMMIOHandler::GetThreadStateRip(void* thread_state_ptr) {
auto thread_state = reinterpret_cast<x86_thread_state64_t*>(thread_state_ptr);
return thread_state->__rip;
}
void MachMMIOHandler::SetThreadStateRip(void* thread_state_ptr, uint64_t rip) {
auto thread_state = reinterpret_cast<x86_thread_state64_t*>(thread_state_ptr);
thread_state->__rip = rip;
}
uint64_t* MachMMIOHandler::GetThreadStateRegPtr(void* thread_state_ptr,
int32_t be_reg_index) {
// Map from BeaEngine register order to x86_thread_state64 order.
static const uint32_t mapping[] = {
0, // REG0 / RAX -> 0
2, // REG1 / RCX -> 2
3, // REG2 / RDX -> 3
1, // REG3 / RBX -> 1
7, // REG4 / RSP -> 7
6, // REG5 / RBP -> 6
5, // REG6 / RSI -> 5
4, // REG7 / RDI -> 4
8, // REG8 / R8 -> 8
9, // REG9 / R9 -> 9
10, // REG10 / R10 -> 10
11, // REG11 / R11 -> 11
12, // REG12 / R12 -> 12
13, // REG13 / R13 -> 13
14, // REG14 / R14 -> 14
15, // REG15 / R15 -> 15
};
auto thread_state = reinterpret_cast<x86_thread_state64_t*>(thread_state_ptr);
return &thread_state->__rax + mapping[be_reg_index];
}
} // namespace cpu
} // namespace xe
// Exported and called by exc_server.
extern "C" kern_return_t catch_exception_raise(
mach_port_t exception_port, mach_port_t thread, mach_port_t task,
exception_type_t exception, exception_data_t code,
mach_msg_type_number_t code_count) {
// We get/set the states manually instead of using catch_exception_raise_state
// variant because that truncates everything to 32bit.
return xe::cpu::CatchExceptionRaise(thread);
}

View File

@ -1,110 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2014 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/cpu/mmio_handler.h"
#include "xenia/base/platform_win.h"
#include "xenia/profiling.h"
namespace xe {
void CrashDump();
} // namespace xe
namespace xe {
namespace cpu {
LONG CALLBACK MMIOExceptionHandler(PEXCEPTION_POINTERS ex_info);
class WinMMIOHandler : public MMIOHandler {
public:
WinMMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase)
: MMIOHandler(virtual_membase, physical_membase) {}
~WinMMIOHandler() override;
protected:
bool Initialize() override;
uint64_t GetThreadStateRip(void* thread_state_ptr) override;
void SetThreadStateRip(void* thread_state_ptr, uint64_t rip) override;
uint64_t* GetThreadStateRegPtr(void* thread_state_ptr,
int32_t reg_index) override;
};
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* virtual_membase,
uint8_t* physical_membase) {
return std::make_unique<WinMMIOHandler>(virtual_membase, physical_membase);
}
bool WinMMIOHandler::Initialize() {
// If there is a debugger attached the normal exception handler will not
// fire and we must instead add the continue handler.
AddVectoredExceptionHandler(1, MMIOExceptionHandler);
if (IsDebuggerPresent()) {
// TODO(benvanik): is this really required?
// AddVectoredContinueHandler(1, MMIOExceptionHandler);
}
return true;
}
WinMMIOHandler::~WinMMIOHandler() {
// Remove exception handlers.
RemoveVectoredExceptionHandler(MMIOExceptionHandler);
RemoveVectoredContinueHandler(MMIOExceptionHandler);
}
// Handles potential accesses to mmio. We look for access violations to
// addresses in our range and call into the registered handlers, if any.
// If there are none, we continue.
LONG CALLBACK MMIOExceptionHandler(PEXCEPTION_POINTERS ex_info) {
// Fast path for SetThreadName.
if (ex_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
return EXCEPTION_CONTINUE_SEARCH;
}
// Disabled because this can cause odd issues during handling.
// SCOPE_profile_cpu_i("cpu", "MMIOExceptionHandler");
// http://msdn.microsoft.com/en-us/library/ms679331(v=vs.85).aspx
// http://msdn.microsoft.com/en-us/library/aa363082(v=vs.85).aspx
auto code = ex_info->ExceptionRecord->ExceptionCode;
if (code == STATUS_ACCESS_VIOLATION) {
auto fault_address = ex_info->ExceptionRecord->ExceptionInformation[1];
if (MMIOHandler::global_handler()->HandleAccessFault(ex_info->ContextRecord,
fault_address)) {
// Handled successfully - RIP has been updated and we can continue.
return EXCEPTION_CONTINUE_EXECUTION;
} else {
// Failed to handle; continue search for a handler (and die if no other
// handler is found).
xe::CrashDump();
return EXCEPTION_CONTINUE_SEARCH;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
uint64_t WinMMIOHandler::GetThreadStateRip(void* thread_state_ptr) {
auto context = reinterpret_cast<LPCONTEXT>(thread_state_ptr);
return context->Rip;
}
void WinMMIOHandler::SetThreadStateRip(void* thread_state_ptr, uint64_t rip) {
auto context = reinterpret_cast<LPCONTEXT>(thread_state_ptr);
context->Rip = rip;
}
uint64_t* WinMMIOHandler::GetThreadStateRegPtr(void* thread_state_ptr,
int32_t reg_index) {
auto context = reinterpret_cast<LPCONTEXT>(thread_state_ptr);
// Register indices line up with the CONTEXT structure format.
return &context->Rax + reg_index;
}
} // namespace cpu
} // namespace xe

View File

@ -13,8 +13,8 @@
#include <memory>
#include <string>
#include "xenia/base/x64_context.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/x64_context.h"
namespace xe {
namespace cpu {
@ -82,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,
const X64Context* in_host_context,
X64Context* out_host_context,
uint64_t* out_stack_hash = nullptr) = 0;

View File

@ -152,6 +152,7 @@ class Win32StackWalker : public StackWalker {
size_t CaptureStackTrace(void* thread_handle, uint64_t* frame_host_pcs,
size_t frame_offset, size_t frame_count,
const X64Context* in_host_context,
X64Context* out_host_context,
uint64_t* out_stack_hash) override {
// TODO(benvanik): use xstate?
@ -160,19 +161,33 @@ class Win32StackWalker : public StackWalker {
// Need at least CONTEXT_CONTROL (for rip and rsp) and CONTEXT_INTEGER (for
// rbp).
CONTEXT thread_context;
thread_context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(thread_handle, &thread_context)) {
XELOGE("Unable to read thread context for stack walk");
return 0;
if (!in_host_context) {
// If not given a context we need to ask for it.
// This gets the state of the thread exactly where it was when suspended.
thread_context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(thread_handle, &thread_context)) {
XELOGE("Unable to read thread context for stack walk");
return 0;
}
} else {
// Copy thread context local. We will be modifying it during stack
// walking, so we don't want to mess with the incoming copy.
thread_context.Rip = in_host_context->rip;
thread_context.EFlags = in_host_context->eflags;
std::memcpy(&thread_context.Rax, in_host_context->int_registers,
sizeof(in_host_context->int_registers));
std::memcpy(&thread_context.Xmm0, in_host_context->xmm_registers,
sizeof(in_host_context->xmm_registers));
}
if (out_host_context) {
// Write out the captured thread context if the caller asked for it.
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));
std::memcpy(out_host_context->int_registers, &thread_context.Rax,
sizeof(out_host_context->int_registers));
std::memcpy(out_host_context->xmm_registers, &thread_context.Xmm0,
sizeof(out_host_context->xmm_registers));
}
// Setup the frame for walking.

View File

@ -0,0 +1,135 @@
/**
******************************************************************************
* 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/debug/breakpoint.h"
#include "xenia/base/memory.h"
#include "xenia/base/string_util.h"
#include "xenia/cpu/backend/code_cache.h"
#include "xenia/debug/debugger.h"
#include "xenia/emulator.h"
namespace xe {
namespace debug {
void CodeBreakpoint::ForEachHostAddress(
std::function<void(uintptr_t)> callback) const {
auto processor = debugger_->emulator()->processor();
if (address_type_ == AddressType::kGuest) {
auto guest_address = this->guest_address();
// Lookup all functions that contain this guest address and patch them.
auto functions = processor->FindFunctionsWithAddress(guest_address);
if (functions.empty()) {
// If function does not exist demand it, as we need someplace to put our
// breakpoint. Note that this follows the same resolution rules as the
// JIT, so what's returned is the function the JIT would have jumped to.
auto fn = processor->ResolveFunction(guest_address);
if (!fn) {
// TODO(benvanik): error out better with 'invalid breakpoint'?
assert_not_null(fn);
return;
}
functions.push_back(fn);
}
assert_false(functions.empty());
for (auto function : functions) {
// TODO(benvanik): other function types.
assert_true(function->is_guest());
auto guest_function = reinterpret_cast<cpu::GuestFunction*>(function);
uintptr_t host_address = guest_function->MapSourceToCode(guest_address);
assert_not_zero(host_address);
callback(host_address);
}
} else {
// Direct host address patching.
callback(host_address());
}
}
xe::cpu::GuestFunction* CodeBreakpoint::guest_function() const {
auto processor = debugger_->emulator()->processor();
if (address_type_ == AddressType::kGuest) {
auto functions = processor->FindFunctionsWithAddress(guest_address());
if (functions.empty()) {
return nullptr;
} else if (functions[0]->is_guest()) {
return static_cast<xe::cpu::GuestFunction*>(functions[0]);
}
return nullptr;
} else {
return processor->backend()->code_cache()->LookupFunction(host_address());
}
}
bool CodeBreakpoint::ContainsHostAddress(uintptr_t search_address) const {
bool contains = false;
ForEachHostAddress([&contains, search_address](uintptr_t host_address) {
if (host_address == search_address) {
contains = true;
}
});
return contains;
}
void CodeBreakpoint::Install() {
Breakpoint::Install();
assert_true(patches_.empty());
ForEachHostAddress([this](uintptr_t host_address) {
patches_.emplace_back(host_address, PatchAddress(host_address));
});
}
void CodeBreakpoint::Uninstall() {
// Simply unpatch all locations we patched when installing.
for (auto& location : patches_) {
UnpatchAddress(location.first, location.second);
}
patches_.clear();
Breakpoint::Uninstall();
}
uint16_t CodeBreakpoint::PatchAddress(uintptr_t host_address) {
auto ptr = reinterpret_cast<void*>(host_address);
auto original_bytes = xe::load_and_swap<uint16_t>(ptr);
assert_true(original_bytes != 0x0F0B);
xe::store_and_swap<uint16_t>(ptr, 0x0F0B);
return original_bytes;
}
void CodeBreakpoint::UnpatchAddress(uintptr_t host_address,
uint16_t original_bytes) {
auto ptr = reinterpret_cast<uint8_t*>(host_address);
auto instruction_bytes = xe::load_and_swap<uint16_t>(ptr);
assert_true(instruction_bytes == 0x0F0B);
xe::store_and_swap<uint16_t>(ptr, original_bytes);
}
std::string CodeBreakpoint::to_string() const {
if (address_type_ == AddressType::kGuest) {
auto str =
std::string("PPC ") + xe::string_util::to_hex_string(guest_address());
auto processor = debugger_->emulator()->processor();
auto functions = processor->FindFunctionsWithAddress(guest_address());
if (functions.empty()) {
return str;
}
str += " " + functions[0]->name();
return str;
} else {
return std::string("x64 ") + xe::string_util::to_hex_string(host_address());
}
}
} // namespace debug
} // namespace xe

View File

@ -10,33 +10,173 @@
#ifndef XENIA_DEBUG_BREAKPOINT_H_
#define XENIA_DEBUG_BREAKPOINT_H_
#include <functional>
#include <string>
#include <utility>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/cpu/function.h"
namespace xe {
namespace debug {
class Debugger;
class Breakpoint {
public:
enum Type {
TEMP_TYPE,
CODE_TYPE,
enum class Type {
kStepping,
kCode,
kData,
kExport,
kGpu,
};
public:
Breakpoint(Type type, uint32_t address);
~Breakpoint();
Breakpoint(Debugger* debugger, Type type)
: debugger_(debugger), type_(type) {}
virtual ~Breakpoint() { assert_false(installed_); }
Debugger* debugger() const { return debugger_; }
Type type() const { return type_; }
uint32_t address() const { return address_; }
const char* id() const { return id_.c_str(); }
void set_id(const char* id) { id_ = std::string(id); }
// Whether the breakpoint has been enabled by the user.
bool is_enabled() const { return enabled_; }
// Toggles the breakpoint state.
// Assumes the caller holds the global lock.
void set_enabled(bool is_enabled) {
enabled_ = is_enabled;
if (!enabled_ && installed_) {
Uninstall();
} else if (enabled_ && !installed_ && !suspend_count_) {
Install();
}
}
virtual std::string to_string() const { return "Breakpoint"; }
private:
Type type_;
uint32_t address_;
friend class Debugger;
// Suspends the breakpoint until it is resumed with Resume.
// This preserves the user enabled state.
// Assumes the caller holds the global lock.
void Suspend() {
++suspend_count_;
if (installed_) {
Uninstall();
}
}
std::string id_;
// Resumes a previously-suspended breakpoint, reinstalling it if required.
// Assumes the caller holds the global lock.
void Resume() {
--suspend_count_;
if (!suspend_count_ && enabled_) {
Install();
}
}
protected:
virtual void Install() { installed_ = true; }
virtual void Uninstall() { installed_ = false; }
Debugger* debugger_ = nullptr;
Type type_;
// True if patched into code.
bool installed_ = false;
// True if user enabled.
bool enabled_ = true;
// Depth of suspends (must be 0 to be installed). Defaults to suspended so
// that it's never installed unless the debugger knows about it.
int suspend_count_ = 1;
};
class CodeBreakpoint : public Breakpoint {
public:
enum class AddressType {
kGuest,
kHost,
};
CodeBreakpoint(Debugger* debugger, AddressType address_type, uint64_t address)
: CodeBreakpoint(debugger, Type::kCode, address_type, address) {}
AddressType address_type() const { return address_type_; }
uint64_t address() const { return address_; }
uint32_t guest_address() const {
assert_true(address_type_ == AddressType::kGuest);
return static_cast<uint32_t>(address_);
}
uintptr_t host_address() const {
assert_true(address_type_ == AddressType::kHost);
return static_cast<uintptr_t>(address_);
}
// TODO(benvanik): additional support:
// - conditions (expressions? etc?)
// - thread restrictions (set of thread ids)
// - hit counters
// Returns a guest function that contains the guest address, if any.
// If there are multiple functions that contain the address a random one will
// be returned. If this is a host-address code breakpoint this will attempt to
// find a function with that address and otherwise return nullptr.
xe::cpu::GuestFunction* guest_function() const;
// Returns true if this breakpoint, when installed, contains the given host
// address.
bool ContainsHostAddress(uintptr_t search_address) const;
std::string to_string() const override;
protected:
CodeBreakpoint(Debugger* debugger, Type type, AddressType address_type,
uint64_t address)
: Breakpoint(debugger, type),
address_type_(address_type),
address_(address) {}
void ForEachHostAddress(std::function<void(uintptr_t)> callback) const;
void Install() override;
void Uninstall() override;
uint16_t PatchAddress(uintptr_t host_address);
void UnpatchAddress(uintptr_t host_address, uint16_t original_bytes);
AddressType address_type_;
uint64_t address_ = 0;
// Pairs of x64 address : original byte values when installed.
// These locations have been patched and must be restored when the breakpoint
// is uninstalled.
std::vector<std::pair<uintptr_t, uint16_t>> patches_;
};
class StepBreakpoint : public CodeBreakpoint {
public:
StepBreakpoint(Debugger* debugger, AddressType address_type, uint64_t address)
: CodeBreakpoint(debugger, Type::kStepping, address_type, address) {}
};
class DataBreakpoint : public Breakpoint {
public:
explicit DataBreakpoint(Debugger* debugger)
: Breakpoint(debugger, Type::kData) {}
};
class ExportBreakpoint : public Breakpoint {
public:
explicit ExportBreakpoint(Debugger* debugger)
: Breakpoint(debugger, Type::kExport) {}
};
class GpuBreakpoint : public Breakpoint {
public:
explicit GpuBreakpoint(Debugger* debugger)
: Breakpoint(debugger, Type::kGpu) {}
};
} // namespace debug

View File

@ -1,271 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/debug_client.h"
#include "xenia/base/logging.h"
#include "xenia/ui/loop.h"
namespace xe {
namespace debug {
using namespace xe::debug::proto; // NOLINT(build/namespaces)
constexpr size_t kReceiveBufferSize = 1 * 1024 * 1024;
constexpr size_t kTransmitBufferSize = 1 * 1024 * 1024;
DebugClient::DebugClient()
: packet_reader_(kReceiveBufferSize), packet_writer_(kTransmitBufferSize) {
receive_buffer_.resize(kReceiveBufferSize);
}
DebugClient::~DebugClient() {
socket_->Close();
xe::threading::Wait(thread_.get(), true);
thread_.reset();
}
bool DebugClient::Connect(std::string hostname, uint16_t port) {
socket_ = Socket::Connect(std::move(hostname), port);
if (!socket_) {
XELOGE("Unable to connect to remote host");
return false;
}
// Create a thread to manage the connection.
thread_ = xe::threading::Thread::Create({}, [this]() {
// Main loop.
bool running = true;
while (running) {
auto wait_result = xe::threading::Wait(socket_->wait_handle(), true);
switch (wait_result) {
case xe::threading::WaitResult::kSuccess:
// Event (read or close).
running = HandleSocketEvent();
continue;
case xe::threading::WaitResult::kAbandoned:
case xe::threading::WaitResult::kFailed:
// Error - kill the thread.
running = false;
continue;
default:
// Eh. Continue.
continue;
}
}
// Kill socket (likely already disconnected).
socket_.reset();
});
return true;
}
bool DebugClient::HandleSocketEvent() {
if (!socket_->is_connected()) {
// Known-disconnected.
return false;
}
// Attempt to read into our buffer.
size_t bytes_read =
socket_->Receive(receive_buffer_.data(), receive_buffer_.capacity());
if (bytes_read == -1) {
// Disconnected.
return false;
} else if (bytes_read == 0) {
// No data available. Wait again.
return true;
}
// Pass off the command processor to do with it what it wants.
if (!ProcessBuffer(receive_buffer_.data(), bytes_read)) {
// Error.
XELOGE("Error processing incoming XDP command; forcing disconnect");
return false;
}
return true;
}
bool DebugClient::ProcessBuffer(const uint8_t* buffer, size_t buffer_length) {
// Grow and append the bytes to the receive buffer.
packet_reader_.AppendBuffer(buffer, buffer_length);
// Process all packets in the buffer.
while (true) {
auto packet = packet_reader_.Begin();
if (!packet) {
// Out of packets. Compact any unused space.
packet_reader_.Compact();
break;
}
// Emit packet. Possibly dispatch to a loop, if desired.
if (loop_) {
auto clone = packet_reader_.ClonePacket();
auto clone_ptr = clone.release();
loop_->Post([this, clone_ptr]() {
std::unique_ptr<PacketReader> clone(clone_ptr);
auto packet = clone->Begin();
if (!ProcessPacket(clone.get(), packet)) {
// Failed to process packet, but there's no good way to report that.
XELOGE("Failed to process incoming packet");
assert_always();
}
clone->End();
});
} else {
// Process inline.
if (!ProcessPacket(&packet_reader_, packet)) {
// Failed to process packet.
XELOGE("Failed to process incoming packet");
return false;
}
}
packet_reader_.End();
}
return true;
}
bool DebugClient::ProcessPacket(proto::PacketReader* packet_reader,
const proto::Packet* packet) {
// Hold lock during processing.
std::lock_guard<std::recursive_mutex> lock(mutex_);
switch (packet->packet_type) {
case PacketType::kGenericResponse: {
//
} break;
case PacketType::kExecutionNotification: {
auto body = packet_reader->Read<ExecutionNotification>();
switch (body->current_state) {
case ExecutionNotification::State::kStopped: {
// body->stop_reason
execution_state_ = ExecutionState::kStopped;
listener_->OnExecutionStateChanged(execution_state_);
BeginUpdateAllState();
} break;
case ExecutionNotification::State::kRunning: {
execution_state_ = ExecutionState::kRunning;
listener_->OnExecutionStateChanged(execution_state_);
} break;
case ExecutionNotification::State::kExited: {
execution_state_ = ExecutionState::kExited;
listener_->OnExecutionStateChanged(execution_state_);
BeginUpdateAllState();
} break;
}
} break;
case PacketType::kModuleListResponse: {
auto body = packet_reader->Read<ModuleListResponse>();
auto entries = packet_reader->ReadArray<ModuleListEntry>(body->count);
listener_->OnModulesUpdated(std::move(entries));
} break;
case PacketType::kThreadListResponse: {
auto body = packet_reader->Read<ThreadListResponse>();
auto entries = packet_reader->ReadArray<ThreadListEntry>(body->count);
listener_->OnThreadsUpdated(std::move(entries));
} break;
case PacketType::kThreadStatesResponse: {
auto body = packet_reader->Read<ThreadStatesResponse>();
for (size_t i = 0; i < body->count; ++i) {
auto entry = packet_reader->Read<ThreadStateEntry>();
auto frames =
packet_reader->ReadArray<ThreadCallStackFrame>(entry->frame_count);
listener_->OnThreadStateUpdated(entry->thread_handle, entry,
std::move(frames));
}
} break;
default: {
XELOGE("Unknown incoming packet type");
return false;
} break;
}
return true;
}
void DebugClient::Flush() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (!packet_writer_.buffer_offset()) {
return;
}
socket_->Send(packet_writer_.buffer(), packet_writer_.buffer_offset());
packet_writer_.Reset();
}
void DebugClient::Continue() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
packet_writer_.Begin(PacketType::kExecutionRequest);
auto body = packet_writer_.Append<ExecutionRequest>();
body->action = ExecutionRequest::Action::kContinue;
packet_writer_.End();
Flush();
}
void DebugClient::StepOne(uint32_t thread_id) {
std::lock_guard<std::recursive_mutex> lock(mutex_);
packet_writer_.Begin(PacketType::kExecutionRequest);
auto body = packet_writer_.Append<ExecutionRequest>();
body->action = ExecutionRequest::Action::kStep;
body->step_mode = ExecutionRequest::StepMode::kStepOne;
body->thread_id = thread_id;
packet_writer_.End();
Flush();
}
void DebugClient::StepTo(uint32_t thread_id, uint32_t target_pc) {
std::lock_guard<std::recursive_mutex> lock(mutex_);
packet_writer_.Begin(PacketType::kExecutionRequest);
auto body = packet_writer_.Append<ExecutionRequest>();
body->action = ExecutionRequest::Action::kStep;
body->step_mode = ExecutionRequest::StepMode::kStepTo;
body->thread_id = thread_id;
body->target_pc = target_pc;
packet_writer_.End();
Flush();
}
void DebugClient::Interrupt() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
packet_writer_.Begin(PacketType::kExecutionRequest);
auto body = packet_writer_.Append<ExecutionRequest>();
body->action = ExecutionRequest::Action::kInterrupt;
packet_writer_.End();
Flush();
}
void DebugClient::Exit() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
packet_writer_.Begin(PacketType::kExecutionRequest);
auto body = packet_writer_.Append<ExecutionRequest>();
body->action = ExecutionRequest::Action::kExit;
packet_writer_.End();
Flush();
}
void DebugClient::BeginUpdateAllState() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
// Request all module infos.
packet_writer_.Begin(PacketType::kModuleListRequest);
packet_writer_.End();
// Request all thread infos.
packet_writer_.Begin(PacketType::kThreadListRequest);
packet_writer_.End();
// Request the full call stacks for all threads.
packet_writer_.Begin(PacketType::kThreadStatesRequest);
packet_writer_.End();
Flush();
}
} // namespace debug
} // namespace xe

View File

@ -1,106 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_DEBUG_CLIENT_H_
#define XENIA_DEBUG_DEBUG_CLIENT_H_
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "xenia/base/socket.h"
#include "xenia/base/threading.h"
#include "xenia/debug/proto/packet_reader.h"
#include "xenia/debug/proto/packet_writer.h"
#include "xenia/debug/proto/xdp_protocol.h"
namespace xe {
namespace ui {
class Loop;
} // namespace ui
} // namespace xe
namespace xe {
namespace debug {
using proto::ModuleListEntry;
using proto::ThreadCallStackFrame;
using proto::ThreadListEntry;
using proto::ThreadStateEntry;
enum class ExecutionState {
kRunning,
kStopped,
kExited,
};
// NOTE: all callbacks are issued on the client thread and should be marshalled
// to the UI thread. All other methods can be called from any thread.
class DebugClientListener {
public:
virtual ~DebugClientListener() = default;
virtual void OnExecutionStateChanged(ExecutionState execution_state) = 0;
virtual void OnModulesUpdated(
std::vector<const ModuleListEntry*> entries) = 0;
virtual void OnThreadsUpdated(
std::vector<const ThreadListEntry*> entries) = 0;
virtual void OnThreadStateUpdated(
uint32_t thread_handle, const ThreadStateEntry* entry,
std::vector<const ThreadCallStackFrame*> frames) = 0;
};
class DebugClient {
public:
DebugClient();
~DebugClient();
Socket* socket() const { return socket_.get(); }
void set_listener(DebugClientListener* listener) { listener_ = listener; }
void set_loop(xe::ui::Loop* loop) { loop_ = loop; }
ExecutionState execution_state() const { return execution_state_; }
bool Connect(std::string hostname, uint16_t port);
void Continue();
void StepOne(uint32_t thread_id);
void StepTo(uint32_t thread_id, uint32_t target_pc);
void Interrupt();
void Exit();
private:
bool HandleSocketEvent();
bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length);
bool ProcessPacket(proto::PacketReader* packet_reader,
const proto::Packet* packet);
void Flush();
void BeginUpdateAllState();
std::recursive_mutex mutex_;
std::unique_ptr<Socket> socket_;
std::unique_ptr<xe::threading::Thread> thread_;
std::vector<uint8_t> receive_buffer_;
proto::PacketReader packet_reader_;
proto::PacketWriter packet_writer_;
DebugClientListener* listener_ = nullptr;
xe::ui::Loop* loop_ = nullptr;
ExecutionState execution_state_ = ExecutionState::kStopped;
};
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_DEBUG_CLIENT_H_

View File

@ -1,406 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/debug_server.h"
#include <gflags/gflags.h>
#include "xenia/base/logging.h"
#include "xenia/cpu/stack_walker.h"
#include "xenia/debug/debugger.h"
#include "xenia/emulator.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/xmodule.h"
#include "xenia/kernel/xthread.h"
DEFINE_int32(debug_server_port, 9002, "Debugger XDP server TCP port.");
namespace xe {
namespace debug {
using namespace xe::debug::proto; // NOLINT(build/namespaces)
using xe::kernel::XModule;
using xe::kernel::XObject;
using xe::kernel::XThread;
constexpr size_t kReceiveBufferSize = 32 * 1024;
constexpr size_t kReadBufferSize = 1 * 1024 * 1024;
constexpr size_t kWriteBufferSize = 1 * 1024 * 1024;
DebugServer::DebugServer(Debugger* debugger)
: debugger_(debugger),
packet_reader_(kReadBufferSize),
packet_writer_(kWriteBufferSize) {
receive_buffer_.resize(kReceiveBufferSize);
}
DebugServer::~DebugServer() = default;
bool DebugServer::Initialize() {
post_event_ = xe::threading::Event::CreateAutoResetEvent(false);
socket_server_ = SocketServer::Create(uint16_t(FLAGS_debug_server_port),
[this](std::unique_ptr<Socket> client) {
AcceptClient(std::move(client));
});
if (!socket_server_) {
XELOGE("Unable to create XDP socket server - port in use?");
return false;
}
return true;
}
void DebugServer::PostSynchronous(std::function<void()> fn) {
xe::threading::Fence fence;
{
std::lock_guard<std::mutex> lock(post_mutex_);
post_queue_.push_back([&fence, fn]() {
fn();
fence.Signal();
});
}
post_event_->Set();
fence.Wait();
}
void DebugServer::AcceptClient(std::unique_ptr<Socket> client) {
// If we have an existing client, kill it and join its thread.
if (client_) {
// TODO(benvanik): XDP say goodbye?
client_->Close();
xe::threading::Wait(client_thread_.get(), true);
client_thread_.reset();
}
// Take ownership of the new one.
client_ = std::move(client);
// Create a thread to manage the connection.
client_thread_ = xe::threading::Thread::Create({}, [this]() {
xe::threading::set_name("XDP Debug Server");
// Let the debugger know we are present.
debugger_->set_attached(true);
// Notify the client of the current state.
if (debugger_->execution_state() == ExecutionState::kRunning) {
OnExecutionContinued();
} else {
OnExecutionInterrupted();
}
// Main loop.
bool running = true;
while (running) {
xe::threading::WaitHandle* wait_handles[] = {
client_->wait_handle(), post_event_.get(),
};
auto wait_result = xe::threading::WaitMultiple(
wait_handles, xe::countof(wait_handles), false, true);
switch (wait_result.first) {
case xe::threading::WaitResult::kSuccess:
// Event (read or close).
switch (wait_result.second) {
case 0: {
running = HandleClientEvent();
} break;
case 1: {
bool has_remaining = true;
while (has_remaining) {
std::function<void()> fn;
{
std::lock_guard<std::mutex> lock(post_mutex_);
fn = std::move(post_queue_.front());
post_queue_.pop_front();
has_remaining = !post_queue_.empty();
if (!has_remaining) {
post_event_->Reset();
}
}
fn();
}
} break;
}
continue;
case xe::threading::WaitResult::kAbandoned:
case xe::threading::WaitResult::kFailed:
// Error - kill the thread.
running = false;
continue;
default:
// Eh. Continue.
continue;
}
}
// Kill client (likely already disconnected).
client_.reset();
// Notify debugger we are no longer attached.
debugger_->set_attached(false);
});
}
bool DebugServer::HandleClientEvent() {
if (!client_->is_connected()) {
// Known-disconnected.
return false;
}
// Attempt to read into our buffer.
size_t bytes_read =
client_->Receive(receive_buffer_.data(), receive_buffer_.capacity());
if (bytes_read == -1) {
// Disconnected.
return false;
} else if (bytes_read == 0) {
// No data available. Wait again.
return true;
}
// Pass off the command processor to do with it what it wants.
if (!ProcessBuffer(receive_buffer_.data(), bytes_read)) {
// Error.
XELOGE("Error processing incoming XDP command; forcing disconnect");
return false;
}
return true;
}
bool DebugServer::ProcessBuffer(const uint8_t* buffer, size_t buffer_length) {
// Grow and append the bytes to the receive buffer.
packet_reader_.AppendBuffer(buffer, buffer_length);
// Process all packets in the buffer.
while (true) {
auto packet = packet_reader_.Begin();
if (!packet) {
// Out of packets. Compact any unused space.
packet_reader_.Compact();
break;
}
// Emit packet.
if (!ProcessPacket(packet)) {
// Failed to process packet.
XELOGE("Failed to process incoming packet");
return false;
}
packet_reader_.End();
}
Flush();
return true;
}
bool DebugServer::ProcessPacket(const proto::Packet* packet) {
auto emulator = debugger()->emulator();
auto kernel_state = emulator->kernel_state();
auto object_table = kernel_state->object_table();
switch (packet->packet_type) {
case PacketType::kExecutionRequest: {
auto body = packet_reader_.Read<ExecutionRequest>();
switch (body->action) {
case ExecutionRequest::Action::kContinue: {
debugger_->Continue();
} break;
case ExecutionRequest::Action::kStep: {
switch (body->step_mode) {
case ExecutionRequest::StepMode::kStepOne: {
debugger_->StepOne(body->thread_id);
} break;
case ExecutionRequest::StepMode::kStepTo: {
debugger_->StepTo(body->thread_id, body->target_pc);
} break;
default:
assert_unhandled_case(body->step_mode);
return false;
}
} break;
case ExecutionRequest::Action::kInterrupt: {
debugger_->Interrupt();
} break;
case ExecutionRequest::Action::kExit: {
XELOGI("Debug client requested exit");
exit(1);
} break;
}
SendSuccess(packet->request_id);
} break;
case PacketType::kModuleListRequest: {
packet_writer_.Begin(PacketType::kModuleListResponse);
auto body = packet_writer_.Append<ModuleListResponse>();
auto modules =
object_table->GetObjectsByType<XModule>(XObject::Type::kTypeModule);
body->count = uint32_t(modules.size());
for (auto& module : modules) {
auto entry = packet_writer_.Append<ModuleListEntry>();
entry->module_handle = module->handle();
entry->module_ptr = module->hmodule_ptr();
entry->is_kernel_module =
module->module_type() == XModule::ModuleType::kKernelModule;
std::strncpy(entry->path, module->path().c_str(),
xe::countof(entry->path));
std::strncpy(entry->name, module->name().c_str(),
xe::countof(entry->name));
}
packet_writer_.End();
} break;
case PacketType::kThreadListRequest: {
packet_writer_.Begin(PacketType::kThreadListResponse);
auto body = packet_writer_.Append<ThreadListResponse>();
auto threads =
object_table->GetObjectsByType<XThread>(XObject::Type::kTypeThread);
body->count = uint32_t(threads.size());
for (auto& thread : threads) {
auto entry = packet_writer_.Append<ThreadListEntry>();
entry->thread_handle = thread->handle();
entry->thread_id = thread->thread_id();
entry->is_host_thread = !thread->is_guest_thread();
std::strncpy(entry->name, thread->name().c_str(),
xe::countof(entry->name));
entry->exit_code;
entry->priority = thread->priority();
entry->xapi_thread_startup =
thread->creation_params()->xapi_thread_startup;
entry->start_address = thread->creation_params()->start_address;
entry->start_context = thread->creation_params()->start_context;
entry->creation_flags = thread->creation_params()->creation_flags;
entry->stack_address = thread->thread_state()->stack_address();
entry->stack_size = thread->thread_state()->stack_size();
entry->stack_base = thread->thread_state()->stack_base();
entry->stack_limit = thread->thread_state()->stack_limit();
entry->pcr_address = thread->pcr_ptr();
entry->tls_address = thread->tls_ptr();
}
packet_writer_.End();
} break;
case PacketType::kThreadStatesRequest: {
packet_writer_.Begin(PacketType::kThreadStatesResponse);
auto body = packet_writer_.Append<ThreadStatesResponse>();
auto stack_walker = emulator->processor()->stack_walker();
auto threads =
emulator->kernel_state()->object_table()->GetObjectsByType<XThread>(
XObject::kTypeThread);
body->count = uint32_t(threads.size());
uint64_t frame_host_pcs[64];
cpu::StackFrame frames[64];
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;
size_t count = stack_walker->CaptureStackTrace(
thread->GetWaitHandle()->native_handle(), frame_host_pcs, 0, 64,
&thread_entry_body->host_context, &hash);
stack_walker->ResolveStack(frame_host_pcs, frames, count);
// 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];
auto frame_body = packet_writer_.Append<ThreadCallStackFrame>();
frame_body->host_pc = frame.host_pc;
frame_body->host_function_address = frame.host_symbol.address;
frame_body->guest_pc = frame.guest_pc;
frame_body->guest_function_address = 0;
auto function = frame.guest_symbol.function;
if (frame.type == cpu::StackFrame::Type::kGuest && function) {
frame_body->guest_function_address = function->address();
std::strncpy(frame_body->name, function->name().c_str(),
xe::countof(frame_body->name));
} else {
std::strncpy(frame_body->name, frame.host_symbol.name,
xe::countof(frame_body->name));
}
}
}
packet_writer_.End();
} break;
default: {
XELOGE("Unknown packet type");
SendError(packet->request_id, "Unknown packet type");
return false;
} break;
}
return true;
}
void DebugServer::OnExecutionContinued() {
packet_writer_.Begin(PacketType::kExecutionNotification);
auto body = packet_writer_.Append<ExecutionNotification>();
body->current_state = ExecutionNotification::State::kRunning;
packet_writer_.End();
Flush();
}
void DebugServer::OnExecutionInterrupted() {
packet_writer_.Begin(PacketType::kExecutionNotification);
auto body = packet_writer_.Append<ExecutionNotification>();
body->current_state = ExecutionNotification::State::kStopped;
body->stop_reason = ExecutionNotification::Reason::kInterrupt;
packet_writer_.End();
Flush();
}
void DebugServer::Flush() {
if (!packet_writer_.buffer_offset()) {
return;
}
client_->Send(packet_writer_.buffer(), packet_writer_.buffer_offset());
packet_writer_.Reset();
}
void DebugServer::SendSuccess(proto::request_id_t request_id) {
packet_writer_.Begin(PacketType::kGenericResponse, request_id);
auto body = packet_writer_.Append<GenericResponse>();
body->code = GenericResponse::Code::kSuccess;
packet_writer_.End();
Flush();
}
void DebugServer::SendError(proto::request_id_t request_id,
const char* error_message) {
packet_writer_.Begin(PacketType::kGenericResponse, request_id);
auto body = packet_writer_.Append<GenericResponse>();
body->code = GenericResponse::Code::kError;
packet_writer_.AppendString(error_message ? error_message : "");
packet_writer_.End();
Flush();
}
void DebugServer::SendError(proto::request_id_t request_id,
const std::string& error_message) {
packet_writer_.Begin(PacketType::kGenericResponse, request_id);
auto body = packet_writer_.Append<GenericResponse>();
body->code = GenericResponse::Code::kError;
packet_writer_.AppendString(error_message);
packet_writer_.End();
Flush();
}
} // namespace debug
} // namespace xe

View File

@ -1,88 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_DEBUG_SERVER_H_
#define XENIA_DEBUG_DEBUG_SERVER_H_
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "xenia/base/socket.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/processor.h"
#include "xenia/debug/breakpoint.h"
#include "xenia/debug/proto/packet_reader.h"
#include "xenia/debug/proto/packet_writer.h"
#include "xenia/debug/proto/xdp_protocol.h"
namespace xe {
namespace debug {
class Debugger;
class DebugServer {
public:
explicit DebugServer(Debugger* debugger);
~DebugServer();
Debugger* debugger() const { return debugger_; }
Socket* client() const { return client_.get(); }
bool Initialize();
void PostSynchronous(std::function<void()> fn);
// TODO(benvanik): better thread type (XThread?)
// void OnThreadCreated(ThreadState* thread_state);
// void OnThreadDestroyed(ThreadState* thread_state);
void OnExecutionContinued();
void OnExecutionInterrupted();
/*void OnBreakpointHit(xe::cpu::ThreadState* thread_state,
Breakpoint* breakpoint);*/
private:
void AcceptClient(std::unique_ptr<Socket> client);
bool HandleClientEvent();
bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length);
bool ProcessPacket(const proto::Packet* packet);
void Flush();
void SendSuccess(proto::request_id_t request_id);
void SendError(proto::request_id_t request_id,
const char* error_message = nullptr);
void SendError(proto::request_id_t request_id,
const std::string& error_message);
Debugger* debugger_ = nullptr;
std::unique_ptr<SocketServer> socket_server_;
std::unique_ptr<Socket> client_;
std::unique_ptr<xe::threading::Thread> client_thread_;
std::mutex post_mutex_;
std::unique_ptr<xe::threading::Event> post_event_;
std::list<std::function<void()>> post_queue_;
std::vector<uint8_t> receive_buffer_;
proto::PacketReader packet_reader_;
proto::PacketWriter packet_writer_;
};
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_DEBUG_SERVER_H_

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,7 @@
#include <unordered_map>
#include <vector>
#include "xenia/base/exception_handler.h"
#include "xenia/base/mapped_memory.h"
#include "xenia/base/mutex.h"
#include "xenia/base/threading.h"
@ -38,11 +39,127 @@ class XThread;
namespace xe {
namespace debug {
class DebugServer;
// Describes the current state of the debug target as known to the debugger.
// This determines which state the debugger is in and what operations are
// allowed.
enum class ExecutionState {
// Target is running; the debugger is not waiting for any events.
kRunning,
kStopped,
// Target is running in stepping mode with the debugger waiting for feedback.
kStepping,
// Target is paused for debugging.
kPaused,
// Target has been stopped and cannot be restarted (crash, etc).
kEnded,
};
// Per-XThread structure holding debugger state and a cache of the sampled call
// stack.
//
// In most cases debug consumers should rely only on data in this structure as
// it is never removed (even when a thread is destroyed) and always available
// even when running.
struct ThreadExecutionInfo {
ThreadExecutionInfo();
~ThreadExecutionInfo();
enum class State {
// Thread is alive and running.
kAlive,
// Thread is in a wait state.
kWaiting,
// Thread has exited but not yet been killed.
kExited,
// Thread has been killed.
kZombie,
};
// XThread::handle() of the thread.
uint32_t thread_handle = 0;
// Target XThread, if it has not been destroyed.
// TODO(benvanik): hold a ref here to keep zombie threads around?
kernel::XThread* thread = nullptr;
// Current state of the thread.
State state = State::kAlive;
// Whether the debugger has forcefully suspended this thread.
bool suspended = false;
// A breakpoint managed by the stepping system, installed as required to
// trigger a break at the next instruction.
std::unique_ptr<StepBreakpoint> step_breakpoint;
// A breakpoint managed by the stepping system, installed as required to
// trigger after a step over a disabled breakpoint.
// When this breakpoint is hit the breakpoint referenced in
// restore_original_breakpoint will be reinstalled.
// Breakpoint restore_step_breakpoint;
// If the thread is stepping over a disabled breakpoint this will point to
// that breakpoint so it can be restored.
// Breakpoint* restore_original_breakpoint = nullptr;
// Last-sampled PPC context.
// This is updated whenever the debugger stops.
xe::cpu::frontend::PPCContext guest_context;
// Last-sampled host x64 context.
// This is updated whenever the debugger stops and must be used instead of any
// value taken from the StackWalker as it properly respects exception stacks.
X64Context host_context;
// A single frame in a call stack.
struct Frame {
// PC of the current instruction in host code.
uint64_t host_pc = 0;
// Base of the function the current host_pc is located within.
uint64_t host_function_address = 0;
// PC of the current instruction in guest code.
// 0 if not a guest address or not known.
uint32_t guest_pc = 0;
// Base of the function the current guest_pc is located within.
uint32_t guest_function_address = 0;
// Function the current guest_pc is located within.
cpu::Function* guest_function = nullptr;
// Name of the function, if known.
// TODO(benvanik): string table?
char name[256] = {0};
};
// Last-sampled call stack.
// This is updated whenever the debugger stops.
std::vector<Frame> frames;
};
// Debug event listener interface.
// Implementations will receive calls from arbitrary threads and must marshal
// them as required.
class DebugListener {
public:
// Handles request for debugger focus (such as when the user requests the
// debugger be brought to the front).
virtual void OnFocus() = 0;
// Handles the debugger detaching from the target.
// This will be called on shutdown or when the user requests the debug session
// end.
virtual void OnDetached() = 0;
// Handles execution being interrupted and transitioning to
// ExceutionState::kPaused.
virtual void OnExecutionPaused() = 0;
// Handles execution continuing when transitioning to
// ExecutionState::kRunning or ExecutionState::kStepping.
virtual void OnExecutionContinued() = 0;
// Handles execution ending when transitioning to ExecutionState::kEnded.
virtual void OnExecutionEnded() = 0;
// Handles step completion events when a requested step operation completes.
// The thread is the one that hit its step target. Note that because multiple
// threads could be stepping simultaneously (such as a run-to-cursor) use the
// thread passed instead of keeping any other state.
virtual void OnStepCompleted(xe::kernel::XThread* thread) = 0;
// Handles breakpoint events as they are hit per-thread.
// Breakpoints may be hit during stepping.
virtual void OnBreakpointHit(Breakpoint* breakpoint,
xe::kernel::XThread* thread) = 0;
};
class Debugger {
@ -52,63 +169,147 @@ class Debugger {
Emulator* emulator() const { return emulator_; }
// True if a debug listener is attached and the debugger is active.
bool is_attached() const { return !!debug_listener_; }
// Gets the active debug listener, if any.
DebugListener* debug_listener() const { return debug_listener_; }
// Sets the active debug listener, if any.
// This can be used to detach the listener.
void set_debug_listener(DebugListener* debug_listener);
// Sets a handler that will be called from a random thread when a debugger
// listener is required (such as on a breakpoint hit/etc).
// Will only be called if the debug listener has not already been specified
// with set_debug_listener.
void set_debug_listener_request_handler(
std::function<DebugListener*(Debugger*)> handler) {
debug_listener_handler_ = std::move(handler);
}
// The current execution state of the target.
ExecutionState execution_state() const { return execution_state_; }
// TODO(benvanik): remove/cleanup.
bool StartSession();
void PreLaunch();
void StopSession();
void FlushSession();
// TODO(benvanik): hide.
uint8_t* AllocateFunctionData(size_t size);
uint8_t* AllocateFunctionTraceData(size_t size);
bool is_attached() const { return is_attached_; }
void set_attached(bool attached);
// Returns a list of debugger info for all threads that have ever existed.
// This is the preferred way to sample thread state vs. attempting to ask
// the kernel.
std::vector<ThreadExecutionInfo*> QueryThreadExecutionInfos();
// Returns the debugger info for the given thread.
ThreadExecutionInfo* QueryThreadExecutionInfo(uint32_t thread_handle);
ExecutionState execution_state() const { return execution_state_; }
void DumpThreadStacks();
int AddBreakpoint(Breakpoint* breakpoint);
int RemoveBreakpoint(Breakpoint* breakpoint);
void FindBreakpoints(uint32_t address,
std::vector<Breakpoint*>* out_breakpoints);
// Adds a breakpoint to the debugger and activates it (if enabled).
// The given breakpoint will not be owned by the debugger and must remain
// allocated so long as it is added.
void AddBreakpoint(Breakpoint* breakpoint);
// Removes a breakpoint from the debugger and deactivates it.
void RemoveBreakpoint(Breakpoint* breakpoint);
// Returns all currently registered breakpoints.
const std::vector<Breakpoint*>& breakpoints() const { return breakpoints_; }
// TODO(benvanik): utility functions for modification (make function ignored,
// etc).
void Interrupt();
void Continue();
void StepOne(uint32_t thread_id);
void StepTo(uint32_t thread_id, uint32_t target_pc);
// Shows the debug listener, focusing it if it already exists.
void Show();
// Pauses target execution by suspending all threads.
// The debug listener will be requested if it has not been attached.
void Pause();
// Continues target execution from wherever it is.
// This will cancel any active step operations and resume all threads.
void Continue();
// Steps the given thread a single PPC guest instruction.
// If the step is over a branch the branch will be followed.
void StepGuestInstruction(uint32_t thread_handle);
// Steps the given thread a single x64 host instruction.
// If the step is over a branch the branch will be followed.
void StepHostInstruction(uint32_t thread_handle);
public:
// TODO(benvanik): hide.
void OnThreadCreated(xe::kernel::XThread* thread);
void OnThreadExit(xe::kernel::XThread* thread);
void OnThreadDestroyed(xe::kernel::XThread* thread);
void OnThreadEnteringWait(xe::kernel::XThread* thread);
void OnThreadLeavingWait(xe::kernel::XThread* thread);
void OnFunctionDefined(cpu::Function* function);
void OnFunctionDefined(xe::cpu::Function* function);
void OnBreakpointHit(xe::kernel::XThread* thread, Breakpoint* breakpoint);
bool OnUnhandledException(Exception* ex);
private:
// Synchronously demands a debug listener.
void DemandDebugListener();
// Suspends all known threads (except the caller).
bool SuspendAllThreads();
bool ResumeThread(uint32_t thread_id);
// Resumes the given thread.
bool ResumeThread(uint32_t thread_handle);
// Resumes all known threads (except the caller).
bool ResumeAllThreads();
// Updates all cached thread execution info (state, call stacks, etc).
// The given override thread handle and context will be used in place of
// sampled values for that thread.
void UpdateThreadExecutionStates(uint32_t override_handle = 0,
X64Context* override_context = nullptr);
// Suspends all breakpoints, uninstalling them as required.
// No breakpoints will be triggered until they are resumed.
void SuspendAllBreakpoints();
// Resumes all breakpoints, re-installing them if required.
void ResumeAllBreakpoints();
static bool ExceptionCallbackThunk(Exception* ex, void* data);
bool ExceptionCallback(Exception* ex);
void OnStepCompleted(ThreadExecutionInfo* thread_info);
void OnBreakpointHit(ThreadExecutionInfo* thread_info,
Breakpoint* breakpoint);
// Calculates the next guest instruction based on the current thread state and
// current PC. This will look for branches and other control flow
// instructions.
uint32_t CalculateNextGuestInstruction(ThreadExecutionInfo* thread_info,
uint32_t current_pc);
// Calculates the next host instruction based on the current thread state and
// current PC. This will look for branches and other control flow
// instructions.
uint64_t CalculateNextHostInstruction(ThreadExecutionInfo* thread_info,
uint64_t current_pc);
Emulator* emulator_ = nullptr;
std::unique_ptr<DebugServer> server_;
bool is_attached_ = false;
xe::threading::Fence attach_fence_;
// TODO(benvanik): move this to the CPU backend and remove x64-specific stuff?
uintptr_t capstone_handle_ = 0;
std::function<DebugListener*(Debugger*)> debug_listener_handler_;
DebugListener* debug_listener_ = nullptr;
// TODO(benvanik): cleanup - maybe remove now that in-process?
std::wstring functions_path_;
std::unique_ptr<ChunkedMappedMemoryWriter> functions_file_;
std::wstring functions_trace_path_;
std::unique_ptr<ChunkedMappedMemoryWriter> functions_trace_file_;
std::recursive_mutex mutex_;
ExecutionState execution_state_ = ExecutionState::kStopped;
xe::global_critical_region global_critical_region_;
std::multimap<uint32_t, Breakpoint*> breakpoints_;
ExecutionState execution_state_ = ExecutionState::kPaused;
// Maps thread ID to state. Updated on thread create, and threads are never
// removed.
std::map<uint32_t, std::unique_ptr<ThreadExecutionInfo>>
thread_execution_infos_;
// TODO(benvanik): cleanup/change structures.
std::vector<Breakpoint*> breakpoints_;
};
} // namespace debug

View File

@ -16,4 +16,3 @@ project("xenia-debug")
project_root.."/build_tools/third_party/gflags/src",
})
local_platform_files()
recursive_platform_files("proto")

View File

@ -1,161 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_PROTO_PACKET_READER_H_
#define XENIA_DEBUG_PROTO_PACKET_READER_H_
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/debug/proto/varint.h"
#include "xenia/debug/proto/xdp_protocol.h"
namespace xe {
namespace debug {
namespace proto {
class PacketReader {
public:
explicit PacketReader(size_t buffer_capacity) : buffer_(buffer_capacity) {}
const uint8_t* buffer() { return buffer_.data(); }
size_t buffer_capacity() const { return buffer_.size(); }
size_t buffer_offset() const { return buffer_offset_; }
void Reset() {
buffer_offset_ = 0;
buffer_size_ = 0;
}
void AppendBuffer(const uint8_t* buffer, size_t buffer_length) {
if (buffer_size_ + buffer_length > buffer_.size()) {
size_t new_size =
std::max(buffer_.size() * 2,
xe::round_up(buffer_size_ + buffer_length, 16 * 1024));
buffer_.resize(new_size);
}
std::memcpy(buffer_.data() + buffer_size_, buffer, buffer_length);
buffer_size_ += buffer_length;
}
void Compact() {
assert_null(current_packet_);
if (!buffer_offset_) {
return;
}
std::memmove(buffer_.data(), buffer_.data() + buffer_offset_,
buffer_size_ - buffer_offset_);
buffer_size_ -= buffer_offset_;
buffer_offset_ = 0;
}
const Packet* Begin() {
assert_null(current_packet_);
size_t bytes_remaining = buffer_size_ - buffer_offset_;
if (bytes_remaining < sizeof(Packet)) {
return nullptr;
}
auto packet =
reinterpret_cast<const Packet*>(buffer_.data() + buffer_offset_);
if (bytes_remaining < sizeof(Packet) + packet->body_length) {
return nullptr;
}
packet_offset_ = buffer_offset_ + sizeof(Packet);
buffer_offset_ += sizeof(Packet) + packet->body_length;
current_packet_ = packet;
return packet;
}
void End() {
assert_not_null(current_packet_);
current_packet_ = nullptr;
packet_offset_ = 0;
}
std::unique_ptr<PacketReader> ClonePacket() {
assert_not_null(current_packet_);
size_t length = sizeof(Packet) + current_packet_->body_length;
auto clone = std::make_unique<PacketReader>(length);
std::memcpy(clone->buffer_.data(), buffer_.data() + buffer_offset_ - length,
length);
clone->buffer_size_ = length;
return clone;
}
const uint8_t* Read(size_t length) {
// Can't read into next packet/off of end.
assert_not_null(current_packet_);
assert_true(packet_offset_ + length <= buffer_offset_);
auto ptr = buffer_.data() + packet_offset_;
packet_offset_ += length;
return ptr;
}
template <typename T>
const T* Read() {
return reinterpret_cast<const T*>(Read(sizeof(T)));
}
template <typename T>
std::vector<const T*> ReadArray(size_t count) {
std::vector<const T*> entries;
for (size_t i = 0; i < count; ++i) {
entries.push_back(Read<T>());
}
return entries;
}
void Read(void* buffer, size_t buffer_length) {
assert_not_null(current_packet_);
auto src = Read(buffer_length);
std::memcpy(buffer, src, buffer_length);
}
void ReadString(char* buffer, size_t buffer_length) {
assert_not_null(current_packet_);
varint_t length;
auto src = length.Read(buffer_.data() + packet_offset_);
assert_not_null(src);
assert_true(length < buffer_length);
assert_true(length.size() + length < (buffer_offset_ - packet_offset_));
std::memcpy(buffer, src, length);
packet_offset_ += length;
}
std::string ReadString() {
assert_not_null(current_packet_);
varint_t length;
auto src = length.Read(buffer_.data() + packet_offset_);
assert_not_null(src);
assert_true(length.size() + length < (buffer_offset_ - packet_offset_));
std::string value;
value.resize(length.value());
std::memcpy(const_cast<char*>(value.data()), src, length);
packet_offset_ += length;
return value;
}
private:
std::vector<uint8_t> buffer_;
size_t buffer_offset_ = 0;
size_t buffer_size_ = 0;
const Packet* current_packet_ = nullptr;
size_t packet_offset_ = 0;
};
} // namespace proto
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_PROTO_PACKET_READER_H_

View File

@ -1,98 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_PROTO_PACKET_WRITER_H_
#define XENIA_DEBUG_PROTO_PACKET_WRITER_H_
#include <algorithm>
#include <string>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/debug/proto/varint.h"
#include "xenia/debug/proto/xdp_protocol.h"
namespace xe {
namespace debug {
namespace proto {
class PacketWriter {
public:
explicit PacketWriter(size_t buffer_capacity) : buffer_(buffer_capacity) {}
uint8_t* buffer() { return buffer_.data(); }
size_t buffer_capacity() const { return buffer_.size(); }
size_t buffer_offset() const { return buffer_offset_; }
void Reset() { buffer_offset_ = 0; }
void Begin(PacketType packet_type, request_id_t request_id = 0) {
assert_null(current_packet_);
current_packet_ = Append<Packet>();
current_packet_->packet_type = packet_type;
current_packet_->request_id = request_id;
current_packet_->body_length = 0;
}
void End() {
assert_not_null(current_packet_);
current_packet_->body_length =
uint32_t((buffer_.data() + buffer_offset_) -
reinterpret_cast<uint8_t*>(current_packet_) - sizeof(Packet));
current_packet_ = nullptr;
}
uint8_t* Append(size_t length) {
if (buffer_offset_ + length > buffer_.size()) {
size_t new_size = std::max(
xe::round_up(buffer_offset_ + length, 16 * 1024), buffer_.size() * 2);
buffer_.resize(new_size);
}
auto ptr = buffer_.data() + buffer_offset_;
buffer_offset_ += length;
return ptr;
}
template <typename T>
T* Append() {
return reinterpret_cast<T*>(Append(sizeof(T)));
}
void Append(const void* buffer, size_t buffer_length) {
auto dest = Append(buffer_length);
std::memcpy(dest, buffer, buffer_length);
}
void AppendString(const char* value) {
size_t value_length = std::strlen(value);
varint_t length(value_length);
auto dest = Append(length.size() + value_length);
dest = length.Write(dest);
std::memcpy(dest, value, value_length);
}
void AppendString(const std::string& value) {
varint_t length(value.size());
auto dest = Append(length.size() + value.size());
dest = length.Write(dest);
std::memcpy(dest, value.data(), value.size());
}
private:
std::vector<uint8_t> buffer_;
size_t buffer_offset_ = 0;
Packet* current_packet_ = nullptr;
};
} // namespace proto
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_PROTO_PACKET_WRITER_H_

View File

@ -1,74 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_PROTO_VARINT_H_
#define XENIA_DEBUG_PROTO_VARINT_H_
#include <cstdint>
namespace xe {
namespace debug {
namespace proto {
struct varint_t {
varint_t() = default;
varint_t(uint64_t value) : value_(value) {} // NOLINT(runtime/explicit)
varint_t(const varint_t& other) : value_(other.value_) {}
operator uint64_t() const { return value_; }
uint64_t value() const { return value_; }
size_t size() const {
uint64_t value = value_;
size_t i = 0;
do {
value >>= 7;
++i;
} while (value >= 0x80);
return i + 1;
}
const uint8_t* Read(const uint8_t* src) {
if (!(src[0] & 0x80)) {
value_ = src[0];
return src + 1;
}
uint64_t r = src[0] & 0x7F;
size_t i;
for (i = 1; i < 10; ++i) {
r |= (src[i] & 0x7F) << (7 * i);
if (!(src[i] & 0x80)) {
break;
}
}
value_ = r;
return src + i + 1;
}
uint8_t* Write(uint8_t* dest) {
uint64_t value = value_;
size_t i = 0;
do {
dest[i] = uint8_t(value | 0x80);
value >>= 7;
++i;
} while (value >= 0x80);
dest[i] = uint8_t(value);
return dest + i + 1;
}
private:
uint64_t value_ = 0;
};
} // namespace proto
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_PROTO_VARINT_H_

View File

@ -1,210 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_PROTO_XDP_PROTOCOL_H_
#define XENIA_DEBUG_PROTO_XDP_PROTOCOL_H_
#include <cstdint>
#include "xenia/base/vec128.h"
#include "xenia/cpu/frontend/ppc_context.h"
#include "xenia/cpu/stack_walker.h"
namespace xe {
namespace debug {
namespace proto {
enum class PacketType : uint16_t {
kUnknown = 0,
kGenericResponse = 1,
kExecutionRequest = 10,
kExecutionNotification = 11,
kModuleListRequest = 20,
kModuleListResponse = 21,
kThreadListRequest = 30,
kThreadListResponse = 31,
kThreadStatesRequest = 32,
kThreadStatesResponse = 33,
};
using request_id_t = uint16_t;
struct Packet {
PacketType packet_type;
request_id_t request_id;
uint32_t body_length;
uint8_t* body() { return reinterpret_cast<uint8_t*>(this) + sizeof(this); }
};
static_assert(sizeof(Packet) == 8, "Fixed packet size");
// Generic response to requests (S->C).
// Used for any request type that doesn't define its own response.
struct GenericResponse {
static const PacketType type = PacketType::kGenericResponse;
enum class Code : uint32_t {
// Command was received and successfully acted upon.
// If the request is handled asynchronously this only indicates that the
// command was recognized.
kSuccess = 0,
// Command was invalid or could not be executed.
// A string will follow this response containing an error message.
kError = 1,
};
Code code;
bool is_success() const { return code == Code::kSuccess; }
};
// Requests execution state changes (C->S).
// Response is a GenericResponse, and as execution changes one or more
// ExecutionNotification packets will be sent S->C.
struct ExecutionRequest {
static const PacketType type = PacketType::kExecutionRequest;
enum class Action : uint32_t {
// Continues execution until the next breakpoint.
kContinue,
// Steps execution by the given step_mode.
kStep,
// Interrupts execution and breaks.
kInterrupt,
// Stops execution and causes the server to exit.
kExit,
};
enum class StepMode : uint32_t {
// Steps a single statement, following branches.
kStepOne,
// Runs until the given target_pc.
kStepTo,
};
Action action;
StepMode step_mode;
uint32_t thread_id;
uint32_t target_pc;
};
// Notifies clients of changes in execution state (S->C).
// This is fired asynchronously from any request and possibly due to events
// external to the client requested ones.
// After stopping clients should query execution state.
struct ExecutionNotification {
static const PacketType type = PacketType::kExecutionNotification;
enum class State : uint32_t {
// Execution has stopped for stop_reason.
kStopped,
// Execution has resumed and the target is now running.
kRunning,
// The target has exited.
kExited,
};
enum class Reason : uint32_t {
// Stopped because the client requested it with Action::kInterrupt.
kInterrupt,
// Stopped due to a completed step.
kStep,
// Stopped at a breakpoint.
kBreakpoint,
// TODO(benvanik): others? catches? library loads? etc?
};
State current_state;
Reason stop_reason;
};
struct ModuleListRequest {
static const PacketType type = PacketType::kModuleListRequest;
};
struct ModuleListResponse {
static const PacketType type = PacketType::kModuleListResponse;
uint32_t count;
// ModuleListEntry[count]
};
struct ModuleListEntry {
uint32_t module_handle;
uint32_t module_ptr;
bool is_kernel_module;
char path[256];
char name[256];
};
struct ThreadListRequest {
static const PacketType type = PacketType::kThreadListRequest;
};
struct ThreadListResponse {
static const PacketType type = PacketType::kThreadListResponse;
uint32_t count;
// ThreadListEntry[count]
};
struct ThreadListEntry {
uint32_t thread_handle;
uint32_t thread_id;
bool is_host_thread;
char name[64];
uint32_t exit_code;
int32_t priority;
uint32_t affinity;
uint32_t xapi_thread_startup;
uint32_t start_address;
uint32_t start_context;
uint32_t creation_flags;
uint32_t stack_address;
uint32_t stack_size;
uint32_t stack_base;
uint32_t stack_limit;
uint32_t pcr_address;
uint32_t tls_address;
};
struct ThreadStatesRequest {
static const PacketType type = PacketType::kThreadStatesRequest;
};
struct ThreadStatesResponse {
static const PacketType type = PacketType::kThreadStatesResponse;
uint32_t count;
// ThreadStateEntry[count]
};
struct ThreadStateEntry {
uint32_t thread_handle;
cpu::frontend::PPCContext guest_context;
cpu::X64Context host_context;
uint32_t frame_count;
// ThreadCallStackFrame[frame_count]
};
struct ThreadCallStackFrame {
// PC of the current instruction in host code.
uint64_t host_pc;
// Base of the function the current host_pc is located within.
uint64_t host_function_address;
// PC of the current instruction in guest code.
// 0 if not a guest address or not known.
uint32_t guest_pc;
// Base of the function the current guest_pc is located within.
uint32_t guest_function_address;
// Name of the function, if known.
// TODO(benvanik): string table?
char name[256];
};
} // namespace proto
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_PROTO_XDP_PROTOCOL_H_

View File

@ -1,44 +0,0 @@
XDP: Xenia Debug Protocol
--------------------------
## Overview
### General
* Packets flow in both directions (client and server).
* Each packet is length-prefixed (exclusive) and tagged with a type.
* The entire protocol is fully asynchronous, and supports both request/response
style RPCs as well as notifications.
* Request/response RPCs are matched on ID and multiple are allowed to be in
flight at a time.
* Notifications may come in any order.
### Server
The debug server is single threaded and processes commands as a FIFO. This
means that all requests will be responded to in the order they are received.
### Client
TODO
## Packet Structure
Each packet consists of a length, type, and request ID. The length denotes
the total number of bytes within the packet body, excluding the packet header.
The request ID is an opaque value sent by the client when making a request and
the server returns whatever the ID is when it responds to that request. The ID
is unused for notifications.
```
struct Packet {
uint16_t packet_type;
uint16_t request_id;
uint32_t body_length;
uint8_t body[body_length];
};
```
## Packets
TODO(benvanik): document the various types, etc.

View File

@ -1,88 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/ui/application.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/base/threading.h"
#include "xenia/debug/ui/main_window.h"
#include "xenia/profiling.h"
namespace xe {
namespace debug {
namespace ui {
Application* current_application_ = nullptr;
Application* Application::current() {
assert_not_null(current_application_);
return current_application_;
}
Application::Application() {
current_application_ = this;
loop_ = xe::ui::Loop::Create();
}
Application::~Application() {
assert_true(current_application_ == this);
current_application_ = nullptr;
}
std::unique_ptr<Application> Application::Create() {
std::unique_ptr<Application> app(new Application());
xe::threading::Fence fence;
app->loop()->Post([&app, &fence]() {
xe::threading::set_name("Win32 Loop");
xe::Profiler::ThreadEnter("Win32 Loop");
if (!app->Initialize()) {
xe::FatalError("Failed to initialize application");
return;
}
fence.Signal();
});
fence.Wait();
return app;
}
bool Application::Initialize() {
// Bind the object model to the client so it'll maintain state.
system_ = std::make_unique<model::System>(loop(), &client_);
// TODO(benvanik): flags and such.
if (!client_.Connect("localhost", 9002)) {
return false;
}
main_window_ = std::make_unique<MainWindow>(this);
if (!main_window_->Initialize()) {
XELOGE("Unable to initialize main window");
return false;
}
return true;
}
void Application::Quit() {
loop_->Quit();
// TODO(benvanik): proper exit.
XELOGI("User-initiated death!");
exit(1);
}
} // namespace ui
} // namespace debug
} // namespace xe

View File

@ -1,55 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_APPLICATION_H_
#define XENIA_DEBUG_UI_APPLICATION_H_
#include <memory>
#include "xenia/debug/debug_client.h"
#include "xenia/debug/ui/model/system.h"
#include "xenia/ui/loop.h"
namespace xe {
namespace debug {
namespace ui {
class MainWindow;
class Application {
public:
~Application();
static std::unique_ptr<Application> Create();
static Application* current();
xe::ui::Loop* loop() { return loop_.get(); }
MainWindow* main_window() const { return main_window_.get(); }
DebugClient* client() { return &client_; }
model::System* system() const { return system_.get(); }
void Quit();
private:
Application();
bool Initialize();
std::unique_ptr<xe::ui::Loop> loop_;
std::unique_ptr<MainWindow> main_window_;
DebugClient client_;
std::unique_ptr<model::System> system_;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_APPLICATION_H_

View File

@ -1,49 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_CONTROL_H_
#define XENIA_DEBUG_UI_CONTROL_H_
#include <memory>
#include <string>
#include "el/elements.h"
#include "el/event_handler.h"
#include "xenia/debug/ui/application.h"
namespace xe {
namespace debug {
namespace ui {
class Control {
public:
virtual ~Control() = default;
el::LayoutBox* root_element() { return &root_element_; }
xe::ui::Loop* loop() const { return Application::current()->loop(); }
DebugClient* client() const { return client_; }
model::System* system() const { return Application::current()->system(); }
virtual el::Element* BuildUI() = 0;
virtual void Setup(xe::debug::DebugClient* client) = 0;
protected:
Control() = default;
el::LayoutBox root_element_;
std::unique_ptr<el::EventHandler> handler_;
xe::debug::DebugClient* client_ = nullptr;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_CONTROL_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,153 @@
/**
******************************************************************************
* 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_DEBUG_WINDOW_H_
#define XENIA_DEBUG_UI_DEBUG_WINDOW_H_
#include <memory>
#include <vector>
#include "xenia/base/x64_context.h"
#include "xenia/debug/debugger.h"
#include "xenia/emulator.h"
#include "xenia/ui/loop.h"
#include "xenia/ui/menu_item.h"
#include "xenia/ui/window.h"
#include "xenia/xbox.h"
namespace xe {
namespace debug {
namespace ui {
class ImGuiRenderer;
class DebugWindow : public DebugListener {
public:
~DebugWindow();
static std::unique_ptr<DebugWindow> Create(Emulator* emulator,
xe::ui::Loop* loop);
Emulator* emulator() const { return emulator_; }
xe::ui::Loop* loop() const { return loop_; }
xe::ui::Window* window() const { return window_.get(); }
void OnFocus() override;
void OnDetached() override;
void OnExecutionPaused() override;
void OnExecutionContinued() override;
void OnExecutionEnded() override;
void OnStepCompleted(xe::kernel::XThread* thread) override;
void OnBreakpointHit(Breakpoint* breakpoint,
xe::kernel::XThread* thread) override;
private:
explicit DebugWindow(Emulator* emulator, xe::ui::Loop* loop);
bool Initialize();
void DrawFrame();
void DrawToolbar();
void DrawFunctionsPane();
void DrawSourcePane();
void DrawGuestFunctionSource();
void DrawMachineCodeSource(const uint8_t* ptr, size_t length);
void DrawBreakpointGutterButton(bool has_breakpoint,
CodeBreakpoint::AddressType address_type,
uint64_t address);
void ScrollToSourceIfPcChanged();
void DrawRegistersPane();
bool DrawRegisterTextBox(int id, uint32_t* value);
bool DrawRegisterTextBox(int id, uint64_t* value);
bool DrawRegisterTextBox(int id, double* value);
bool DrawRegisterTextBoxes(int id, float* value);
void DrawThreadsPane();
void DrawMemoryPane();
void DrawBreakpointsPane();
void DrawLogPane();
void SelectThreadStackFrame(kernel::XThread* thread, size_t stack_frame_index,
bool always_navigate);
void NavigateToFunction(cpu::Function* function, uint32_t guest_pc = 0,
uint64_t host_pc = 0);
// void NavigateToMemory(uint64_t address, uint64_t length = 0);
// void ToggleLogThreadFocus(thread | nullptr);
void UpdateCache();
void CreateCodeBreakpoint(CodeBreakpoint::AddressType address_type,
uint64_t address);
void DeleteCodeBreakpoint(CodeBreakpoint* breakpoint);
void DeleteBreakpoint(Breakpoint* breakpoint);
CodeBreakpoint* LookupBreakpointAtAddress(
CodeBreakpoint::AddressType address_type, uint64_t address);
Emulator* emulator_ = nullptr;
Debugger* debugger_ = nullptr;
xe::ui::Loop* loop_ = nullptr;
std::unique_ptr<xe::ui::Window> window_;
std::unique_ptr<ImGuiRenderer> imgui_renderer_;
uint64_t last_draw_tick_count_ = 0;
uintptr_t capstone_handle_ = 0;
// Cached debugger data, updated on every break before a frame is drawn.
// Prefer putting stuff here that will be queried either each frame or
// multiple times per frame to avoid expensive redundant work.
struct ImDataCache {
bool is_running = false;
std::vector<kernel::object_ref<kernel::XModule>> modules;
std::vector<ThreadExecutionInfo*> thread_execution_infos;
} cache_;
enum class RegisterGroup {
kGuestGeneral,
kGuestFloat,
kGuestVector,
kHostGeneral,
kHostVector,
};
// The current state of the UI. Use this to synchronize multiple parts of the
// UI.
struct ImState {
static const int kRightPaneThreads = 0;
static const int kRightPaneMemory = 1;
int right_pane_tab = kRightPaneThreads;
xe::kernel::XThread* thread = nullptr;
ThreadExecutionInfo* thread_info = nullptr;
size_t thread_stack_frame_index = 0;
bool has_changed_thread = false;
xe::cpu::Function* function = nullptr;
uint64_t last_host_pc = 0;
bool has_changed_pc = false;
int source_display_mode = 3;
RegisterGroup register_group = RegisterGroup::kGuestGeneral;
bool register_input_hex = true;
struct {
char kernel_call_filter[64] = {0};
std::vector<std::unique_ptr<Breakpoint>> all_breakpoints;
std::unordered_map<uint32_t, CodeBreakpoint*>
code_breakpoints_by_guest_address;
std::unordered_map<uintptr_t, CodeBreakpoint*>
code_breakpoints_by_host_address;
} breakpoints;
xe::kernel::XThread* isolated_log_thread = nullptr;
} state_;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_DEBUG_WINDOW_H_

View File

@ -1,32 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include <gflags/gflags.h>
#include <cstring>
#include "xenia/base/main.h"
#include "xenia/debug/ui/application.h"
namespace xe {
namespace debug {
namespace ui {
int main(const std::vector<std::wstring>& args) {
auto app = Application::Create();
app->loop()->AwaitQuit();
return 0;
}
} // namespace ui
} // namespace debug
} // namespace xe
DEFINE_ENTRY_POINT(L"xenia-debug-ui", L"xenia-debug-ui ??",
xe::debug::ui::main);

View File

@ -1,6 +0,0 @@
//{{NO_DEPENDENCIES}}
#include "third_party\\elemental-forms\\resources.rc"
//IDR_xe_debug_ui_resources_skin_bg_tile_png RCDATA ".\\resources\\skin\\bg_tile.png"
IDR_xe_debug_ui_resources_smaller_skin_skin_tb_txt RCDATA ".\\resources\\smaller_skin\\skin.tb.txt"

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,55 @@
/**
******************************************************************************
* 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_IMGUI_RENDERER_H_
#define XENIA_DEBUG_UI_IMGUI_RENDERER_H_
#include <memory>
#include "third_party/imgui/imgui.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/ui/gl/circular_buffer.h"
#include "xenia/ui/gl/gl_context.h"
#include "xenia/ui/window.h"
namespace xe {
namespace debug {
namespace ui {
class ImGuiRenderer {
public:
ImGuiRenderer(xe::ui::Window* window, xe::ui::GraphicsContext* context);
~ImGuiRenderer();
private:
static ImGuiRenderer* global_renderer_;
void Initialize();
void InitializeShaders();
void InitializeFontTextures();
void Shutdown();
void RenderDrawLists(ImDrawData* data);
xe::ui::Window* window_ = nullptr;
xe::ui::GraphicsContext* context_ = nullptr;
GLuint program_ = 0;
GLuint vao_ = 0;
xe::ui::gl::CircularBuffer vertex_buffer_;
xe::ui::gl::CircularBuffer index_buffer_;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_IMGUI_RENDERER_H_

View File

@ -1,197 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/ui/main_window.h"
#include "el/animation_manager.h"
#include "el/io/file_manager.h"
#include "el/util/debug.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/base/threading.h"
#include "xenia/ui/gl/gl_context.h"
#if XE_PLATFORM_WIN32
#include "el/io/win32_res_file_system_win.h"
#endif // XE_PLATFORM_WIN32
namespace xe {
namespace debug {
namespace ui {
using xe::ui::MenuItem;
const std::wstring kBaseTitle = L"xenia debugger";
MainWindow::MainWindow(Application* app) : app_(app) {}
MainWindow::~MainWindow() = default;
bool MainWindow::Initialize() {
client_ = app_->client();
window_ = xe::ui::Window::Create(app()->loop(), kBaseTitle);
if (!window_) {
return false;
}
window_->Initialize();
window_->set_context(xe::ui::gl::GLContext::Create(window_.get()));
#if XE_PLATFORM_WIN32
el::io::FileManager::RegisterFileSystem(
std::make_unique<el::io::Win32ResFileSystem>(
"IDR_xe_debug_ui_resources_"));
#endif // XE_PLATFORM_WIN32
window_->LoadSkin("smaller_skin/skin.tb.txt");
window_->on_closed.AddListener(std::bind(&MainWindow::OnClose, this));
window_->on_key_down.AddListener([this](xe::ui::KeyEvent* e) {
bool handled = true;
switch (e->key_code()) {
case 0x1B: { // VK_ESCAPE
// Allow users to escape fullscreen (but not enter it).
if (window_->is_fullscreen()) {
window_->ToggleFullscreen(false);
}
} break;
case 0x70: { // VK_F1
LaunchBrowser("http://xenia.jp");
} break;
default: { handled = false; } break;
}
e->set_handled(handled);
});
// Main menu.
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&File");
{
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, L"E&xit",
L"Alt+F4",
[this]() { window_->Close(); }));
}
main_menu->AddChild(std::move(file_menu));
// Help menu.
auto help_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&Help");
{
help_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, L"&Website...", L"F1",
[]() { LaunchBrowser("http://xenia.jp"); }));
help_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, L"&About...",
[]() { LaunchBrowser("http://xenia.jp/about/"); }));
}
main_menu->AddChild(std::move(help_menu));
window_->set_main_menu(std::move(main_menu));
window_->Resize(1440, 1200);
BuildUI();
return true;
}
void MainWindow::BuildUI() {
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto root_element = window_->root_element();
form_ = std::make_unique<el::Form>();
form_->set_settings(el::FormSettings::kFullScreen);
root_element->AddChild(form_.get());
auto root_node =
LayoutBoxNode()
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kY)
.child(
LayoutBoxNode()
.id("toolbar_box")
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kGravity)
.distribution_position(LayoutDistributionPosition::kLeftTop)
.child(ButtonNode("Pause").id("pause_button"))
.child(ButtonNode("Continue").id("resume_button")))
.child(
SplitContainerNode()
.id("split_container")
.gravity(Gravity::kAll)
.axis(Axis::kX)
.fixed_pane(FixedPane::kSecond)
.min(128)
.value(250)
.pane(TabContainerNode()
.id("tab_container")
.gravity(Gravity::kAll)
.align(Align::kTop))
.pane(LayoutBoxNode().id("log_box").gravity(Gravity::kAll)));
form_->LoadNodeTree(root_node);
form_->GetElementsById({
{TBIDC("split_container"), &ui_.split_container},
{TBIDC("toolbar_box"), &ui_.toolbar_box},
{TBIDC("tab_container"), &ui_.tab_container},
});
handler_ = std::make_unique<el::EventHandler>(form_.get());
handler_->Listen(el::EventType::kClick, TBIDC("pause_button"),
[this](const el::Event& ev) {
client_->Interrupt();
return true;
});
handler_->Listen(el::EventType::kClick, TBIDC("resume_button"),
[this](const el::Event& ev) {
client_->Continue();
return true;
});
system()->on_execution_state_changed.AddListener(
[this]() { UpdateElementState(); });
ui_.tab_container->tab_bar()->LoadNodeTree(ButtonNode(cpu_view_.name()));
ui_.tab_container->content_root()->AddChild(cpu_view_.BuildUI());
cpu_view_.Setup(Application::current()->client());
ui_.tab_container->tab_bar()->LoadNodeTree(ButtonNode(gpu_view_.name()));
ui_.tab_container->content_root()->AddChild(gpu_view_.BuildUI());
gpu_view_.Setup(Application::current()->client());
UpdateElementState();
el::util::ShowDebugInfoSettingsForm(root_element);
}
void MainWindow::UpdateElementState() {
bool is_running = client_->execution_state() == ExecutionState::kRunning;
el::TabContainer* tab_container;
el::Button* pause_button;
el::Button* resume_button;
form_->GetElementsById({
{TBIDC("tab_container"), &tab_container},
{TBIDC("pause_button"), &pause_button},
{TBIDC("resume_button"), &resume_button},
});
tab_container->set_enabled(!is_running);
pause_button->set_enabled(is_running);
resume_button->set_enabled(!is_running);
}
void MainWindow::OnClose() { app_->Quit(); }
} // namespace ui
} // namespace debug
} // namespace xe

View File

@ -1,63 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_MAIN_WINDOW_H_
#define XENIA_DEBUG_UI_MAIN_WINDOW_H_
#include <memory>
#include "el/elements.h"
#include "xenia/debug/ui/application.h"
#include "xenia/debug/ui/views/cpu/cpu_view.h"
#include "xenia/debug/ui/views/gpu/gpu_view.h"
#include "xenia/ui/window.h"
namespace xe {
namespace debug {
namespace ui {
class MainWindow {
public:
explicit MainWindow(Application* app);
~MainWindow();
Application* app() const { return app_; }
xe::ui::Loop* loop() const { return app_->loop(); }
model::System* system() const { return app_->system(); }
bool Initialize();
// void NavigateToCpuView(uint32_t address);
private:
void BuildUI();
void UpdateElementState();
void OnClose();
Application* app_ = nullptr;
xe::debug::DebugClient* client_ = nullptr;
std::unique_ptr<xe::ui::Window> window_;
std::unique_ptr<el::Form> form_;
struct {
el::SplitContainer* split_container;
el::LayoutBox* toolbar_box;
el::TabContainer* tab_container;
} ui_ = {0};
std::unique_ptr<el::EventHandler> handler_;
views::cpu::CpuView cpu_view_;
views::gpu::GpuView gpu_view_;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_MAIN_WINDOW_H_

View File

@ -1,22 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/ui/model/function.h"
namespace xe {
namespace debug {
namespace ui {
namespace model {
//
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe

View File

@ -1,29 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_MODEL_FUNCTION_H_
#define XENIA_DEBUG_UI_MODEL_FUNCTION_H_
#include <cstdint>
namespace xe {
namespace debug {
namespace ui {
namespace model {
class Function {
public:
};
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_MODEL_FUNCTION_H_

View File

@ -1,26 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/ui/model/module.h"
#include "xenia/debug/ui/model/system.h"
namespace xe {
namespace debug {
namespace ui {
namespace model {
void Module::Update(const proto::ModuleListEntry* entry) {
std::memcpy(&entry_, entry, sizeof(entry_));
}
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe

View File

@ -1,50 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_MODEL_MODULE_H_
#define XENIA_DEBUG_UI_MODEL_MODULE_H_
#include <cstdint>
#include <string>
#include "xenia/debug/proto/xdp_protocol.h"
namespace xe {
namespace debug {
namespace ui {
namespace model {
class System;
class Module {
public:
explicit Module(System* system) : system_(system) {}
bool is_dead() const { return is_dead_; }
void set_dead(bool is_dead) { is_dead_ = is_dead; }
uint32_t module_handle() const { return entry_.module_handle; }
bool is_kernel_module() const { return entry_.is_kernel_module; }
std::string name() const { return entry_.name; }
const proto::ModuleListEntry* entry() const { return &entry_; }
void Update(const proto::ModuleListEntry* entry);
private:
System* system_ = nullptr;
bool is_dead_ = false;
proto::ModuleListEntry entry_ = {0};
};
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_MODEL_MODULE_H_

View File

@ -1,128 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/ui/model/system.h"
#include <unordered_set>
namespace xe {
namespace debug {
namespace ui {
namespace model {
using namespace xe::debug::proto; // NOLINT(build/namespaces)
System::System(xe::ui::Loop* loop, DebugClient* client)
: loop_(loop), client_(client) {
client_->set_listener(this);
client_->set_loop(loop);
}
System::~System() { client_->set_listener(nullptr); }
ExecutionState System::execution_state() { return client_->execution_state(); }
std::vector<Module*> System::modules() {
std::vector<Module*> result;
for (auto& module : modules_) {
result.push_back(module.get());
}
return result;
}
std::vector<Thread*> System::threads() {
std::vector<Thread*> result;
for (auto& thread : threads_) {
result.push_back(thread.get());
}
return result;
}
Module* System::GetModuleByHandle(uint32_t module_handle) {
auto it = modules_by_handle_.find(module_handle);
return it != modules_by_handle_.end() ? it->second : nullptr;
}
Thread* System::GetThreadByHandle(uint32_t thread_handle) {
auto it = threads_by_handle_.find(thread_handle);
return it != threads_by_handle_.end() ? it->second : nullptr;
}
void System::OnExecutionStateChanged(ExecutionState execution_state) {
on_execution_state_changed();
}
void System::OnModulesUpdated(std::vector<const ModuleListEntry*> entries) {
std::unordered_set<uint32_t> extra_modules;
for (size_t i = 0; i < modules_.size(); ++i) {
extra_modules.emplace(modules_[i]->module_handle());
}
for (auto entry : entries) {
auto existing_module = modules_by_handle_.find(entry->module_handle);
if (existing_module == modules_by_handle_.end()) {
auto module = std::make_unique<Module>(this);
module->Update(entry);
modules_by_handle_.emplace(entry->module_handle, module.get());
modules_.emplace_back(std::move(module));
} else {
existing_module->second->Update(entry);
extra_modules.erase(existing_module->first);
}
}
for (auto module_handle : extra_modules) {
auto module = modules_by_handle_.find(module_handle);
if (module != modules_by_handle_.end()) {
module->second->set_dead(true);
}
}
on_modules_updated();
}
void System::OnThreadsUpdated(std::vector<const ThreadListEntry*> entries) {
std::unordered_set<uint32_t> extra_threads;
for (size_t i = 0; i < threads_.size(); ++i) {
extra_threads.emplace(threads_[i]->thread_handle());
}
for (auto entry : entries) {
auto existing_thread = threads_by_handle_.find(entry->thread_handle);
if (existing_thread == threads_by_handle_.end()) {
auto thread = std::make_unique<Thread>(this);
thread->Update(entry);
threads_by_handle_.emplace(entry->thread_handle, thread.get());
threads_.emplace_back(std::move(thread));
} else {
existing_thread->second->Update(entry);
extra_threads.erase(existing_thread->first);
}
}
for (auto thread_handle : extra_threads) {
auto thread = threads_by_handle_.find(thread_handle);
if (thread != threads_by_handle_.end()) {
thread->second->set_dead(true);
}
}
on_threads_updated();
}
void System::OnThreadStateUpdated(
uint32_t thread_handle, const ThreadStateEntry* entry,
std::vector<const ThreadCallStackFrame*> frames) {
auto thread = threads_by_handle_[thread_handle];
if (thread != nullptr) {
thread->UpdateState(entry, std::move(frames));
on_thread_state_updated(thread);
}
}
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe

View File

@ -1,75 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_MODEL_SYSTEM_H_
#define XENIA_DEBUG_UI_MODEL_SYSTEM_H_
#include <cstdint>
#include <memory>
#include <unordered_map>
#include <vector>
#include "xenia/base/delegate.h"
#include "xenia/debug/debug_client.h"
#include "xenia/debug/ui/model/function.h"
#include "xenia/debug/ui/model/module.h"
#include "xenia/debug/ui/model/thread.h"
#include "xenia/ui/loop.h"
namespace xe {
namespace debug {
namespace ui {
namespace model {
class System : public DebugClientListener {
public:
System(xe::ui::Loop* loop, DebugClient* client);
~System() override;
xe::ui::Loop* loop() const { return loop_; }
DebugClient* client() const { return client_; }
ExecutionState execution_state();
std::vector<Module*> modules();
std::vector<Thread*> threads();
Module* GetModuleByHandle(uint32_t module_handle);
Thread* GetThreadByHandle(uint32_t thread_handle);
Delegate<void> on_execution_state_changed;
Delegate<void> on_modules_updated;
Delegate<void> on_threads_updated;
Delegate<Thread*> on_thread_state_updated;
private:
void OnExecutionStateChanged(ExecutionState execution_state) override;
void OnModulesUpdated(
std::vector<const proto::ModuleListEntry*> entries) override;
void OnThreadsUpdated(
std::vector<const proto::ThreadListEntry*> entries) override;
void OnThreadStateUpdated(
uint32_t thread_handle, const ThreadStateEntry* entry,
std::vector<const ThreadCallStackFrame*> frames) override;
xe::ui::Loop* loop_ = nullptr;
DebugClient* client_ = nullptr;
std::vector<std::unique_ptr<Module>> modules_;
std::unordered_map<uint32_t, Module*> modules_by_handle_;
std::vector<std::unique_ptr<Thread>> threads_;
std::unordered_map<uint32_t, Thread*> threads_by_handle_;
};
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_MODEL_SYSTEM_H_

View File

@ -1,50 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/ui/model/thread.h"
#include "xenia/debug/ui/model/system.h"
namespace xe {
namespace debug {
namespace ui {
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 value = entry_.name;
if (is_host_thread()) {
value += " (host)";
}
return value;
}
void Thread::Update(const proto::ThreadListEntry* entry) {
std::memcpy(&entry_, entry, sizeof(entry_));
}
void Thread::UpdateState(
const proto::ThreadStateEntry* entry,
std::vector<const proto::ThreadCallStackFrame*> 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));
}
}
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe

View File

@ -1,69 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_MODEL_THREAD_H_
#define XENIA_DEBUG_UI_MODEL_THREAD_H_
#include <cstdint>
#include <string>
#include <vector>
#include "xenia/debug/proto/xdp_protocol.h"
namespace xe {
namespace debug {
namespace ui {
namespace model {
class System;
class Thread {
public:
using Frame = proto::ThreadCallStackFrame;
explicit 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 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_; }
std::string to_string();
void Update(const proto::ThreadListEntry* entry);
void UpdateState(const proto::ThreadStateEntry* entry,
std::vector<const proto::ThreadCallStackFrame*> frames);
private:
System* system_ = nullptr;
bool is_dead_ = false;
proto::ThreadListEntry entry_ = {0};
proto::ThreadStateEntry* state_ = nullptr;
std::vector<Frame> call_stack_;
};
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_MODEL_THREAD_H_

View File

@ -3,56 +3,25 @@ include(project_root.."/build_tools")
group("src")
project("xenia-debug-ui")
uuid("ed128630-8b4e-4dd7-afc9-ef00052fe3a7")
kind("WindowedApp")
uuid("9193a274-f4c2-4746-bd85-93fcfc5c3e38")
kind("StaticLib")
language("C++")
links({
"elemental-forms",
"gflags",
"xenia-apu",
"xenia-apu-nop",
"xenia-apu-xaudio2",
"glew",
"imgui",
"xenia-base",
"xenia-core",
"xenia-cpu",
"xenia-cpu-backend-x64",
"xenia-debug",
"xenia-gpu",
"xenia-gpu-gl4",
"xenia-hid-nop",
"xenia-hid-winkey",
"xenia-hid-xinput",
"xenia-kernel",
"xenia-ui",
"xenia-ui-gl",
"xenia-vfs",
})
flags({
"WinMain", -- Use WinMain instead of main.
})
defines({
"GLEW_STATIC=1",
"GLEW_MX=1",
})
includedirs({
project_root.."/third_party/elemental-forms/src",
project_root.."/build_tools/third_party/gflags/src",
project_root.."/third_party/elemental-forms/src",
})
recursive_platform_files()
files({
"debugger_main.cc",
"../../base/main_"..platform_suffix..".cc",
})
files({
"debugger_resources.rc",
})
resincludedirs({
project_root,
project_root.."/third_party/elemental-forms",
})
filter("platforms:Windows")
debugdir(project_root)
debugargs({
"--flagfile=scratch/flags.txt",
"2>&1",
"1>scratch/stdout-debugger.txt",
})
local_platform_files()

View File

@ -1,11 +0,0 @@
elements
Button
padding 3 4
Button.flat
padding 3 4
ButtonInGroup
padding 3 4
Box
padding 5
ListItem
padding 1 4

View File

@ -1,51 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_VIEW_H_
#define XENIA_DEBUG_UI_VIEW_H_
#include <memory>
#include <string>
#include "el/elements.h"
#include "el/event_handler.h"
#include "xenia/debug/ui/application.h"
namespace xe {
namespace debug {
namespace ui {
class View {
public:
virtual ~View() = default;
std::string name() const { return name_; }
el::LayoutBox* root_element() { return &root_element_; }
xe::ui::Loop* loop() const { return Application::current()->loop(); }
DebugClient* client() const { return client_; }
model::System* system() const { return Application::current()->system(); }
virtual el::Element* BuildUI() = 0;
virtual void Setup(xe::debug::DebugClient* client) = 0;
protected:
explicit View(std::string name) : name_(name) {}
std::string name_;
el::LayoutBox root_element_;
std::unique_ptr<el::EventHandler> handler_;
xe::debug::DebugClient* client_ = nullptr;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_VIEW_H_

View File

@ -1,152 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/ui/views/cpu/call_stack_control.h"
#include "el/animation_manager.h"
#include "xenia/base/string_buffer.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
class CallStackItem : public el::GenericStringItem {
public:
CallStackItem(size_t ordinal, const ThreadCallStackFrame* frame)
: GenericStringItem(""), ordinal_(ordinal), frame_(frame) {
StringBuffer sb;
if (frame_->guest_pc) {
sb.AppendFormat(" %.2lld %.16llX %.8X %s", ordinal_, frame_->host_pc,
frame_->guest_pc, frame_->name);
} else {
sb.AppendFormat(" %.2lld %.16llX %s", ordinal_, frame_->host_pc,
frame_->name);
}
str = sb.to_string();
}
private:
size_t ordinal_ = 0;
const ThreadCallStackFrame* frame_ = nullptr;
};
class CallStackItemElement : public el::LayoutBox {
public:
CallStackItemElement(CallStackItem* item, CallStackItemSource* 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 = LabelNode(item_->str.c_str())
.gravity(Gravity::kLeft)
.ignore_input(true)
.text_align(el::TextAlign::kLeft);
content_root()->LoadNodeTree(node);
}
bool OnEvent(const el::Event& ev) override {
return el::LayoutBox::OnEvent(ev);
}
private:
CallStackItem* item_ = nullptr;
CallStackItemSource* source_ = nullptr;
el::ListItemObserver* source_viewer_ = nullptr;
size_t index_ = 0;
};
class CallStackItemSource : public el::ListItemSourceList<CallStackItem> {
public:
el::Element* CreateItemElement(size_t index,
el::ListItemObserver* viewer) override {
return new CallStackItemElement(at(index), this, viewer, index);
}
};
CallStackControl::CallStackControl()
: item_source_(new CallStackItemSource()) {}
CallStackControl::~CallStackControl() = default;
el::Element* CallStackControl::BuildUI() {
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto node =
LayoutBoxNode()
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.child(ListBoxNode().id("stack_listbox").gravity(Gravity::kAll));
root_element_.set_gravity(Gravity::kAll);
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
root_element_.LoadNodeTree(node);
auto stack_listbox =
root_element_.GetElementById<el::ListBox>(TBIDC("stack_listbox"));
stack_listbox->set_source(item_source_.get());
stack_listbox->scroll_container()->set_scroll_mode(
el::ScrollMode::kAutoXAutoY);
handler_ = std::make_unique<el::EventHandler>(&root_element_);
return &root_element_;
}
void CallStackControl::Setup(DebugClient* client) {
client_ = client;
system()->on_thread_state_updated.AddListener([this](model::Thread* thread) {
if (thread == thread_) {
InvalidateCallStack();
}
});
}
void CallStackControl::set_thread(model::Thread* thread) {
thread_ = thread;
InvalidateCallStack();
}
void CallStackControl::InvalidateCallStack() {
item_source_->clear();
if (!thread_) {
return;
}
auto& call_stack = thread_->call_stack();
for (size_t i = 0; i < call_stack.size(); ++i) {
auto& frame = call_stack[i];
size_t ordinal = call_stack.size() - i - 1;
auto item = std::make_unique<CallStackItem>(ordinal, &frame);
item->tag.set_integer(static_cast<int>(i));
item_source_->push_back(std::move(item));
}
item_source_->InvokeAllItemsRemoved();
}
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe

View File

@ -1,51 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_
#define XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_
#include <memory>
#include <string>
#include "xenia/debug/ui/control.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
class CallStackItemSource;
class CallStackControl : public Control {
public:
CallStackControl();
~CallStackControl() 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 InvalidateCallStack();
model::Thread* thread_ = nullptr;
std::unique_ptr<CallStackItemSource> item_source_;
};
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_

View File

@ -1,278 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/ui/views/cpu/cpu_view.h"
#include "el/animation_manager.h"
#include "xenia/base/string_buffer.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace 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;
el::Element* CpuView::BuildUI() {
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto functions_node =
LayoutBoxNode()
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kY)
.child(LayoutBoxNode()
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kX)
.skin("button_group")
.child(DropDownButtonNode().id("module_dropdown")))
.child(ListBoxNode().id("function_listbox").gravity(Gravity::kAll))
.child(LayoutBoxNode()
.gravity(Gravity::kBottom | Gravity::kLeftRight)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kX)
.child(TextBoxNode()
.type(EditType::kSearch)
.placeholder("Filter")));
auto register_list_node = ListBoxNode()
.gravity(Gravity::kAll)
.item("A")
.item("A")
.item("A")
.item("A");
auto source_registers_node =
TabContainerNode()
.gravity(Gravity::kAll)
.tab(ButtonNode("GR"), LabelNode("<register list control>")
.id("gr_registers_placeholder"))
.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 =
TabContainerNode()
.gravity(Gravity::kAll)
.align(Align::kLeft)
.tab(ButtonNode("Stack"),
LabelNode("<callstack control>").id("call_stack_placeholder"))
.tab(ButtonNode("BPs"), LabelNode("BREAKPOINTS"));
auto source_pane_node =
LayoutBoxNode()
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kY)
.child(
LayoutBoxNode()
.id("source_toolbar")
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kGravity)
.distribution_position(LayoutDistributionPosition::kLeftTop)
.axis(Axis::kX)
.child(DropDownButtonNode().id("thread_dropdown")))
.child(LayoutBoxNode()
.id("source_content")
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.child(SplitContainerNode()
.gravity(Gravity::kAll)
.axis(Axis::kX)
.fixed_pane(FixedPane::kSecond)
.value(250)
.pane(SplitContainerNode()
.gravity(Gravity::kAll)
.axis(Axis::kY)
.fixed_pane(FixedPane::kSecond)
.value(240)
.pane(LabelNode("<source control>")
.id("source_placeholder"))
.pane(source_registers_node))
.pane(source_tools_node)));
auto memory_pane_node = LabelNode("MEMORY");
auto node = SplitContainerNode()
.gravity(Gravity::kAll)
.axis(Axis::kY)
.min(128)
.max(512)
.value(128)
.pane(functions_node)
.pane(SplitContainerNode()
.gravity(Gravity::kAll)
.axis(Axis::kY)
.value(800)
.pane(source_pane_node)
.pane(memory_pane_node));
root_element_.set_gravity(Gravity::kAll);
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
root_element_.LoadNodeTree(node);
el::Label* source_placeholder;
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("source_placeholder"), &source_placeholder},
{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},
});
source_placeholder->parent()->ReplaceChild(source_placeholder,
source_control_.BuildUI());
source_control_.Setup(client_);
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_);
handler_ = std::make_unique<el::EventHandler>(&root_element_);
handler_->Listen(el::EventType::kChanged, TBIDC("module_dropdown"),
[this](const el::Event& ev) {
UpdateFunctionList();
return true;
});
handler_->Listen(
el::EventType::kChanged, TBIDC("thread_dropdown"),
[this](const el::Event& ev) {
auto thread_dropdown = root_element_.GetElementById<el::DropDownButton>(
TBIDC("thread_dropdown"));
auto thread_handle = uint32_t(thread_dropdown->selected_item_id());
if (thread_handle) {
current_thread_ = system()->GetThreadByHandle(thread_handle);
} else {
current_thread_ = nullptr;
}
SwitchCurrentThread(current_thread_);
return true;
});
return &root_element_;
}
void CpuView::Setup(DebugClient* client) {
client_ = client;
system()->on_execution_state_changed.AddListener(
[this]() { UpdateElementState(); });
system()->on_modules_updated.AddListener([this]() { UpdateModuleList(); });
system()->on_threads_updated.AddListener([this]() { UpdateThreadList(); });
}
void CpuView::UpdateElementState() {
root_element_.GetElementsById({
//
});
}
void CpuView::UpdateModuleList() {
el::DropDownButton* module_dropdown;
root_element_.GetElementsById({
{TBIDC("module_dropdown"), &module_dropdown},
});
auto module_items = module_dropdown->default_source();
auto modules = system()->modules();
bool is_first = module_items->size() == 0;
for (size_t i = module_items->size(); i < modules.size(); ++i) {
auto module = modules[i];
auto item = std::make_unique<el::GenericStringItem>(module->name());
item->id = module->module_handle();
module_items->push_back(std::move(item));
}
if (is_first) {
module_dropdown->set_value(static_cast<int>(module_items->size() - 1));
}
}
void CpuView::UpdateFunctionList() {
el::DropDownButton* module_dropdown;
el::ListBox* function_listbox;
root_element_.GetElementsById({
{TBIDC("module_dropdown"), &module_dropdown},
{TBIDC("function_listbox"), &function_listbox},
});
auto module_handle = module_dropdown->selected_item_id();
auto module = system()->GetModuleByHandle(module_handle);
if (!module) {
function_listbox->default_source()->clear();
return;
}
// TODO(benvanik): fetch list.
}
void CpuView::UpdateThreadList() {
el::DropDownButton* thread_dropdown;
root_element_.GetElementsById({
{TBIDC("thread_dropdown"), &thread_dropdown},
});
auto thread_items = thread_dropdown->default_source();
auto threads = system()->threads();
bool is_first = thread_items->size() == 0;
for (size_t i = 0; i < thread_items->size(); ++i) {
auto thread = threads[i];
auto item = thread_items->at(i);
item->str = thread->to_string();
}
for (size_t i = thread_items->size(); i < threads.size(); ++i) {
auto thread = threads[i];
auto item = std::make_unique<el::GenericStringItem>(thread->to_string());
item->id = thread->thread_handle();
thread_items->push_back(std::move(item));
}
if (is_first) {
thread_dropdown->set_value(static_cast<int>(thread_items->size() - 1));
}
}
void CpuView::SwitchCurrentThread(model::Thread* thread) {
current_thread_ = thread;
source_control_.set_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);
}
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe

View File

@ -1,61 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_VIEWS_CPU_CPU_VIEW_H_
#define XENIA_DEBUG_UI_VIEWS_CPU_CPU_VIEW_H_
#include <memory>
#include <string>
#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"
#include "xenia/debug/ui/views/cpu/source_control.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
class CpuView : public View {
public:
CpuView();
~CpuView() override;
el::Element* BuildUI() override;
void Setup(xe::debug::DebugClient* client) override;
protected:
void UpdateElementState();
void UpdateModuleList();
void UpdateFunctionList();
void UpdateThreadList();
void SwitchCurrentThread(model::Thread* thread);
// TODO(benvanik): better state machine.
model::Thread* current_thread_ = nullptr;
SourceControl source_control_;
RegisterListControl gr_registers_control_;
RegisterListControl fr_registers_control_;
RegisterListControl vr_registers_control_;
RegisterListControl host_registers_control_;
CallStackControl call_stack_control_;
};
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_VIEWS_CPU_CPU_VIEW_H_

View File

@ -1,325 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/ui/views/cpu/register_list_control.h"
#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"
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

View File

@ -1,59 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#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:
explicit 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_

View File

@ -1,75 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/ui/views/cpu/source_control.h"
#include "el/animation_manager.h"
#include "xenia/base/string_buffer.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
SourceControl::SourceControl() = default;
SourceControl::~SourceControl() = default;
el::Element* SourceControl::BuildUI() {
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto node = LayoutBoxNode()
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kY)
.child(LayoutBoxNode()
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kGravity)
.distribution_position(
LayoutDistributionPosition::kLeftTop)
.axis(Axis::kX)
.child(ButtonNode("A")))
.child(TextBoxNode("source!")
.id("source_textbox")
.gravity(Gravity::kAll)
.is_multiline(true)
.is_read_only(true));
root_element_.set_gravity(Gravity::kAll);
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
root_element_.LoadNodeTree(node);
handler_ = std::make_unique<el::EventHandler>(&root_element_);
return &root_element_;
}
void SourceControl::Setup(DebugClient* client) {
client_ = client;
// system()->on_thread_state_updated.AddListener([this](model::Thread* thread)
// {
// if (thread == thread_) {
// InvalidateCallStack();
// }
//});
}
void SourceControl::set_thread(model::Thread* thread) {
thread_ = thread;
// InvalidateCallStack();
}
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe

View File

@ -1,46 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_VIEWS_CPU_SOURCE_CONTROL_H_
#define XENIA_DEBUG_UI_VIEWS_CPU_SOURCE_CONTROL_H_
#include <memory>
#include <string>
#include "xenia/debug/ui/control.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
class SourceControl : public Control {
public:
SourceControl();
~SourceControl() 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:
model::Thread* thread_ = nullptr;
};
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_VIEWS_CPU_SOURCE_CONTROL_H_

View File

@ -1,47 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "el/animation_manager.h"
#include "xenia/debug/ui/views/gpu/gpu_view.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace gpu {
GpuView::GpuView() : View("GPU") {}
GpuView::~GpuView() = default;
el::Element* GpuView::BuildUI() {
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto node = LabelNode("TODO");
root_element_.set_gravity(Gravity::kAll);
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
root_element_.LoadNodeTree(node);
root_element_.GetElementsById({
//
});
handler_ = std::make_unique<el::EventHandler>(&root_element_);
return &root_element_;
}
void GpuView::Setup(xe::debug::DebugClient* client) {
//
}
} // namespace gpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe

View File

@ -1,42 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_VIEWS_GPU_GPU_VIEW_H_
#define XENIA_DEBUG_UI_VIEWS_GPU_GPU_VIEW_H_
#include <memory>
#include <string>
#include "xenia/debug/ui/view.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace gpu {
class GpuView : public View {
public:
GpuView();
~GpuView() override;
el::Element* BuildUI() override;
void Setup(xe::debug::DebugClient* client) override;
protected:
};
} // namespace gpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_VIEWS_GPU_GPU_VIEW_H_

View File

@ -1,101 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{C5BA52F0-C86B-4817-921C-CCA257FC04BE}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>xedebugui</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).Common.props" />
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).$(Configuration).props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).Common.props" />
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).$(Configuration).props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>libgflags.lib;libglew.lib;libxenia.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>libgflags.lib;libglew.lib;libxenia.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\..\base\main.h" />
<ClInclude Include="application.h" />
<ClInclude Include="main_window.h" />
<ClInclude Include="view.h" />
<ClInclude Include="views\cpu\cpu_view.h" />
<ClInclude Include="views\gpu\gpu_view.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\base\main_win.cc" />
<ClCompile Include="application.cc" />
<ClCompile Include="main_window.cc" />
<ClCompile Include="views\cpu\cpu_view.cc" />
<ClCompile Include="views\gpu\gpu_view.cc" />
<ClCompile Include="xe-debug-ui.cc" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc">
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)third_party/elemental-forms;$(SolutionDir);.</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir)third_party/elemental-forms;$(SolutionDir);.</AdditionalIncludeDirectories>
</ResourceCompile>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="src">
<UniqueIdentifier>{52d1c314-7464-4640-bee4-59b30d2b2df4}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia">
<UniqueIdentifier>{f8d1867c-424d-4942-ba97-a7b29fa8c87c}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug">
<UniqueIdentifier>{1b70e26e-0024-410f-b27f-ceaba190d5a9}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug\ui">
<UniqueIdentifier>{9b5a4bce-210a-4488-863a-9175f0543d00}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\base">
<UniqueIdentifier>{9a5724c2-5473-4d53-93b4-26531201f38d}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug\ui\elements">
<UniqueIdentifier>{f0ac4999-4700-4b41-b73d-c6dc8b23c5e6}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug\ui\views">
<UniqueIdentifier>{5601b0a2-a720-4d89-9fca-df1637fce0b1}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug\ui\views\cpu">
<UniqueIdentifier>{5b186e9a-1fef-42f5-b06b-2ac0b10a8a5b}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug\ui\views\gpu">
<UniqueIdentifier>{c66e9a6b-0947-4610-9b2b-29db99c08e91}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\base\main.h">
<Filter>src\xenia\base</Filter>
</ClInclude>
<ClInclude Include="main_window.h">
<Filter>src\xenia\debug\ui</Filter>
</ClInclude>
<ClInclude Include="application.h">
<Filter>src\xenia\debug\ui</Filter>
</ClInclude>
<ClInclude Include="view.h">
<Filter>src\xenia\debug\ui</Filter>
</ClInclude>
<ClInclude Include="views\gpu\gpu_view.h">
<Filter>src\xenia\debug\ui\views\gpu</Filter>
</ClInclude>
<ClInclude Include="views\cpu\cpu_view.h">
<Filter>src\xenia\debug\ui\views\cpu</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\base\main_win.cc">
<Filter>src\xenia\base</Filter>
</ClCompile>
<ClCompile Include="xe-debug-ui.cc">
<Filter>src\xenia\debug\ui</Filter>
</ClCompile>
<ClCompile Include="main_window.cc">
<Filter>src\xenia\debug\ui</Filter>
</ClCompile>
<ClCompile Include="application.cc">
<Filter>src\xenia\debug\ui</Filter>
</ClCompile>
<ClCompile Include="views\gpu\gpu_view.cc">
<Filter>src\xenia\debug\ui\views\gpu</Filter>
</ClCompile>
<ClCompile Include="views\cpu\cpu_view.cc">
<Filter>src\xenia\debug\ui\views\cpu</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc">
<Filter>src\xenia\debug\ui</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View File

@ -11,6 +11,7 @@
#include <gflags/gflags.h>
#include "third_party/elemental-forms/src/el/elements.h"
#include "xenia/apu/audio_system.h"
#include "xenia/base/assert.h"
#include "xenia/base/clock.h"
@ -30,8 +31,6 @@
#include "xenia/vfs/devices/stfs_container_device.h"
#include "xenia/vfs/virtual_file_system.h"
#include "el/elements/message_form.h"
DEFINE_double(time_scalar, 1.0,
"Scalar used to speed or slow time (1x, 2x, 1/2x, etc).");
@ -64,6 +63,8 @@ Emulator::~Emulator() {
debugger_.reset();
export_resolver_.reset();
ExceptionHandler::Uninstall(Emulator::ExceptionCallbackThunk, this);
}
X_STATUS Emulator::Setup(ui::Window* display_window) {
@ -157,6 +158,9 @@ X_STATUS Emulator::Setup(ui::Window* display_window) {
kernel_state_->LoadKernelModule<kernel::xboxkrnl::XboxkrnlModule>();
kernel_state_->LoadKernelModule<kernel::xam::XamModule>();
// Initialize emulator fallback exception handling last.
ExceptionHandler::Install(Emulator::ExceptionCallbackThunk, this);
// Finish initializing the display.
display_window_->loop()->PostSynchronous([this]() {
xe::ui::GraphicsContextLock context_lock(display_window_->context());
@ -164,11 +168,6 @@ X_STATUS Emulator::Setup(ui::Window* display_window) {
Profiler::set_display(std::move(profiler_display));
});
// Install an exception handler.
ExceptionHandler::Initialize();
ExceptionHandler::Install(
std::bind(&Emulator::ExceptionCallback, this, std::placeholders::_1));
return result;
}
@ -272,61 +271,65 @@ X_STATUS Emulator::LaunchStfsContainer(std::wstring path) {
return CompleteLaunch(path, "game:\\default.xex");
}
bool Emulator::ExceptionCallback(ExceptionHandler::Info* info) {
bool Emulator::ExceptionCallbackThunk(Exception* ex, void* data) {
return reinterpret_cast<Emulator*>(data)->ExceptionCallback(ex);
}
bool Emulator::ExceptionCallback(Exception* ex) {
// Check to see if the exception occurred in guest code.
auto code_cache = processor()->backend()->code_cache();
auto code_base = code_cache->base_address();
auto code_end = code_base + code_cache->total_size();
if (!debugger()->is_attached() && debugging::IsDebuggerAttached()) {
if (!debugger() ||
!debugger()->is_attached() && debugging::IsDebuggerAttached()) {
// If Xenia's debugger isn't attached but another one is, pass it to that
// debugger.
return false;
} else if (debugger() && debugger()->is_attached()) {
// Let the debugger handle this exception. It may decide to continue past it
// (if it was a stepping breakpoint, etc).
return debugger()->OnUnhandledException(ex);
}
if (info->pc >= code_base && info->pc < code_end) {
using namespace xe::kernel;
auto global_lock = global_critical_region::AcquireDirect();
// Within range. Pause the emulator and eat the exception.
auto threads = kernel_state()->object_table()->GetObjectsByType<XThread>(
XObject::kTypeThread);
auto cur_thread = XThread::GetCurrentThread();
for (auto thread : threads) {
if (!thread->is_guest_thread()) {
// Don't pause host threads.
continue;
}
if (cur_thread == thread.get()) {
continue;
}
thread->Suspend(nullptr);
}
if (debugger()->is_attached()) {
// TODO: Send the exception to Xenia's debugger
}
// Display a dialog telling the user the guest has crashed.
display_window()->loop()->PostSynchronous([&]() {
auto msgbox = new el::elements::MessageForm(
display_window()->root_element(), "none");
msgbox->Show("Uh-oh!",
"The guest has crashed.\n\n"
"Xenia has now paused itself.");
});
// Now suspend ourself (we should be a guest thread).
cur_thread->Suspend(nullptr);
// We should not arrive here!
assert_always();
if (!(ex->pc() >= code_base && ex->pc() < code_end)) {
// Didn't occur in guest code. Let it pass.
return false;
}
// Didn't occur in guest code. Let it pass.
auto global_lock = global_critical_region::AcquireDirect();
// Within range. Pause the emulator and eat the exception.
auto threads =
kernel_state()->object_table()->GetObjectsByType<kernel::XThread>(
kernel::XObject::kTypeThread);
auto current_thread = kernel::XThread::GetCurrentThread();
for (auto thread : threads) {
if (!thread->is_guest_thread()) {
// Don't pause host threads.
continue;
}
if (current_thread == thread.get()) {
continue;
}
thread->Suspend(nullptr);
}
// Display a dialog telling the user the guest has crashed.
display_window()->loop()->PostSynchronous([&]() {
auto message_form = new el::MessageForm(display_window()->root_element(),
TBIDC("crash_form"));
message_form->Show("Uh-oh!",
"The guest has crashed.\n\n"
"Xenia has now paused itself.");
});
// Now suspend ourself (we should be a guest thread).
assert_true(current_thread->is_guest_thread());
current_thread->Suspend(nullptr);
// We should not arrive here!
assert_always();
return false;
}

View File

@ -76,7 +76,8 @@ class Emulator {
X_STATUS LaunchStfsContainer(std::wstring path);
private:
bool ExceptionCallback(ExceptionHandler::Info* info);
static bool ExceptionCallbackThunk(Exception* ex, void* data);
bool ExceptionCallback(Exception* ex);
X_STATUS CompleteLaunch(const std::wstring& path,
const std::string& module_path);

View File

@ -292,6 +292,9 @@ void GL4GraphicsSystem::MarkVblank() {
}
void GL4GraphicsSystem::Swap(xe::ui::UIEvent* e) {
if (!command_processor_) {
return;
}
// Check for pending swap.
auto& swap_state = command_processor_->swap_state();
{

View File

@ -515,8 +515,6 @@ bool DisasmPacketType3(const uint8_t* base_ptr, uint32_t packet,
return result;
}
bool DisasmPacket(const uint8_t* base_ptr, PacketInfo* out_info) {
std::memset(out_info, 0, sizeof(PacketInfo));
const uint32_t packet = xe::load_and_swap<uint32_t>(base_ptr);
const uint32_t packet_type = packet >> 30;
switch (packet_type) {
@ -977,7 +975,7 @@ void DrawCommandListUI(xe::ui::Window* window, TracePlayer& player,
if (ImGui::Selectable(label, &is_selected)) {
player.SeekCommand(i);
}
ImGui::SameLine(column_width - 60);
ImGui::SameLine(column_width - 60.0f);
ImGui::Text("%d", i);
ImGui::PopID();
if (did_seek && target_command == i) {
@ -2098,7 +2096,7 @@ void DrawPacketDisassemblerUI(xe::ui::Window* window, TracePlayer& player,
auto cmd = reinterpret_cast<const PacketEndCommand*>(trace_ptr);
trace_ptr += sizeof(*cmd);
if (pending_packet) {
PacketInfo packet_info;
PacketInfo packet_info = {0};
if (DisasmPacket(reinterpret_cast<const uint8_t*>(pending_packet) +
sizeof(PacketStartCommand),
&packet_info)) {
@ -2107,7 +2105,7 @@ void DrawPacketDisassemblerUI(xe::ui::Window* window, TracePlayer& player,
}
ImGui::BulletText(packet_info.type_info->name);
ImGui::TreePush((const char*)0);
for (auto& action : packet_info.actions) {
for (auto action : packet_info.actions) {
switch (action.type) {
case PacketAction::Type::kRegisterWrite: {
auto register_info = xe::gpu::RegisterFile::GetRegisterInfo(
@ -2343,7 +2341,7 @@ static int texture_location, proj_mtx_location;
static int position_location, uv_location, colour_location;
static size_t vbo_max_size = 20000;
static unsigned int vbo_handle, vao_handle;
void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count);
void ImImpl_RenderDrawLists(ImDrawData* data);
void ImImpl_Setup() {
ImGuiIO& io = ImGui::GetIO();
@ -2483,7 +2481,7 @@ void ImImpl_Setup() {
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
io.KeyMap[ImGuiKey_UpArrow] = VK_UP;
io.KeyMap[ImGuiKey_DownArrow] = VK_UP;
io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN;
io.KeyMap[ImGuiKey_Home] = VK_HOME;
io.KeyMap[ImGuiKey_End] = VK_END;
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
@ -2510,8 +2508,8 @@ void ImImpl_Shutdown() {
glDeleteTextures(1, &tex_id);
ImGui::Shutdown();
}
void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) {
if (cmd_lists_count == 0) return;
void ImImpl_RenderDrawLists(ImDrawData* data) {
if (data->CmdListsCount == 0) return;
// Setup render state: alpha-blending enabled, no face culling, no depth
// testing, scissor enabled
@ -2537,52 +2535,52 @@ void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) {
&ortho_projection[0][0]);
// Grow our buffer according to what we need
size_t total_vtx_count = 0;
for (int n = 0; n < cmd_lists_count; n++)
total_vtx_count += cmd_lists[n]->vtx_buffer.size();
glBindBuffer(GL_ARRAY_BUFFER, vbo_handle);
size_t neededBufferSize = total_vtx_count * sizeof(ImDrawVert);
size_t neededBufferSize = data->TotalVtxCount * sizeof(ImDrawVert);
if (neededBufferSize > vbo_max_size) {
vbo_max_size = neededBufferSize + 5000; // Grow buffer
glBufferData(GL_ARRAY_BUFFER, vbo_max_size, NULL, GL_STREAM_DRAW);
}
// Copy and convert all vertices into a single contiguous buffer
unsigned char* buffer_data =
(unsigned char*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
if (!buffer_data) return;
for (int n = 0; n < cmd_lists_count; n++) {
const ImDrawList* cmd_list = cmd_lists[n];
memcpy(buffer_data, &cmd_list->vtx_buffer[0],
cmd_list->vtx_buffer.size() * sizeof(ImDrawVert));
buffer_data += cmd_list->vtx_buffer.size() * sizeof(ImDrawVert);
}
glUnmapBuffer(GL_ARRAY_BUFFER);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(vao_handle);
glUseProgram(shader_handle);
int cmd_offset = 0;
GLuint ib;
glGenBuffers(1, &ib);
ImTextureID prev_texture_id = 0;
for (int n = 0; n < cmd_lists_count; n++) {
const ImDrawList* cmd_list = cmd_lists[n];
int vtx_offset = cmd_offset;
const ImDrawCmd* pcmd_end = cmd_list->commands.end();
for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end;
pcmd++) {
if (pcmd->texture_id != prev_texture_id) {
glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->texture_id);
prev_texture_id = pcmd->texture_id;
for (int n = 0; n < data->CmdListsCount; n++) {
const ImDrawList* cmd_list = data->CmdLists[n];
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
(GLsizeiptr)cmd_list->IdxBuffer.size() * sizeof(ImDrawIdx),
(GLvoid*)&cmd_list->IdxBuffer.front(), GL_STREAM_DRAW);
// Copy and convert all vertices into a single contiguous buffer
unsigned char* buffer_data =
(unsigned char*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
memcpy(buffer_data, &cmd_list->VtxBuffer[0],
cmd_list->VtxBuffer.size() * sizeof(ImDrawVert));
glUnmapBuffer(GL_ARRAY_BUFFER);
const ImDrawIdx* idx_buffer_offset = 0;
for (auto cmd = cmd_list->CmdBuffer.begin();
cmd != cmd_list->CmdBuffer.end(); ++cmd) {
if (cmd->TextureId != prev_texture_id) {
glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)cmd->TextureId);
prev_texture_id = cmd->TextureId;
}
glScissor((int)pcmd->clip_rect.x, (int)(height - pcmd->clip_rect.w),
(int)(pcmd->clip_rect.z - pcmd->clip_rect.x),
(int)(pcmd->clip_rect.w - pcmd->clip_rect.y));
glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count);
vtx_offset += pcmd->vtx_count;
glScissor((int)cmd->ClipRect.x, (int)(height - cmd->ClipRect.w),
(int)(cmd->ClipRect.z - cmd->ClipRect.x),
(int)(cmd->ClipRect.w - cmd->ClipRect.y));
glDrawElements(GL_TRIANGLES, (GLsizei)cmd->ElemCount, GL_UNSIGNED_SHORT,
idx_buffer_offset);
idx_buffer_offset += cmd->ElemCount;
}
cmd_offset = vtx_offset;
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &ib);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// Restore modified state
glBindVertexArray(0);
glUseProgram(0);

View File

@ -28,7 +28,8 @@ KernelModule::KernelModule(KernelState* kernel_state, const char* path)
KernelModule::~KernelModule() {}
uint32_t KernelModule::GetProcAddressByOrdinal(uint16_t ordinal) {
auto export_entry = export_resolver_->GetExportByOrdinal(name(), ordinal);
auto export_entry =
export_resolver_->GetExportByOrdinal(name().c_str(), ordinal);
if (!export_entry) {
// Export (or its parent library) not found.
return 0;

View File

@ -636,6 +636,10 @@ void XThread::SetActiveCpu(uint32_t cpu_index) {
xe::store_and_swap<uint8_t>(pcr + 0x10C, cpu_index);
}
uint32_t XThread::suspend_count() {
return guest_object<X_KTHREAD>()->suspend_count;
}
X_STATUS XThread::Resume(uint32_t* out_suspend_count) {
--guest_object<X_KTHREAD>()->suspend_count;

View File

@ -161,6 +161,7 @@ class XThread : public XObject {
uint32_t active_cpu() const;
void SetActiveCpu(uint32_t cpu_index);
uint32_t suspend_count();
X_STATUS Resume(uint32_t* out_suspend_count = nullptr);
X_STATUS Suspend(uint32_t* out_suspend_count);
X_STATUS Delay(uint32_t processor_mode, uint32_t alertable,

View File

@ -39,6 +39,11 @@ Loop::~Loop() {
}
void Loop::PostSynchronous(std::function<void()> fn) {
if (is_on_loop_thread()) {
// Prevent deadlock if we are executing on ourselves.
fn();
return;
}
xe::threading::Fence fence;
Post([&fn, &fence]() {
fn();

View File

@ -31,6 +31,9 @@ class Loop {
Loop();
virtual ~Loop();
// Returns true if the currently executing code is within the loop thread.
virtual bool is_on_loop_thread() = 0;
virtual void Post(std::function<void()> fn) = 0;
virtual void PostDelayed(std::function<void()> fn, uint64_t delay_millis) = 0;
void PostSynchronous(std::function<void()> fn);

View File

@ -87,6 +87,10 @@ void Win32Loop::ThreadMain() {
on_quit(&e);
}
bool Win32Loop::is_on_loop_thread() {
return thread_id_ == GetCurrentThreadId();
}
void Win32Loop::Post(std::function<void()> fn) {
assert_true(thread_id_ != 0);
if (!PostThreadMessage(

View File

@ -26,6 +26,8 @@ class Win32Loop : public Loop {
Win32Loop();
~Win32Loop() override;
bool is_on_loop_thread() override;
void Post(std::function<void()> fn) override;
void PostDelayed(std::function<void()> fn, uint64_t delay_millis) override;

View File

@ -493,6 +493,7 @@ void Window::OnKeyUp(KeyEvent* e) {
void Window::OnKeyChar(KeyEvent* e) {
OnKeyPress(e, true, true);
on_key_char(e);
OnKeyPress(e, false, true);
}

View File

@ -320,6 +320,10 @@ LRESULT CALLBACK Win32Window::WndProcThunk(HWND hWnd, UINT message,
LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
LPARAM lParam) {
if (hWnd != hwnd_) {
return DefWindowProc(hWnd, message, wParam, lParam);
}
if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) {
if (HandleMouse(message, wParam, lParam)) {
return 0;
@ -328,7 +332,6 @@ LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
}
} else if (message >= WM_KEYFIRST && message <= WM_KEYLAST) {
if (HandleKeyboard(message, wParam, lParam)) {
SetFocus(hwnd_);
return 0;
} else {
return DefWindowProc(hWnd, message, wParam, lParam);
@ -363,10 +366,14 @@ LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
case WM_PAINT: {
ValidateRect(hwnd_, nullptr);
auto e = UIEvent(this);
OnPaint(&e);
static bool in_paint = false;
if (!in_paint) {
in_paint = true;
auto e = UIEvent(this);
OnPaint(&e);
in_paint = false;
}
return 0; // Ignored because of custom paint.
break;
}
case WM_ERASEBKGND:
return 0; // Ignored because of custom paint.

@ -1 +1 @@
Subproject commit b6906423349431e54bc646be61b7c64b13c442c3
Subproject commit f0f692cb72e0d603d526db803d45f32ffabfe8bb

2
third_party/imgui vendored

@ -1 +1 @@
Subproject commit 14f189b2f62ec2f79875e7c897a42415fe96fde6
Subproject commit 127f44c12bc4b1e5b6d7eb90b9a934668b01e8c2

View File

@ -15,7 +15,13 @@ project("imgui")
"imgui/imconfig.h",
"imgui/imgui.cpp",
"imgui/imgui.h",
"imgui/imgui_draw.cpp",
"imgui/imgui_demo.cpp",
"imgui/imgui_internal.h",
"imgui/stb_rect_pack.h",
"imgui/stb_textedit.h",
"imgui/stb_truetype.h",
})
buildoptions({
"/wd4312", -- Ugh.
})