diff --git a/building.md b/building.md index 83a6bcb80..595bc6037 100644 --- a/building.md +++ b/building.md @@ -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. diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 150958f66..3226a886b 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -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(); } diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index 5e35380e6..6b76eccfa 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -43,6 +43,7 @@ class EmulatorWindow { void CpuTimeScalarReset(); void CpuTimeScalarSetHalf(); void CpuTimeScalarSetDouble(); + void CpuBreakIntoDebugger(); void GpuTraceFrame(); void GpuClearCaches(); void ToggleFullscreen(); diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index a7746755e..234270a81 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -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", diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index ec5dc137a..fbc921be3 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -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& args) { return 1; } + // Set a debug handler. + // This will respond to debugging requests so we can open the debug UI. + std::unique_ptr 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& args) { emulator->display_window()->loop()->AwaitQuit(); } + debug_window.reset(); emulator.reset(); emulator_window.reset(); diff --git a/src/xenia/base/exception_handler.h b/src/xenia/base/exception_handler.h index cf7cc9fbb..f8fe449e0 100644 --- a/src/xenia/base/exception_handler.h +++ b/src/xenia/base/exception_handler.h @@ -13,37 +13,61 @@ #include #include +#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 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& 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 handlers_; + Code code_ = Code::kInvalidException; + X64Context* thread_context_ = nullptr; + uint64_t fault_address_ = 0; }; -}; // namespace xe -#endif // XENIA_BASE_EXCEPTION_HANDLER_H_ \ No newline at end of file +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_ diff --git a/src/xenia/base/exception_handler_win.cc b/src/xenia/base/exception_handler_win.cc index 56667a0a3..742c54fa9 100644 --- a/src/xenia/base/exception_handler_win.cc +++ b/src/xenia/base/exception_handler_win.cc @@ -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::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 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 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; + } + } } -} \ No newline at end of file + +} // namespace xe diff --git a/src/xenia/base/math.h b/src/xenia/base/math.h index 2ed2de0f7..ce27b4b18 100644 --- a/src/xenia/base/math.h +++ b/src/xenia/base/math.h @@ -179,6 +179,12 @@ inline uint64_t rotate_left(uint64_t v, uint8_t sh) { } #endif // XE_PLATFORM_WIN32 +template +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 float m128_f32(const __m128& v) { diff --git a/src/xenia/base/string.cc b/src/xenia/base/string.cc index 4e31292a9..129323cf7 100644 --- a/src/xenia/base/string.cc +++ b/src/xenia/base/string.cc @@ -16,6 +16,7 @@ #include #endif // XE_PLATFORM_LINUX +#include #include #include @@ -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 > converter; + static std::wstring_convert> 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 > converter; + static std::wstring_convert> 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> fuzzy_filter(const std::string& pattern, + const void* const* entries, + size_t entry_count, + size_t string_offset) { + std::vector> results; + results.reserve(entry_count); + for (size_t i = 0; i < entry_count; ++i) { + auto entry_value = + reinterpret_cast(entries[i]) + string_offset; + int score = fuzzy_match(pattern, entry_value); + results.emplace_back(i, score); + } + return results; +} + } // namespace xe diff --git a/src/xenia/base/string.h b/src/xenia/base/string.h index f611a2b5c..a23951798 100644 --- a/src/xenia/base/string.h +++ b/src/xenia/base/string.h @@ -12,6 +12,7 @@ #include #include +#include #include #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> fuzzy_filter(const std::string& pattern, + const void* const* entries, + size_t entry_count, + size_t string_offset); +template +std::vector> fuzzy_filter(const std::string& pattern, + const std::vector& entries, + size_t string_offset) { + return fuzzy_filter(pattern, reinterpret_cast(entries.data()), + entries.size(), string_offset); +} + } // namespace xe #endif // XENIA_BASE_STRING_H_ diff --git a/src/xenia/base/string_buffer.cc b/src/xenia/base/string_buffer.cc index 3f2a8bb79..5258ea7f5 100644 --- a/src/xenia/base/string_buffer.cc +++ b/src/xenia/base/string_buffer.cc @@ -19,6 +19,7 @@ namespace xe { StringBuffer::StringBuffer(size_t initial_capacity) { buffer_capacity_ = std::max(initial_capacity, static_cast(16 * 1024)); buffer_ = reinterpret_cast(malloc(buffer_capacity_)); + buffer_[0] = 0; } StringBuffer::~StringBuffer() { diff --git a/src/xenia/base/string_util.h b/src/xenia/base/string_util.h index c4dc7ea1d..357d180c6 100644 --- a/src/xenia/base/string_util.h +++ b/src/xenia/base/string_util.h @@ -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 -inline T from_string(const char* value); +inline T from_string(const char* value, bool force_hex = false); template <> -inline uint64_t from_string(const char* value) { - if (std::strchr(value, 'h') != nullptr) { +inline int32_t from_string(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(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(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(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(const char* value) { } template <> -inline double from_string(const char* value) { - if (std::strstr(value, "0x") == value || std::strchr(value, 'h') != nullptr) { +inline float from_string(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(value, force_hex); + return v.flt; + } + return std::strtof(value, nullptr); +} + +template <> +inline double from_string(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(value); + v.ui = from_string(value, force_hex); return v.dbl; } return std::strtod(value, nullptr); } template <> -inline vec128_t from_string(const char* value) { +inline vec128_t from_string(const char* value, bool force_hex) { vec128_t v; char* p = const_cast(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(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(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 -inline T from_string(const std::string& value) { - return from_string(value.c_str()); +inline T from_string(const std::string& value, bool force_hex = false) { + return from_string(value.c_str(), force_hex); } } // namespace string_util diff --git a/src/xenia/cpu/x64_context.cc b/src/xenia/base/x64_context.cc similarity index 69% rename from src/xenia/cpu/x64_context.cc rename to src/xenia/base/x64_context.cc index 0016115da..8671c4017 100644 --- a/src/xenia/cpu/x64_context.cc +++ b/src/xenia/base/x64_context.cc @@ -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(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(reg) >= static_cast(X64Register::kRax) && static_cast(reg) <= static_cast(X64Register::kR15)) { - return string_util::to_hex_string( - int_registers.values[static_cast(reg) - - static_cast(X64Register::kRax)]); + auto value = int_registers[static_cast(reg) - + static_cast(X64Register::kRax)]; + return hex ? string_util::to_hex_string(value) : std::to_string(value); } else if (static_cast(reg) >= static_cast(X64Register::kXmm0) && static_cast(reg) <= static_cast(X64Register::kXmm15)) { - return string_util::to_hex_string( - xmm_registers.values[static_cast(reg) - - static_cast(X64Register::kXmm0)]); + auto value = xmm_registers[static_cast(reg) - + static_cast(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 diff --git a/src/xenia/cpu/x64_context.h b/src/xenia/base/x64_context.h similarity index 81% rename from src/xenia/cpu/x64_context.h rename to src/xenia/base/x64_context.h index a545f8179..b07eb6e16 100644 --- a/src/xenia/cpu/x64_context.h +++ b/src/xenia/base/x64_context.h @@ -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 #include 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_ diff --git a/src/xenia/cpu/backend/x64/x64_emitter.cc b/src/xenia/cpu/backend/x64/x64_emitter.cc index bc5f74269..c4661376c 100644 --- a/src/xenia/cpu/backend/x64/x64_emitter.cc +++ b/src/xenia/cpu/backend/x64/x64_emitter.cc @@ -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(); entry->source_offset = static_cast(i->src1.offset); entry->hir_offset = uint32_t(i->block->ordinal << 16) | i->ordinal; - entry->code_offset = static_cast(getSize() + 1); + entry->code_offset = static_cast(getSize()); - if (FLAGS_debug) { + if (FLAGS_emit_source_annotations) { nop(); nop(); mov(eax, entry->source_offset); diff --git a/src/xenia/cpu/export_resolver.cc b/src/xenia/cpu/export_resolver.cc index 313b9a519..729756ecf 100644 --- a/src/xenia/cpu/export_resolver.cc +++ b/src/xenia/cpu/export_resolver.cc @@ -15,50 +15,84 @@ namespace xe { namespace cpu { +ExportResolver::Table::Table(const char* module_name, + const std::vector* 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(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* exports) { - tables_.emplace_back(library_name, exports); + const char* module_name, const std::vector* 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; diff --git a/src/xenia/cpu/export_resolver.h b/src/xenia/cpu/export_resolver.h index 7824be86a..fd9d612a1 100644 --- a/src/xenia/cpu/export_resolver.h +++ b/src/xenia/cpu/export_resolver.h @@ -99,37 +99,46 @@ class Export { class ExportResolver { public: + class Table { + public: + Table(const char* module_name, const std::vector* exports); + + const char* module_name() const { return module_name_; } + const std::vector& exports_by_ordinal() const { + return *exports_by_ordinal_; + } + const std::vector& exports_by_name() const { + return exports_by_name_; + } + + private: + char module_name_[32] = {0}; + const std::vector* exports_by_ordinal_ = nullptr; + std::vector exports_by_name_; + }; + ExportResolver(); ~ExportResolver(); - void RegisterTable(const std::string& library_name, + void RegisterTable(const char* module_name, const std::vector* exports); + const std::vector& tables() const { return tables_; } + const std::vector& 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* exports; - ExportTable(const std::string& name, const std::vector* 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 tables_; + std::vector
tables_; + std::vector all_exports_by_name_; }; } // namespace cpu diff --git a/src/xenia/cpu/frontend/ppc_context.h b/src/xenia/cpu/frontend/ppc_context.h index 12f78bd07..59d8c7a86 100644 --- a/src/xenia/cpu/frontend/ppc_context.h +++ b/src/xenia/cpu/frontend/ppc_context.h @@ -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; diff --git a/src/xenia/cpu/function.cc b/src/xenia/cpu/function.cc index 256f3a77d..768f10474 100644 --- a/src/xenia/cpu/function.cc +++ b/src/xenia/cpu/function.cc @@ -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(machine_code()); +} + +uint32_t GuestFunction::MapCodeToSource(uintptr_t host_address) const { + auto entry = LookupCodeOffset(static_cast( + host_address - reinterpret_cast(machine_code()))); + return entry ? entry->source_offset : address(); +} + bool GuestFunction::Call(ThreadState* thread_state, uint32_t return_address) { // SCOPE_profile_cpu_f("cpu"); diff --git a/src/xenia/cpu/function.h b/src/xenia/cpu/function.h index 0cfd225e0..0688da17c 100644 --- a/src/xenia/cpu/function.h +++ b/src/xenia/cpu/function.h @@ -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: diff --git a/src/xenia/cpu/mmio_handler.cc b/src/xenia/cpu/mmio_handler.cc index 0074eb595..42e804e6c 100644 --- a/src/xenia/cpu/mmio_handler.cc +++ b/src/xenia/cpu/mmio_handler.cc @@ -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 CreateMMIOHandler(uint8_t* virtual_membase, - uint8_t* physical_membase); - std::unique_ptr 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( + 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(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(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(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(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(*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(value)); } } - range->write(nullptr, range->callback_context, fault_address & 0xFFFFFFFF, - value); + range->write(nullptr, range->callback_context, + static_cast(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; } diff --git a/src/xenia/cpu/mmio_handler.h b/src/xenia/cpu/mmio_handler.h index 2d5b3fdde..4151da5bb 100644 --- a/src/xenia/cpu/mmio_handler.h +++ b/src/xenia/cpu/mmio_handler.h @@ -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_; diff --git a/src/xenia/cpu/mmio_handler_mac.cc b/src/xenia/cpu/mmio_handler_mac.cc deleted file mode 100644 index 28c510048..000000000 --- a/src/xenia/cpu/mmio_handler_mac.cc +++ /dev/null @@ -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 -#include - -#include - -#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 thread_; - // Port listening for exceptions on the worker thread. - mach_port_t listen_port_; -}; - -std::unique_ptr CreateMMIOHandler(uint8_t* mapping_base) { - return std::make_unique(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 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(&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), - &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(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(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), - 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(thread_state_ptr); - return thread_state->__rip; -} - -void MachMMIOHandler::SetThreadStateRip(void* thread_state_ptr, uint64_t rip) { - auto thread_state = reinterpret_cast(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(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); -} diff --git a/src/xenia/cpu/mmio_handler_win.cc b/src/xenia/cpu/mmio_handler_win.cc deleted file mode 100644 index f9ef3b919..000000000 --- a/src/xenia/cpu/mmio_handler_win.cc +++ /dev/null @@ -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 CreateMMIOHandler(uint8_t* virtual_membase, - uint8_t* physical_membase) { - return std::make_unique(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(thread_state_ptr); - return context->Rip; -} - -void WinMMIOHandler::SetThreadStateRip(void* thread_state_ptr, uint64_t rip) { - auto context = reinterpret_cast(thread_state_ptr); - context->Rip = rip; -} - -uint64_t* WinMMIOHandler::GetThreadStateRegPtr(void* thread_state_ptr, - int32_t reg_index) { - auto context = reinterpret_cast(thread_state_ptr); - // Register indices line up with the CONTEXT structure format. - return &context->Rax + reg_index; -} - -} // namespace cpu -} // namespace xe diff --git a/src/xenia/cpu/stack_walker.h b/src/xenia/cpu/stack_walker.h index f5286e085..0d120d1e6 100644 --- a/src/xenia/cpu/stack_walker.h +++ b/src/xenia/cpu/stack_walker.h @@ -13,8 +13,8 @@ #include #include +#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; diff --git a/src/xenia/cpu/stack_walker_win.cc b/src/xenia/cpu/stack_walker_win.cc index 86b3e7fc8..f80108bed 100644 --- a/src/xenia/cpu/stack_walker_win.cc +++ b/src/xenia/cpu/stack_walker_win.cc @@ -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. diff --git a/src/xenia/debug/breakpoint.cc b/src/xenia/debug/breakpoint.cc new file mode 100644 index 000000000..9b96d1f22 --- /dev/null +++ b/src/xenia/debug/breakpoint.cc @@ -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 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(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(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(host_address); + auto original_bytes = xe::load_and_swap(ptr); + assert_true(original_bytes != 0x0F0B); + xe::store_and_swap(ptr, 0x0F0B); + return original_bytes; +} + +void CodeBreakpoint::UnpatchAddress(uintptr_t host_address, + uint16_t original_bytes) { + auto ptr = reinterpret_cast(host_address); + auto instruction_bytes = xe::load_and_swap(ptr); + assert_true(instruction_bytes == 0x0F0B); + xe::store_and_swap(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 diff --git a/src/xenia/debug/breakpoint.h b/src/xenia/debug/breakpoint.h index 148825d10..f4ec9619a 100644 --- a/src/xenia/debug/breakpoint.h +++ b/src/xenia/debug/breakpoint.h @@ -10,33 +10,173 @@ #ifndef XENIA_DEBUG_BREAKPOINT_H_ #define XENIA_DEBUG_BREAKPOINT_H_ +#include #include +#include +#include + +#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(address_); + } + uintptr_t host_address() const { + assert_true(address_type_ == AddressType::kHost); + return static_cast(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 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> 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 diff --git a/src/xenia/debug/debug_client.cc b/src/xenia/debug/debug_client.cc deleted file mode 100644 index ff4230697..000000000 --- a/src/xenia/debug/debug_client.cc +++ /dev/null @@ -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 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 lock(mutex_); - - switch (packet->packet_type) { - case PacketType::kGenericResponse: { - // - } break; - case PacketType::kExecutionNotification: { - auto body = packet_reader->Read(); - 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(); - auto entries = packet_reader->ReadArray(body->count); - listener_->OnModulesUpdated(std::move(entries)); - } break; - case PacketType::kThreadListResponse: { - auto body = packet_reader->Read(); - auto entries = packet_reader->ReadArray(body->count); - listener_->OnThreadsUpdated(std::move(entries)); - } break; - case PacketType::kThreadStatesResponse: { - auto body = packet_reader->Read(); - for (size_t i = 0; i < body->count; ++i) { - auto entry = packet_reader->Read(); - auto frames = - packet_reader->ReadArray(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 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 lock(mutex_); - packet_writer_.Begin(PacketType::kExecutionRequest); - auto body = packet_writer_.Append(); - body->action = ExecutionRequest::Action::kContinue; - packet_writer_.End(); - Flush(); -} - -void DebugClient::StepOne(uint32_t thread_id) { - std::lock_guard lock(mutex_); - packet_writer_.Begin(PacketType::kExecutionRequest); - auto body = packet_writer_.Append(); - 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 lock(mutex_); - packet_writer_.Begin(PacketType::kExecutionRequest); - auto body = packet_writer_.Append(); - 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 lock(mutex_); - packet_writer_.Begin(PacketType::kExecutionRequest); - auto body = packet_writer_.Append(); - body->action = ExecutionRequest::Action::kInterrupt; - packet_writer_.End(); - Flush(); -} - -void DebugClient::Exit() { - std::lock_guard lock(mutex_); - packet_writer_.Begin(PacketType::kExecutionRequest); - auto body = packet_writer_.Append(); - body->action = ExecutionRequest::Action::kExit; - packet_writer_.End(); - Flush(); -} - -void DebugClient::BeginUpdateAllState() { - std::lock_guard 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 diff --git a/src/xenia/debug/debug_client.h b/src/xenia/debug/debug_client.h deleted file mode 100644 index c749da8a4..000000000 --- a/src/xenia/debug/debug_client.h +++ /dev/null @@ -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 -#include -#include -#include - -#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 entries) = 0; - virtual void OnThreadsUpdated( - std::vector entries) = 0; - virtual void OnThreadStateUpdated( - uint32_t thread_handle, const ThreadStateEntry* entry, - std::vector 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_; - std::unique_ptr thread_; - std::vector 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_ diff --git a/src/xenia/debug/debug_server.cc b/src/xenia/debug/debug_server.cc deleted file mode 100644 index 64ea25ce7..000000000 --- a/src/xenia/debug/debug_server.cc +++ /dev/null @@ -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 - -#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 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 fn) { - xe::threading::Fence fence; - { - std::lock_guard lock(post_mutex_); - post_queue_.push_back([&fence, fn]() { - fn(); - fence.Signal(); - }); - } - post_event_->Set(); - fence.Wait(); -} - -void DebugServer::AcceptClient(std::unique_ptr 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 fn; - { - std::lock_guard 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(); - 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(); - auto modules = - object_table->GetObjectsByType(XObject::Type::kTypeModule); - body->count = uint32_t(modules.size()); - for (auto& module : modules) { - auto entry = packet_writer_.Append(); - 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(); - auto threads = - object_table->GetObjectsByType(XObject::Type::kTypeThread); - body->count = uint32_t(threads.size()); - for (auto& thread : threads) { - auto entry = packet_writer_.Append(); - 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(); - auto stack_walker = emulator->processor()->stack_walker(); - auto threads = - emulator->kernel_state()->object_table()->GetObjectsByType( - 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(); - 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(); - 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(); - body->current_state = ExecutionNotification::State::kRunning; - packet_writer_.End(); - Flush(); -} - -void DebugServer::OnExecutionInterrupted() { - packet_writer_.Begin(PacketType::kExecutionNotification); - auto body = packet_writer_.Append(); - 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(); - 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(); - 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(); - body->code = GenericResponse::Code::kError; - packet_writer_.AppendString(error_message); - packet_writer_.End(); - Flush(); -} - -} // namespace debug -} // namespace xe diff --git a/src/xenia/debug/debug_server.h b/src/xenia/debug/debug_server.h deleted file mode 100644 index f038bac71..000000000 --- a/src/xenia/debug/debug_server.h +++ /dev/null @@ -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 -#include -#include -#include -#include -#include - -#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 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 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 socket_server_; - - std::unique_ptr client_; - std::unique_ptr client_thread_; - - std::mutex post_mutex_; - std::unique_ptr post_event_; - std::list> post_queue_; - - std::vector receive_buffer_; - proto::PacketReader packet_reader_; - proto::PacketWriter packet_writer_; -}; - -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_DEBUG_SERVER_H_ diff --git a/src/xenia/debug/debugger.cc b/src/xenia/debug/debugger.cc index 288a6e9d9..adac2368f 100644 --- a/src/xenia/debug/debugger.cc +++ b/src/xenia/debug/debugger.cc @@ -11,17 +11,21 @@ #include +#include #include +#include "third_party/capstone/include/capstone.h" +#include "third_party/capstone/include/x86.h" +#include "xenia/base/debugging.h" #include "xenia/base/filesystem.h" #include "xenia/base/logging.h" #include "xenia/base/string.h" #include "xenia/base/threading.h" #include "xenia/cpu/backend/code_cache.h" +#include "xenia/cpu/frontend/ppc_instr.h" #include "xenia/cpu/function.h" #include "xenia/cpu/processor.h" #include "xenia/cpu/stack_walker.h" -#include "xenia/debug/debug_server.h" #include "xenia/emulator.h" #include "xenia/kernel/kernel_module.h" #include "xenia/kernel/user_module.h" @@ -37,9 +41,7 @@ DEFINE_bool(debug, DEFAULT_DEBUG_FLAG, "Allow debugging and retain debug information."); DEFINE_string(debug_session_path, "", "Debug output path."); -DEFINE_bool(wait_for_debugger, false, - "Waits for a debugger to attach before starting the game."); -DEFINE_bool(exit_with_debugger, true, "Exit whe the debugger disconnects."); +DEFINE_bool(break_on_start, false, "Break into the debugger on startup."); namespace xe { namespace debug { @@ -48,28 +50,63 @@ using xe::cpu::ThreadState; using xe::kernel::XObject; using xe::kernel::XThread; -Breakpoint::Breakpoint(Type type, uint32_t address) - : type_(type), address_(address) {} +ThreadExecutionInfo::ThreadExecutionInfo() = default; -Breakpoint::~Breakpoint() = default; +ThreadExecutionInfo::~ThreadExecutionInfo() = default; -Debugger::Debugger(Emulator* emulator) : emulator_(emulator) {} +Debugger::Debugger(Emulator* emulator) : emulator_(emulator) { + ExceptionHandler::Install(ExceptionCallbackThunk, this); -Debugger::~Debugger() { StopSession(); } + if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) != CS_ERR_OK) { + assert_always("Failed to initialize capstone"); + } + cs_option(capstone_handle_, CS_OPT_SYNTAX, CS_OPT_SYNTAX_INTEL); + cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_ON); + cs_option(capstone_handle_, CS_OPT_SKIPDATA, CS_OPT_OFF); +} -void Debugger::set_attached(bool attached) { - if (is_attached_ && !attached) { - // Debugger detaching. - if (FLAGS_exit_with_debugger) { - XELOGE("Debugger detached, --exit_with_debugger is killing us"); - exit(1); - return; +Debugger::~Debugger() { + ExceptionHandler::Uninstall(ExceptionCallbackThunk, this); + + if (capstone_handle_) { + cs_close(&capstone_handle_); + } + + set_debug_listener(nullptr); + StopSession(); +} + +void Debugger::set_debug_listener(DebugListener* debug_listener) { + if (debug_listener == debug_listener_) { + return; + } + if (debug_listener_) { + // Detach old debug listener. + debug_listener_->OnDetached(); + debug_listener_ = nullptr; + } + if (debug_listener) { + debug_listener_ = debug_listener; + } else { + if (execution_state_ == ExecutionState::kPaused) { + XELOGI("Debugger detaching while execution is paused; continuing..."); + Continue(); } } - is_attached_ = attached; - if (is_attached_) { - attach_fence_.Signal(); +} + +void Debugger::DemandDebugListener() { + if (debug_listener_) { + // Already present. + debug_listener_->OnFocus(); + return; } + if (!debug_listener_handler_) { + XELOGE("Debugger demanded a listener but no handler was registered."); + xe::debugging::Break(); + return; + } + set_debug_listener(debug_listener_handler_(this)); } bool Debugger::StartSession() { @@ -87,25 +124,15 @@ bool Debugger::StartSession() { functions_trace_file_ = ChunkedMappedMemoryWriter::Open( functions_trace_path_, 32 * 1024 * 1024, true); - server_ = std::make_unique(this); - if (!server_->Initialize()) { - XELOGE("Unable to initialize XDP debug server"); - return false; - } - return true; } void Debugger::PreLaunch() { - if (FLAGS_wait_for_debugger) { - // Wait for the first client. - XELOGI("Waiting for debugger because of --wait_for_debugger..."); - attach_fence_.Wait(); - XELOGI("Debugger attached, breaking and waiting for continue..."); - + if (FLAGS_break_on_start) { // Start paused. + XELOGI("Breaking into debugger because of --break_on_start..."); execution_state_ = ExecutionState::kRunning; - server_->PostSynchronous([this]() { Interrupt(); }); + Pause(); } else { // Start running. execution_state_ = ExecutionState::kRunning; @@ -113,13 +140,10 @@ void Debugger::PreLaunch() { } void Debugger::StopSession() { - std::lock_guard lock(mutex_); + auto global_lock = global_critical_region_.Acquire(); FlushSession(); - // Kill debug server. - server_.reset(); - functions_file_.reset(); functions_trace_file_.reset(); } @@ -147,185 +171,309 @@ uint8_t* Debugger::AllocateFunctionTraceData(size_t size) { return functions_trace_file_->Allocate(size); } -void Debugger::DumpThreadStacks() { - // NOTE: if any other thread is suspended in a logging line, this will - // hang. So, this probably shouldn't be used. - auto stack_walker = emulator()->processor()->stack_walker(); - auto threads = - emulator_->kernel_state()->object_table()->GetObjectsByType( - XObject::kTypeThread); - for (auto& thread : threads) { - XELOGI("Thread %s (%s)", thread->name().c_str(), - thread->is_guest_thread() ? "guest" : "host"); - uint64_t frame_host_pcs[64]; - uint64_t hash; - size_t count = stack_walker->CaptureStackTrace( - thread->GetWaitHandle()->native_handle(), frame_host_pcs, 0, 64, - nullptr, &hash); - cpu::StackFrame frames[64]; - stack_walker->ResolveStack(frame_host_pcs, frames, count); - for (size_t i = 0; i < count; ++i) { - auto& frame = frames[i]; - if (frame.type == cpu::StackFrame::Type::kHost) { - XELOGI(" %.2lld %.16llX %s", count - i - 1, frame.host_pc, - frame.host_symbol.name); - } else { - auto function = frame.guest_symbol.function; - XELOGI(" %.2lld %.16llX %.8X %s", count - i - 1, frame.host_pc, - frame.guest_pc, function ? function->name().c_str() : "?"); - } - } +std::vector Debugger::QueryThreadExecutionInfos() { + auto global_lock = global_critical_region_.Acquire(); + std::vector result; + for (auto& it : thread_execution_infos_) { + result.push_back(it.second.get()); } + return result; } -int Debugger::AddBreakpoint(Breakpoint* breakpoint) { +ThreadExecutionInfo* Debugger::QueryThreadExecutionInfo( + uint32_t thread_handle) { + auto global_lock = global_critical_region_.Acquire(); + auto& it = thread_execution_infos_.find(thread_handle); + if (it == thread_execution_infos_.end()) { + return nullptr; + } + return it->second.get(); +} + +void Debugger::AddBreakpoint(Breakpoint* breakpoint) { + auto global_lock = global_critical_region_.Acquire(); + // Add to breakpoints map. - { - std::lock_guard lock(mutex_); - breakpoints_.insert( - std::pair(breakpoint->address(), breakpoint)); + breakpoints_.push_back(breakpoint); + + if (execution_state_ == ExecutionState::kRunning) { + breakpoint->Resume(); } - - // Find all functions that contain the breakpoint address. - auto fns = - emulator_->processor()->FindFunctionsWithAddress(breakpoint->address()); - - // TODO(benvanik): breakpoints. - // Add. - // for (auto fn : fns) { - // if (fn->AddBreakpoint(breakpoint)) { - // return 1; - // } - //} - - return 0; } -int Debugger::RemoveBreakpoint(Breakpoint* breakpoint) { +void Debugger::RemoveBreakpoint(Breakpoint* breakpoint) { + auto global_lock = global_critical_region_.Acquire(); + + // Uninstall (if needed). + if (execution_state_ == ExecutionState::kRunning) { + breakpoint->Suspend(); + } + // Remove from breakpoint map. - { - std::lock_guard lock(mutex_); - auto range = breakpoints_.equal_range(breakpoint->address()); - if (range.first == range.second) { - return 1; - } - bool found = false; - for (auto it = range.first; it != range.second; ++it) { - if (it->second == breakpoint) { - breakpoints_.erase(it); - found = true; - break; - } - } - if (!found) { - return 1; - } - } - - // Find all functions that have the breakpoint set. - auto fns = - emulator_->processor()->FindFunctionsWithAddress(breakpoint->address()); - - // Remove. - // TODO(benvanik): breakpoints. - /*for (auto fn : fns) { - fn->RemoveBreakpoint(breakpoint); - }*/ - - return 0; -} - -void Debugger::FindBreakpoints(uint32_t address, - std::vector* out_breakpoints) { - std::lock_guard lock(mutex_); - - out_breakpoints->clear(); - - auto range = breakpoints_.equal_range(address); - if (range.first == range.second) { - return; - } - - for (auto it = range.first; it != range.second; ++it) { - Breakpoint* breakpoint = it->second; - out_breakpoints->push_back(breakpoint); - } + auto it = std::find(breakpoints_.begin(), breakpoints_.end(), breakpoint); + breakpoints_.erase(it); } bool Debugger::SuspendAllThreads() { auto global_lock = global_critical_region_.Acquire(); - auto threads = - emulator_->kernel_state()->object_table()->GetObjectsByType( - XObject::kTypeThread); - for (auto& thread : threads) { - if (!XSUCCEEDED(thread->Suspend(nullptr))) { - return false; + for (auto& it : thread_execution_infos_) { + auto thread_info = it.second.get(); + if (thread_info->suspended) { + // Already suspended - ignore. + continue; + } else if (thread_info->state == ThreadExecutionInfo::State::kZombie || + thread_info->state == ThreadExecutionInfo::State::kExited) { + // Thread is dead and cannot be suspended - ignore. + continue; + } else if (!thread_info->thread->is_guest_thread()) { + // Thread is a host thread, and we aren't suspending those (for now). + continue; + } else if (XThread::IsInThread() && + thread_info->thread_handle == + XThread::GetCurrentThreadHandle()) { + // Can't suspend ourselves. + continue; } + bool did_suspend = XSUCCEEDED(thread_info->thread->Suspend(nullptr)); + assert_true(did_suspend); + thread_info->suspended = true; } return true; } -bool Debugger::ResumeThread(uint32_t thread_id) { - auto thread = emulator_->kernel_state()->GetThreadByID(thread_id); - if (!thread) { +bool Debugger::ResumeThread(uint32_t thread_handle) { + auto global_lock = global_critical_region_.Acquire(); + auto it = thread_execution_infos_.find(thread_handle); + if (it == thread_execution_infos_.end()) { return false; } - return XSUCCEEDED(thread->Resume()); + auto thread_info = it->second.get(); + assert_true(thread_info->suspended); + assert_false(thread_info->state == ThreadExecutionInfo::State::kExited || + thread_info->state == ThreadExecutionInfo::State::kZombie); + thread_info->suspended = false; + return XSUCCEEDED(thread_info->thread->Resume()); } bool Debugger::ResumeAllThreads() { - auto threads = - emulator_->kernel_state()->object_table()->GetObjectsByType( - XObject::kTypeThread); - for (auto& thread : threads) { - if (!XSUCCEEDED(thread->Resume())) { - return false; + auto global_lock = global_critical_region_.Acquire(); + for (auto& it : thread_execution_infos_) { + auto thread_info = it.second.get(); + if (!thread_info->suspended) { + // Not suspended by us - ignore. + continue; + } else if (thread_info->state == ThreadExecutionInfo::State::kZombie || + thread_info->state == ThreadExecutionInfo::State::kExited) { + // Thread is dead and cannot be resumed - ignore. + continue; + } else if (!thread_info->thread->is_guest_thread()) { + // Thread is a host thread, and we aren't suspending those (for now). + continue; + } else if (XThread::IsInThread() && + thread_info->thread_handle == + XThread::GetCurrentThreadHandle()) { + // Can't resume ourselves. + continue; } + thread_info->suspended = false; + bool did_resume = XSUCCEEDED(thread_info->thread->Resume()); + assert_true(did_resume); } return true; } -void Debugger::Interrupt() { - std::lock_guard lock(mutex_); - assert_true(execution_state_ == ExecutionState::kRunning); - SuspendAllThreads(); - execution_state_ = ExecutionState::kStopped; - server_->OnExecutionInterrupted(); +void Debugger::UpdateThreadExecutionStates(uint32_t override_handle, + X64Context* override_context) { + auto global_lock = global_critical_region_.Acquire(); + auto stack_walker = emulator_->processor()->stack_walker(); + uint64_t frame_host_pcs[64]; + xe::cpu::StackFrame cpu_frames[64]; + for (auto& it : thread_execution_infos_) { + auto thread_info = it.second.get(); + auto thread = thread_info->thread; + + // 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_info->guest_context, + thread->thread_state()->context(), + sizeof(thread_info->guest_context)); + } + + // Grab stack trace and X64 context then resolve all symbols. + uint64_t hash; + X64Context* in_host_context = nullptr; + if (override_handle == thread_info->thread_handle) { + // If we were passed an override context we use that. Otherwise, ask the + // stack walker for a new context. + in_host_context = override_context; + } + size_t count = stack_walker->CaptureStackTrace( + thread->GetWaitHandle()->native_handle(), frame_host_pcs, 0, + xe::countof(frame_host_pcs), in_host_context, + &thread_info->host_context, &hash); + stack_walker->ResolveStack(frame_host_pcs, cpu_frames, count); + thread_info->frames.resize(count); + for (size_t i = 0; i < count; ++i) { + auto& cpu_frame = cpu_frames[i]; + auto& frame = thread_info->frames[i]; + frame.host_pc = cpu_frame.host_pc; + frame.host_function_address = cpu_frame.host_symbol.address; + frame.guest_pc = cpu_frame.guest_pc; + frame.guest_function_address = 0; + frame.guest_function = nullptr; + auto function = cpu_frame.guest_symbol.function; + if (cpu_frame.type == cpu::StackFrame::Type::kGuest && function) { + frame.guest_function_address = function->address(); + frame.guest_function = function; + } else { + std::strncpy(frame.name, cpu_frame.host_symbol.name, + xe::countof(frame.name)); + frame.name[xe::countof(frame.name) - 1] = 0; + } + } + } +} + +void Debugger::SuspendAllBreakpoints() { + for (auto breakpoint : breakpoints_) { + breakpoint->Suspend(); + } +} + +void Debugger::ResumeAllBreakpoints() { + for (auto breakpoint : breakpoints_) { + breakpoint->Resume(); + } +} + +void Debugger::Show() { + if (debug_listener_) { + debug_listener_->OnFocus(); + } else { + DemandDebugListener(); + } +} + +void Debugger::Pause() { + { + auto global_lock = global_critical_region_.Acquire(); + assert_true(execution_state_ == ExecutionState::kRunning); + SuspendAllThreads(); + SuspendAllBreakpoints(); + UpdateThreadExecutionStates(); + execution_state_ = ExecutionState::kPaused; + if (debug_listener_) { + debug_listener_->OnExecutionPaused(); + } + } + DemandDebugListener(); } void Debugger::Continue() { - std::lock_guard lock(mutex_); - assert_true(execution_state_ == ExecutionState::kStopped); - ResumeAllThreads(); + auto global_lock = global_critical_region_.Acquire(); + if (execution_state_ == ExecutionState::kRunning) { + return; + } else if (execution_state_ == ExecutionState::kStepping) { + assert_always("cancel stepping not done yet"); + } execution_state_ = ExecutionState::kRunning; - server_->OnExecutionContinued(); + ResumeAllBreakpoints(); + ResumeAllThreads(); + if (debug_listener_) { + debug_listener_->OnExecutionContinued(); + } } -void Debugger::StepOne(uint32_t thread_id) { - std::lock_guard lock(mutex_); - assert_true(execution_state_ == ExecutionState::kStopped); - // +void Debugger::StepGuestInstruction(uint32_t thread_handle) { + auto global_lock = global_critical_region_.Acquire(); + assert_true(execution_state_ == ExecutionState::kPaused); + execution_state_ = ExecutionState::kStepping; + + auto thread_info = thread_execution_infos_[thread_handle].get(); + + uint32_t next_pc = CalculateNextGuestInstruction( + thread_info, thread_info->frames[0].guest_pc); + + assert_null(thread_info->step_breakpoint.get()); + thread_info->step_breakpoint = std::make_unique( + this, StepBreakpoint::AddressType::kGuest, next_pc); + AddBreakpoint(thread_info->step_breakpoint.get()); + thread_info->step_breakpoint->Resume(); + + // ResumeAllBreakpoints(); + ResumeThread(thread_handle); } -void Debugger::StepTo(uint32_t thread_id, uint32_t target_pc) { - std::lock_guard lock(mutex_); - assert_true(execution_state_ == ExecutionState::kStopped); - // +void Debugger::StepHostInstruction(uint32_t thread_handle) { + auto global_lock = global_critical_region_.Acquire(); + assert_true(execution_state_ == ExecutionState::kPaused); + execution_state_ = ExecutionState::kStepping; + + auto thread_info = thread_execution_infos_[thread_handle].get(); + uint64_t new_host_pc = + CalculateNextHostInstruction(thread_info, thread_info->frames[0].host_pc); + + assert_null(thread_info->step_breakpoint.get()); + thread_info->step_breakpoint = std::make_unique( + this, StepBreakpoint::AddressType::kHost, new_host_pc); + AddBreakpoint(thread_info->step_breakpoint.get()); + thread_info->step_breakpoint->Resume(); + + // ResumeAllBreakpoints(); + ResumeThread(thread_handle); } -void Debugger::OnThreadCreated(xe::kernel::XThread* thread) { - // TODO(benvanik): notify transports. +void Debugger::OnThreadCreated(XThread* thread) { + auto global_lock = global_critical_region_.Acquire(); + auto thread_info = std::make_unique(); + thread_info->thread_handle = thread->handle(); + thread_info->thread = thread; + thread_info->state = ThreadExecutionInfo::State::kAlive; + thread_info->suspended = false; + thread_execution_infos_.emplace(thread_info->thread_handle, + std::move(thread_info)); } -void Debugger::OnThreadExit(xe::kernel::XThread* thread) { - // TODO(benvanik): notify transports. +void Debugger::OnThreadExit(XThread* thread) { + auto global_lock = global_critical_region_.Acquire(); + auto it = thread_execution_infos_.find(thread->handle()); + assert_true(it != thread_execution_infos_.end()); + auto thread_info = it->second.get(); + thread_info->state = ThreadExecutionInfo::State::kExited; } -void Debugger::OnThreadDestroyed(xe::kernel::XThread* thread) { - // TODO(benvanik): notify transports. +void Debugger::OnThreadDestroyed(XThread* thread) { + auto global_lock = global_critical_region_.Acquire(); + auto it = thread_execution_infos_.find(thread->handle()); + assert_true(it != thread_execution_infos_.end()); + auto thread_info = it->second.get(); + // TODO(benvanik): retain the thread? + thread_info->thread = nullptr; + thread_info->state = ThreadExecutionInfo::State::kZombie; +} + +void Debugger::OnThreadEnteringWait(XThread* thread) { + auto global_lock = global_critical_region_.Acquire(); + auto it = thread_execution_infos_.find(thread->handle()); + assert_true(it != thread_execution_infos_.end()); + auto thread_info = it->second.get(); + thread_info->state = ThreadExecutionInfo::State::kWaiting; +} + +void Debugger::OnThreadLeavingWait(XThread* thread) { + auto global_lock = global_critical_region_.Acquire(); + auto it = thread_execution_infos_.find(thread->handle()); + assert_true(it != thread_execution_infos_.end()); + auto thread_info = it->second.get(); + if (thread_info->state == ThreadExecutionInfo::State::kWaiting) { + thread_info->state = ThreadExecutionInfo::State::kAlive; + } } void Debugger::OnFunctionDefined(cpu::Function* function) { + auto global_lock = global_critical_region_.Acquire(); // TODO(benvanik): breakpoints? // Man, I'd love not to take this lock. // std::vector breakpoints; @@ -351,15 +499,455 @@ void Debugger::OnFunctionDefined(cpu::Function* function) { //} } -void Debugger::OnBreakpointHit(xe::kernel::XThread* thread, - Breakpoint* breakpoint) { - // Suspend all threads immediately. +bool Debugger::ExceptionCallbackThunk(Exception* ex, void* data) { + return reinterpret_cast(data)->ExceptionCallback(ex); +} + +bool Debugger::ExceptionCallback(Exception* ex) { + if (ex->code() != Exception::Code::kIllegalInstruction) { + // We only care about illegal instructions. Other things will be handled by + // other handlers (probably). If nothing else picks it up we'll be called + // with OnUnhandledException to do real crash handling. + return false; + } + + // Verify an expected illegal instruction. + auto instruction_bytes = + xe::load_and_swap(reinterpret_cast(ex->pc())); + if (instruction_bytes != 0x0F0B) { + // Not our ud2 - not us. + return false; + } + + auto global_lock = global_critical_region_.Acquire(); + + // Suspend all threads (but ourselves). SuspendAllThreads(); - // TODO(benvanik): notify transports. + // Lookup thread info block. + auto it = thread_execution_infos_.find(XThread::GetCurrentThreadHandle()); + if (it == thread_execution_infos_.end()) { + // Not found - exception on a thread we don't know about? + assert_always("UD2 on a thread we don't track"); + return false; + } + auto thread_info = it->second.get(); + + // Run through and uninstall all breakpoint UD2s to get us back to a clean + // state. + if (execution_state_ != ExecutionState::kStepping) { + SuspendAllBreakpoints(); + } + + // Update all thread states with their latest values, using the context we got + // from the exception instead of a sampled value (as it would just show the + // exception handler). + UpdateThreadExecutionStates(thread_info->thread_handle, ex->thread_context()); + + // Whether we should block waiting for a continue event. + bool wait_for_continue = false; + + // if (thread_info->restore_original_breakpoint && + // ex->pc() == thread_info->restore_step_breakpoint.host_address) { + // assert_always("TODO"); + // // We were temporarily stepping to restore a breakpoint. Reinstall it. + // PatchBreakpoint(thread_info->restore_original_breakpoint); + // thread_info->restore_original_breakpoint = nullptr; + // thread_info->is_stepping = false; + // execution_state_ = ExecutionState::kRunning; + // return true; + //} + + if (thread_info->step_breakpoint.get()) { + // Thread is stepping. This is likely a stepping breakpoint. + if (thread_info->step_breakpoint->ContainsHostAddress(ex->pc())) { + // Our step request has completed. Remove the breakpoint and fire event. + thread_info->step_breakpoint->Suspend(); + RemoveBreakpoint(thread_info->step_breakpoint.get()); + thread_info->step_breakpoint.reset(); + OnStepCompleted(thread_info); + wait_for_continue = true; + } + } + + // If we weren't stepping check other breakpoints. + if (!wait_for_continue && execution_state_ != ExecutionState::kStepping) { + // TODO(benvanik): faster lookup. + for (auto breakpoint : breakpoints_) { + if (!breakpoint->is_enabled() || + breakpoint->type() != Breakpoint::Type::kCode) { + continue; + } + auto code_breakpoint = static_cast(breakpoint); + if (code_breakpoint->address_type() == + CodeBreakpoint::AddressType::kHost && + code_breakpoint->host_address() == ex->pc()) { + // Hit host address breakpoint, which we can be confident is where we + // want to be. + OnBreakpointHit(thread_info, breakpoint); + wait_for_continue = true; + } else if (code_breakpoint->ContainsHostAddress(ex->pc())) { + // Hit guest address breakpoint - maybe. + OnBreakpointHit(thread_info, breakpoint); + wait_for_continue = true; + } + } + // if (breakpoint->is_stepping()) { + // assert_always("TODO"); + // // Hit a stepping breakpoint for another thread. + // // Skip it by uninstalling the previous step, adding a step for one + // // after us, and resuming just our thread. The step handler will + // // reinstall the restore_breakpoint after stepping and continue. + // thread_info->restore_original_breakpoint = breakpoint; + // // thread_info->restore_step_breakpoint(rip + N) + // PatchBreakpoint(&thread_info->restore_step_breakpoint); + // thread_info->is_stepping = true; + // execution_state_ = ExecutionState::kStepping; + // return true; + //} + } + + // We are waiting on the debugger now. Either wait for it to continue, add a + // new step, or direct us somewhere else. + if (wait_for_continue) { + // The debugger will ResumeAllThreads or just resume us (depending on what + // it wants to do). + execution_state_ = ExecutionState::kPaused; + thread_info->suspended = true; + + // Must unlock, or we will deadlock. + global_lock.unlock(); + + thread_info->thread->Suspend(nullptr); + } + + // Apply thread context changes. + // TODO(benvanik): apply to all threads? + ex->set_resume_pc(thread_info->host_context.rip); + + // Resume execution. + return true; +} + +void Debugger::OnStepCompleted(ThreadExecutionInfo* thread_info) { + auto global_lock = global_critical_region_.Acquire(); + execution_state_ = ExecutionState::kPaused; + if (debug_listener_) { + debug_listener_->OnExecutionPaused(); + } // Note that we stay suspended. } +void Debugger::OnBreakpointHit(ThreadExecutionInfo* thread_info, + Breakpoint* breakpoint) { + auto global_lock = global_critical_region_.Acquire(); + execution_state_ = ExecutionState::kPaused; + if (debug_listener_) { + debug_listener_->OnExecutionPaused(); + } + + // Note that we stay suspended. +} + +bool Debugger::OnUnhandledException(Exception* ex) { + // If we have no listener return right away. + // TODO(benvanik): DemandDebugListener()? + if (!debug_listener_) { + return false; + } + + // If this isn't a managed thread, fail - let VS handle it for now. + if (!XThread::IsInThread()) { + return false; + } + + auto global_lock = global_critical_region_.Acquire(); + + // Suspend all guest threads (but this one). + SuspendAllThreads(); + + UpdateThreadExecutionStates(XThread::GetCurrentThreadHandle(), + ex->thread_context()); + + // Stop and notify the listener. + // This will take control. + assert_true(execution_state_ == ExecutionState::kRunning); + execution_state_ = ExecutionState::kPaused; + + // Notify debugger that exceution stopped. + // debug_listener_->OnException(info); + debug_listener_->OnExecutionPaused(); + + // Suspend self. + XThread::GetCurrentThread()->Suspend(nullptr); + + return true; +} + +bool TestPpcCondition(const xe::cpu::frontend::PPCContext* context, uint32_t bo, + uint32_t bi, bool check_ctr, bool check_cond) { + bool ctr_ok = true; + if (check_ctr) { + if (xe::cpu::frontend::select_bits(bo, 2, 2)) { + ctr_ok = true; + } else { + uint32_t new_ctr_value = static_cast(context->ctr - 1); + if (xe::cpu::frontend::select_bits(bo, 1, 1)) { + ctr_ok = new_ctr_value == 0; + } else { + ctr_ok = new_ctr_value != 0; + } + } + } + bool cond_ok = true; + if (check_cond) { + if (xe::cpu::frontend::select_bits(bo, 4, 4)) { + cond_ok = true; + } else { + uint8_t cr = *(reinterpret_cast(&context->cr0) + + (4 * (bi >> 2)) + (bi & 3)); + if (xe::cpu::frontend::select_bits(bo, 3, 3)) { + cond_ok = cr != 0; + } else { + cond_ok = cr == 0; + } + } + } + return ctr_ok && cond_ok; +} + +uint32_t Debugger::CalculateNextGuestInstruction( + ThreadExecutionInfo* thread_info, uint32_t current_pc) { + xe::cpu::frontend::InstrData i; + i.address = current_pc; + i.code = xe::load_and_swap( + emulator_->memory()->TranslateVirtual(i.address)); + i.type = xe::cpu::frontend::GetInstrType(i.code); + if (!i.type) { + return current_pc + 4; + } else if (i.code == 0x4E800020) { + // blr -- unconditional branch to LR. + uint32_t target_pc = static_cast(thread_info->guest_context.lr); + return target_pc; + } else if (i.code == 0x4E800420) { + // bctr -- unconditional branch to CTR. + uint32_t target_pc = static_cast(thread_info->guest_context.ctr); + return target_pc; + } else if (i.type->opcode == 0x48000000) { + // b/ba/bl/bla + uint32_t target_pc = + static_cast(xe::cpu::frontend::XEEXTS26(i.I.LI << 2)) + + (i.I.AA ? 0u : i.address); + return target_pc; + } else if (i.type->opcode == 0x40000000) { + // bc/bca/bcl/bcla + uint32_t target_pc = + static_cast(xe::cpu::frontend::XEEXTS16(i.B.BD << 2)) + + (i.B.AA ? 0u : i.address); + bool test_passed = TestPpcCondition(&thread_info->guest_context, i.B.BO, + i.B.BI, true, true); + return test_passed ? target_pc : current_pc + 4; + } else if (i.type->opcode == 0x4C000020) { + // bclr/bclrl + uint32_t target_pc = static_cast(thread_info->guest_context.lr); + bool test_passed = TestPpcCondition(&thread_info->guest_context, i.XL.BO, + i.XL.BI, true, true); + return test_passed ? target_pc : current_pc + 4; + } else if (i.type->opcode == 0x4C000420) { + // bcctr/bcctrl + uint32_t target_pc = static_cast(thread_info->guest_context.ctr); + bool test_passed = TestPpcCondition(&thread_info->guest_context, i.XL.BO, + i.XL.BI, false, true); + return test_passed ? target_pc : current_pc + 4; + } else { + return current_pc + 4; + } +} + +uint64_t ReadCapstoneReg(X64Context* context, x86_reg reg) { + switch (reg) { + case X86_REG_RAX: + return context->rax; + case X86_REG_RCX: + return context->rcx; + case X86_REG_RDX: + return context->rdx; + case X86_REG_RBX: + return context->rbx; + case X86_REG_RSP: + return context->rsp; + case X86_REG_RBP: + return context->rbp; + case X86_REG_RSI: + return context->rsi; + case X86_REG_RDI: + return context->rdi; + case X86_REG_R8: + return context->r8; + case X86_REG_R9: + return context->r9; + case X86_REG_R10: + return context->r10; + case X86_REG_R11: + return context->r11; + case X86_REG_R12: + return context->r12; + case X86_REG_R13: + return context->r13; + case X86_REG_R14: + return context->r14; + case X86_REG_R15: + return context->r15; + default: + assert_unhandled_case(reg); + return 0; + } +} + +#define X86_EFLAGS_CF 0x00000001 // Carry Flag +#define X86_EFLAGS_PF 0x00000004 // Parity Flag +#define X86_EFLAGS_ZF 0x00000040 // Zero Flag +#define X86_EFLAGS_SF 0x00000080 // Sign Flag +#define X86_EFLAGS_OF 0x00000800 // Overflow Flag +bool TestCapstoneEflags(uint32_t eflags, uint32_t insn) { + // http://www.felixcloutier.com/x86/Jcc.html + switch (insn) { + case X86_INS_JAE: + // CF=0 && ZF=0 + return ((eflags & X86_EFLAGS_CF) == 0) && ((eflags & X86_EFLAGS_ZF) == 0); + case X86_INS_JA: + // CF=0 + return (eflags & X86_EFLAGS_CF) == 0; + case X86_INS_JBE: + // CF=1 || ZF=1 + return ((eflags & X86_EFLAGS_CF) == X86_EFLAGS_CF) || + ((eflags & X86_EFLAGS_ZF) == X86_EFLAGS_ZF); + case X86_INS_JB: + // CF=1 + return (eflags & X86_EFLAGS_CF) == X86_EFLAGS_CF; + case X86_INS_JE: + // ZF=1 + return (eflags & X86_EFLAGS_ZF) == X86_EFLAGS_ZF; + case X86_INS_JGE: + // SF=OF + return (eflags & X86_EFLAGS_SF) == (eflags & X86_EFLAGS_OF); + case X86_INS_JG: + // ZF=0 && SF=OF + return ((eflags & X86_EFLAGS_ZF) == 0) && + ((eflags & X86_EFLAGS_SF) == (eflags & X86_EFLAGS_OF)); + case X86_INS_JLE: + // ZF=1 || SF!=OF + return ((eflags & X86_EFLAGS_ZF) == X86_EFLAGS_ZF) || + ((eflags & X86_EFLAGS_SF) != X86_EFLAGS_OF); + case X86_INS_JL: + // SF!=OF + return (eflags & X86_EFLAGS_SF) != (eflags & X86_EFLAGS_OF); + case X86_INS_JNE: + // ZF=0 + return (eflags & X86_EFLAGS_ZF) == 0; + case X86_INS_JNO: + // OF=0 + return (eflags & X86_EFLAGS_OF) == 0; + case X86_INS_JNP: + // PF=0 + return (eflags & X86_EFLAGS_PF) == 0; + case X86_INS_JNS: + // SF=0 + return (eflags & X86_EFLAGS_SF) == 0; + case X86_INS_JO: + // OF=1 + return (eflags & X86_EFLAGS_OF) == X86_EFLAGS_OF; + case X86_INS_JP: + // PF=1 + return (eflags & X86_EFLAGS_PF) == X86_EFLAGS_PF; + case X86_INS_JS: + // SF=1 + return (eflags & X86_EFLAGS_SF) == X86_EFLAGS_SF; + default: + assert_unhandled_case(insn); + return false; + } +} + +uint64_t Debugger::CalculateNextHostInstruction( + ThreadExecutionInfo* thread_info, uint64_t current_pc) { + auto machine_code_ptr = reinterpret_cast(current_pc); + size_t remaining_machine_code_size = 64; + uint64_t host_address = current_pc; + cs_insn insn = {0}; + cs_detail all_detail = {0}; + insn.detail = &all_detail; + cs_disasm_iter(capstone_handle_, &machine_code_ptr, + &remaining_machine_code_size, &host_address, &insn); + auto& detail = all_detail.x86; + switch (insn.id) { + default: + // Not a branching instruction - just move over it. + return current_pc + insn.size; + case X86_INS_CALL: { + assert_true(detail.op_count == 1); + assert_true(detail.operands[0].type == X86_OP_REG); + uint64_t target_pc = + ReadCapstoneReg(&thread_info->host_context, detail.operands[0].reg); + return target_pc; + } break; + case X86_INS_RET: { + assert_zero(detail.op_count); + auto stack_ptr = + reinterpret_cast(thread_info->host_context.rsp); + uint64_t target_pc = stack_ptr[0]; + return target_pc; + } break; + case X86_INS_JMP: { + assert_true(detail.op_count == 1); + if (detail.operands[0].type == X86_OP_IMM) { + uint64_t target_pc = static_cast(detail.operands[0].imm); + return target_pc; + } else if (detail.operands[0].type == X86_OP_REG) { + uint64_t target_pc = + ReadCapstoneReg(&thread_info->host_context, detail.operands[0].reg); + return target_pc; + } else { + // TODO(benvanik): find some more uses of this. + assert_always("jmp branch emulation not yet implemented"); + return current_pc + insn.size; + } + } break; + case X86_INS_JCXZ: + case X86_INS_JECXZ: + case X86_INS_JRCXZ: + assert_always("j*cxz branch emulation not yet implemented"); + return current_pc + insn.size; + case X86_INS_JAE: + case X86_INS_JA: + case X86_INS_JBE: + case X86_INS_JB: + case X86_INS_JE: + case X86_INS_JGE: + case X86_INS_JG: + case X86_INS_JLE: + case X86_INS_JL: + case X86_INS_JNE: + case X86_INS_JNO: + case X86_INS_JNP: + case X86_INS_JNS: + case X86_INS_JO: + case X86_INS_JP: + case X86_INS_JS: { + assert_true(detail.op_count == 1); + assert_true(detail.operands[0].type == X86_OP_IMM); + uint64_t target_pc = static_cast(detail.operands[0].imm); + bool test_passed = + TestCapstoneEflags(thread_info->host_context.eflags, insn.id); + if (test_passed) { + return target_pc; + } else { + return current_pc + insn.size; + } + } break; + } +} + } // namespace debug } // namespace xe diff --git a/src/xenia/debug/debugger.h b/src/xenia/debug/debugger.h index 889c1e06c..4269299eb 100644 --- a/src/xenia/debug/debugger.h +++ b/src/xenia/debug/debugger.h @@ -19,6 +19,7 @@ #include #include +#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 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 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 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 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* 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& 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 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 debug_listener_handler_; + DebugListener* debug_listener_ = nullptr; + + // TODO(benvanik): cleanup - maybe remove now that in-process? std::wstring functions_path_; std::unique_ptr functions_file_; std::wstring functions_trace_path_; std::unique_ptr functions_trace_file_; - std::recursive_mutex mutex_; - ExecutionState execution_state_ = ExecutionState::kStopped; - xe::global_critical_region global_critical_region_; - std::multimap breakpoints_; + + ExecutionState execution_state_ = ExecutionState::kPaused; + + // Maps thread ID to state. Updated on thread create, and threads are never + // removed. + std::map> + thread_execution_infos_; + // TODO(benvanik): cleanup/change structures. + std::vector breakpoints_; }; } // namespace debug diff --git a/src/xenia/debug/premake5.lua b/src/xenia/debug/premake5.lua index ad6670fc5..fe613ff09 100644 --- a/src/xenia/debug/premake5.lua +++ b/src/xenia/debug/premake5.lua @@ -16,4 +16,3 @@ project("xenia-debug") project_root.."/build_tools/third_party/gflags/src", }) local_platform_files() - recursive_platform_files("proto") diff --git a/src/xenia/debug/proto/packet_reader.h b/src/xenia/debug/proto/packet_reader.h deleted file mode 100644 index 26639fd9c..000000000 --- a/src/xenia/debug/proto/packet_reader.h +++ /dev/null @@ -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 -#include -#include -#include - -#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(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 ClonePacket() { - assert_not_null(current_packet_); - size_t length = sizeof(Packet) + current_packet_->body_length; - auto clone = std::make_unique(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 - const T* Read() { - return reinterpret_cast(Read(sizeof(T))); - } - - template - std::vector ReadArray(size_t count) { - std::vector entries; - for (size_t i = 0; i < count; ++i) { - entries.push_back(Read()); - } - 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(value.data()), src, length); - packet_offset_ += length; - return value; - } - - private: - std::vector 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_ diff --git a/src/xenia/debug/proto/packet_writer.h b/src/xenia/debug/proto/packet_writer.h deleted file mode 100644 index d19959606..000000000 --- a/src/xenia/debug/proto/packet_writer.h +++ /dev/null @@ -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 -#include -#include - -#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(); - 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(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 - T* Append() { - return reinterpret_cast(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 buffer_; - size_t buffer_offset_ = 0; - - Packet* current_packet_ = nullptr; -}; - -} // namespace proto -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_PROTO_PACKET_WRITER_H_ diff --git a/src/xenia/debug/proto/varint.h b/src/xenia/debug/proto/varint.h deleted file mode 100644 index 67a021c97..000000000 --- a/src/xenia/debug/proto/varint.h +++ /dev/null @@ -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 - -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_ diff --git a/src/xenia/debug/proto/xdp_protocol.h b/src/xenia/debug/proto/xdp_protocol.h deleted file mode 100644 index 60ec37c2c..000000000 --- a/src/xenia/debug/proto/xdp_protocol.h +++ /dev/null @@ -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 - -#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(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_ diff --git a/src/xenia/debug/proto/xdp_protocol.md b/src/xenia/debug/proto/xdp_protocol.md deleted file mode 100644 index f8f8b9c4c..000000000 --- a/src/xenia/debug/proto/xdp_protocol.md +++ /dev/null @@ -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. diff --git a/src/xenia/debug/ui/application.cc b/src/xenia/debug/ui/application.cc deleted file mode 100644 index 75c8ab5a3..000000000 --- a/src/xenia/debug/ui/application.cc +++ /dev/null @@ -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::Create() { - std::unique_ptr 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(loop(), &client_); - - // TODO(benvanik): flags and such. - if (!client_.Connect("localhost", 9002)) { - return false; - } - - main_window_ = std::make_unique(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 diff --git a/src/xenia/debug/ui/application.h b/src/xenia/debug/ui/application.h deleted file mode 100644 index 86336e408..000000000 --- a/src/xenia/debug/ui/application.h +++ /dev/null @@ -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 - -#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 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 loop_; - std::unique_ptr main_window_; - DebugClient client_; - - std::unique_ptr system_; -}; - -} // namespace ui -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_UI_APPLICATION_H_ diff --git a/src/xenia/debug/ui/control.h b/src/xenia/debug/ui/control.h deleted file mode 100644 index ff657e7fe..000000000 --- a/src/xenia/debug/ui/control.h +++ /dev/null @@ -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 -#include - -#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 handler_; - xe::debug::DebugClient* client_ = nullptr; -}; - -} // namespace ui -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_UI_CONTROL_H_ diff --git a/src/xenia/debug/ui/debug_window.cc b/src/xenia/debug/ui/debug_window.cc new file mode 100644 index 000000000..d72908cf1 --- /dev/null +++ b/src/xenia/debug/ui/debug_window.cc @@ -0,0 +1,1683 @@ +/** + ****************************************************************************** + * 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/debug_window.h" + +#include + +#include +#include +#include + +#include "third_party/capstone/include/capstone.h" +#include "third_party/capstone/include/x86.h" +#include "third_party/elemental-forms/src/el/util/clipboard.h" +#include "third_party/imgui/imgui.h" +#include "third_party/imgui/imgui_internal.h" +#include "xenia/base/clock.h" +#include "xenia/base/logging.h" +#include "xenia/base/math.h" +#include "xenia/base/platform.h" +#include "xenia/base/string_util.h" +#include "xenia/base/threading.h" +#include "xenia/cpu/frontend/ppc_disasm.h" +#include "xenia/cpu/stack_walker.h" +#include "xenia/debug/ui/imgui_renderer.h" +#include "xenia/gpu/graphics_system.h" +#include "xenia/kernel/xmodule.h" +#include "xenia/kernel/xthread.h" +#include "xenia/ui/gl/gl_context.h" + +DEFINE_bool(imgui_debug, false, "Show ImGui debugging tools."); + +namespace xe { +namespace debug { +namespace ui { + +using xe::cpu::frontend::PPCContext; +using xe::cpu::frontend::PPCRegister; +using xe::kernel::XModule; +using xe::kernel::XObject; +using xe::kernel::XThread; +using xe::ui::KeyEvent; +using xe::ui::MenuItem; +using xe::ui::MouseEvent; +using xe::ui::UIEvent; + +const std::wstring kBaseTitle = L"Xenia Debugger"; + +DebugWindow::DebugWindow(Emulator* emulator, xe::ui::Loop* loop) + : emulator_(emulator), + debugger_(emulator->debugger()), + loop_(loop), + window_(xe::ui::Window::Create(loop_, kBaseTitle)) { + if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) != CS_ERR_OK) { + assert_always("Failed to initialize capstone"); + } + cs_option(capstone_handle_, CS_OPT_SYNTAX, CS_OPT_SYNTAX_INTEL); + cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_OFF); +} + +DebugWindow::~DebugWindow() { + loop_->PostSynchronous([this]() { window_.reset(); }); + + if (capstone_handle_) { + cs_close(&capstone_handle_); + } +} + +std::unique_ptr DebugWindow::Create(Emulator* emulator, + xe::ui::Loop* loop) { + std::unique_ptr debug_window(new DebugWindow(emulator, loop)); + if (!debug_window->Initialize()) { + xe::FatalError("Failed to initialize debug window"); + return nullptr; + } + + return debug_window; +} + +bool DebugWindow::Initialize() { + if (!window_->Initialize()) { + XELOGE("Failed to initialize platform window"); + return false; + } + + window_->on_closed.AddListener([this](UIEvent* e) { + // Kill now while we have a GL context. + imgui_renderer_.reset(); + }); + loop_->on_quit.AddListener([this](UIEvent* e) { window_.reset(); }); + + // 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"&Close", + L"Alt+F4", + [this]() { window_->Close(); })); + } + main_menu->AddChild(std::move(file_menu)); + window_->set_main_menu(std::move(main_menu)); + + window_->Resize(1500, 1000); + + // If there exists a display window we need to share resources with it. + xe::ui::gl::GLContext* parent_context = nullptr; + if (emulator_->display_window()) { + parent_context = reinterpret_cast( + emulator_->display_window()->context()); + } + + // Create the GL context used for drawing. + auto context = xe::ui::gl::GLContext::Create(window_.get(), parent_context); + window_->set_context(std::move(context)); + + // Setup ImGui. + imgui_renderer_ = + std::make_unique(window_.get(), window_->context()); + window_->on_key_char.AddListener([](xe::ui::KeyEvent* e) { + auto& io = ImGui::GetIO(); + if (e->key_code() > 0 && e->key_code() < 0x10000) { + io.AddInputCharacter(e->key_code()); + } + e->set_handled(true); + }); + window_->on_key_down.AddListener([](xe::ui::KeyEvent* e) { + auto& io = ImGui::GetIO(); + io.KeysDown[e->key_code()] = true; + switch (e->key_code()) { + case 16: + io.KeyShift = true; + break; + case 17: + io.KeyCtrl = true; + break; + } + }); + window_->on_key_up.AddListener([](xe::ui::KeyEvent* e) { + auto& io = ImGui::GetIO(); + io.KeysDown[e->key_code()] = false; + switch (e->key_code()) { + case 16: + io.KeyShift = false; + break; + case 17: + io.KeyCtrl = false; + break; + } + }); + window_->on_mouse_down.AddListener([](xe::ui::MouseEvent* e) { + auto& io = ImGui::GetIO(); + io.MousePos = + ImVec2(static_cast(e->x()), static_cast(e->y())); + switch (e->button()) { + case xe::ui::MouseEvent::Button::kLeft: + io.MouseDown[0] = true; + break; + case xe::ui::MouseEvent::Button::kRight: + io.MouseDown[1] = true; + break; + } + }); + window_->on_mouse_move.AddListener([](xe::ui::MouseEvent* e) { + auto& io = ImGui::GetIO(); + io.MousePos = + ImVec2(static_cast(e->x()), static_cast(e->y())); + }); + window_->on_mouse_up.AddListener([](xe::ui::MouseEvent* e) { + auto& io = ImGui::GetIO(); + io.MousePos = + ImVec2(static_cast(e->x()), static_cast(e->y())); + switch (e->button()) { + case xe::ui::MouseEvent::Button::kLeft: + io.MouseDown[0] = false; + break; + case xe::ui::MouseEvent::Button::kRight: + io.MouseDown[1] = false; + break; + } + }); + window_->on_mouse_wheel.AddListener([](xe::ui::MouseEvent* e) { + auto& io = ImGui::GetIO(); + io.MousePos = + ImVec2(static_cast(e->x()), static_cast(e->y())); + io.MouseWheel += e->dy() / 120.0f; + }); + + window_->on_painting.AddListener([this](UIEvent* e) { DrawFrame(); }); + + UpdateCache(); + window_->Invalidate(); + + return true; +} + +void DebugWindow::DrawFrame() { + xe::ui::GraphicsContextLock lock(window_->context()); + glClearColor(0.0, 0.0, 0.0, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + + auto& io = ImGui::GetIO(); + auto current_tick_count = Clock::QueryHostTickCount(); + io.DeltaTime = (current_tick_count - last_draw_tick_count_) / + static_cast(Clock::host_tick_frequency()); + last_draw_tick_count_ = current_tick_count; + + io.DisplaySize = ImVec2(static_cast(window_->width()), + static_cast(window_->height())); + + ImGui::NewFrame(); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(-1, 0)); + ImGui::Begin("main_window", nullptr, + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoSavedSettings); + ImGui::SetWindowPos(ImVec2(0, 0)); + ImGui::SetWindowSize(io.DisplaySize); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4, 4)); + + constexpr float kSplitterWidth = 5.0f; + static float function_pane_width = 150.0f; + static float source_pane_width = 600.0f; + static float registers_pane_width = 150.0f; + static float bottom_panes_height = 300.0f; + static float breakpoints_pane_width = 300.0f; + float top_panes_height = + ImGui::GetContentRegionAvail().y - bottom_panes_height; + float log_pane_width = + ImGui::GetContentRegionAvailWidth() - breakpoints_pane_width; + + ImGui::BeginChild("##toolbar", ImVec2(0, 25), true); + DrawToolbar(); + ImGui::EndChild(); + + if (!cache_.is_running) { + // Disabled state? + // https://github.com/ocornut/imgui/issues/211 + } + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + ImGui::BeginChild("##function_pane", + ImVec2(function_pane_width, top_panes_height), true); + DrawFunctionsPane(); + ImGui::EndChild(); + ImGui::SameLine(); + ImGui::InvisibleButton("##vsplitter0", + ImVec2(kSplitterWidth, top_panes_height)); + if (ImGui::IsItemActive()) { + function_pane_width += ImGui::GetIO().MouseDelta.x; + function_pane_width = xe::clamp(function_pane_width, 30.0f, FLT_MAX); + } + ImGui::SameLine(); + ImGui::BeginChild("##source_pane", + ImVec2(source_pane_width, top_panes_height), true); + DrawSourcePane(); + ImGui::EndChild(); + ImGui::SameLine(); + ImGui::InvisibleButton("##vsplitter1", + ImVec2(kSplitterWidth, top_panes_height)); + if (ImGui::IsItemActive()) { + source_pane_width += ImGui::GetIO().MouseDelta.x; + source_pane_width = xe::clamp(source_pane_width, 30.0f, FLT_MAX); + } + ImGui::SameLine(); + ImGui::BeginChild("##registers_pane", + ImVec2(registers_pane_width, top_panes_height), true); + DrawRegistersPane(); + ImGui::EndChild(); + ImGui::SameLine(); + ImGui::InvisibleButton("##vsplitter2", + ImVec2(kSplitterWidth, top_panes_height)); + if (ImGui::IsItemActive()) { + registers_pane_width += ImGui::GetIO().MouseDelta.x; + registers_pane_width = xe::clamp(registers_pane_width, 30.0f, FLT_MAX); + } + ImGui::SameLine(); + ImGui::BeginChild("##right_pane", ImVec2(0, top_panes_height), true); + ImGui::BeginGroup(); + ImGui::RadioButton("Threads", &state_.right_pane_tab, + ImState::kRightPaneThreads); + ImGui::SameLine(); + ImGui::RadioButton("Memory", &state_.right_pane_tab, + ImState::kRightPaneMemory); + ImGui::EndGroup(); + ImGui::Separator(); + switch (state_.right_pane_tab) { + case ImState::kRightPaneThreads: + ImGui::BeginChild("##threads_pane"); + DrawThreadsPane(); + ImGui::EndChild(); + break; + case ImState::kRightPaneMemory: + ImGui::BeginChild("##memory_pane"); + DrawMemoryPane(); + ImGui::EndChild(); + break; + } + ImGui::EndChild(); + ImGui::InvisibleButton("##hsplitter0", ImVec2(-1, kSplitterWidth)); + if (ImGui::IsItemActive()) { + bottom_panes_height -= ImGui::GetIO().MouseDelta.y; + bottom_panes_height = xe::clamp(bottom_panes_height, 30.0f, FLT_MAX); + } + ImGui::BeginChild("##log_pane", ImVec2(log_pane_width, bottom_panes_height), + true); + DrawLogPane(); + ImGui::EndChild(); + ImGui::SameLine(); + ImGui::InvisibleButton("##vsplitter3", + ImVec2(kSplitterWidth, bottom_panes_height)); + if (ImGui::IsItemActive()) { + breakpoints_pane_width -= ImGui::GetIO().MouseDelta.x; + breakpoints_pane_width = xe::clamp(breakpoints_pane_width, 30.0f, FLT_MAX); + } + ImGui::SameLine(); + ImGui::BeginChild("##breakpoints_pane", ImVec2(0, 0), true); + DrawBreakpointsPane(); + ImGui::EndChild(); + ImGui::PopStyleVar(); + + ImGui::PopStyleVar(); + ImGui::End(); + ImGui::PopStyleVar(); + + if (FLAGS_imgui_debug) { + ImGui::ShowTestWindow(); + ImGui::ShowMetricsWindow(); + } + + ImGui::Render(); + + // Continuous paint. + window_->Invalidate(); +} + +void DebugWindow::DrawToolbar() { + // TODO(benvanik): save/load database? other app options? + + // Program control. + if (cache_.is_running) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + ImVec4(0.9f, 0.0f, 0.0f, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + ImVec4(1.0f, 0.0f, 0.0f, 1.0f)); + if (ImGui::Button("Pause", ImVec2(80, 0))) { + debugger_->Pause(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Interrupt the program and break into the debugger."); + } + ImGui::PopStyleColor(3); + } else { + if (ImGui::Button("Continue", ImVec2(80, 0))) { + debugger_->Continue(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Resume the program from the current location."); + } + } + ImGui::SameLine(); + + // Thread dropdown. + // Fast selection of the thread and reports the currently active thread for + // global operations. + // TODO(benvanik): use a popup + custom rendering to get richer view. + int current_thread_index = 0; + StringBuffer thread_combo; + int i = 0; + for (auto thread_info : cache_.thread_execution_infos) { + if (thread_info == state_.thread_info) { + current_thread_index = i; + } + thread_combo.Append(thread_info->thread->name()); + thread_combo.Append(static_cast(0)); + ++i; + } + if (ImGui::Combo("##thread_combo", ¤t_thread_index, + thread_combo.GetString(), 10)) { + // Thread changed. + SelectThreadStackFrame( + cache_.thread_execution_infos[current_thread_index]->thread, 0, true); + } +} + +void DebugWindow::DrawFunctionsPane() { + ImGui::Text(""); + // bar: analyze (?), goto current + // combo with module (+ emulator itself?) + // list with all functions known + // filter box + search button -> search dialog +} + +void DebugWindow::DrawSourcePane() { + auto function = state_.function; + if (!function) { + ImGui::Text("(no function selected)"); + return; + } + ImGui::BeginGroup(); + // top bar: + // copy button + // address start - end + // name text box (editable) + // combo for interleaved + [ppc, hir, opt hir, x64 + byte with sizes] + ImGui::AlignFirstTextHeightToWidgets(); + ImGui::Text(function->module()->name().c_str()); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + char address_str[9]; + std::snprintf(address_str, xe::countof(address_str), "%.8X", + function->address()); + ImGui::PushItemWidth(50); + ImGui::InputText("##address", address_str, xe::countof(address_str), + ImGuiInputTextFlags_AutoSelectAll); + ImGui::PopItemWidth(); + ImGui::SameLine(); + ImGui::Text(" - %.8X", function->end_address()); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + char name[256]; + std::strcpy(name, function->name().c_str()); + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() - 10); + if (ImGui::InputText("##name", name, sizeof(name), + ImGuiInputTextFlags_AutoSelectAll)) { + function->set_name(name); + } + ImGui::PopItemWidth(); + ImGui::EndGroup(); + + ImGui::BeginGroup(); + ImGui::PushButtonRepeat(true); + bool can_step = !cache_.is_running && state_.thread; + if (ImGui::ButtonEx("Step PPC", ImVec2(0, 0), + can_step ? 0 : ImGuiButtonFlags_Disabled)) { + // By enabling the button when stepping we allow repeat behavior. + if (debugger_->execution_state() != ExecutionState::kStepping) { + debugger_->StepGuestInstruction(state_.thread->handle()); + } + } + ImGui::PopButtonRepeat(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Step one PPC instruction on the current thread (hold for many)."); + } + ImGui::SameLine(); + if (state_.source_display_mode > 0) { + // Only show x64 step button if we have x64 visible. + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + ImGui::PushButtonRepeat(true); + if (ImGui::ButtonEx("Step x64", ImVec2(0, 0), + can_step ? 0 : ImGuiButtonFlags_Disabled)) { + // By enabling the button when stepping we allow repeat behavior. + if (debugger_->execution_state() != ExecutionState::kStepping) { + debugger_->StepHostInstruction(state_.thread->handle()); + } + } + ImGui::PopButtonRepeat(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Step one x64 instruction on the current thread (hold for many)."); + } + ImGui::SameLine(); + } + ImGui::Dummy(ImVec2(16, 0)); + ImGui::SameLine(); + if (ImGui::Button("Copy")) { + el::util::Clipboard::SetText("TODO"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + if (function->is_guest()) { + const char* kSourceDisplayModes[] = { + "PPC", "PPC+HIR+x64", "PPC+HIR (opt)+x64", "PPC+x64", + }; + ImGui::PushItemWidth(90); + ImGui::Combo("##display_mode", &state_.source_display_mode, + kSourceDisplayModes, + static_cast(xe::countof(kSourceDisplayModes))); + ImGui::PopItemWidth(); + ImGui::SameLine(); + } + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + ImGui::Text("(profile options?)"); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + ImGui::Text("(hit count)"); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + ImGui::Text("(code size?)"); + ImGui::EndGroup(); + + ImGui::Separator(); + ImGui::BeginChild("##code"); + if (function->is_guest()) { + DrawGuestFunctionSource(); + } else { + // TODO(benvanik): load PDB and display source code? + ImGui::Text("(system function?)"); + } + ImGui::EndChild(); +} + +void DebugWindow::DrawGuestFunctionSource() { + // source code: + // | 8200000 ppc_label: + // | [I] 8200000 FFCCDDEE ppc disam # comment + // | v0 = add v1, v2 + // | A123456 xadd rax, rbx + // icon = active lines/etc (other threads too) + // color gutter background for trace data? (hit count, etc) + // show address + code bytes + ppc diasm with annotation comments + // labels get their own line with duped addresses + // show xrefs to labels? + // hir greyed and offset (background color change?) + // x64 greyed and offset with native address + // hover on registers/etc for tooltip/highlight others + // click register to go to location of last write + // click code address to jump to code + // click memory address to jump to memory browser + // if historical data for memory/etc present, show combo boxes + auto memory = emulator_->memory(); + auto function = static_cast(state_.function); + auto& source_map = function->source_map(); + uint32_t source_map_index = 0; + + bool draw_hir = false; + bool draw_hir_opt = false; + bool draw_x64 = false; + switch (state_.source_display_mode) { + case 1: + draw_hir = true; + draw_x64 = true; + break; + case 2: + draw_hir_opt = true; + draw_x64 = true; + break; + case 3: + draw_x64 = true; + break; + } + + auto guest_pc = + state_.thread_info + ? state_.thread_info->frames[state_.thread_stack_frame_index].guest_pc + : 0; + + if (draw_hir) { + // TODO(benvanik): get HIR and draw preamble. + } + if (draw_hir_opt) { + // TODO(benvanik): get HIR and draw preamble. + } + if (draw_x64) { + // x64 preamble. + DrawMachineCodeSource(function->machine_code(), source_map[0].code_offset); + } + + StringBuffer str; + for (uint32_t address = function->address(); + address <= function->end_address(); address += 4) { + ImGui::PushID(address); + + // TODO(benvanik): check other threads? + bool is_current_instr = address == guest_pc; + if (is_current_instr) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 1.0f, 0.0f, 1.0f)); + if (!draw_x64) { + ScrollToSourceIfPcChanged(); + } + } + + bool has_guest_bp = + LookupBreakpointAtAddress(CodeBreakpoint::AddressType::kGuest, + address) != nullptr; + DrawBreakpointGutterButton(has_guest_bp, + CodeBreakpoint::AddressType::kGuest, address); + ImGui::SameLine(); + + ImGui::Text(" %c ", is_current_instr ? '>' : ' '); + ImGui::SameLine(); + + cpu::frontend::InstrData i; + i.address = address; + i.code = xe::load_and_swap(memory->TranslateVirtual(address)); + i.type = cpu::frontend::GetInstrType(i.code); + cpu::frontend::DisasmPPC(&i, &str); + ImGui::Text("%.8X %.8X %s", address, i.code, str.GetString()); + str.Reset(); + + if (is_current_instr) { + ImGui::PopStyleColor(); + } + + while (source_map_index < source_map.size() && + source_map[source_map_index].source_offset != address) { + ++source_map_index; + } + if (source_map_index < source_map.size()) { + if (draw_hir) { + // TODO(benvanik): get HIR and draw for this PPC function. + } + if (draw_hir_opt) { + // TODO(benvanik): get HIR and draw for this PPC function. + } + if (draw_x64) { + const uint8_t* machine_code_start = + function->machine_code() + source_map[source_map_index].code_offset; + const size_t machine_code_length = + (source_map_index == source_map.size() - 1 + ? function->machine_code_length() + : source_map[source_map_index + 1].code_offset) - + source_map[source_map_index].code_offset; + DrawMachineCodeSource(machine_code_start, machine_code_length); + } + } + + ImGui::PopID(); + } +} + +void DebugWindow::DrawMachineCodeSource(const uint8_t* machine_code_ptr, + size_t length) { + auto host_pc = + state_.thread_info + ? state_.thread_info->frames[state_.thread_stack_frame_index].host_pc + : 0; + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 0.5f)); + auto machine_code_start_ptr = machine_code_ptr; + size_t remaining_machine_code_size = length; + uint64_t host_address = uint64_t(machine_code_ptr); + cs_insn insn = {0}; + while (remaining_machine_code_size && + cs_disasm_iter(capstone_handle_, &machine_code_ptr, + &remaining_machine_code_size, &host_address, &insn)) { + ImGui::PushID(reinterpret_cast(insn.address)); + + bool is_current_instr = state_.thread_info && insn.address == host_pc; + if (is_current_instr) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.0f, 1.0f, 0.0f, 1.0f)); + ScrollToSourceIfPcChanged(); + } + + bool has_host_bp = + LookupBreakpointAtAddress(CodeBreakpoint::AddressType::kHost, + insn.address) != nullptr; + DrawBreakpointGutterButton(has_host_bp, CodeBreakpoint::AddressType::kHost, + insn.address); + ImGui::SameLine(); + + ImGui::Text(" %c ", is_current_instr ? '>' : ' '); + ImGui::SameLine(); + ImGui::Text(" %.8X %-8s %s", uint32_t(insn.address), insn.mnemonic, + insn.op_str); + + if (is_current_instr) { + ImGui::PopStyleColor(); + } + + ImGui::PopID(); + } + ImGui::PopStyleColor(); +} + +void DebugWindow::DrawBreakpointGutterButton( + bool has_breakpoint, CodeBreakpoint::AddressType address_type, + uint64_t address) { + ImGui::PushStyleColor(ImGuiCol_Button, + has_breakpoint + ? ImVec4(1.0f, 0.0f, 0.0f, 0.6f) + : ImGui::GetStyle().Colors[ImGuiCol_FrameBg]); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + !has_breakpoint + ? ImVec4(1.0f, 0.0f, 0.0f, 0.8f) + : ImGui::GetStyle().Colors[ImGuiCol_FrameBg]); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + !has_breakpoint + ? ImVec4(1.0f, 0.0f, 0.0f, 1.0f) + : ImGui::GetStyle().Colors[ImGuiCol_FrameBg]); + if (ImGui::Button(" ##toggle_line_bp")) { + if (!has_breakpoint) { + CreateCodeBreakpoint(address_type, address); + } else { + auto breakpoint = LookupBreakpointAtAddress(address_type, address); + assert_not_null(breakpoint); + DeleteCodeBreakpoint(breakpoint); + } + } + ImGui::PopStyleColor(3); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip(has_breakpoint ? "Remove breakpoint at this address." + : "Add a breakpoint at this address."); + } +} + +void DebugWindow::ScrollToSourceIfPcChanged() { + if (state_.has_changed_pc) { + // TODO(benvanik): not so annoying scroll. + ImGui::SetScrollHere(); + state_.has_changed_pc = false; + } +} + +bool DebugWindow::DrawRegisterTextBox(int id, uint32_t* value) { + char buffer[256] = {0}; + ImGuiInputTextFlags input_flags = + ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank; + if (state_.register_input_hex) { + input_flags |= ImGuiInputTextFlags_CharsHexadecimal | + ImGuiInputTextFlags_AlwaysInsertMode | + ImGuiInputTextFlags_NoHorizontalScroll; + auto src_value = xe::string_util::to_hex_string(*value); + std::strcpy(buffer, src_value.c_str()); + } else { + input_flags |= + ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll; + auto src_value = std::to_string(*value); + std::strcpy(buffer, src_value.c_str()); + } + char label[16] = {0}; + std::snprintf(label, xe::countof(label), "##iregister%d", id); + bool any_changed = false; + ImGui::PushItemWidth(50); + if (ImGui::InputText(label, buffer, + state_.register_input_hex ? 9 : sizeof(buffer), + input_flags)) { + if (state_.register_input_hex) { + *value = string_util::from_string(buffer, true); + } else { + *value = string_util::from_string(buffer); + } + any_changed = true; + } + ImGui::PopItemWidth(); + if (ImGui::IsItemHovered()) { + auto alt_value = state_.register_input_hex + ? std::to_string(*value) + : string_util::to_hex_string(*value); + ImGui::SetTooltip(alt_value.c_str()); + } + return any_changed; +} + +bool DebugWindow::DrawRegisterTextBox(int id, uint64_t* value) { + char buffer[256] = {0}; + ImGuiInputTextFlags input_flags = + ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank; + if (state_.register_input_hex) { + input_flags |= ImGuiInputTextFlags_CharsHexadecimal | + ImGuiInputTextFlags_AlwaysInsertMode | + ImGuiInputTextFlags_NoHorizontalScroll; + auto src_value = xe::string_util::to_hex_string(*value); + std::strcpy(buffer, src_value.c_str()); + } else { + input_flags |= + ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll; + auto src_value = std::to_string(*value); + std::strcpy(buffer, src_value.c_str()); + } + char label[16] = {0}; + std::snprintf(label, xe::countof(label), "##lregister%d", id); + bool any_changed = false; + ImGui::PushItemWidth(95); + if (ImGui::InputText(label, buffer, + state_.register_input_hex ? 17 : sizeof(buffer), + input_flags)) { + if (state_.register_input_hex) { + *value = string_util::from_string(buffer, true); + } else { + *value = string_util::from_string(buffer); + } + any_changed = true; + } + ImGui::PopItemWidth(); + if (ImGui::IsItemHovered()) { + auto alt_value = state_.register_input_hex + ? std::to_string(*value) + : string_util::to_hex_string(*value); + ImGui::SetTooltip(alt_value.c_str()); + } + return any_changed; +} + +bool DebugWindow::DrawRegisterTextBox(int id, double* value) { + char buffer[256] = {0}; + ImGuiInputTextFlags input_flags = + ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank; + if (state_.register_input_hex) { + input_flags |= ImGuiInputTextFlags_CharsHexadecimal | + ImGuiInputTextFlags_AlwaysInsertMode | + ImGuiInputTextFlags_NoHorizontalScroll; + auto src_value = xe::string_util::to_hex_string(*value); + std::strcpy(buffer, src_value.c_str()); + } else { + input_flags |= + ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll; + std::snprintf(buffer, xe::countof(buffer), "%.8LF", *value); + } + char label[16] = {0}; + std::snprintf(label, xe::countof(label), "##dregister%d", id); + bool any_changed = false; + ImGui::PushItemWidth(95); + if (ImGui::InputText(label, buffer, + state_.register_input_hex ? 17 : sizeof(buffer), + input_flags)) { + if (state_.register_input_hex) { + *value = string_util::from_string(buffer, true); + } else { + *value = string_util::from_string(buffer); + } + any_changed = true; + } + ImGui::PopItemWidth(); + if (ImGui::IsItemHovered()) { + auto alt_value = state_.register_input_hex + ? std::to_string(*value) + : string_util::to_hex_string(*value); + ImGui::SetTooltip(alt_value.c_str()); + } + return any_changed; +} + +bool DebugWindow::DrawRegisterTextBoxes(int id, float* value) { + char buffer[256] = {0}; + ImGuiInputTextFlags input_flags = + ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank; + if (state_.register_input_hex) { + input_flags |= ImGuiInputTextFlags_CharsHexadecimal | + ImGuiInputTextFlags_AlwaysInsertMode | + ImGuiInputTextFlags_NoHorizontalScroll; + } else { + input_flags |= + ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_AutoSelectAll; + } + bool any_changed = false; + char label[16] = {0}; + for (int i = 0; i < 4; ++i) { + if (state_.register_input_hex) { + auto src_value = xe::string_util::to_hex_string(value[i]); + std::strcpy(buffer, src_value.c_str()); + } else { + std::snprintf(buffer, xe::countof(buffer), "%F", value[i]); + } + std::snprintf(label, xe::countof(label), "##vregister%d_%d", id, i); + ImGui::PushItemWidth(50); + if (ImGui::InputText(label, buffer, + state_.register_input_hex ? 9 : sizeof(buffer), + input_flags)) { + if (state_.register_input_hex) { + value[i] = string_util::from_string(buffer, true); + } else { + value[i] = string_util::from_string(buffer); + } + any_changed = true; + } + ImGui::PopItemWidth(); + if (ImGui::IsItemHovered()) { + auto alt_value = state_.register_input_hex + ? std::to_string(value[i]) + : string_util::to_hex_string(value[i]); + ImGui::SetTooltip(alt_value.c_str()); + } + if (i < 3) { + ImGui::SameLine(); + ImGui::Dummy(ImVec2(1, 0)); + ImGui::SameLine(); + } + } + return any_changed; +} + +void DebugWindow::DrawRegistersPane() { + if (state_.register_group == RegisterGroup::kGuestGeneral) { + ImGui::PushStyleColor(ImGuiCol_Button, + ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + ImGui::Button("GPR"); + ImGui::PopStyleColor(); + } else { + if (ImGui::Button("GPR")) { + state_.register_group = RegisterGroup::kGuestGeneral; + } + } + ImGui::SameLine(); + if (state_.register_group == RegisterGroup::kGuestFloat) { + ImGui::PushStyleColor(ImGuiCol_Button, + ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + ImGui::Button("FPR"); + ImGui::PopStyleColor(); + } else { + if (ImGui::Button("FPR")) { + state_.register_group = RegisterGroup::kGuestFloat; + } + } + ImGui::SameLine(); + if (state_.register_group == RegisterGroup::kGuestVector) { + ImGui::PushStyleColor(ImGuiCol_Button, + ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + ImGui::Button("VMX"); + ImGui::PopStyleColor(); + } else { + if (ImGui::Button("VMX")) { + state_.register_group = RegisterGroup::kGuestVector; + } + } + ImGui::SameLine(); + if (state_.register_group == RegisterGroup::kHostGeneral) { + ImGui::PushStyleColor(ImGuiCol_Button, + ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + ImGui::Button("x64"); + ImGui::PopStyleColor(); + } else { + if (ImGui::Button("x64")) { + state_.register_group = RegisterGroup::kHostGeneral; + } + } + ImGui::SameLine(); + if (state_.register_group == RegisterGroup::kHostVector) { + ImGui::PushStyleColor(ImGuiCol_Button, + ImGui::GetStyle().Colors[ImGuiCol_ButtonActive]); + ImGui::Button("XMM"); + ImGui::PopStyleColor(); + } else { + if (ImGui::Button("XMM")) { + state_.register_group = RegisterGroup::kHostVector; + } + } + + ImGui::Checkbox("Hex", &state_.register_input_hex); + + if (!state_.thread_info) { + return; + } + auto thread_info = state_.thread_info; + + bool dirty_guest_context = false; + bool dirty_host_context = false; + switch (state_.register_group) { + case RegisterGroup::kGuestGeneral: { + if (!thread_info->guest_context.physical_membase) { + return; + } + ImGui::BeginChild("##guest_general"); + ImGui::BeginGroup(); + ImGui::AlignFirstTextHeightToWidgets(); + ImGui::Text(" lr"); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + dirty_guest_context |= + DrawRegisterTextBox(100, &thread_info->guest_context.lr); + ImGui::EndGroup(); + ImGui::BeginGroup(); + ImGui::AlignFirstTextHeightToWidgets(); + ImGui::Text("ctr"); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + dirty_guest_context |= + DrawRegisterTextBox(101, &thread_info->guest_context.ctr); + ImGui::EndGroup(); + // CR + // XER + // FPSCR + // VSCR + for (int i = 0; i < 32; ++i) { + ImGui::BeginGroup(); + ImGui::AlignFirstTextHeightToWidgets(); + ImGui::Text(i < 10 ? " r%d" : "r%d", i); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + dirty_guest_context |= + DrawRegisterTextBox(i, &thread_info->guest_context.r[i]); + ImGui::EndGroup(); + } + ImGui::EndChild(); + } break; + case RegisterGroup::kGuestFloat: { + if (!thread_info->guest_context.physical_membase) { + return; + } + ImGui::BeginChild("##guest_float"); + for (int i = 0; i < 32; ++i) { + ImGui::BeginGroup(); + ImGui::AlignFirstTextHeightToWidgets(); + ImGui::Text(i < 10 ? " f%d" : "f%d", i); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + dirty_guest_context |= + DrawRegisterTextBox(i, &thread_info->guest_context.f[i]); + ImGui::EndGroup(); + } + ImGui::EndChild(); + } break; + case RegisterGroup::kGuestVector: { + if (!thread_info->guest_context.physical_membase) { + return; + } + ImGui::BeginChild("##guest_vector"); + for (int i = 0; i < 128; ++i) { + ImGui::BeginGroup(); + ImGui::AlignFirstTextHeightToWidgets(); + ImGui::Text(i < 10 ? " v%d" : (i < 100 ? " v%d" : "v%d"), i); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + dirty_guest_context |= + DrawRegisterTextBoxes(i, thread_info->guest_context.v[i].f32); + ImGui::EndGroup(); + } + ImGui::EndChild(); + } break; + case RegisterGroup::kHostGeneral: { + ImGui::BeginChild("##host_general"); + for (int i = 0; i < 18; ++i) { + auto reg = static_cast(i); + ImGui::BeginGroup(); + ImGui::AlignFirstTextHeightToWidgets(); + ImGui::Text("%3s", X64Context::GetRegisterName(reg)); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + if (reg == X64Register::kRip) { + dirty_guest_context |= + DrawRegisterTextBox(i, &thread_info->host_context.rip); + } else if (reg == X64Register::kEflags) { + dirty_guest_context = + DrawRegisterTextBox(i, &thread_info->host_context.eflags); + } else { + dirty_guest_context |= DrawRegisterTextBox( + i, &thread_info->host_context.int_registers[i - 2]); + } + ImGui::EndGroup(); + } + ImGui::EndChild(); + } break; + case RegisterGroup::kHostVector: { + ImGui::BeginChild("##host_vector"); + for (int i = 0; i < 16; ++i) { + auto reg = + static_cast(static_cast(X64Register::kXmm0) + i); + ImGui::BeginGroup(); + ImGui::AlignFirstTextHeightToWidgets(); + ImGui::Text("%5s", X64Context::GetRegisterName(reg)); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + dirty_host_context |= DrawRegisterTextBoxes( + i, thread_info->host_context.xmm_registers[i].m128_f32); + ImGui::EndGroup(); + } + ImGui::EndChild(); + } + } + + if (dirty_guest_context) { + // TODO(benvanik): write context to thread. + // NOTE: this will only work if context promotion is disabled! + } + if (dirty_host_context) { + // TODO(benvanik): write context to thread. + } +} + +void DebugWindow::DrawThreadsPane() { + ImGui::BeginGroup(); + // checkbox to show host threads + // expand all toggle + ImGui::EndGroup(); + ImGui::BeginChild("##threads_listing"); + for (size_t i = 0; i < cache_.thread_execution_infos.size(); ++i) { + auto thread_info = cache_.thread_execution_infos[i]; + auto thread = thread_info->thread; + bool is_current_thread = thread == state_.thread; + if (is_current_thread && state_.has_changed_thread) { + ImGui::SetScrollHere(); + state_.has_changed_thread = false; + } + if (!is_current_thread) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 0.6f)); + } else { + ImGui::PushStyleColor(ImGuiCol_Header, + ImGui::GetStyle().Colors[ImGuiCol_HeaderActive]); + } + ImGui::PushID(thread); + if (is_current_thread) { + ImGui::SetNextTreeNodeOpened(true, ImGuiSetCond_Always); + } + const char* state_label = "?"; + if (thread->is_guest_thread()) { + if (thread->is_running()) { + if (thread->suspend_count() > 1) { + state_label = "SUSPEND"; + } else { + state_label = "RUNNING"; + } + } else { + state_label = "ZOMBIE"; + } + } + char thread_label[256]; + std::snprintf(thread_label, xe::countof(thread_label), + "%-5s %-7s id=%.4X hnd=%.4X %s", + thread->is_guest_thread() ? "guest" : "host", state_label, + thread->thread_id(), thread->handle(), + thread->name().c_str()); + if (ImGui::CollapsingHeader(thread_label, nullptr, true, + is_current_thread)) { + // | (log button) detail of kernel call categories + // log button toggles only logging that thread + ImGui::BulletText("Call Stack"); + ImGui::Indent(); + for (size_t j = 0; j < thread_info->frames.size(); ++j) { + bool is_current_frame = + is_current_thread && j == state_.thread_stack_frame_index; + auto& frame = thread_info->frames[j]; + if (is_current_thread && !frame.guest_pc) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 0.6f)); + } + char host_label[64]; + std::snprintf(host_label, xe::countof(host_label), "%016" PRIX64 "##%p", + frame.host_pc, &frame); + if (ImGui::Selectable(host_label, is_current_frame, + ImGuiSelectableFlags_SpanAllColumns)) { + SelectThreadStackFrame(thread, j, true); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(8, 0)); + ImGui::SameLine(); + if (frame.guest_pc) { + ImGui::Text("%08X", frame.guest_pc); + } else { + ImGui::Text(" "); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(8, 0)); + ImGui::SameLine(); + // breakpoints set? or something? + ImGui::Text(" "); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(8, 0)); + ImGui::SameLine(); + if (frame.guest_function) { + ImGui::Text(frame.guest_function->name().c_str()); + } else { + ImGui::Text(frame.name); + } + if (is_current_thread && !frame.guest_pc) { + ImGui::PopStyleColor(); + } + } + ImGui::Unindent(); + } + ImGui::PopStyleColor(); + ImGui::PopID(); + } + ImGui::EndChild(); +} + +void DebugWindow::DrawMemoryPane() { + ImGui::Text(""); + // tools for searching: + // search bytes | text | pattern + // https://github.com/ocornut/imgui/wiki/memory_editor_example +} + +void DebugWindow::DrawBreakpointsPane() { + auto& state = state_.breakpoints; + + ImGui::BeginChild("##toolbar", ImVec2(-1, 20)); + bool all_breakpoints_enabled = true; + for (auto& breakpoint : state.all_breakpoints) { + if (!breakpoint->is_enabled()) { + all_breakpoints_enabled = false; + break; + } + } + if (ImGui::Checkbox("##toggle", &all_breakpoints_enabled)) { + // Toggle all breakpoints on/off. + // If any are not enabled we will enable all, otherwise disable all. + for (auto& breakpoint : state.all_breakpoints) { + breakpoint->set_enabled(all_breakpoints_enabled); + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Toggle all breakpoints."); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(8, 0)); + ImGui::SameLine(); + if (ImGui::Button("+ Code")) { + ImGui::OpenPopup("##add_code_breakpoint"); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Add a code breakpoint for either PPC or x64."); + } + // TODO(benvanik): remove this set focus workaround when imgui is fixed: + // https://github.com/ocornut/imgui/issues/343 + static int add_code_popup_render_count = 0; + if (ImGui::BeginPopup("##add_code_breakpoint")) { + ++add_code_popup_render_count; + + ImGui::AlignFirstTextHeightToWidgets(); + ImGui::Text("PPC"); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(2, 0)); + ImGui::SameLine(); + if (add_code_popup_render_count == 2) { + ImGui::SetKeyboardFocusHere(); + } + static char ppc_buffer[32] = {0}; + ImGuiInputTextFlags input_flags = ImGuiInputTextFlags_CharsUppercase | + ImGuiInputTextFlags_CharsNoBlank | + ImGuiInputTextFlags_CharsHexadecimal | + ImGuiInputTextFlags_AlwaysInsertMode | + ImGuiInputTextFlags_NoHorizontalScroll | + ImGuiInputTextFlags_EnterReturnsTrue; + ImGui::PushItemWidth(50); + if (ImGui::InputText("##guest_address", ppc_buffer, 9, input_flags)) { + uint32_t address = string_util::from_string(ppc_buffer, true); + ppc_buffer[0] = 0; + CreateCodeBreakpoint(CodeBreakpoint::AddressType::kGuest, address); + ImGui::CloseCurrentPopup(); + } + ImGui::PopItemWidth(); + ImGui::Dummy(ImVec2(0, 2)); + + ImGui::AlignFirstTextHeightToWidgets(); + ImGui::Text("x64"); + ImGui::SameLine(); + ImGui::Dummy(ImVec2(2, 0)); + ImGui::SameLine(); + static char x64_buffer[64] = {0}; + ImGui::PushItemWidth(100); + if (ImGui::InputText("##host_address", x64_buffer, 17, input_flags)) { + uint64_t address = string_util::from_string(x64_buffer, true); + x64_buffer[0] = 0; + CreateCodeBreakpoint(CodeBreakpoint::AddressType::kHost, address); + ImGui::CloseCurrentPopup(); + } + ImGui::PopItemWidth(); + + ImGui::EndPopup(); + } else { + add_code_popup_render_count = 0; + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(2, 0)); + ImGui::SameLine(); + + if (ImGui::Button("+ Data")) { + ImGui::OpenPopup("##add_data_breakpoint"); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Add a memory data breakpoint."); + } + if (ImGui::BeginPopup("##add_data_breakpoint")) { + ImGui::Text("TODO: data stuff"); + ImGui::EndPopup(); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(2, 0)); + ImGui::SameLine(); + + if (ImGui::Button("+ Kernel")) { + ImGui::OpenPopup("##add_kernel_breakpoint"); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Select active kernel breakpoints."); + } + // TODO(benvanik): remove this set focus workaround when imgui is fixed: + // https://github.com/ocornut/imgui/issues/343 + static int kernel_popup_render_count = 0; + if (ImGui::BeginPopup("##add_kernel_breakpoint")) { + ++kernel_popup_render_count; + + auto export_tables = emulator_->export_resolver()->tables(); + + ImGui::PushItemWidth(300); + // TODO(benvanik): tag filtering. + const char* its[] = { + "All", + }; + int ci = 0; + ImGui::Combo("##kernel_categories", &ci, its, 1, 1); + ImGui::Dummy(ImVec2(0, 3)); + ImGui::ListBoxHeader("##kernel_calls", 1000, 15); + auto& all_exports = emulator_->export_resolver()->all_exports_by_name(); + auto call_rankings = xe::fuzzy_filter(state.kernel_call_filter, all_exports, + offsetof(cpu::Export, name)); + bool has_any_call_filter = std::strlen(state.kernel_call_filter) > 0; + if (has_any_call_filter) { + std::sort(call_rankings.begin(), call_rankings.end(), + [](std::pair& a, std::pair& b) { + if (a.second == b.second) { + return a.first > b.first; + } else { + return a.second > b.second; + } + }); + } + for (size_t i = 0; i < call_rankings.size(); ++i) { + if (has_any_call_filter && !call_rankings[i].second) { + continue; + } + auto export = all_exports[call_rankings[i].first]; + if (export->type != cpu::Export::Type::kFunction || + !export->is_implemented()) { + continue; + } + // TODO(benvanik): skip unused kernel calls. + ImGui::PushID(export); + // TODO(benvanik): selection, hover info (module name, ordinal, etc). + bool is_pre_enabled = false; + bool is_post_enabled = false; + if (ImGui::Checkbox("##pre", &is_pre_enabled)) { + // TODO(benvanik): add pre breakpoint (lookup thunk, add before + // syscall). + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Break immediately before this export is called."); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(1, 0)); + ImGui::SameLine(); + if (ImGui::Checkbox("##post", &is_post_enabled)) { + // TODO(benvanik): add pre breakpoint (lookup thunk, add after + // syscall). + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Break immediately after this export returns."); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + ImGui::Text(export->name); + ImGui::Dummy(ImVec2(0, 1)); + ImGui::PopID(); + } + ImGui::ListBoxFooter(); + ImGui::Dummy(ImVec2(0, 3)); + if (kernel_popup_render_count == 2) { + ImGui::SetKeyboardFocusHere(); + } + ImGui::InputText( + "##kernel_call_filter", state.kernel_call_filter, + xe::countof(state.kernel_call_filter), + ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_CharsNoBlank); + ImGui::PopItemWidth(); + ImGui::EndPopup(); + } else { + kernel_popup_render_count = 0; + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(2, 0)); + ImGui::SameLine(); + + if (ImGui::Button("+ GPU")) { + ImGui::OpenPopup("##add_gpu_breakpoint"); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Select active GPU breakpoints."); + } + if (ImGui::BeginPopup("##add_gpu_breakpoint")) { + ImGui::Text("TODO: swap, kick, etc"); + ImGui::EndPopup(); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(8, 0)); + ImGui::SameLine(); + + if (ImGui::Button("Clear")) { + // Unregister and delete all breakpoints. + while (!state.all_breakpoints.empty()) { + DeleteBreakpoint(state.all_breakpoints.front().get()); + } + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Clear all breakpoints."); + } + ImGui::EndChild(); + ImGui::Separator(); + + ImGui::PushItemWidth(-1); + if (ImGui::ListBoxHeader("##empty", + ImVec2(-1, ImGui::GetContentRegionAvail().y))) { + std::vector to_delete; + for (auto& breakpoint : state.all_breakpoints) { + ImGui::PushID(breakpoint.get()); + bool is_enabled = breakpoint->is_enabled(); + if (ImGui::Checkbox("##toggle", &is_enabled)) { + breakpoint->set_enabled(is_enabled); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4, 0)); + ImGui::SameLine(); + auto breakpoint_str = breakpoint->to_string(); + bool is_selected = false; // in function/stopped on line? + if (ImGui::Selectable(breakpoint_str.c_str(), &is_selected, + ImGuiSelectableFlags_SpanAllColumns)) { + switch (breakpoint->type()) { + case Breakpoint::Type::kCode: { + auto code_breakpoint = + static_cast(breakpoint.get()); + auto function = code_breakpoint->guest_function(); + assert_not_null(function); + if (code_breakpoint->address_type() == + CodeBreakpoint::AddressType::kGuest) { + NavigateToFunction( + code_breakpoint->guest_function(), + code_breakpoint->guest_address(), + function->MapSourceToCode(code_breakpoint->guest_address())); + } else { + NavigateToFunction(function, function->MapCodeToSource( + code_breakpoint->host_address()), + code_breakpoint->host_address()); + } + break; + } + } + } + if (ImGui::BeginPopupContextItem("##breakpoint_context_menu")) { + if (ImGui::MenuItem("Delete")) { + to_delete.push_back(breakpoint.get()); + } + ImGui::EndPopup(); + } + ImGui::PopID(); + } + ImGui::ListBoxFooter(); + if (!to_delete.empty()) { + for (auto breakpoint : to_delete) { + DeleteBreakpoint(breakpoint); + } + } + } + ImGui::PopItemWidth(); +} + +void DebugWindow::DrawLogPane() { + ImGui::Text(""); + // bar: + // combo for log level + // check combo for areas (cpu/gpu/etc) + // combo for thread checkboxes + // filter text box + // ... + // copy visible button + // follow button + // clear button + // visible lines / total lines + // log box: + // line per log line + // if big, click to open dialog with contents +} + +void DebugWindow::SelectThreadStackFrame(XThread* thread, + size_t stack_frame_index, + bool always_navigate) { + state_.has_changed_thread = false; + if (thread != state_.thread) { + state_.has_changed_thread = true; + state_.thread = thread; + + state_.thread_info = nullptr; + for (auto thread_info : cache_.thread_execution_infos) { + if (thread_info->thread == thread) { + state_.thread_info = thread_info; + break; + } + } + } + if (state_.thread_info) { + stack_frame_index = + std::min(state_.thread_info->frames.size() - 1, stack_frame_index); + } + if (stack_frame_index != state_.thread_stack_frame_index) { + state_.thread_stack_frame_index = stack_frame_index; + state_.has_changed_thread = true; + } + if (state_.thread_info) { + auto new_host_pc = + state_.thread_info->frames[state_.thread_stack_frame_index].host_pc; + if (new_host_pc != state_.last_host_pc) { + state_.last_host_pc = new_host_pc; + state_.has_changed_pc = true; + } + } + if ((always_navigate || state_.has_changed_thread) && state_.thread_info) { + auto& frame = state_.thread_info->frames[state_.thread_stack_frame_index]; + if (frame.guest_function) { + NavigateToFunction(frame.guest_function, frame.guest_pc, frame.host_pc); + } + } +} + +void DebugWindow::NavigateToFunction(cpu::Function* function, uint32_t guest_pc, + uint64_t host_pc) { + state_.function = function; + state_.last_host_pc = host_pc; + state_.has_changed_pc = true; +} + +void DebugWindow::UpdateCache() { + auto kernel_state = emulator_->kernel_state(); + auto object_table = kernel_state->object_table(); + + loop_->Post([this]() { + std::wstring title = kBaseTitle; + switch (debugger_->execution_state()) { + case ExecutionState::kEnded: + title += L" (ended)"; + break; + case ExecutionState::kPaused: + title += L" (paused)"; + break; + case ExecutionState::kRunning: + title += L" (running)"; + break; + case ExecutionState::kStepping: + title += L" (stepping)"; + break; + } + window_->set_title(title); + }); + + cache_.is_running = debugger_->execution_state() == ExecutionState::kRunning; + if (cache_.is_running) { + // Early exit - the rest of the data is kept stale on purpose. + return; + } + + // Fetch module listing. + // We hold refs so that none are unloaded. + cache_.modules = + object_table->GetObjectsByType(XObject::Type::kTypeModule); + + cache_.thread_execution_infos = debugger_->QueryThreadExecutionInfos(); + + SelectThreadStackFrame(state_.thread, state_.thread_stack_frame_index, false); +} + +void DebugWindow::CreateCodeBreakpoint(CodeBreakpoint::AddressType address_type, + uint64_t address) { + auto& state = state_.breakpoints; + auto breakpoint = + std::make_unique(debugger_, address_type, address); + if (breakpoint->address_type() == CodeBreakpoint::AddressType::kGuest) { + auto& map = state.code_breakpoints_by_guest_address; + auto it = map.find(breakpoint->guest_address()); + if (it != map.end()) { + // Already exists! + return; + } + map.emplace(breakpoint->guest_address(), breakpoint.get()); + } else { + auto& map = state.code_breakpoints_by_host_address; + auto it = map.find(breakpoint->host_address()); + if (it != map.end()) { + // Already exists! + return; + } + map.emplace(breakpoint->host_address(), breakpoint.get()); + } + debugger_->AddBreakpoint(breakpoint.get()); + state.all_breakpoints.emplace_back(std::move(breakpoint)); +} + +void DebugWindow::DeleteCodeBreakpoint(CodeBreakpoint* breakpoint) { + auto& state = state_.breakpoints; + for (size_t i = 0; i < state.all_breakpoints.size(); ++i) { + if (state.all_breakpoints[i].get() != breakpoint) { + continue; + } + debugger_->RemoveBreakpoint(breakpoint); + if (breakpoint->address_type() == CodeBreakpoint::AddressType::kGuest) { + auto& map = state.code_breakpoints_by_guest_address; + auto it = map.find(breakpoint->guest_address()); + if (it != map.end()) { + map.erase(it); + } + } else { + auto& map = state.code_breakpoints_by_host_address; + auto it = map.find(breakpoint->host_address()); + if (it != map.end()) { + map.erase(it); + } + } + state.all_breakpoints.erase(state.all_breakpoints.begin() + i); + break; + } +} + +void DebugWindow::DeleteBreakpoint(Breakpoint* breakpoint) { + auto& state = state_.breakpoints; + if (breakpoint->type() == Breakpoint::Type::kCode) { + DeleteCodeBreakpoint(static_cast(breakpoint)); + } else { + for (size_t i = 0; i < state.all_breakpoints.size(); ++i) { + if (state.all_breakpoints[i].get() == breakpoint) { + debugger_->RemoveBreakpoint(breakpoint); + state.all_breakpoints.erase(state.all_breakpoints.begin() + i); + break; + } + } + } +} + +CodeBreakpoint* DebugWindow::LookupBreakpointAtAddress( + CodeBreakpoint::AddressType address_type, uint64_t address) { + auto& state = state_.breakpoints; + if (address_type == CodeBreakpoint::AddressType::kGuest) { + auto& map = state.code_breakpoints_by_guest_address; + auto it = map.find(static_cast(address)); + return it == map.end() ? nullptr : it->second; + } else { + auto& map = state.code_breakpoints_by_host_address; + auto it = map.find(static_cast(address)); + return it == map.end() ? nullptr : it->second; + } +} + +void DebugWindow::OnFocus() { + loop_->Post([this]() { window_->set_focus(true); }); +} + +void DebugWindow::OnDetached() { + UpdateCache(); + + // Remove all breakpoints. + while (!state_.breakpoints.all_breakpoints.empty()) { + DeleteBreakpoint(state_.breakpoints.all_breakpoints.front().get()); + } +} + +void DebugWindow::OnExecutionPaused() { + UpdateCache(); + loop_->Post([this]() { window_->set_focus(true); }); +} + +void DebugWindow::OnExecutionContinued() { + UpdateCache(); + loop_->Post([this]() { window_->set_focus(true); }); +} + +void DebugWindow::OnExecutionEnded() { + UpdateCache(); + loop_->Post([this]() { window_->set_focus(true); }); +} + +void DebugWindow::OnStepCompleted(xe::kernel::XThread* thread) { + UpdateCache(); + SelectThreadStackFrame(thread, 0, true); + loop_->Post([this]() { window_->set_focus(true); }); +} + +void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint, + xe::kernel::XThread* thread) { + UpdateCache(); + SelectThreadStackFrame(thread, 0, true); + loop_->Post([this]() { window_->set_focus(true); }); +} + +} // namespace ui +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/ui/debug_window.h b/src/xenia/debug/ui/debug_window.h new file mode 100644 index 000000000..833bfad06 --- /dev/null +++ b/src/xenia/debug/ui/debug_window.h @@ -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 +#include + +#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 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 window_; + std::unique_ptr 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> modules; + std::vector 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> all_breakpoints; + std::unordered_map + code_breakpoints_by_guest_address; + std::unordered_map + 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_ diff --git a/src/xenia/debug/ui/debugger_main.cc b/src/xenia/debug/ui/debugger_main.cc deleted file mode 100644 index fe7b35daa..000000000 --- a/src/xenia/debug/ui/debugger_main.cc +++ /dev/null @@ -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 - -#include - -#include "xenia/base/main.h" -#include "xenia/debug/ui/application.h" - -namespace xe { -namespace debug { -namespace ui { - -int main(const std::vector& 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); diff --git a/src/xenia/debug/ui/debugger_resources.rc b/src/xenia/debug/ui/debugger_resources.rc deleted file mode 100644 index fc922d058..000000000 --- a/src/xenia/debug/ui/debugger_resources.rc +++ /dev/null @@ -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" diff --git a/src/xenia/debug/ui/imgui_renderer.cc b/src/xenia/debug/ui/imgui_renderer.cc new file mode 100644 index 000000000..350dc4bdf --- /dev/null +++ b/src/xenia/debug/ui/imgui_renderer.cc @@ -0,0 +1,341 @@ +/** +****************************************************************************** +* 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/imgui_renderer.h" + +#include + +#include "xenia/base/assert.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" + +namespace xe { +namespace debug { +namespace ui { + +constexpr uint32_t kMaxDrawVertices = 64 * 1024; +constexpr uint32_t kMaxDrawIndices = 64 * 1024; + +// File: 'ProggyTiny.ttf' (35656 bytes) +// Exported using binary_to_compressed_c.cpp +const char kProggyTinyCompressedDataBase85[10950 + 1] = + R"(7])#######LJg=:'/###[),##/l:$#Q6>##5[n42>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCm<]vf.r$<$u7k;hb';9C'mm?]XmKVeU2cD4Eo3R/[WB]b(MC;$jPfY.;h^`ItLw6Lh2TlS+f-s$o6Q#X14,MZ[Z##UE31#J&###Q-F%b>-nw'w++GM-]u)Nx0#,M[LH>#Zsvx+6O_^#l(FS7f`C_&E?g'&kcg-6Y,/;M#@2G`Bf%=(`5LF%fv$8#,+[0#veg>$EB&sQSSDgEKnIS7EM9>Z,KO_/78pQWqJE#$nt-4$F&###E`J&#uU'B#*9D6N;@;=-:U>hL&Y5<-%A9;-Y+Z&P^)9##._L&#awnk+ib*.-Z06X1>LcA#'rB#$o4ve6)fbA#kt72LN.72L=CG&#*iXZWt(F,>>#_03:)`(@2L@^x4Sj2B@PN#[xO8QkJNRR()N@#f.Mr#)t-L5FGMm8#&#/2TkLi3n##-/5##MwQ0#EB^1v&ols-)-mTMQ@-##qlQ08*lkA#aNRV7KRYML%4_s[kNa=_0Z%7Nd4[3#S@1g_/v`W#'`Fm#B$(Q#n$oqvc$&Svv$`,TM%,PS=%OeJE%s+]l%A=Fe%']K#&7aW5&O-Nd&q&>^&GZs1'w.bA'c>u>'B-1R'%gJ.(t1tx'_jH4(iNdc(GJ*X(l`uf(^Wqr(-=Jx(=[%5)')Gb)$1vV)57Vk),8n<*BYl/*qs%]*OI5R*Fkgb*H<+q*TQv(+Xak6+?C@H+5SaT+o2VhLKd)k+i$xl+4YW=,sJd,,C*oT,Eb:K,mSPgLsF8e,Z$=rJ[<5J`E:E&#k&bV7uVco]JfaJ2M'8L#xArJ27FJx?Zgt%uov/vZ@?Gj:Kl;,jo%*K;AL7L#7G'3/J(*t.5xO+/0r+N/%ipJ/Bq_k/A>4Y/^iwl/%K:K0[HW=04D'N0wQq_00Kjt0]NJ21?p?d1T:=Y1e*&i1HLr@28x*:29A[L2Mpd%3pFIp2igO+3aXRX3M#PN3uY$d37p2=4c,s54.3SI4v0iw4JqN65G$S*5rh<65ld7E5.IRt5.f-16A/U(6IoFR6Nj7I6Y3i[6>s#s6EF=P90>=W6-Mc##=(V$#MXI%#^3=&#nd0'#(?$(#8pm(#HJa)#X%T*#iUG+##1;,#3b.-#C#$i0B#'2[0#s6aW-AS*wp1W,/$-pZw'%]#AOC+[]O>X`=-9_cHMN8r&MsKH##77N/)8r_G3=^x]O].[]-/(pI$^=Kn<00k-$t`%/LDK5x76,G&#$or>I?v+sQ;koJM>,CS-14,dM,Hv<-cLH?01FQ*NGx0='H9V&#;Rov$8ooX_i7d;)]]>R*sVi.Lt3NM-$@dXM:uSGMDn%>-30[b's6Ct_.39I$3#bo7;FP&#YKh9&#d)KE$tok&L1tY-sTf2LP]K&s9L]u-c4Au9*A>-<'3UN-PZL-NIV+85p0eZ3:.Q8bj1S*(h)Z$lel,MX_CH-.Nck-(veHZwdJe$ej+_frio0cKB$HFtRZ>#DiaWqFq7Q84okA#tiUi'Qumo%<]Xl8As(?@iLT[%tDn8gsDGA#hDu-$+HM3X_?@_8:N+q7v3G&#a7>0H3=t-?ZKm.HK+U58E/.`AcQV,tUd+Z-$fQ-Haotl8Zx2Fn)&UQ8c6E&docd.%&^R]u)x:p.N*wIL8+fsrk+5###>jq:9%/v2;f`?J8fDrG%fmWw9gl'ENgjG:,EC%<-WW5x'6eaR86kf2`5alP&u]::.'a0i);c)3LN3wK#gZb19YvMa,?IggL3xoFMTK_P85.)0xLp7gw]m_oM++.`=JfLm)1#.gGKd4N^@N%M'Np7ZO:k)VTqt%EO`gurjj;-0r%;%I'M,W-(hdnXP4bA,%GLp75c`QUWh<_&.ZoDuWmLCVJ+7:P&#Wj7n$+8sb<:+R.Qx7m<-T`&0%3TK<-h.oN'eSYW-g7D^6mu7Rc;:cIH%5hWHX9uCq'RC/2'GZZ(=:.$ekS>k((WP_=-,8dT%;]DeHjNJ'HOsgj-vUa$UFQO68Ic+k2HwQ'(0Kgn8V=:'_5'r1GX`4;kNbkh&@-HCp[+c+Z68=Z9:BM#Jn$R+0Au6A)K:YXr1d^8ILE65V'#Y_%n8Mc`3r:>H9%PMhj9GVCh3F3wm81EG&#,`**<3AEYLN1pA#>q0p&(^?@'Bl+&>klY'vO%co7juS^d)a#9%=&m9.m`0i)rQNm8*GF]uI9+W-wmw5_L07xLC8qT;`9%90i^Gx'abQp7)>5wLq3n0#/U0V8+B^G3%3,0:3wR]4fJ(d%2N9Z-r_&7rjQS,sM;n9g#>ve^2SK)71JTRxD)o0@1wWA2#E;xov>0f^-QQQYVBeT+?-7kMD5d0B#QZAW0:ZjUK[OZi&61L&#>CCj;r]/RLH'(j>+$P-R9bF69`%f@[p-JZ*.hnp7;-ge$NSi?-qx8;-V]Z##,B?nrCn,)'(Q%a-sI^W&9'i&#SrRfL`Zwe%k.jA,xf:-%$+:t>-v^)'%of?pg=`N_*o'w)3(ip7XqF]uN-Fj9l=K/+sAH^*I=5qBCRt-,T163BO%ov7%,sb&T=XaZ$(#GM0#Qp%a]Cs7HNbxum=g@>wb%?7N:Fk'0PYRhUv-tLWr+P(lLM/:9N*H=KRZT'Pf2;.@2<)#pVl1MwLk0&;tUAuP3w.Le.]T/*Mc##O->>#9NCU.73rI3ZbA;%^xT3BS2L#$uLjf%53Kt-2SJMBFZ.m0cmcPS)aX%(c]Yg<^[G6;$W(8*2&$X->B+kk^$D'8E@P&#I-nT'u5pm8u;Be=AJ8F-T6po)A:&?-CPcd$rDtJjLUsv'7Hx_onecgHu78k:D#]4;tb)$-UHAm8h;2c>8J<@.(W=p&oVoY?&@+w7-)ri'bb=+X:#29.*DW/tNqT&QAl29xj+AuD:*+lnW]D,3l6<-PX9YYw)vX&=WuT8H=AbIs[`Am2xcW-jqbn*cZV%_t/Z&QpvGJ(i2.^==iWDurfn:Ml;-##/-U%)x$+1:lROdt*mpM=i4/)Zdr'H'P[N>-EKHl$hUvf:P'Q3`u*IM&uZA39^0F[pUB+n8hq+?I`L'-)2>Cq71g/6(?(oR&iBRiLr7w;-[HuL3u6e2&V:QjBJ:9iuF8.a##PHT<-.4r1&,qBE#LK,W-fIO4kX@%%#tUB#$57>uHN^KeX'-cD)d.s*#P2+c&#ok:/:3l(RsPD###2d#<-%,.t$5@HgL/mu<+PXhv[Bgb4)GO;eMZQMr?,tXvIIe;t-P2l?G2j1v^)3l$'mEa68K1l@7.`V[GG#)C]Y&f;]?OM>&x]i.L(/5##BO+k9Xp0B#NS9>#+7E<-d]nl$Yw6v9YK*6(sxGug]oko$_'l_-Ai%RMq<&_8o2@2L@@AS)c8(<-c&r]$J9oq7g?(m9LIS;H-)KfF@qVI*^ACO9fKc'&6k/q7RD&Fe&*l2LSQUV$vC#W-lwf&v:'n]%D4xVH4&(^#0jg<-@r'29EQT_6Gx)Q&':nKC>s6.6*;X^ZH.->#>atJC6`hJ>NjO?-^5l-8Tj72LFIRp7,:-.wruM;q0)(YHWp7@i('#'2,##8ZDKM9dvR3kYKd&#V(f+MR?xK#liDE<[/RM3M7-##1na_$+q)'%xheG*DsXbN^BxK#R90%'vrIfL.r&/LT*=(&A's2;O56>>/jsX_Z_+/(NSi.L>jEG']iNUR238^&?MR49neA>.M5N=h#=eq:T?k)/:;SF&#;k-gLXL0e,e6JdDNHj?@ihvi&wNT-;6`E.FAl):%3WK<-:EI+'^([:.+lQS%?_,c<8L[W&T7-##`QHXA=(xn&Yqbf(kge2-g)[OksHT*bm+Do5IM<_jILK4Pv'=u5a*E#Z^FHmn),Dheq.+Sl##04kWoAl[W]rYHI%+d@a-.dm<%#1[,MtRBt)O(35&>-f;-J=.g:(xp/)U]W]+RCwgCbE0#Aes-h%vr_BF0;=K34Yv',/GoEYQQ-U&5&Aw>]ewGt?k,l$1oR8VCkF<%+nTH4Z8f%/M&dQ/(2###S`%/L*cS5JX&V_$iac=(LG:;$ZcRPA.$+bHU7-###c7^O1[qS%)S#qT=lI(#=,o_Q.^r#(w1I<-+PK`&,o'^#GhsWQt6j(,]_##IN]p7i4mFuXih@-t=58.1&>uu$&h2`2H:%'T):wPGJuD%5DJTIUXbA#pvRdM=WcO-uhKj%0ej?Ppruu9k,h%cwfi'B.x`=Tg^d4%45GM@iZQ'YGHP9(MGGGvbG3jZJp7FAeEP&ePL'8k8k91/D?[-7(&(m@?q7kdH)cDfYN)@9TDSe;DG)uQh&#k+'p74N)^ohl=,'';[P9_kisBjgU,&g>Ok2=4'K%cl@Nii)3q-_.1U9,.QL/2&>uuF*^TVA7Bs&;W36AZ(j'muJG4M_CpO4)0kNeFKG2V?'jkgNvkK<&MQU+<[xKVaY>/T@&Jp'HtA$a&5U&R8bs:RWYiYeQu4k(NgxE$%X6V&#X+3O-u_dQ8/_-ldRf1W-2dpGe*E^r7d>S^JisoC%s`^68r*d;))C[p7W?[6OfId2P&;Hr7cpB#$X8-l93rg996Nb4):v0<-Y7`[eEdoW*l/xNN9<&v,%nra*-?078.F8o8aP+Au]ZX2L:1Bn*fuW1;N&&3M5U#x'-KA$ZAf0YS&_;AL;>g6pgV==5A6R.db1Wbs'MC9-)5u:ENe7-##H:O&=$^]712c&gL,%,cM+4(5SmwUF-x-*j0c-G(-9Y`N*$0_k2ece`*#JdQ8Fk=9W'&%kCjYeaHC@nL-$[d;B*<^#DuHQNYF#>Q8L*fW]8cJX/,CWR83+pVooXXk'1HCL:6I%T(`2VY(An&R8?uN]'WLJM'$/*JLXm@8JhS](6l0tv'x06oDXFC'%=7CP8fCKetbqp0%;=0iC/kRkrjK5x'qtD_Or/9x:VWF&#LW`<04=q0E8/c&hItt'a8J0)kY#Q8nnV78o=6UT`-1t%-FuN:xA1J[Oq`p77%72L9$2-'[qp)mH^*[eL]u/o(HF0iu.-vwoW_wn=O:[3NPcDh)Zn)ex[T%LaP-VC%f$36t1CvSw,X>^>Z4q=Z58]evqT5.xP33h839>So>InZb%=w=>F%$Mdm_FjSEEwGJMN3B,-(b@mHM;uFr$r&V@-Vp;m$bF6XJbM0-'-PgQJ=Z.lBE?=x>YBPX9N3?&+0)xm'wQsH$K]MP9cmv9glREr(>=n-k;/6t$r]2@-Ips&d-8oS@pb5r@lwcQ:aum))u=KkrVv[n>Lu,@RvlOE.^Puk;v4[+9.2A2LrPn'&]/?pg&.Rq$9-vc6BUpD*8[?:BmMq*9.HFt_QSl##O->>#b7278#r%34A$;M%+=hlTsVPp'X8N&Zu/To%mDh:.,umo%5VIl90wn5F9;_OFJ?=?JbjcX($^)Rj2vao7W9Udkr[F%8:@(4F@5W5_oHOG%M4Y@G:P+JGUsRA%UeO-;Tr+OOHi8i:F$aC=K@82L(__3:>H-g)S65e;B@:xnT_x0+x,2N:rmL4)VtH#)NF7WAs,Zx'uQpEU78_TX=?1s?cZYlBd'BugDAVB-vlc_fV5gc*s&Y9.;25##F7,W.P'OC&aTZ`*65m_&WRJM'vGl_&==(S*2)7`&27@U1G^4?-:_`=-+()t-c'ChLGF%q.0l:$#:T__&Pi68%0xi_&Zh+/(77j_&JWoF.V735&S)[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&lM4;-xCJcM6X;uM6X;uM(.a..^2TkL%oR(#;u.T%eAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFk-c/>tBc/>`9IL2a)Ph#WL](#O:Jr1Btu+#TH4.#a5C/#vS3rL<1^NMowY.##t6qLw`5oL_R#.#2HwqLUwXrLp/w+#ALx>-1xRu-'*IqL@KCsLB@]qL]cYs-dpao7Om#K)l?1?%;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4TR-&Mglr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4A1OY5EI0;6Ibgr6M$HS7Q<)58UT`l8Ym@M9^/x.:bGXf:f`9G;jxp((F+;?,_br?0wBS@49$5A8QZlAQ#]V-kw:8.o9ro.sQRP/wj320%-ki0)EKJ1-^,,21vcc258DD39P%&4=i[]4A+=>5ECtu5I[TV6Mt587Q6mo7tB'DW-fJcMxUq4S=Gj(N=eC]OkKu=Yc/;ip3#T(j:6s7R`?U+rH#5PSpL7]bIFtIqmW:YYdQqFrhod(WEH1VdDMSrZ>vViBn_t.CTp;JCbMMrdku.Sek+f4ft(XfCsOFlfOuo7[&+T.q6jHPA$HHPB*QHPC0ZHPD6dHPD3Q-P_aQL2RenderDrawLists(data); + }; + + auto& style = ImGui::GetStyle(); + style.ScrollbarRounding = 0; + style.WindowFillAlphaDefault = 1.0f; + style.WindowRounding = 0; + style.Colors[ImGuiCol_Text] = ImVec4(0.89f, 0.90f, 0.90f, 1.00f); + style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.60f, 0.60f, 0.60f, 1.00f); + style.Colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.06f, 0.00f, 1.00f); + style.Colors[ImGuiCol_ChildWindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + style.Colors[ImGuiCol_Border] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + style.Colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + style.Colors[ImGuiCol_FrameBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.30f); + style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.90f, 0.80f, 0.80f, 0.40f); + style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.90f, 0.65f, 0.65f, 0.45f); + style.Colors[ImGuiCol_TitleBg] = ImVec4(0.00f, 0.40f, 0.00f, 1.00f); + style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.33f, 0.00f, 1.00f); + style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.16f, 0.65f, 0.00f, 1.00f); + style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.00f, 0.35f, 0.00f, 1.00f); + style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.00f, 0.40f, 0.11f, 0.59f); + style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.00f, 0.68f, 0.00f, 0.68f); + style.Colors[ImGuiCol_ScrollbarGrabHovered] = + ImVec4(0.00f, 1.00f, 0.15f, 0.62f); + style.Colors[ImGuiCol_ScrollbarGrabActive] = + ImVec4(0.00f, 0.91f, 0.09f, 0.40f); + style.Colors[ImGuiCol_ComboBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.99f); + style.Colors[ImGuiCol_CheckMark] = ImVec4(0.74f, 0.90f, 0.72f, 0.50f); + style.Colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); + style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.34f, 0.75f, 0.11f, 1.00f); + style.Colors[ImGuiCol_Button] = ImVec4(0.15f, 0.56f, 0.11f, 0.60f); + style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.19f, 0.72f, 0.09f, 1.00f); + style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.19f, 0.60f, 0.09f, 1.00f); + style.Colors[ImGuiCol_Header] = ImVec4(0.00f, 0.40f, 0.00f, 0.71f); + style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.00f, 0.60f, 0.26f, 0.80f); + style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.00f, 0.75f, 0.00f, 0.80f); + style.Colors[ImGuiCol_Column] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + style.Colors[ImGuiCol_ColumnHovered] = ImVec4(0.36f, 0.89f, 0.38f, 1.00f); + style.Colors[ImGuiCol_ColumnActive] = ImVec4(0.13f, 0.50f, 0.11f, 1.00f); + style.Colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.30f); + style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(1.00f, 1.00f, 1.00f, 0.60f); + style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.90f); + style.Colors[ImGuiCol_CloseButton] = ImVec4(0.00f, 0.72f, 0.00f, 0.96f); + style.Colors[ImGuiCol_CloseButtonHovered] = + ImVec4(0.38f, 1.00f, 0.42f, 0.60f); + style.Colors[ImGuiCol_CloseButtonActive] = ImVec4(0.56f, 1.00f, 0.64f, 1.00f); + style.Colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); + style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + style.Colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); + style.Colors[ImGuiCol_PlotHistogramHovered] = + ImVec4(1.00f, 0.60f, 0.00f, 1.00f); + style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.00f, 1.00f, 0.00f, 0.21f); + style.Colors[ImGuiCol_TooltipBg] = ImVec4(0.05f, 0.05f, 0.10f, 0.90f); + style.Colors[ImGuiCol_ModalWindowDarkening] = + ImVec4(0.20f, 0.20f, 0.20f, 0.35f); + + io.KeyMap[ImGuiKey_Tab] = 0x09; // VK_TAB; + io.KeyMap[ImGuiKey_LeftArrow] = 0x25; + io.KeyMap[ImGuiKey_RightArrow] = 0x27; + io.KeyMap[ImGuiKey_UpArrow] = 0x26; + io.KeyMap[ImGuiKey_DownArrow] = 0x28; + io.KeyMap[ImGuiKey_Home] = 0x24; + io.KeyMap[ImGuiKey_End] = 0x23; + io.KeyMap[ImGuiKey_Delete] = 0x2E; + io.KeyMap[ImGuiKey_Backspace] = 0x08; + io.KeyMap[ImGuiKey_Enter] = 0x0D; + io.KeyMap[ImGuiKey_Escape] = 0x1B; + io.KeyMap[ImGuiKey_A] = 'A'; + io.KeyMap[ImGuiKey_C] = 'C'; + io.KeyMap[ImGuiKey_V] = 'V'; + io.KeyMap[ImGuiKey_X] = 'X'; + io.KeyMap[ImGuiKey_Y] = 'Y'; + io.KeyMap[ImGuiKey_Z] = 'Z'; +} + +void ImGuiRenderer::InitializeShaders() { + const std::string vertex_shader_source = + R"( +#version 450 +#extension GL_ARB_explicit_uniform_location : require +precision highp float; +layout(location = 0) uniform mat4 projection_matrix; +layout(location = 0) in vec2 in_pos; +layout(location = 1) in vec4 in_color; +layout(location = 2) in vec2 in_uv; +layout(location = 0) out vec4 vtx_color; +layout(location = 1) out vec2 vtx_uv; +void main() { + gl_Position = projection_matrix * vec4(in_pos.xy, 0.0, 1.0); + vtx_color = in_color; + vtx_uv = in_uv; +})"; + + const std::string fragment_shader_source = + R"( +#version 450 +#extension GL_ARB_explicit_uniform_location : require +precision highp float; +layout(location = 1) uniform sampler2D texture_sampler; +layout(location = 2) uniform float texture_mix; +layout(location = 0) in vec4 vtx_color; +layout(location = 1) in vec2 vtx_uv; +layout(location = 0) out vec4 out_color; +void main() { + out_color = vtx_color * texture(texture_sampler, vtx_uv); +})"; + + GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); + const char* vertex_shader_source_ptr = vertex_shader_source.c_str(); + GLint vertex_shader_source_length = GLint(vertex_shader_source.size()); + glShaderSource(vertex_shader, 1, &vertex_shader_source_ptr, + &vertex_shader_source_length); + glCompileShader(vertex_shader); + + GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + const char* fragment_shader_source_ptr = fragment_shader_source.c_str(); + GLint fragment_shader_source_length = GLint(fragment_shader_source.size()); + glShaderSource(fragment_shader, 1, &fragment_shader_source_ptr, + &fragment_shader_source_length); + glCompileShader(fragment_shader); + + program_ = glCreateProgram(); + glAttachShader(program_, vertex_shader); + glAttachShader(program_, fragment_shader); + glLinkProgram(program_); + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + glCreateVertexArrays(1, &vao_); + glEnableVertexArrayAttrib(vao_, 0); + glVertexArrayAttribBinding(vao_, 0, 0); + glVertexArrayAttribFormat(vao_, 0, 2, GL_FLOAT, GL_FALSE, + offsetof(ImDrawVert, pos)); + glEnableVertexArrayAttrib(vao_, 1); + glVertexArrayAttribBinding(vao_, 1, 0); + glVertexArrayAttribFormat(vao_, 1, 4, GL_UNSIGNED_BYTE, GL_TRUE, + offsetof(ImDrawVert, col)); + glEnableVertexArrayAttrib(vao_, 2); + glVertexArrayAttribBinding(vao_, 2, 0); + glVertexArrayAttribFormat(vao_, 2, 2, GL_FLOAT, GL_FALSE, + offsetof(ImDrawVert, uv)); + glVertexArrayVertexBuffer(vao_, 0, vertex_buffer_.handle(), 0, + sizeof(ImDrawVert)); +} + +void ImGuiRenderer::InitializeFontTextures() { + auto& io = ImGui::GetIO(); + + ImFontConfig font_config; + font_config.OversampleH = font_config.OversampleV = 1; + font_config.PixelSnapH = true; + static const ImWchar font_glyph_ranges[] = { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0, + }; + io.Fonts->AddFontFromMemoryCompressedBase85TTF( + kProggyTinyCompressedDataBase85, 10.0f, &font_config, font_glyph_ranges); + + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + GLuint font_texture; + glCreateTextures(GL_TEXTURE_2D, 1, &font_texture); + glTextureParameteri(font_texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTextureParameteri(font_texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTextureStorage2D(font_texture, 1, GL_RGBA8, width, height); + glTextureSubImage2D(font_texture, 0, 0, 0, width, height, GL_RGBA, + GL_UNSIGNED_BYTE, pixels); + io.Fonts->TexID = + reinterpret_cast(static_cast(font_texture)); +} + +void ImGuiRenderer::Shutdown() { + xe::ui::GraphicsContextLock lock(context_); + vertex_buffer_.Shutdown(); + index_buffer_.Shutdown(); + glDeleteVertexArrays(1, &vao_); + glDeleteProgram(program_); + + ImGuiIO& io = ImGui::GetIO(); + auto tex_id = + static_cast(reinterpret_cast(io.Fonts->TexID)); + glDeleteTextures(1, &tex_id); + + ImGui::Shutdown(); +} + +void ImGuiRenderer::RenderDrawLists(ImDrawData* data) { + // Setup render state: + // alpha-blending enabled + // no face culling, + // no depth testing + // scissor enabled + glEnablei(GL_BLEND, 0); + glBlendEquationi(0, GL_FUNC_ADD); + glBlendFunci(0, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + + // Setup orthographic projection matrix. + const float width = ImGui::GetIO().DisplaySize.x; + const float height = ImGui::GetIO().DisplaySize.y; + const float ortho_projection[4][4] = { + {2.0f / width, 0.0f, 0.0f, 0.0f}, + {0.0f, 2.0f / -height, 0.0f, 0.0f}, + {0.0f, 0.0f, -1.0f, 0.0f}, + {-1.0f, 1.0f, 0.0f, 1.0f}, + }; + glProgramUniformMatrix4fv(program_, 0, 1, GL_FALSE, &ortho_projection[0][0]); + + // Prepare drawing resources. + glUseProgram(program_); + glBindVertexArray(vao_); + + // Run through and upload all the vertex and index data. + auto vertex_alloc = + vertex_buffer_.Acquire(data->TotalVtxCount * sizeof(ImDrawVert)); + auto index_alloc = + index_buffer_.Acquire(data->TotalIdxCount * sizeof(ImDrawIdx)); + uintptr_t vertex_offset = 0; + uintptr_t index_offset = 0; + for (int i = 0; i < data->CmdListsCount; ++i) { + auto cmd_list = data->CmdLists[i]; + size_t vertex_bytes = cmd_list->VtxBuffer.size() * sizeof(ImDrawVert); + std::memcpy( + reinterpret_cast(vertex_alloc.host_ptr) + vertex_offset, + cmd_list->VtxBuffer.Data, vertex_bytes); + vertex_offset += vertex_bytes; + size_t index_bytes = cmd_list->IdxBuffer.size() * sizeof(ImDrawIdx); + std::memcpy(reinterpret_cast(index_alloc.host_ptr) + index_offset, + cmd_list->IdxBuffer.Data, index_bytes); + index_offset += index_bytes; + } + vertex_buffer_.Commit(std::move(vertex_alloc)); + vertex_buffer_.Flush(); + index_buffer_.Commit(std::move(index_alloc)); + index_buffer_.Flush(); + + // Second pass to issue draws. + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_.handle()); + ImTextureID prev_texture_id = 0; + const ImDrawIdx* index_buffer_offset = 0; + GLint base_vertex = 0; + for (int i = 0; i < data->CmdListsCount; ++i) { + const auto cmd_list = data->CmdLists[i]; + for (int j = 0; j < cmd_list->CmdBuffer.size(); ++j) { + const auto& cmd = cmd_list->CmdBuffer[j]; + if (cmd.TextureId != prev_texture_id) { + glBindTextureUnit( + 0, static_cast(reinterpret_cast(cmd.TextureId))); + prev_texture_id = cmd.TextureId; + } + glScissorIndexed(0, static_cast(cmd.ClipRect.x), + static_cast(height - cmd.ClipRect.w), + static_cast(cmd.ClipRect.z - cmd.ClipRect.x), + static_cast(cmd.ClipRect.w - cmd.ClipRect.y)); + glDrawElementsBaseVertex(GL_TRIANGLES, cmd.ElemCount, GL_UNSIGNED_SHORT, + index_buffer_offset, base_vertex); + index_buffer_offset += cmd.ElemCount; + } + base_vertex += cmd_list->VtxBuffer.Size; + } + + // TODO(benvanik): don't finish here. + vertex_buffer_.WaitUntilClean(); + index_buffer_.WaitUntilClean(); + + // Restore modified state. + glBindTextureUnit(0, 0); + glUseProgram(0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +} // namespace ui +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/ui/imgui_renderer.h b/src/xenia/debug/ui/imgui_renderer.h new file mode 100644 index 000000000..2fe153942 --- /dev/null +++ b/src/xenia/debug/ui/imgui_renderer.h @@ -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 + +#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_ diff --git a/src/xenia/debug/ui/main_window.cc b/src/xenia/debug/ui/main_window.cc deleted file mode 100644 index d4e3cdeda..000000000 --- a/src/xenia/debug/ui/main_window.cc +++ /dev/null @@ -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( - "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(); - 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(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 diff --git a/src/xenia/debug/ui/main_window.h b/src/xenia/debug/ui/main_window.h deleted file mode 100644 index c3410f027..000000000 --- a/src/xenia/debug/ui/main_window.h +++ /dev/null @@ -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 - -#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 window_; - std::unique_ptr form_; - struct { - el::SplitContainer* split_container; - el::LayoutBox* toolbar_box; - el::TabContainer* tab_container; - } ui_ = {0}; - std::unique_ptr handler_; - views::cpu::CpuView cpu_view_; - views::gpu::GpuView gpu_view_; -}; - -} // namespace ui -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_UI_MAIN_WINDOW_H_ diff --git a/src/xenia/debug/ui/model/function.cc b/src/xenia/debug/ui/model/function.cc deleted file mode 100644 index 9808fff1a..000000000 --- a/src/xenia/debug/ui/model/function.cc +++ /dev/null @@ -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 diff --git a/src/xenia/debug/ui/model/function.h b/src/xenia/debug/ui/model/function.h deleted file mode 100644 index a31f7b9ea..000000000 --- a/src/xenia/debug/ui/model/function.h +++ /dev/null @@ -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 - -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_ diff --git a/src/xenia/debug/ui/model/module.cc b/src/xenia/debug/ui/model/module.cc deleted file mode 100644 index 70c435d54..000000000 --- a/src/xenia/debug/ui/model/module.cc +++ /dev/null @@ -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 diff --git a/src/xenia/debug/ui/model/module.h b/src/xenia/debug/ui/model/module.h deleted file mode 100644 index efbc62b7e..000000000 --- a/src/xenia/debug/ui/model/module.h +++ /dev/null @@ -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 -#include - -#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_ diff --git a/src/xenia/debug/ui/model/system.cc b/src/xenia/debug/ui/model/system.cc deleted file mode 100644 index a3b96e249..000000000 --- a/src/xenia/debug/ui/model/system.cc +++ /dev/null @@ -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 - -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 System::modules() { - std::vector result; - for (auto& module : modules_) { - result.push_back(module.get()); - } - return result; -} - -std::vector System::threads() { - std::vector 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 entries) { - std::unordered_set 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(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 entries) { - std::unordered_set 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(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 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 diff --git a/src/xenia/debug/ui/model/system.h b/src/xenia/debug/ui/model/system.h deleted file mode 100644 index c48e1c3d0..000000000 --- a/src/xenia/debug/ui/model/system.h +++ /dev/null @@ -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 -#include -#include -#include - -#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 modules(); - std::vector threads(); - - Module* GetModuleByHandle(uint32_t module_handle); - Thread* GetThreadByHandle(uint32_t thread_handle); - - Delegate on_execution_state_changed; - Delegate on_modules_updated; - Delegate on_threads_updated; - Delegate on_thread_state_updated; - - private: - void OnExecutionStateChanged(ExecutionState execution_state) override; - void OnModulesUpdated( - std::vector entries) override; - void OnThreadsUpdated( - std::vector entries) override; - void OnThreadStateUpdated( - uint32_t thread_handle, const ThreadStateEntry* entry, - std::vector frames) override; - - xe::ui::Loop* loop_ = nullptr; - DebugClient* client_ = nullptr; - - std::vector> modules_; - std::unordered_map modules_by_handle_; - std::vector> threads_; - std::unordered_map threads_by_handle_; -}; - -} // namespace model -} // namespace ui -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_UI_MODEL_SYSTEM_H_ diff --git a/src/xenia/debug/ui/model/thread.cc b/src/xenia/debug/ui/model/thread.cc deleted file mode 100644 index b0ba6c944..000000000 --- a/src/xenia/debug/ui/model/thread.cc +++ /dev/null @@ -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(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 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 diff --git a/src/xenia/debug/ui/model/thread.h b/src/xenia/debug/ui/model/thread.h deleted file mode 100644 index efbf1dd30..000000000 --- a/src/xenia/debug/ui/model/thread.h +++ /dev/null @@ -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 -#include -#include - -#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& call_stack() const { return call_stack_; } - - std::string to_string(); - - void Update(const proto::ThreadListEntry* entry); - void UpdateState(const proto::ThreadStateEntry* entry, - std::vector frames); - - private: - System* system_ = nullptr; - bool is_dead_ = false; - proto::ThreadListEntry entry_ = {0}; - proto::ThreadStateEntry* state_ = nullptr; - std::vector call_stack_; -}; - -} // namespace model -} // namespace ui -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_UI_MODEL_THREAD_H_ diff --git a/src/xenia/debug/ui/premake5.lua b/src/xenia/debug/ui/premake5.lua index e7f24d51c..7467afdcb 100644 --- a/src/xenia/debug/ui/premake5.lua +++ b/src/xenia/debug/ui/premake5.lua @@ -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() diff --git a/src/xenia/debug/ui/resources/smaller_skin/skin.tb.txt b/src/xenia/debug/ui/resources/smaller_skin/skin.tb.txt deleted file mode 100644 index df2986260..000000000 --- a/src/xenia/debug/ui/resources/smaller_skin/skin.tb.txt +++ /dev/null @@ -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 diff --git a/src/xenia/debug/ui/view.h b/src/xenia/debug/ui/view.h deleted file mode 100644 index f093a9c00..000000000 --- a/src/xenia/debug/ui/view.h +++ /dev/null @@ -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 -#include - -#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 handler_; - xe::debug::DebugClient* client_ = nullptr; -}; - -} // namespace ui -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_UI_VIEW_H_ diff --git a/src/xenia/debug/ui/views/cpu/call_stack_control.cc b/src/xenia/debug/ui/views/cpu/call_stack_control.cc deleted file mode 100644 index cdd6368ea..000000000 --- a/src/xenia/debug/ui/views/cpu/call_stack_control.cc +++ /dev/null @@ -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 { - 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(TBIDC("stack_listbox")); - stack_listbox->set_source(item_source_.get()); - stack_listbox->scroll_container()->set_scroll_mode( - el::ScrollMode::kAutoXAutoY); - - handler_ = std::make_unique(&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(ordinal, &frame); - item->tag.set_integer(static_cast(i)); - item_source_->push_back(std::move(item)); - } - item_source_->InvokeAllItemsRemoved(); -} - -} // namespace cpu -} // namespace views -} // namespace ui -} // namespace debug -} // namespace xe diff --git a/src/xenia/debug/ui/views/cpu/call_stack_control.h b/src/xenia/debug/ui/views/cpu/call_stack_control.h deleted file mode 100644 index 91a487fa7..000000000 --- a/src/xenia/debug/ui/views/cpu/call_stack_control.h +++ /dev/null @@ -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 -#include - -#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 item_source_; -}; - -} // namespace cpu -} // namespace views -} // namespace ui -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_ diff --git a/src/xenia/debug/ui/views/cpu/cpu_view.cc b/src/xenia/debug/ui/views/cpu/cpu_view.cc deleted file mode 100644 index c4f05a8d4..000000000 --- a/src/xenia/debug/ui/views/cpu/cpu_view.cc +++ /dev/null @@ -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("") - .id("gr_registers_placeholder")) - .tab(ButtonNode("FR"), LabelNode("") - .id("fr_registers_placeholder")) - .tab(ButtonNode("VR"), LabelNode("") - .id("vr_registers_placeholder")) - .tab(ButtonNode("X64"), LabelNode("") - .id("host_registers_placeholder")); - - auto source_tools_node = - TabContainerNode() - .gravity(Gravity::kAll) - .align(Align::kLeft) - .tab(ButtonNode("Stack"), - LabelNode("").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("") - .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(&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( - 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(module->name()); - item->id = module->module_handle(); - module_items->push_back(std::move(item)); - } - if (is_first) { - module_dropdown->set_value(static_cast(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(thread->to_string()); - item->id = thread->thread_handle(); - thread_items->push_back(std::move(item)); - } - if (is_first) { - thread_dropdown->set_value(static_cast(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 diff --git a/src/xenia/debug/ui/views/cpu/cpu_view.h b/src/xenia/debug/ui/views/cpu/cpu_view.h deleted file mode 100644 index 1e70bcbbc..000000000 --- a/src/xenia/debug/ui/views/cpu/cpu_view.h +++ /dev/null @@ -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 -#include - -#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_ diff --git a/src/xenia/debug/ui/views/cpu/register_list_control.cc b/src/xenia/debug/ui/views/cpu/register_list_control.cc deleted file mode 100644 index 9726de17a..000000000 --- a/src/xenia/debug/ui/views/cpu/register_list_control.cc +++ /dev/null @@ -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 -#include - -#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(reg)), reg_(reg) { - name_ = xe::cpu::frontend::PPCContext::GetRegisterName(reg_); - } - - void set_thread(model::Thread* thread) override { - RegisterItem::set_thread(thread); - if (thread_) { - str = thread_->guest_context()->GetStringFromValue(reg_); - } else { - str = "unknown"; - } - } - - private: - PPCRegister reg_; -}; - -class HostRegisterItem : public RegisterItem { - public: - HostRegisterItem(RegisterSet set, RegisterType type, X64Register reg) - : RegisterItem(set, type, static_cast(reg)), reg_(reg) { - name_ = xe::cpu::X64Context::GetRegisterName(reg_); - } - - void set_thread(model::Thread* thread) override { - RegisterItem::set_thread(thread); - if (thread_) { - str = thread_->host_context()->GetStringFromValue(reg_); - } else { - str = "unknown"; - } - } - - private: - X64Register reg_; -}; - -class RegisterItemElement : public el::LayoutBox { - public: - RegisterItemElement(RegisterItem* item, RegisterItemSource* source, - el::ListItemObserver* source_viewer, size_t index) - : item_(item), - source_(source), - source_viewer_(source_viewer), - index_(index) { - set_background_skin(TBIDC("ListItem")); - set_axis(el::Axis::kY); - set_gravity(el::Gravity::kAll); - set_layout_position(el::LayoutPosition::kLeftTop); - set_layout_distribution(el::LayoutDistribution::kAvailable); - set_layout_distribution_position(el::LayoutDistributionPosition::kLeftTop); - set_layout_size(el::LayoutSize::kAvailable); - set_paint_overflow_fadeout(false); - - using namespace el::dsl; // NOLINT(build/namespaces) - el::AnimationBlocker animation_blocker; - - auto node = LayoutBoxNode() - .axis(Axis::kX) - .gravity(Gravity::kLeft) - .distribution(LayoutDistribution::kAvailable) - .child(LabelNode(item_->name()) - .ignore_input(true) - .width(45_px) - .gravity(Gravity::kLeft)) - .child(TextBoxNode(item_->str.c_str()) - .gravity(Gravity::kLeftRight)); - content_root()->LoadNodeTree(node); - } - - bool OnEvent(const el::Event& ev) override { - return el::LayoutBox::OnEvent(ev); - } - - private: - RegisterItem* item_ = nullptr; - RegisterItemSource* source_ = nullptr; - el::ListItemObserver* source_viewer_ = nullptr; - size_t index_ = 0; -}; - -class RegisterItemSource : public el::ListItemSourceList { - public: - el::Element* CreateItemElement(size_t index, - el::ListItemObserver* viewer) override { - return new RegisterItemElement(at(index), this, viewer, index); - } -}; - -void DefineRegisterItem(RegisterItemSource* item_source, RegisterSet set, - RegisterType type, PPCRegister reg) { - auto item = std::make_unique(set, type, reg); - item->tag.set_integer(static_cast(reg)); - item_source->push_back(std::move(item)); -} -void DefineRegisterItem(RegisterItemSource* item_source, RegisterSet set, - RegisterType type, X64Register reg) { - auto item = std::make_unique(set, type, reg); - item->tag.set_integer(static_cast(reg)); - item_source->push_back(std::move(item)); -} - -RegisterListControl::RegisterListControl(RegisterSet set) - : set_(set), item_source_(new RegisterItemSource()) { - switch (set_) { - case RegisterSet::kGeneral: { - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - PPCRegister::kLR); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - PPCRegister::kCTR); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl, - PPCRegister::kXER); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl, - PPCRegister::kCR); - for (size_t i = 0; i < 32; ++i) { - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - static_cast( - static_cast(PPCRegister::kR0) + i)); - } - } break; - case RegisterSet::kFloat: { - DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl, - PPCRegister::kFPSCR); - for (size_t i = 0; i < 32; ++i) { - DefineRegisterItem(item_source_.get(), set_, RegisterType::kFloat, - static_cast( - static_cast(PPCRegister::kFR0) + i)); - } - } break; - case RegisterSet::kVector: { - DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl, - PPCRegister::kVSCR); - for (size_t i = 0; i < 128; ++i) { - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - static_cast( - static_cast(PPCRegister::kVR0) + i)); - } - } break; - case RegisterSet::kHost: { - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kRip); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl, - X64Register::kEflags); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kRax); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kRcx); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kRdx); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kRbx); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kRsp); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kRbp); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kRsi); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kRdi); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kR8); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kR9); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kR10); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kR11); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kR12); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kR13); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kR14); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger, - X64Register::kR15); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm0); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm1); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm2); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm3); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm4); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm5); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm6); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm7); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm8); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm9); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm10); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm11); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm12); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm13); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm14); - DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector, - X64Register::kXmm15); - } break; - } -} - -RegisterListControl::~RegisterListControl() = default; - -el::Element* RegisterListControl::BuildUI() { - using namespace el::dsl; // NOLINT(build/namespaces) - el::AnimationBlocker animation_blocker; - - auto node = - LayoutBoxNode() - .gravity(Gravity::kAll) - .distribution(LayoutDistribution::kAvailable) - .child(ListBoxNode().id("register_listbox").gravity(Gravity::kAll)); - - root_element_.set_gravity(Gravity::kAll); - root_element_.set_layout_distribution(LayoutDistribution::kAvailable); - root_element_.LoadNodeTree(node); - - auto register_listbox = - root_element_.GetElementById(TBIDC("register_listbox")); - register_listbox->set_source(item_source_.get()); - register_listbox->scroll_container()->set_scroll_mode( - el::ScrollMode::kAutoXAutoY); - - handler_ = std::make_unique(&root_element_); - - return &root_element_; -} - -void RegisterListControl::Setup(DebugClient* client) { - client_ = client; - - system()->on_thread_state_updated.AddListener([this](model::Thread* thread) { - if (thread == thread_) { - InvalidateRegisters(); - } - }); -} - -void RegisterListControl::set_thread(model::Thread* thread) { - thread_ = thread; - InvalidateRegisters(); -} - -void RegisterListControl::InvalidateRegisters() { - for (size_t i = 0; i < item_source_->size(); ++i) { - item_source_->at(i)->set_thread(thread_); - } - - auto register_listbox = - root_element_.GetElementById(TBIDC("register_listbox")); - register_listbox->InvalidateList(); -} - -} // namespace cpu -} // namespace views -} // namespace ui -} // namespace debug -} // namespace xe diff --git a/src/xenia/debug/ui/views/cpu/register_list_control.h b/src/xenia/debug/ui/views/cpu/register_list_control.h deleted file mode 100644 index f9b290a9b..000000000 --- a/src/xenia/debug/ui/views/cpu/register_list_control.h +++ /dev/null @@ -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 -#include - -#include "xenia/debug/ui/control.h" - -namespace xe { -namespace debug { -namespace ui { -namespace views { -namespace cpu { - -class RegisterItemSource; - -enum class RegisterSet { - kGeneral, - kFloat, - kVector, - kHost, -}; - -class RegisterListControl : public Control { - public: - 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 item_source_; -}; - -} // namespace cpu -} // namespace views -} // namespace ui -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_UI_VIEWS_CPU_REGISTER_LIST_CONTROL_H_ diff --git a/src/xenia/debug/ui/views/cpu/source_control.cc b/src/xenia/debug/ui/views/cpu/source_control.cc deleted file mode 100644 index d9dec3232..000000000 --- a/src/xenia/debug/ui/views/cpu/source_control.cc +++ /dev/null @@ -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(&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 diff --git a/src/xenia/debug/ui/views/cpu/source_control.h b/src/xenia/debug/ui/views/cpu/source_control.h deleted file mode 100644 index d49a57c8c..000000000 --- a/src/xenia/debug/ui/views/cpu/source_control.h +++ /dev/null @@ -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 -#include - -#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_ diff --git a/src/xenia/debug/ui/views/gpu/gpu_view.cc b/src/xenia/debug/ui/views/gpu/gpu_view.cc deleted file mode 100644 index cdbd2704b..000000000 --- a/src/xenia/debug/ui/views/gpu/gpu_view.cc +++ /dev/null @@ -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(&root_element_); - return &root_element_; -} - -void GpuView::Setup(xe::debug::DebugClient* client) { - // -} - -} // namespace gpu -} // namespace views -} // namespace ui -} // namespace debug -} // namespace xe diff --git a/src/xenia/debug/ui/views/gpu/gpu_view.h b/src/xenia/debug/ui/views/gpu/gpu_view.h deleted file mode 100644 index a88d9c34c..000000000 --- a/src/xenia/debug/ui/views/gpu/gpu_view.h +++ /dev/null @@ -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 -#include - -#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_ diff --git a/src/xenia/debug/ui/xe-debug-ui.vcxproj b/src/xenia/debug/ui/xe-debug-ui.vcxproj deleted file mode 100644 index 632353b68..000000000 --- a/src/xenia/debug/ui/xe-debug-ui.vcxproj +++ /dev/null @@ -1,101 +0,0 @@ - - - - - Debug - x64 - - - Release - x64 - - - - {C5BA52F0-C86B-4817-921C-CCA257FC04BE} - Win32Proj - xedebugui - - - - Application - true - v140 - Unicode - - - Application - false - v140 - true - Unicode - - - - - - - - - - - - - - - - - - - - - - - - Level3 - - - Windows - true - libgflags.lib;libglew.lib;libxenia.lib;%(AdditionalDependencies) - - - - - Level3 - - - true - true - - - Windows - true - libgflags.lib;libglew.lib;libxenia.lib;%(AdditionalDependencies) - - - - - - - - - - - - - - - - - - - - - $(SolutionDir)third_party/elemental-forms;$(SolutionDir);. - $(SolutionDir)third_party/elemental-forms;$(SolutionDir);. - - - - - - \ No newline at end of file diff --git a/src/xenia/debug/ui/xe-debug-ui.vcxproj.filters b/src/xenia/debug/ui/xe-debug-ui.vcxproj.filters deleted file mode 100644 index 971c75985..000000000 --- a/src/xenia/debug/ui/xe-debug-ui.vcxproj.filters +++ /dev/null @@ -1,77 +0,0 @@ - - - - - {52d1c314-7464-4640-bee4-59b30d2b2df4} - - - {f8d1867c-424d-4942-ba97-a7b29fa8c87c} - - - {1b70e26e-0024-410f-b27f-ceaba190d5a9} - - - {9b5a4bce-210a-4488-863a-9175f0543d00} - - - {9a5724c2-5473-4d53-93b4-26531201f38d} - - - {f0ac4999-4700-4b41-b73d-c6dc8b23c5e6} - - - {5601b0a2-a720-4d89-9fca-df1637fce0b1} - - - {5b186e9a-1fef-42f5-b06b-2ac0b10a8a5b} - - - {c66e9a6b-0947-4610-9b2b-29db99c08e91} - - - - - src\xenia\base - - - src\xenia\debug\ui - - - src\xenia\debug\ui - - - src\xenia\debug\ui - - - src\xenia\debug\ui\views\gpu - - - src\xenia\debug\ui\views\cpu - - - - - src\xenia\base - - - src\xenia\debug\ui - - - src\xenia\debug\ui - - - src\xenia\debug\ui - - - src\xenia\debug\ui\views\gpu - - - src\xenia\debug\ui\views\cpu - - - - - src\xenia\debug\ui - - - \ No newline at end of file diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 87c5d987a..1ce91ddd4 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -11,6 +11,7 @@ #include +#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_state_->LoadKernelModule(); + // 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(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( - 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::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; } diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index 2a370c6c7..628a3456f 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -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); diff --git a/src/xenia/gpu/gl4/gl4_graphics_system.cc b/src/xenia/gpu/gl4/gl4_graphics_system.cc index f97793471..056af726e 100644 --- a/src/xenia/gpu/gl4/gl4_graphics_system.cc +++ b/src/xenia/gpu/gl4/gl4_graphics_system.cc @@ -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(); { diff --git a/src/xenia/gpu/gl4/trace_viewer_main.cc b/src/xenia/gpu/gl4/trace_viewer_main.cc index bdf639afc..c7c4d5e67 100644 --- a/src/xenia/gpu/gl4/trace_viewer_main.cc +++ b/src/xenia/gpu/gl4/trace_viewer_main.cc @@ -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(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(trace_ptr); trace_ptr += sizeof(*cmd); if (pending_packet) { - PacketInfo packet_info; + PacketInfo packet_info = {0}; if (DisasmPacket(reinterpret_cast(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); diff --git a/src/xenia/kernel/kernel_module.cc b/src/xenia/kernel/kernel_module.cc index 2442dde8c..6b34cb23b 100644 --- a/src/xenia/kernel/kernel_module.cc +++ b/src/xenia/kernel/kernel_module.cc @@ -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; diff --git a/src/xenia/kernel/xthread.cc b/src/xenia/kernel/xthread.cc index 131f3881a..2437952bd 100644 --- a/src/xenia/kernel/xthread.cc +++ b/src/xenia/kernel/xthread.cc @@ -636,6 +636,10 @@ void XThread::SetActiveCpu(uint32_t cpu_index) { xe::store_and_swap(pcr + 0x10C, cpu_index); } +uint32_t XThread::suspend_count() { + return guest_object()->suspend_count; +} + X_STATUS XThread::Resume(uint32_t* out_suspend_count) { --guest_object()->suspend_count; diff --git a/src/xenia/kernel/xthread.h b/src/xenia/kernel/xthread.h index 8bcef6ac8..f340f8867 100644 --- a/src/xenia/kernel/xthread.h +++ b/src/xenia/kernel/xthread.h @@ -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, diff --git a/src/xenia/ui/loop.cc b/src/xenia/ui/loop.cc index aacb98955..45d7d5d06 100644 --- a/src/xenia/ui/loop.cc +++ b/src/xenia/ui/loop.cc @@ -39,6 +39,11 @@ Loop::~Loop() { } void Loop::PostSynchronous(std::function fn) { + if (is_on_loop_thread()) { + // Prevent deadlock if we are executing on ourselves. + fn(); + return; + } xe::threading::Fence fence; Post([&fn, &fence]() { fn(); diff --git a/src/xenia/ui/loop.h b/src/xenia/ui/loop.h index 67f63ad20..6db86f31e 100644 --- a/src/xenia/ui/loop.h +++ b/src/xenia/ui/loop.h @@ -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 fn) = 0; virtual void PostDelayed(std::function fn, uint64_t delay_millis) = 0; void PostSynchronous(std::function fn); diff --git a/src/xenia/ui/loop_win.cc b/src/xenia/ui/loop_win.cc index b0317c48f..a139feae4 100644 --- a/src/xenia/ui/loop_win.cc +++ b/src/xenia/ui/loop_win.cc @@ -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 fn) { assert_true(thread_id_ != 0); if (!PostThreadMessage( diff --git a/src/xenia/ui/loop_win.h b/src/xenia/ui/loop_win.h index f8c6538cd..abd6a9c39 100644 --- a/src/xenia/ui/loop_win.h +++ b/src/xenia/ui/loop_win.h @@ -26,6 +26,8 @@ class Win32Loop : public Loop { Win32Loop(); ~Win32Loop() override; + bool is_on_loop_thread() override; + void Post(std::function fn) override; void PostDelayed(std::function fn, uint64_t delay_millis) override; diff --git a/src/xenia/ui/window.cc b/src/xenia/ui/window.cc index 43db896d8..ddbb761b2 100644 --- a/src/xenia/ui/window.cc +++ b/src/xenia/ui/window.cc @@ -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); } diff --git a/src/xenia/ui/window_win.cc b/src/xenia/ui/window_win.cc index 614b1f1fa..e741144f1 100644 --- a/src/xenia/ui/window_win.cc +++ b/src/xenia/ui/window_win.cc @@ -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. diff --git a/third_party/elemental-forms b/third_party/elemental-forms index b69064233..f0f692cb7 160000 --- a/third_party/elemental-forms +++ b/third_party/elemental-forms @@ -1 +1 @@ -Subproject commit b6906423349431e54bc646be61b7c64b13c442c3 +Subproject commit f0f692cb72e0d603d526db803d45f32ffabfe8bb diff --git a/third_party/imgui b/third_party/imgui index 14f189b2f..127f44c12 160000 --- a/third_party/imgui +++ b/third_party/imgui @@ -1 +1 @@ -Subproject commit 14f189b2f62ec2f79875e7c897a42415fe96fde6 +Subproject commit 127f44c12bc4b1e5b6d7eb90b9a934668b01e8c2 diff --git a/third_party/imgui.lua b/third_party/imgui.lua index 2026b6fd1..693366777 100644 --- a/third_party/imgui.lua +++ b/third_party/imgui.lua @@ -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. + })