A new debugger.
Lots of bugs/rough edges/etc - issues will be filed. Old-style debugging still works (just use --emit_source_annotations to get the helpful movs back and --break_on_instruction will still fire).
This commit is contained in:
parent
8046c19898
commit
5d033f9cb3
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ class EmulatorWindow {
|
|||
void CpuTimeScalarReset();
|
||||
void CpuTimeScalarSetHalf();
|
||||
void CpuTimeScalarSetDouble();
|
||||
void CpuBreakIntoDebugger();
|
||||
void GpuTraceFrame();
|
||||
void GpuClearCaches();
|
||||
void ToggleFullscreen();
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "xenia/app/emulator_window.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/debug/ui/debug_window.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/profiling.h"
|
||||
#include "xenia/ui/file_picker.h"
|
||||
|
@ -39,6 +40,27 @@ int xenia_main(const std::vector<std::wstring>& args) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
// Set a debug handler.
|
||||
// This will respond to debugging requests so we can open the debug UI.
|
||||
std::unique_ptr<xe::debug::ui::DebugWindow> debug_window;
|
||||
if (emulator->debugger()) {
|
||||
emulator->debugger()->set_debug_listener_request_handler([&](
|
||||
xe::debug::Debugger* debugger) {
|
||||
if (debug_window) {
|
||||
return debug_window.get();
|
||||
}
|
||||
emulator_window->loop()->PostSynchronous([&]() {
|
||||
debug_window = xe::debug::ui::DebugWindow::Create(
|
||||
emulator.get(), emulator_window->loop());
|
||||
debug_window->window()->on_closed.AddListener([&](xe::ui::UIEvent* e) {
|
||||
emulator->debugger()->set_debug_listener(nullptr);
|
||||
emulator_window->loop()->Post([&]() { debug_window.reset(); });
|
||||
});
|
||||
});
|
||||
return debug_window.get();
|
||||
});
|
||||
}
|
||||
|
||||
// Grab path from the flag or unnamed argument.
|
||||
std::wstring path;
|
||||
if (!FLAGS_target.empty() || args.size() >= 2) {
|
||||
|
@ -91,6 +113,7 @@ int xenia_main(const std::vector<std::wstring>& args) {
|
|||
emulator->display_window()->loop()->AwaitQuit();
|
||||
}
|
||||
|
||||
debug_window.reset();
|
||||
emulator.reset();
|
||||
emulator_window.reset();
|
||||
|
||||
|
|
|
@ -13,37 +13,61 @@
|
|||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/x64_context.h"
|
||||
|
||||
namespace xe {
|
||||
class ExceptionHandler {
|
||||
|
||||
class Exception {
|
||||
public:
|
||||
struct Info {
|
||||
enum {
|
||||
kInvalidException = 0,
|
||||
kAccessViolation,
|
||||
} code = kInvalidException;
|
||||
|
||||
uint64_t pc = 0; // Program counter address. RIP on x64.
|
||||
uint64_t fault_address =
|
||||
0; // In case of AV, address that was read from/written to.
|
||||
|
||||
void* thread_context = nullptr; // Platform-specific thread context info.
|
||||
enum class Code {
|
||||
kInvalidException = 0,
|
||||
kAccessViolation,
|
||||
kIllegalInstruction,
|
||||
};
|
||||
typedef std::function<bool(Info* ex_info)> Handler;
|
||||
|
||||
// Static initialization. Only call this once!
|
||||
static bool Initialize();
|
||||
void InitializeAccessViolation(X64Context* thread_context,
|
||||
uint64_t fault_address) {
|
||||
code_ = Code::kAccessViolation;
|
||||
thread_context_ = thread_context;
|
||||
fault_address_ = fault_address;
|
||||
}
|
||||
void InitializeIllegalInstruction(X64Context* thread_context) {
|
||||
code_ = Code::kIllegalInstruction;
|
||||
thread_context_ = thread_context;
|
||||
}
|
||||
|
||||
// Install an exception handler. Returns an ID which you can save to remove
|
||||
// this later. This will install the exception handler in the last place.
|
||||
// TODO: ID support!
|
||||
static uint32_t Install(Handler fn);
|
||||
static bool Remove(uint32_t id);
|
||||
Code code() const { return code_; }
|
||||
|
||||
static const std::vector<Handler>& handlers() { return handlers_; }
|
||||
// Returns the platform-specific thread context info.
|
||||
X64Context* thread_context() const { return thread_context_; }
|
||||
|
||||
// Returns the program counter where the exception occurred.
|
||||
// RIP on x64.
|
||||
uint64_t pc() const { return thread_context_->rip; }
|
||||
// Sets the program counter where execution will resume.
|
||||
void set_resume_pc(uint64_t pc) { thread_context_->rip = pc; }
|
||||
|
||||
// In case of AV, address that was read from/written to.
|
||||
uint64_t fault_address() const { return fault_address_; }
|
||||
|
||||
private:
|
||||
static std::vector<Handler> handlers_;
|
||||
Code code_ = Code::kInvalidException;
|
||||
X64Context* thread_context_ = nullptr;
|
||||
uint64_t fault_address_ = 0;
|
||||
};
|
||||
}; // namespace xe
|
||||
|
||||
#endif // XENIA_BASE_EXCEPTION_HANDLER_H_
|
||||
class ExceptionHandler {
|
||||
public:
|
||||
typedef bool (*Handler)(Exception* ex, void* data);
|
||||
|
||||
// Installs an exception handler.
|
||||
// Handlers are called in the order they are installed.
|
||||
static void Install(Handler fn, void* data);
|
||||
|
||||
// Uninstalls a previously-installed exception handler.
|
||||
static void Uninstall(Handler fn, void* data);
|
||||
};
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_BASE_EXCEPTION_HANDLER_H_
|
||||
|
|
|
@ -9,53 +9,118 @@
|
|||
|
||||
#include "xenia/base/exception_handler.h"
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/platform_win.h"
|
||||
|
||||
namespace xe {
|
||||
std::vector<ExceptionHandler::Handler> ExceptionHandler::handlers_;
|
||||
|
||||
// Handle of the added VectoredExceptionHandler.
|
||||
void* veh_handle_ = nullptr;
|
||||
// Handle of the added VectoredContinueHandler.
|
||||
void* vch_handle_ = nullptr;
|
||||
|
||||
// This can be as large as needed, but isn't often needed.
|
||||
// As we will be sometimes firing many exceptions we want to avoid having to
|
||||
// scan the table too much or invoke many custom handlers.
|
||||
constexpr size_t kMaxHandlerCount = 8;
|
||||
|
||||
// All custom handlers, left-aligned and null terminated.
|
||||
// Executed in order.
|
||||
std::pair<ExceptionHandler::Handler, void*> handlers_[kMaxHandlerCount];
|
||||
|
||||
LONG CALLBACK ExceptionHandlerCallback(PEXCEPTION_POINTERS ex_info) {
|
||||
// Visual Studio SetThreadName
|
||||
// Visual Studio SetThreadName.
|
||||
if (ex_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
auto code = ex_info->ExceptionRecord->ExceptionCode;
|
||||
ExceptionHandler::Info info;
|
||||
info.pc = ex_info->ContextRecord->Rip;
|
||||
info.thread_context = ex_info->ContextRecord;
|
||||
// TODO(benvanik): avoid this by mapping X64Context virtual?
|
||||
X64Context thread_context;
|
||||
thread_context.rip = ex_info->ContextRecord->Rip;
|
||||
thread_context.eflags = ex_info->ContextRecord->EFlags;
|
||||
std::memcpy(thread_context.int_registers, &ex_info->ContextRecord->Rax,
|
||||
sizeof(thread_context.int_registers));
|
||||
std::memcpy(thread_context.xmm_registers, &ex_info->ContextRecord->Xmm0,
|
||||
sizeof(thread_context.xmm_registers));
|
||||
|
||||
switch (code) {
|
||||
case STATUS_ACCESS_VIOLATION:
|
||||
info.code = ExceptionHandler::Info::kAccessViolation;
|
||||
info.fault_address = ex_info->ExceptionRecord->ExceptionInformation[1];
|
||||
// http://msdn.microsoft.com/en-us/library/ms679331(v=vs.85).aspx
|
||||
// http://msdn.microsoft.com/en-us/library/aa363082(v=vs.85).aspx
|
||||
Exception ex;
|
||||
switch (ex_info->ExceptionRecord->ExceptionCode) {
|
||||
case STATUS_ILLEGAL_INSTRUCTION:
|
||||
ex.InitializeIllegalInstruction(&thread_context);
|
||||
break;
|
||||
case STATUS_ACCESS_VIOLATION:
|
||||
ex.InitializeAccessViolation(
|
||||
&thread_context, ex_info->ExceptionRecord->ExceptionInformation[1]);
|
||||
break;
|
||||
default:
|
||||
// Unknown/unhandled type.
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
// Only call a handler if we support this type of exception.
|
||||
if (info.code != ExceptionHandler::Info::kInvalidException) {
|
||||
for (auto handler : ExceptionHandler::handlers()) {
|
||||
if (handler(&info)) {
|
||||
// Exception handled.
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
for (size_t i = 0; i < xe::countof(handlers_) && handlers_[i].first; ++i) {
|
||||
if (handlers_[i].first(&ex, handlers_[i].second)) {
|
||||
// Exception handled.
|
||||
// TODO(benvanik): update all thread state? Dirty flags?
|
||||
ex_info->ContextRecord->Rip = thread_context.rip;
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
bool ExceptionHandler::Initialize() {
|
||||
AddVectoredExceptionHandler(0, ExceptionHandlerCallback);
|
||||
void ExceptionHandler::Install(Handler fn, void* data) {
|
||||
if (!veh_handle_) {
|
||||
veh_handle_ = AddVectoredExceptionHandler(1, ExceptionHandlerCallback);
|
||||
|
||||
// TODO: Do we need a continue handler if a debugger is attached?
|
||||
return true;
|
||||
if (IsDebuggerPresent()) {
|
||||
// TODO(benvanik): do we need a continue handler if a debugger is
|
||||
// attached?
|
||||
// vch_handle_ = AddVectoredContinueHandler(1, ExceptionHandlerCallback);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
|
||||
if (!handlers_[i].first) {
|
||||
handlers_[i].first = fn;
|
||||
handlers_[i].second = data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert_always("Too many exception handlers installed");
|
||||
}
|
||||
|
||||
uint32_t ExceptionHandler::Install(std::function<bool(Info* ex_info)> fn) {
|
||||
handlers_.push_back(fn);
|
||||
void ExceptionHandler::Uninstall(Handler fn, void* data) {
|
||||
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
|
||||
if (handlers_[i].first == fn && handlers_[i].second == data) {
|
||||
for (; i < xe::countof(handlers_) - 1; ++i) {
|
||||
handlers_[i] = handlers_[i + 1];
|
||||
}
|
||||
handlers_[i].first = nullptr;
|
||||
handlers_[i].second = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ID support!
|
||||
return 0;
|
||||
bool has_any = false;
|
||||
for (size_t i = 0; i < xe::countof(handlers_); ++i) {
|
||||
if (handlers_[i].first) {
|
||||
has_any = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_any) {
|
||||
if (veh_handle_) {
|
||||
RemoveVectoredExceptionHandler(veh_handle_);
|
||||
veh_handle_ = nullptr;
|
||||
}
|
||||
if (vch_handle_) {
|
||||
RemoveVectoredContinueHandler(vch_handle_);
|
||||
vch_handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xe
|
||||
|
|
|
@ -179,6 +179,12 @@ inline uint64_t rotate_left(uint64_t v, uint8_t sh) {
|
|||
}
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
|
||||
template <typename T>
|
||||
T clamp(T value, T min_value, T max_value) {
|
||||
const T t = value < min_value ? min_value : value;
|
||||
return t > max_value ? max_value : t;
|
||||
}
|
||||
|
||||
// Utilities for SSE values.
|
||||
template <int N>
|
||||
float m128_f32(const __m128& v) {
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include <codecvt>
|
||||
#endif // XE_PLATFORM_LINUX
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <locale>
|
||||
|
||||
|
@ -25,7 +26,7 @@ std::string to_string(const std::wstring& source) {
|
|||
#if NO_CODECVT
|
||||
return std::string(source.begin(), source.end());
|
||||
#else
|
||||
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t> > converter;
|
||||
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||
return converter.to_bytes(source);
|
||||
#endif // XE_PLATFORM_LINUX
|
||||
}
|
||||
|
@ -34,7 +35,7 @@ std::wstring to_wstring(const std::string& source) {
|
|||
#if NO_CODECVT
|
||||
return std::wstring(source.begin(), source.end());
|
||||
#else
|
||||
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t> > converter;
|
||||
static std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
|
||||
return converter.from_bytes(source);
|
||||
#endif // XE_PLATFORM_LINUX
|
||||
}
|
||||
|
@ -227,4 +228,40 @@ std::wstring find_base_path(const std::wstring& path, wchar_t sep) {
|
|||
}
|
||||
}
|
||||
|
||||
int fuzzy_match(const std::string& pattern, const char* value) {
|
||||
// https://github.com/mattyork/fuzzy/blob/master/lib/fuzzy.js
|
||||
// TODO(benvanik): look at https://github.com/atom/fuzzaldrin/tree/master/src
|
||||
// This does not weight complete substrings or prefixes right, which
|
||||
// kind of sucks.
|
||||
size_t pattern_index = 0;
|
||||
size_t value_length = std::strlen(value);
|
||||
int total_score = 0;
|
||||
int local_score = 0;
|
||||
for (size_t i = 0; i < value_length; ++i) {
|
||||
if (std::tolower(value[i]) == std::tolower(pattern[pattern_index])) {
|
||||
++pattern_index;
|
||||
local_score += 1 + local_score;
|
||||
} else {
|
||||
local_score = 0;
|
||||
}
|
||||
total_score += local_score;
|
||||
}
|
||||
return total_score;
|
||||
}
|
||||
|
||||
std::vector<std::pair<size_t, int>> fuzzy_filter(const std::string& pattern,
|
||||
const void* const* entries,
|
||||
size_t entry_count,
|
||||
size_t string_offset) {
|
||||
std::vector<std::pair<size_t, int>> results;
|
||||
results.reserve(entry_count);
|
||||
for (size_t i = 0; i < entry_count; ++i) {
|
||||
auto entry_value =
|
||||
reinterpret_cast<const char*>(entries[i]) + string_offset;
|
||||
int score = fuzzy_match(pattern, entry_value);
|
||||
results.emplace_back(i, score);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
} // namespace xe
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/platform.h"
|
||||
|
@ -56,6 +57,27 @@ std::string find_base_path(const std::string& path,
|
|||
std::wstring find_base_path(const std::wstring& path,
|
||||
wchar_t sep = xe::kPathSeparator);
|
||||
|
||||
// Tests a match against a case-insensitive fuzzy filter.
|
||||
// Returns the score of the match or 0 if none.
|
||||
int fuzzy_match(const std::string& pattern, const char* value);
|
||||
|
||||
// Applies a case-insensitive fuzzy filter to the given entries and ranks
|
||||
// results.
|
||||
// Entries is a list of pointers to opaque structs, each of which contains a
|
||||
// char* string at the given offset.
|
||||
// Returns an unsorted list of {original index, score}.
|
||||
std::vector<std::pair<size_t, int>> fuzzy_filter(const std::string& pattern,
|
||||
const void* const* entries,
|
||||
size_t entry_count,
|
||||
size_t string_offset);
|
||||
template <typename T>
|
||||
std::vector<std::pair<size_t, int>> fuzzy_filter(const std::string& pattern,
|
||||
const std::vector<T>& entries,
|
||||
size_t string_offset) {
|
||||
return fuzzy_filter(pattern, reinterpret_cast<void* const*>(entries.data()),
|
||||
entries.size(), string_offset);
|
||||
}
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_BASE_STRING_H_
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace xe {
|
|||
StringBuffer::StringBuffer(size_t initial_capacity) {
|
||||
buffer_capacity_ = std::max(initial_capacity, static_cast<size_t>(16 * 1024));
|
||||
buffer_ = reinterpret_cast<char*>(malloc(buffer_capacity_));
|
||||
buffer_[0] = 0;
|
||||
}
|
||||
|
||||
StringBuffer::~StringBuffer() {
|
||||
|
|
|
@ -33,6 +33,15 @@ inline std::string to_hex_string(uint64_t value) {
|
|||
return std::string(buffer);
|
||||
}
|
||||
|
||||
inline std::string to_hex_string(float value) {
|
||||
union {
|
||||
uint32_t ui;
|
||||
float flt;
|
||||
} v;
|
||||
v.flt = value;
|
||||
return to_hex_string(v.ui);
|
||||
}
|
||||
|
||||
inline std::string to_hex_string(double value) {
|
||||
union {
|
||||
uint64_t ui;
|
||||
|
@ -57,12 +66,46 @@ inline std::string to_hex_string(const __m128& value) {
|
|||
return std::string(buffer);
|
||||
}
|
||||
|
||||
inline std::string to_string(const __m128& value) {
|
||||
char buffer[128];
|
||||
std::snprintf(buffer, sizeof(buffer), "(%F, %F, %F, %F)", value.m128_f32[0],
|
||||
value.m128_f32[1], value.m128_f32[2], value.m128_f32[3]);
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T from_string(const char* value);
|
||||
inline T from_string(const char* value, bool force_hex = false);
|
||||
|
||||
template <>
|
||||
inline uint64_t from_string<uint64_t>(const char* value) {
|
||||
if (std::strchr(value, 'h') != nullptr) {
|
||||
inline int32_t from_string<int32_t>(const char* value, bool force_hex) {
|
||||
if (force_hex || std::strchr(value, 'h') != nullptr) {
|
||||
return std::strtol(value, nullptr, 16);
|
||||
} else {
|
||||
return std::strtol(value, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint32_t from_string<uint32_t>(const char* value, bool force_hex) {
|
||||
if (force_hex || std::strchr(value, 'h') != nullptr) {
|
||||
return std::strtoul(value, nullptr, 16);
|
||||
} else {
|
||||
return std::strtoul(value, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int64_t from_string<int64_t>(const char* value, bool force_hex) {
|
||||
if (force_hex || std::strchr(value, 'h') != nullptr) {
|
||||
return std::strtoll(value, nullptr, 16);
|
||||
} else {
|
||||
return std::strtoll(value, nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
inline uint64_t from_string<uint64_t>(const char* value, bool force_hex) {
|
||||
if (force_hex || std::strchr(value, 'h') != nullptr) {
|
||||
return std::strtoull(value, nullptr, 16);
|
||||
} else {
|
||||
return std::strtoull(value, nullptr, 0);
|
||||
|
@ -70,23 +113,38 @@ inline uint64_t from_string<uint64_t>(const char* value) {
|
|||
}
|
||||
|
||||
template <>
|
||||
inline double from_string<double>(const char* value) {
|
||||
if (std::strstr(value, "0x") == value || std::strchr(value, 'h') != nullptr) {
|
||||
inline float from_string<float>(const char* value, bool force_hex) {
|
||||
if (force_hex || std::strstr(value, "0x") == value ||
|
||||
std::strchr(value, 'h') != nullptr) {
|
||||
union {
|
||||
uint32_t ui;
|
||||
float flt;
|
||||
} v;
|
||||
v.ui = from_string<uint32_t>(value, force_hex);
|
||||
return v.flt;
|
||||
}
|
||||
return std::strtof(value, nullptr);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline double from_string<double>(const char* value, bool force_hex) {
|
||||
if (force_hex || std::strstr(value, "0x") == value ||
|
||||
std::strchr(value, 'h') != nullptr) {
|
||||
union {
|
||||
uint64_t ui;
|
||||
double dbl;
|
||||
} v;
|
||||
v.ui = from_string<uint64_t>(value);
|
||||
v.ui = from_string<uint64_t>(value, force_hex);
|
||||
return v.dbl;
|
||||
}
|
||||
return std::strtod(value, nullptr);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline vec128_t from_string<vec128_t>(const char* value) {
|
||||
inline vec128_t from_string<vec128_t>(const char* value, bool force_hex) {
|
||||
vec128_t v;
|
||||
char* p = const_cast<char*>(value);
|
||||
bool hex_mode = false;
|
||||
bool hex_mode = force_hex;
|
||||
if (*p == '[') {
|
||||
hex_mode = true;
|
||||
++p;
|
||||
|
@ -119,10 +177,10 @@ inline vec128_t from_string<vec128_t>(const char* value) {
|
|||
}
|
||||
|
||||
template <>
|
||||
inline __m128 from_string<__m128>(const char* value) {
|
||||
inline __m128 from_string<__m128>(const char* value, bool force_hex) {
|
||||
__m128 v;
|
||||
char* p = const_cast<char*>(value);
|
||||
bool hex_mode = false;
|
||||
bool hex_mode = force_hex;
|
||||
if (*p == '[') {
|
||||
hex_mode = true;
|
||||
++p;
|
||||
|
@ -155,8 +213,8 @@ inline __m128 from_string<__m128>(const char* value) {
|
|||
}
|
||||
|
||||
template <typename T>
|
||||
inline T from_string(const std::string& value) {
|
||||
return from_string<T>(value.c_str());
|
||||
inline T from_string(const std::string& value, bool force_hex = false) {
|
||||
return from_string<T>(value.c_str(), force_hex);
|
||||
}
|
||||
|
||||
} // namespace string_util
|
||||
|
|
|
@ -7,13 +7,12 @@
|
|||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/x64_context.h"
|
||||
#include "xenia/base/x64_context.h"
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
// NOTE: this order matches 1:1 with the X64Register enum.
|
||||
static const char* kRegisterNames[] = {
|
||||
|
@ -28,25 +27,26 @@ const char* X64Context::GetRegisterName(X64Register reg) {
|
|||
return kRegisterNames[static_cast<int>(reg)];
|
||||
}
|
||||
|
||||
std::string X64Context::GetStringFromValue(X64Register reg) const {
|
||||
std::string X64Context::GetStringFromValue(X64Register reg, bool hex) const {
|
||||
switch (reg) {
|
||||
case X64Register::kRip:
|
||||
return string_util::to_hex_string(rip);
|
||||
return hex ? string_util::to_hex_string(rip) : std::to_string(rip);
|
||||
case X64Register::kEflags:
|
||||
return string_util::to_hex_string(eflags);
|
||||
return hex ? string_util::to_hex_string(eflags) : std::to_string(eflags);
|
||||
default:
|
||||
if (static_cast<int>(reg) >= static_cast<int>(X64Register::kRax) &&
|
||||
static_cast<int>(reg) <= static_cast<int>(X64Register::kR15)) {
|
||||
return string_util::to_hex_string(
|
||||
int_registers.values[static_cast<int>(reg) -
|
||||
static_cast<int>(X64Register::kRax)]);
|
||||
auto value = int_registers[static_cast<int>(reg) -
|
||||
static_cast<int>(X64Register::kRax)];
|
||||
return hex ? string_util::to_hex_string(value) : std::to_string(value);
|
||||
} else if (static_cast<int>(reg) >=
|
||||
static_cast<int>(X64Register::kXmm0) &&
|
||||
static_cast<int>(reg) <=
|
||||
static_cast<int>(X64Register::kXmm15)) {
|
||||
return string_util::to_hex_string(
|
||||
xmm_registers.values[static_cast<int>(reg) -
|
||||
static_cast<int>(X64Register::kXmm0)]);
|
||||
auto value = xmm_registers[static_cast<int>(reg) -
|
||||
static_cast<int>(X64Register::kXmm0)];
|
||||
return hex ? string_util::to_hex_string(value)
|
||||
: string_util::to_string(value);
|
||||
} else {
|
||||
assert_unhandled_case(reg);
|
||||
return "";
|
||||
|
@ -54,10 +54,10 @@ std::string X64Context::GetStringFromValue(X64Register reg) const {
|
|||
}
|
||||
}
|
||||
|
||||
void X64Context::SetValueFromString(X64Register reg, std::string value) {
|
||||
void X64Context::SetValueFromString(X64Register reg, std::string value,
|
||||
bool hex) {
|
||||
// TODO(benvanik): set value from string.
|
||||
assert_always(false);
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
|
@ -7,14 +7,13 @@
|
|||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_CPU_X64_CONTEXT_H_
|
||||
#define XENIA_CPU_X64_CONTEXT_H_
|
||||
#ifndef XENIA_BASE_X64_CONTEXT_H_
|
||||
#define XENIA_BASE_X64_CONTEXT_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
enum class X64Register {
|
||||
// NOTE: this order matches 1:1 with the order in the X64Context.
|
||||
|
@ -55,8 +54,8 @@ enum class X64Register {
|
|||
kXmm15,
|
||||
};
|
||||
|
||||
struct X64Context {
|
||||
// TODO(benvanik): x64 registers.
|
||||
class X64Context {
|
||||
public:
|
||||
uint64_t rip;
|
||||
uint32_t eflags;
|
||||
union {
|
||||
|
@ -78,8 +77,9 @@ struct X64Context {
|
|||
uint64_t r14;
|
||||
uint64_t r15;
|
||||
};
|
||||
uint64_t values[16];
|
||||
} int_registers;
|
||||
uint64_t int_registers[16];
|
||||
};
|
||||
|
||||
union {
|
||||
struct {
|
||||
__m128 xmm0;
|
||||
|
@ -99,15 +99,14 @@ struct X64Context {
|
|||
__m128 xmm14;
|
||||
__m128 xmm15;
|
||||
};
|
||||
__m128 values[16];
|
||||
} xmm_registers;
|
||||
__m128 xmm_registers[16];
|
||||
};
|
||||
|
||||
static const char* GetRegisterName(X64Register reg);
|
||||
std::string GetStringFromValue(X64Register reg) const;
|
||||
void SetValueFromString(X64Register reg, std::string value);
|
||||
std::string GetStringFromValue(X64Register reg, bool hex) const;
|
||||
void SetValueFromString(X64Register reg, std::string value, bool hex);
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_X64_CONTEXT_H_
|
||||
#endif // XENIA_BASE_X64_CONTEXT_H_
|
|
@ -38,6 +38,8 @@ DEFINE_bool(enable_debugprint_log, false,
|
|||
"Log debugprint traps to the active debugger");
|
||||
DEFINE_bool(ignore_undefined_externs, true,
|
||||
"Don't exit when an undefined extern is called.");
|
||||
DEFINE_bool(emit_source_annotations, false,
|
||||
"Add extra movs and nops to make disassembly easier to read.");
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
@ -239,7 +241,7 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t* out_stack_size) {
|
|||
add(rsp, (uint32_t)stack_size);
|
||||
ret();
|
||||
|
||||
if (FLAGS_debug) {
|
||||
if (FLAGS_emit_source_annotations) {
|
||||
nop();
|
||||
nop();
|
||||
nop();
|
||||
|
@ -254,9 +256,9 @@ void X64Emitter::MarkSourceOffset(const Instr* i) {
|
|||
auto entry = source_map_arena_.Alloc<SourceMapEntry>();
|
||||
entry->source_offset = static_cast<uint32_t>(i->src1.offset);
|
||||
entry->hir_offset = uint32_t(i->block->ordinal << 16) | i->ordinal;
|
||||
entry->code_offset = static_cast<uint32_t>(getSize() + 1);
|
||||
entry->code_offset = static_cast<uint32_t>(getSize());
|
||||
|
||||
if (FLAGS_debug) {
|
||||
if (FLAGS_emit_source_annotations) {
|
||||
nop();
|
||||
nop();
|
||||
mov(eax, entry->source_offset);
|
||||
|
|
|
@ -15,50 +15,84 @@
|
|||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
ExportResolver::Table::Table(const char* module_name,
|
||||
const std::vector<Export*>* exports_by_ordinal)
|
||||
: exports_by_ordinal_(exports_by_ordinal) {
|
||||
auto dot_pos = std::strrchr(module_name, '.');
|
||||
if (dot_pos != nullptr) {
|
||||
std::strncpy(module_name_, module_name,
|
||||
static_cast<size_t>(dot_pos - module_name));
|
||||
} else {
|
||||
std::strncpy(module_name_, module_name, xe::countof(module_name_) - 1);
|
||||
}
|
||||
|
||||
exports_by_name_.reserve(exports_by_ordinal_->size());
|
||||
for (size_t i = 0; i < exports_by_ordinal_->size(); ++i) {
|
||||
auto export = exports_by_ordinal_->at(i);
|
||||
if (export) {
|
||||
exports_by_name_.push_back(export);
|
||||
}
|
||||
}
|
||||
std::sort(
|
||||
exports_by_name_.begin(), exports_by_name_.end(),
|
||||
[](Export* a, Export* b) { return std::strcmp(a->name, b->name) <= 0; });
|
||||
}
|
||||
|
||||
ExportResolver::ExportResolver() = default;
|
||||
|
||||
ExportResolver::~ExportResolver() = default;
|
||||
|
||||
void ExportResolver::RegisterTable(
|
||||
const std::string& library_name,
|
||||
const std::vector<xe::cpu::Export*>* exports) {
|
||||
tables_.emplace_back(library_name, exports);
|
||||
const char* module_name, const std::vector<xe::cpu::Export*>* exports) {
|
||||
tables_.emplace_back(module_name, exports);
|
||||
|
||||
all_exports_by_name_.reserve(all_exports_by_name_.size() + exports->size());
|
||||
for (size_t i = 0; i < exports->size(); ++i) {
|
||||
auto export = exports->at(i);
|
||||
if (export) {
|
||||
all_exports_by_name_.push_back(export);
|
||||
}
|
||||
}
|
||||
std::sort(
|
||||
all_exports_by_name_.begin(), all_exports_by_name_.end(),
|
||||
[](Export* a, Export* b) { return std::strcmp(a->name, b->name) <= 0; });
|
||||
}
|
||||
|
||||
Export* ExportResolver::GetExportByOrdinal(const std::string& library_name,
|
||||
Export* ExportResolver::GetExportByOrdinal(const char* module_name,
|
||||
uint16_t ordinal) {
|
||||
for (const auto& table : tables_) {
|
||||
if (table.name == library_name || table.simple_name == library_name) {
|
||||
if (ordinal > table.exports->size()) {
|
||||
if (std::strncmp(module_name, table.module_name(),
|
||||
std::strlen(table.module_name())) == 0) {
|
||||
if (ordinal > table.exports_by_ordinal().size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return table.exports->at(ordinal);
|
||||
return table.exports_by_ordinal().at(ordinal);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ExportResolver::SetVariableMapping(const std::string& library_name,
|
||||
void ExportResolver::SetVariableMapping(const char* module_name,
|
||||
uint16_t ordinal, uint32_t value) {
|
||||
auto export_entry = GetExportByOrdinal(library_name, ordinal);
|
||||
auto export_entry = GetExportByOrdinal(module_name, ordinal);
|
||||
assert_not_null(export_entry);
|
||||
export_entry->tags |= ExportTag::kImplemented;
|
||||
export_entry->variable_ptr = value;
|
||||
}
|
||||
|
||||
void ExportResolver::SetFunctionMapping(const std::string& library_name,
|
||||
void ExportResolver::SetFunctionMapping(const char* module_name,
|
||||
uint16_t ordinal,
|
||||
xe_kernel_export_shim_fn shim) {
|
||||
auto export_entry = GetExportByOrdinal(library_name, ordinal);
|
||||
auto export_entry = GetExportByOrdinal(module_name, ordinal);
|
||||
assert_not_null(export_entry);
|
||||
export_entry->tags |= ExportTag::kImplemented;
|
||||
export_entry->function_data.shim = shim;
|
||||
}
|
||||
|
||||
void ExportResolver::SetFunctionMapping(const std::string& library_name,
|
||||
void ExportResolver::SetFunctionMapping(const char* module_name,
|
||||
uint16_t ordinal,
|
||||
ExportTrampoline trampoline) {
|
||||
auto export_entry = GetExportByOrdinal(library_name, ordinal);
|
||||
auto export_entry = GetExportByOrdinal(module_name, ordinal);
|
||||
assert_not_null(export_entry);
|
||||
export_entry->tags |= ExportTag::kImplemented;
|
||||
export_entry->function_data.trampoline = trampoline;
|
||||
|
|
|
@ -99,37 +99,46 @@ class Export {
|
|||
|
||||
class ExportResolver {
|
||||
public:
|
||||
class Table {
|
||||
public:
|
||||
Table(const char* module_name, const std::vector<Export*>* exports);
|
||||
|
||||
const char* module_name() const { return module_name_; }
|
||||
const std::vector<Export*>& exports_by_ordinal() const {
|
||||
return *exports_by_ordinal_;
|
||||
}
|
||||
const std::vector<Export*>& exports_by_name() const {
|
||||
return exports_by_name_;
|
||||
}
|
||||
|
||||
private:
|
||||
char module_name_[32] = {0};
|
||||
const std::vector<Export*>* exports_by_ordinal_ = nullptr;
|
||||
std::vector<Export*> exports_by_name_;
|
||||
};
|
||||
|
||||
ExportResolver();
|
||||
~ExportResolver();
|
||||
|
||||
void RegisterTable(const std::string& library_name,
|
||||
void RegisterTable(const char* module_name,
|
||||
const std::vector<Export*>* exports);
|
||||
const std::vector<Table>& tables() const { return tables_; }
|
||||
const std::vector<Export*>& all_exports_by_name() const {
|
||||
return all_exports_by_name_;
|
||||
}
|
||||
|
||||
Export* GetExportByOrdinal(const std::string& library_name, uint16_t ordinal);
|
||||
Export* GetExportByOrdinal(const char* module_name, uint16_t ordinal);
|
||||
|
||||
void SetVariableMapping(const std::string& library_name, uint16_t ordinal,
|
||||
void SetVariableMapping(const char* module_name, uint16_t ordinal,
|
||||
uint32_t value);
|
||||
void SetFunctionMapping(const std::string& library_name, uint16_t ordinal,
|
||||
void SetFunctionMapping(const char* module_name, uint16_t ordinal,
|
||||
xe_kernel_export_shim_fn shim);
|
||||
void SetFunctionMapping(const std::string& library_name, uint16_t ordinal,
|
||||
void SetFunctionMapping(const char* module_name, uint16_t ordinal,
|
||||
ExportTrampoline trampoline);
|
||||
|
||||
private:
|
||||
struct ExportTable {
|
||||
std::string name;
|
||||
std::string simple_name; // without extension
|
||||
const std::vector<Export*>* exports;
|
||||
ExportTable(const std::string& name, const std::vector<Export*>* exports)
|
||||
: name(name), exports(exports) {
|
||||
auto dot_pos = name.find_last_of('.');
|
||||
if (dot_pos != std::string::npos) {
|
||||
simple_name = name.substr(0, dot_pos);
|
||||
} else {
|
||||
simple_name = name;
|
||||
}
|
||||
}
|
||||
};
|
||||
std::vector<ExportTable> tables_;
|
||||
std::vector<Table> tables_;
|
||||
std::vector<Export*> all_exports_by_name_;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -16,8 +16,6 @@
|
|||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
using xe::debug::Breakpoint;
|
||||
|
||||
Function::Function(Module* module, uint32_t address)
|
||||
: Symbol(Symbol::Type::kFunction, module, address) {}
|
||||
|
||||
|
@ -100,6 +98,18 @@ const SourceMapEntry* GuestFunction::LookupCodeOffset(uint32_t offset) const {
|
|||
return source_map_.empty() ? nullptr : &source_map_[0];
|
||||
}
|
||||
|
||||
uintptr_t GuestFunction::MapSourceToCode(uint32_t source_address) const {
|
||||
auto entry = LookupSourceOffset(source_address - address());
|
||||
return entry ? entry->code_offset
|
||||
: reinterpret_cast<uintptr_t>(machine_code());
|
||||
}
|
||||
|
||||
uint32_t GuestFunction::MapCodeToSource(uintptr_t host_address) const {
|
||||
auto entry = LookupCodeOffset(static_cast<uint32_t>(
|
||||
host_address - reinterpret_cast<uintptr_t>(machine_code())));
|
||||
return entry ? entry->source_offset : address();
|
||||
}
|
||||
|
||||
bool GuestFunction::Call(ThreadState* thread_state, uint32_t return_address) {
|
||||
// SCOPE_profile_cpu_f("cpu");
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/byte_order.h"
|
||||
#include "xenia/base/exception_handler.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/memory.h"
|
||||
|
@ -20,33 +21,28 @@ namespace cpu {
|
|||
|
||||
MMIOHandler* MMIOHandler::global_handler_ = nullptr;
|
||||
|
||||
// Implemented in the platform cc file.
|
||||
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* virtual_membase,
|
||||
uint8_t* physical_membase);
|
||||
|
||||
std::unique_ptr<MMIOHandler> MMIOHandler::Install(uint8_t* virtual_membase,
|
||||
uint8_t* physical_membase,
|
||||
uint8_t* memory_end) {
|
||||
uint8_t* membase_end) {
|
||||
// There can be only one handler at a time.
|
||||
assert_null(global_handler_);
|
||||
if (global_handler_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Create the platform-specific handler.
|
||||
auto handler = CreateMMIOHandler(virtual_membase, physical_membase);
|
||||
auto handler = std::unique_ptr<MMIOHandler>(
|
||||
new MMIOHandler(virtual_membase, physical_membase, membase_end));
|
||||
|
||||
// Platform-specific initialization for the handler.
|
||||
if (!handler->Initialize()) {
|
||||
return nullptr;
|
||||
}
|
||||
// Install the exception handler directed at the MMIOHandler.
|
||||
ExceptionHandler::Install(ExceptionCallbackThunk, handler.get());
|
||||
|
||||
handler->memory_end_ = memory_end;
|
||||
global_handler_ = handler.get();
|
||||
return handler;
|
||||
}
|
||||
|
||||
MMIOHandler::~MMIOHandler() {
|
||||
ExceptionHandler::Uninstall(ExceptionCallbackThunk, this);
|
||||
|
||||
assert_true(global_handler_ == this);
|
||||
global_handler_ = nullptr;
|
||||
}
|
||||
|
@ -166,7 +162,8 @@ void MMIOHandler::CancelWriteWatch(uintptr_t watch_handle) {
|
|||
delete entry;
|
||||
}
|
||||
|
||||
bool MMIOHandler::CheckWriteWatch(void* thread_state, uint64_t fault_address) {
|
||||
bool MMIOHandler::CheckWriteWatch(X64Context* thread_context,
|
||||
uint64_t fault_address) {
|
||||
uint32_t physical_address = uint32_t(fault_address);
|
||||
if (physical_address > 0x1FFFFFFF) {
|
||||
physical_address &= 0x1FFFFFFF;
|
||||
|
@ -364,10 +361,16 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool MMIOHandler::HandleAccessFault(void* thread_state,
|
||||
uint64_t fault_address) {
|
||||
if (fault_address < uint64_t(virtual_membase_) ||
|
||||
fault_address > uint64_t(memory_end_)) {
|
||||
bool MMIOHandler::ExceptionCallbackThunk(Exception* ex, void* data) {
|
||||
return reinterpret_cast<MMIOHandler*>(data)->ExceptionCallback(ex);
|
||||
}
|
||||
|
||||
bool MMIOHandler::ExceptionCallback(Exception* ex) {
|
||||
if (ex->code() != Exception::Code::kAccessViolation) {
|
||||
return false;
|
||||
}
|
||||
if (ex->fault_address() < uint64_t(virtual_membase_) ||
|
||||
ex->fault_address() > uint64_t(memory_end_)) {
|
||||
// Quick kill anything outside our mapping.
|
||||
return false;
|
||||
}
|
||||
|
@ -375,9 +378,10 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
|
|||
// Access violations are pretty rare, so we can do a linear search here.
|
||||
// Only check if in the virtual range, as we only support virtual ranges.
|
||||
const MMIORange* range = nullptr;
|
||||
if (fault_address < uint64_t(physical_membase_)) {
|
||||
if (ex->fault_address() < uint64_t(physical_membase_)) {
|
||||
for (const auto& test_range : mapped_ranges_) {
|
||||
if ((uint32_t(fault_address) & test_range.mask) == test_range.address) {
|
||||
if ((static_cast<uint32_t>(ex->fault_address()) & test_range.mask) ==
|
||||
test_range.address) {
|
||||
// Address is within the range of this mapping.
|
||||
range = &test_range;
|
||||
break;
|
||||
|
@ -387,10 +391,10 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
|
|||
if (!range) {
|
||||
// Access is not found within any range, so fail and let the caller handle
|
||||
// it (likely by aborting).
|
||||
return CheckWriteWatch(thread_state, fault_address);
|
||||
return CheckWriteWatch(ex->thread_context(), ex->fault_address());
|
||||
}
|
||||
|
||||
auto rip = GetThreadStateRip(thread_state);
|
||||
auto rip = ex->pc();
|
||||
auto p = reinterpret_cast<const uint8_t*>(rip);
|
||||
DecodedMov mov = {0};
|
||||
bool decoded = TryDecodeMov(p, &mov);
|
||||
|
@ -404,8 +408,8 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
|
|||
// Load of a memory value - read from range, swap, and store in the
|
||||
// register.
|
||||
uint32_t value = range->read(nullptr, range->callback_context,
|
||||
fault_address & 0xFFFFFFFF);
|
||||
uint64_t* reg_ptr = GetThreadStateRegPtr(thread_state, mov.value_reg);
|
||||
static_cast<uint32_t>(ex->fault_address()));
|
||||
uint64_t* reg_ptr = &ex->thread_context()->int_registers[mov.value_reg];
|
||||
if (!mov.byte_swap) {
|
||||
// We swap only if it's not a movbe, as otherwise we are swapping twice.
|
||||
value = xe::byte_swap(value);
|
||||
|
@ -417,19 +421,19 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
|
|||
if (mov.is_constant) {
|
||||
value = uint32_t(mov.constant);
|
||||
} else {
|
||||
uint64_t* reg_ptr = GetThreadStateRegPtr(thread_state, mov.value_reg);
|
||||
uint64_t* reg_ptr = &ex->thread_context()->int_registers[mov.value_reg];
|
||||
value = static_cast<uint32_t>(*reg_ptr);
|
||||
if (!mov.byte_swap) {
|
||||
// We swap only if it's not a movbe, as otherwise we are swapping twice.
|
||||
value = xe::byte_swap(static_cast<uint32_t>(value));
|
||||
}
|
||||
}
|
||||
range->write(nullptr, range->callback_context, fault_address & 0xFFFFFFFF,
|
||||
value);
|
||||
range->write(nullptr, range->callback_context,
|
||||
static_cast<uint32_t>(ex->fault_address()), value);
|
||||
}
|
||||
|
||||
// Advance RIP to the next instruction so that we resume properly.
|
||||
SetThreadStateRip(thread_state, rip + mov.length);
|
||||
ex->set_resume_pc(rip + mov.length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -1,237 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/mmio_handler.h"
|
||||
|
||||
#include <mach/mach.h>
|
||||
#include <signal.h>
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
|
||||
// Mach internal function, not defined in any header.
|
||||
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/exc_server.html
|
||||
extern "C" boolean_t exc_server(mach_msg_header_t* request_msg,
|
||||
mach_msg_header_t* reply_msg);
|
||||
|
||||
// Exported for the kernel to call back into.
|
||||
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/catch_exception_raise.html
|
||||
extern "C" kern_return_t catch_exception_raise(
|
||||
mach_port_t exception_port, mach_port_t thread, mach_port_t task,
|
||||
exception_type_t exception, exception_data_t code,
|
||||
mach_msg_type_number_t code_count);
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
class MachMMIOHandler : public MMIOHandler {
|
||||
public:
|
||||
explicit MachMMIOHandler(uint8_t* mapping_base);
|
||||
~MachMMIOHandler() override;
|
||||
|
||||
protected:
|
||||
bool Initialize() override;
|
||||
|
||||
uint64_t GetThreadStateRip(void* thread_state_ptr) override;
|
||||
void SetThreadStateRip(void* thread_state_ptr, uint64_t rip) override;
|
||||
uint64_t* GetThreadStateRegPtr(void* thread_state_ptr,
|
||||
int32_t be_reg_index) override;
|
||||
|
||||
private:
|
||||
void ThreadEntry();
|
||||
|
||||
// Worker thread processing exceptions.
|
||||
std::unique_ptr<std::thread> thread_;
|
||||
// Port listening for exceptions on the worker thread.
|
||||
mach_port_t listen_port_;
|
||||
};
|
||||
|
||||
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* mapping_base) {
|
||||
return std::make_unique<MachMMIOHandler>(mapping_base);
|
||||
}
|
||||
|
||||
MachMMIOHandler::MachMMIOHandler(uint8_t* mapping_base)
|
||||
: MMIOHandler(mapping_base), listen_port_(0) {}
|
||||
|
||||
bool MachMMIOHandler::Initialize() {
|
||||
// Allocates the port that listens for exceptions.
|
||||
// This will be freed in the dtor.
|
||||
if (mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE,
|
||||
&listen_port_) != KERN_SUCCESS) {
|
||||
XELOGE("Unable to allocate listen port");
|
||||
return false;
|
||||
}
|
||||
|
||||
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/mach_port_insert_right.html
|
||||
if (mach_port_insert_right(mach_task_self(), listen_port_, listen_port_,
|
||||
MACH_MSG_TYPE_MAKE_SEND) != KERN_SUCCESS) {
|
||||
XELOGE("Unable to insert listen port right");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sets our exception filter so that any BAD_ACCESS exceptions go to it.
|
||||
// http://web.mit.edu/darwin/src/modules/xnu/osfmk/man/task_set_exception_ports.html
|
||||
if (task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS,
|
||||
listen_port_, EXCEPTION_DEFAULT,
|
||||
MACHINE_THREAD_STATE) != KERN_SUCCESS) {
|
||||
XELOGE("Unable to set exception port");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Spin up the worker thread.
|
||||
std::unique_ptr<std::thread> thread(
|
||||
new std::thread([this]() { ThreadEntry(); }));
|
||||
thread->detach();
|
||||
thread_ = std::move(thread);
|
||||
return true;
|
||||
}
|
||||
|
||||
MachMMIOHandler::~MachMMIOHandler() {
|
||||
task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, 0,
|
||||
EXCEPTION_DEFAULT, 0);
|
||||
mach_port_deallocate(mach_task_self(), listen_port_);
|
||||
}
|
||||
|
||||
void MachMMIOHandler::ThreadEntry() {
|
||||
while (true) {
|
||||
struct {
|
||||
mach_msg_header_t head;
|
||||
mach_msg_body_t msgh_body;
|
||||
char data[1024];
|
||||
} msg;
|
||||
struct {
|
||||
mach_msg_header_t head;
|
||||
char data[1024];
|
||||
} reply;
|
||||
|
||||
// Wait for a message on the exception port.
|
||||
mach_msg_return_t ret =
|
||||
mach_msg(&msg.head, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(msg),
|
||||
listen_port_, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
||||
if (ret != MACH_MSG_SUCCESS) {
|
||||
XELOGE("mach_msg receive failed with %d %s", ret, mach_error_string(ret));
|
||||
xe::debugging::Break();
|
||||
break;
|
||||
}
|
||||
|
||||
// Call exc_server, which will dispatch the catch_exception_raise.
|
||||
exc_server(&msg.head, &reply.head);
|
||||
|
||||
// Send the reply.
|
||||
if (mach_msg(&reply.head, MACH_SEND_MSG, reply.head.msgh_size, 0,
|
||||
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL) != MACH_MSG_SUCCESS) {
|
||||
XELOGE("mach_msg reply send failed");
|
||||
xe::debugging::Break();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Kills the app when a bad access exception is unhandled.
|
||||
void FailBadAccess() {
|
||||
raise(SIGSEGV);
|
||||
abort();
|
||||
}
|
||||
|
||||
kern_return_t CatchExceptionRaise(mach_port_t thread) {
|
||||
auto state_count = x86_EXCEPTION_STATE64_COUNT;
|
||||
x86_exception_state64_t exc_state;
|
||||
if (thread_get_state(thread, x86_EXCEPTION_STATE64,
|
||||
reinterpret_cast<thread_state_t>(&exc_state),
|
||||
&state_count) != KERN_SUCCESS) {
|
||||
XELOGE("thread_get_state failed to get exception state");
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
state_count = x86_THREAD_STATE64_COUNT;
|
||||
x86_thread_state64_t thread_state;
|
||||
if (thread_get_state(thread, x86_THREAD_STATE64,
|
||||
reinterpret_cast<thread_state_t>(&thread_state),
|
||||
&state_count) != KERN_SUCCESS) {
|
||||
XELOGE("thread_get_state failed to get thread state");
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
|
||||
auto fault_address = exc_state.__faultvaddr;
|
||||
auto mmio_handler =
|
||||
static_cast<MachMMIOHandler*>(MMIOHandler::global_handler());
|
||||
bool handled = mmio_handler->HandleAccessFault(&thread_state, fault_address);
|
||||
if (!handled) {
|
||||
// Unhandled - raise to the system.
|
||||
XELOGE("MMIO unhandled bad access for %llx, bubbling", fault_address);
|
||||
// TODO(benvanik): manipulate stack so that we can rip = break_handler or
|
||||
// something and have the stack trace be valid.
|
||||
xe::debugging::Break();
|
||||
|
||||
// When the thread resumes, kill it.
|
||||
thread_state.__rip = reinterpret_cast<uint64_t>(FailBadAccess);
|
||||
|
||||
// Mach docs say we can return this to continue searching for handlers, but
|
||||
// OSX doesn't seem to have it.
|
||||
// return MIG_DESTROY_REQUEST;
|
||||
}
|
||||
|
||||
// Set the thread state - as we've likely changed it.
|
||||
if (thread_set_state(thread, x86_THREAD_STATE64,
|
||||
reinterpret_cast<thread_state_t>(&thread_state),
|
||||
state_count) != KERN_SUCCESS) {
|
||||
XELOGE("thread_set_state failed to set thread state for continue");
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
uint64_t MachMMIOHandler::GetThreadStateRip(void* thread_state_ptr) {
|
||||
auto thread_state = reinterpret_cast<x86_thread_state64_t*>(thread_state_ptr);
|
||||
return thread_state->__rip;
|
||||
}
|
||||
|
||||
void MachMMIOHandler::SetThreadStateRip(void* thread_state_ptr, uint64_t rip) {
|
||||
auto thread_state = reinterpret_cast<x86_thread_state64_t*>(thread_state_ptr);
|
||||
thread_state->__rip = rip;
|
||||
}
|
||||
|
||||
uint64_t* MachMMIOHandler::GetThreadStateRegPtr(void* thread_state_ptr,
|
||||
int32_t be_reg_index) {
|
||||
// Map from BeaEngine register order to x86_thread_state64 order.
|
||||
static const uint32_t mapping[] = {
|
||||
0, // REG0 / RAX -> 0
|
||||
2, // REG1 / RCX -> 2
|
||||
3, // REG2 / RDX -> 3
|
||||
1, // REG3 / RBX -> 1
|
||||
7, // REG4 / RSP -> 7
|
||||
6, // REG5 / RBP -> 6
|
||||
5, // REG6 / RSI -> 5
|
||||
4, // REG7 / RDI -> 4
|
||||
8, // REG8 / R8 -> 8
|
||||
9, // REG9 / R9 -> 9
|
||||
10, // REG10 / R10 -> 10
|
||||
11, // REG11 / R11 -> 11
|
||||
12, // REG12 / R12 -> 12
|
||||
13, // REG13 / R13 -> 13
|
||||
14, // REG14 / R14 -> 14
|
||||
15, // REG15 / R15 -> 15
|
||||
};
|
||||
auto thread_state = reinterpret_cast<x86_thread_state64_t*>(thread_state_ptr);
|
||||
return &thread_state->__rax + mapping[be_reg_index];
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
// Exported and called by exc_server.
|
||||
extern "C" kern_return_t catch_exception_raise(
|
||||
mach_port_t exception_port, mach_port_t thread, mach_port_t task,
|
||||
exception_type_t exception, exception_data_t code,
|
||||
mach_msg_type_number_t code_count) {
|
||||
// We get/set the states manually instead of using catch_exception_raise_state
|
||||
// variant because that truncates everything to 32bit.
|
||||
return xe::cpu::CatchExceptionRaise(thread);
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/mmio_handler.h"
|
||||
|
||||
#include "xenia/base/platform_win.h"
|
||||
#include "xenia/profiling.h"
|
||||
|
||||
namespace xe {
|
||||
void CrashDump();
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
LONG CALLBACK MMIOExceptionHandler(PEXCEPTION_POINTERS ex_info);
|
||||
|
||||
class WinMMIOHandler : public MMIOHandler {
|
||||
public:
|
||||
WinMMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase)
|
||||
: MMIOHandler(virtual_membase, physical_membase) {}
|
||||
~WinMMIOHandler() override;
|
||||
|
||||
protected:
|
||||
bool Initialize() override;
|
||||
|
||||
uint64_t GetThreadStateRip(void* thread_state_ptr) override;
|
||||
void SetThreadStateRip(void* thread_state_ptr, uint64_t rip) override;
|
||||
uint64_t* GetThreadStateRegPtr(void* thread_state_ptr,
|
||||
int32_t reg_index) override;
|
||||
};
|
||||
|
||||
std::unique_ptr<MMIOHandler> CreateMMIOHandler(uint8_t* virtual_membase,
|
||||
uint8_t* physical_membase) {
|
||||
return std::make_unique<WinMMIOHandler>(virtual_membase, physical_membase);
|
||||
}
|
||||
|
||||
bool WinMMIOHandler::Initialize() {
|
||||
// If there is a debugger attached the normal exception handler will not
|
||||
// fire and we must instead add the continue handler.
|
||||
AddVectoredExceptionHandler(1, MMIOExceptionHandler);
|
||||
if (IsDebuggerPresent()) {
|
||||
// TODO(benvanik): is this really required?
|
||||
// AddVectoredContinueHandler(1, MMIOExceptionHandler);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
WinMMIOHandler::~WinMMIOHandler() {
|
||||
// Remove exception handlers.
|
||||
RemoveVectoredExceptionHandler(MMIOExceptionHandler);
|
||||
RemoveVectoredContinueHandler(MMIOExceptionHandler);
|
||||
}
|
||||
|
||||
// Handles potential accesses to mmio. We look for access violations to
|
||||
// addresses in our range and call into the registered handlers, if any.
|
||||
// If there are none, we continue.
|
||||
LONG CALLBACK MMIOExceptionHandler(PEXCEPTION_POINTERS ex_info) {
|
||||
// Fast path for SetThreadName.
|
||||
if (ex_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
// Disabled because this can cause odd issues during handling.
|
||||
// SCOPE_profile_cpu_i("cpu", "MMIOExceptionHandler");
|
||||
|
||||
// http://msdn.microsoft.com/en-us/library/ms679331(v=vs.85).aspx
|
||||
// http://msdn.microsoft.com/en-us/library/aa363082(v=vs.85).aspx
|
||||
auto code = ex_info->ExceptionRecord->ExceptionCode;
|
||||
if (code == STATUS_ACCESS_VIOLATION) {
|
||||
auto fault_address = ex_info->ExceptionRecord->ExceptionInformation[1];
|
||||
if (MMIOHandler::global_handler()->HandleAccessFault(ex_info->ContextRecord,
|
||||
fault_address)) {
|
||||
// Handled successfully - RIP has been updated and we can continue.
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
} else {
|
||||
// Failed to handle; continue search for a handler (and die if no other
|
||||
// handler is found).
|
||||
xe::CrashDump();
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
}
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
uint64_t WinMMIOHandler::GetThreadStateRip(void* thread_state_ptr) {
|
||||
auto context = reinterpret_cast<LPCONTEXT>(thread_state_ptr);
|
||||
return context->Rip;
|
||||
}
|
||||
|
||||
void WinMMIOHandler::SetThreadStateRip(void* thread_state_ptr, uint64_t rip) {
|
||||
auto context = reinterpret_cast<LPCONTEXT>(thread_state_ptr);
|
||||
context->Rip = rip;
|
||||
}
|
||||
|
||||
uint64_t* WinMMIOHandler::GetThreadStateRegPtr(void* thread_state_ptr,
|
||||
int32_t reg_index) {
|
||||
auto context = reinterpret_cast<LPCONTEXT>(thread_state_ptr);
|
||||
// Register indices line up with the CONTEXT structure format.
|
||||
return &context->Rax + reg_index;
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
|
@ -13,8 +13,8 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/base/x64_context.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
#include "xenia/cpu/x64_context.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
@ -82,6 +82,7 @@ class StackWalker {
|
|||
virtual size_t CaptureStackTrace(void* thread_handle,
|
||||
uint64_t* frame_host_pcs,
|
||||
size_t frame_offset, size_t frame_count,
|
||||
const X64Context* in_host_context,
|
||||
X64Context* out_host_context,
|
||||
uint64_t* out_stack_hash = nullptr) = 0;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/breakpoint.h"
|
||||
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/cpu/backend/code_cache.h"
|
||||
#include "xenia/debug/debugger.h"
|
||||
#include "xenia/emulator.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
void CodeBreakpoint::ForEachHostAddress(
|
||||
std::function<void(uintptr_t)> callback) const {
|
||||
auto processor = debugger_->emulator()->processor();
|
||||
if (address_type_ == AddressType::kGuest) {
|
||||
auto guest_address = this->guest_address();
|
||||
|
||||
// Lookup all functions that contain this guest address and patch them.
|
||||
auto functions = processor->FindFunctionsWithAddress(guest_address);
|
||||
|
||||
if (functions.empty()) {
|
||||
// If function does not exist demand it, as we need someplace to put our
|
||||
// breakpoint. Note that this follows the same resolution rules as the
|
||||
// JIT, so what's returned is the function the JIT would have jumped to.
|
||||
auto fn = processor->ResolveFunction(guest_address);
|
||||
if (!fn) {
|
||||
// TODO(benvanik): error out better with 'invalid breakpoint'?
|
||||
assert_not_null(fn);
|
||||
return;
|
||||
}
|
||||
functions.push_back(fn);
|
||||
}
|
||||
assert_false(functions.empty());
|
||||
|
||||
for (auto function : functions) {
|
||||
// TODO(benvanik): other function types.
|
||||
assert_true(function->is_guest());
|
||||
auto guest_function = reinterpret_cast<cpu::GuestFunction*>(function);
|
||||
uintptr_t host_address = guest_function->MapSourceToCode(guest_address);
|
||||
assert_not_zero(host_address);
|
||||
callback(host_address);
|
||||
}
|
||||
} else {
|
||||
// Direct host address patching.
|
||||
callback(host_address());
|
||||
}
|
||||
}
|
||||
|
||||
xe::cpu::GuestFunction* CodeBreakpoint::guest_function() const {
|
||||
auto processor = debugger_->emulator()->processor();
|
||||
if (address_type_ == AddressType::kGuest) {
|
||||
auto functions = processor->FindFunctionsWithAddress(guest_address());
|
||||
if (functions.empty()) {
|
||||
return nullptr;
|
||||
} else if (functions[0]->is_guest()) {
|
||||
return static_cast<xe::cpu::GuestFunction*>(functions[0]);
|
||||
}
|
||||
return nullptr;
|
||||
} else {
|
||||
return processor->backend()->code_cache()->LookupFunction(host_address());
|
||||
}
|
||||
}
|
||||
|
||||
bool CodeBreakpoint::ContainsHostAddress(uintptr_t search_address) const {
|
||||
bool contains = false;
|
||||
ForEachHostAddress([&contains, search_address](uintptr_t host_address) {
|
||||
if (host_address == search_address) {
|
||||
contains = true;
|
||||
}
|
||||
});
|
||||
return contains;
|
||||
}
|
||||
|
||||
void CodeBreakpoint::Install() {
|
||||
Breakpoint::Install();
|
||||
|
||||
assert_true(patches_.empty());
|
||||
ForEachHostAddress([this](uintptr_t host_address) {
|
||||
patches_.emplace_back(host_address, PatchAddress(host_address));
|
||||
});
|
||||
}
|
||||
|
||||
void CodeBreakpoint::Uninstall() {
|
||||
// Simply unpatch all locations we patched when installing.
|
||||
for (auto& location : patches_) {
|
||||
UnpatchAddress(location.first, location.second);
|
||||
}
|
||||
patches_.clear();
|
||||
|
||||
Breakpoint::Uninstall();
|
||||
}
|
||||
|
||||
uint16_t CodeBreakpoint::PatchAddress(uintptr_t host_address) {
|
||||
auto ptr = reinterpret_cast<void*>(host_address);
|
||||
auto original_bytes = xe::load_and_swap<uint16_t>(ptr);
|
||||
assert_true(original_bytes != 0x0F0B);
|
||||
xe::store_and_swap<uint16_t>(ptr, 0x0F0B);
|
||||
return original_bytes;
|
||||
}
|
||||
|
||||
void CodeBreakpoint::UnpatchAddress(uintptr_t host_address,
|
||||
uint16_t original_bytes) {
|
||||
auto ptr = reinterpret_cast<uint8_t*>(host_address);
|
||||
auto instruction_bytes = xe::load_and_swap<uint16_t>(ptr);
|
||||
assert_true(instruction_bytes == 0x0F0B);
|
||||
xe::store_and_swap<uint16_t>(ptr, original_bytes);
|
||||
}
|
||||
|
||||
std::string CodeBreakpoint::to_string() const {
|
||||
if (address_type_ == AddressType::kGuest) {
|
||||
auto str =
|
||||
std::string("PPC ") + xe::string_util::to_hex_string(guest_address());
|
||||
auto processor = debugger_->emulator()->processor();
|
||||
auto functions = processor->FindFunctionsWithAddress(guest_address());
|
||||
if (functions.empty()) {
|
||||
return str;
|
||||
}
|
||||
str += " " + functions[0]->name();
|
||||
return str;
|
||||
} else {
|
||||
return std::string("x64 ") + xe::string_util::to_hex_string(host_address());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -10,33 +10,173 @@
|
|||
#ifndef XENIA_DEBUG_BREAKPOINT_H_
|
||||
#define XENIA_DEBUG_BREAKPOINT_H_
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
class Debugger;
|
||||
|
||||
class Breakpoint {
|
||||
public:
|
||||
enum Type {
|
||||
TEMP_TYPE,
|
||||
CODE_TYPE,
|
||||
enum class Type {
|
||||
kStepping,
|
||||
kCode,
|
||||
kData,
|
||||
kExport,
|
||||
kGpu,
|
||||
};
|
||||
|
||||
public:
|
||||
Breakpoint(Type type, uint32_t address);
|
||||
~Breakpoint();
|
||||
Breakpoint(Debugger* debugger, Type type)
|
||||
: debugger_(debugger), type_(type) {}
|
||||
|
||||
virtual ~Breakpoint() { assert_false(installed_); }
|
||||
|
||||
Debugger* debugger() const { return debugger_; }
|
||||
Type type() const { return type_; }
|
||||
uint32_t address() const { return address_; }
|
||||
|
||||
const char* id() const { return id_.c_str(); }
|
||||
void set_id(const char* id) { id_ = std::string(id); }
|
||||
// Whether the breakpoint has been enabled by the user.
|
||||
bool is_enabled() const { return enabled_; }
|
||||
|
||||
// Toggles the breakpoint state.
|
||||
// Assumes the caller holds the global lock.
|
||||
void set_enabled(bool is_enabled) {
|
||||
enabled_ = is_enabled;
|
||||
if (!enabled_ && installed_) {
|
||||
Uninstall();
|
||||
} else if (enabled_ && !installed_ && !suspend_count_) {
|
||||
Install();
|
||||
}
|
||||
}
|
||||
|
||||
virtual std::string to_string() const { return "Breakpoint"; }
|
||||
|
||||
private:
|
||||
Type type_;
|
||||
uint32_t address_;
|
||||
friend class Debugger;
|
||||
// Suspends the breakpoint until it is resumed with Resume.
|
||||
// This preserves the user enabled state.
|
||||
// Assumes the caller holds the global lock.
|
||||
void Suspend() {
|
||||
++suspend_count_;
|
||||
if (installed_) {
|
||||
Uninstall();
|
||||
}
|
||||
}
|
||||
|
||||
std::string id_;
|
||||
// Resumes a previously-suspended breakpoint, reinstalling it if required.
|
||||
// Assumes the caller holds the global lock.
|
||||
void Resume() {
|
||||
--suspend_count_;
|
||||
if (!suspend_count_ && enabled_) {
|
||||
Install();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void Install() { installed_ = true; }
|
||||
virtual void Uninstall() { installed_ = false; }
|
||||
|
||||
Debugger* debugger_ = nullptr;
|
||||
Type type_;
|
||||
// True if patched into code.
|
||||
bool installed_ = false;
|
||||
// True if user enabled.
|
||||
bool enabled_ = true;
|
||||
// Depth of suspends (must be 0 to be installed). Defaults to suspended so
|
||||
// that it's never installed unless the debugger knows about it.
|
||||
int suspend_count_ = 1;
|
||||
};
|
||||
|
||||
class CodeBreakpoint : public Breakpoint {
|
||||
public:
|
||||
enum class AddressType {
|
||||
kGuest,
|
||||
kHost,
|
||||
};
|
||||
|
||||
CodeBreakpoint(Debugger* debugger, AddressType address_type, uint64_t address)
|
||||
: CodeBreakpoint(debugger, Type::kCode, address_type, address) {}
|
||||
|
||||
AddressType address_type() const { return address_type_; }
|
||||
uint64_t address() const { return address_; }
|
||||
uint32_t guest_address() const {
|
||||
assert_true(address_type_ == AddressType::kGuest);
|
||||
return static_cast<uint32_t>(address_);
|
||||
}
|
||||
uintptr_t host_address() const {
|
||||
assert_true(address_type_ == AddressType::kHost);
|
||||
return static_cast<uintptr_t>(address_);
|
||||
}
|
||||
|
||||
// TODO(benvanik): additional support:
|
||||
// - conditions (expressions? etc?)
|
||||
// - thread restrictions (set of thread ids)
|
||||
// - hit counters
|
||||
|
||||
// Returns a guest function that contains the guest address, if any.
|
||||
// If there are multiple functions that contain the address a random one will
|
||||
// be returned. If this is a host-address code breakpoint this will attempt to
|
||||
// find a function with that address and otherwise return nullptr.
|
||||
xe::cpu::GuestFunction* guest_function() const;
|
||||
|
||||
// Returns true if this breakpoint, when installed, contains the given host
|
||||
// address.
|
||||
bool ContainsHostAddress(uintptr_t search_address) const;
|
||||
|
||||
std::string to_string() const override;
|
||||
|
||||
protected:
|
||||
CodeBreakpoint(Debugger* debugger, Type type, AddressType address_type,
|
||||
uint64_t address)
|
||||
: Breakpoint(debugger, type),
|
||||
address_type_(address_type),
|
||||
address_(address) {}
|
||||
|
||||
void ForEachHostAddress(std::function<void(uintptr_t)> callback) const;
|
||||
|
||||
void Install() override;
|
||||
void Uninstall() override;
|
||||
|
||||
uint16_t PatchAddress(uintptr_t host_address);
|
||||
void UnpatchAddress(uintptr_t host_address, uint16_t original_bytes);
|
||||
|
||||
AddressType address_type_;
|
||||
uint64_t address_ = 0;
|
||||
|
||||
// Pairs of x64 address : original byte values when installed.
|
||||
// These locations have been patched and must be restored when the breakpoint
|
||||
// is uninstalled.
|
||||
std::vector<std::pair<uintptr_t, uint16_t>> patches_;
|
||||
};
|
||||
|
||||
class StepBreakpoint : public CodeBreakpoint {
|
||||
public:
|
||||
StepBreakpoint(Debugger* debugger, AddressType address_type, uint64_t address)
|
||||
: CodeBreakpoint(debugger, Type::kStepping, address_type, address) {}
|
||||
};
|
||||
|
||||
class DataBreakpoint : public Breakpoint {
|
||||
public:
|
||||
explicit DataBreakpoint(Debugger* debugger)
|
||||
: Breakpoint(debugger, Type::kData) {}
|
||||
};
|
||||
|
||||
class ExportBreakpoint : public Breakpoint {
|
||||
public:
|
||||
explicit ExportBreakpoint(Debugger* debugger)
|
||||
: Breakpoint(debugger, Type::kExport) {}
|
||||
};
|
||||
|
||||
class GpuBreakpoint : public Breakpoint {
|
||||
public:
|
||||
explicit GpuBreakpoint(Debugger* debugger)
|
||||
: Breakpoint(debugger, Type::kGpu) {}
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
|
|
|
@ -1,271 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/debug_client.h"
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
using namespace xe::debug::proto; // NOLINT(build/namespaces)
|
||||
|
||||
constexpr size_t kReceiveBufferSize = 1 * 1024 * 1024;
|
||||
constexpr size_t kTransmitBufferSize = 1 * 1024 * 1024;
|
||||
|
||||
DebugClient::DebugClient()
|
||||
: packet_reader_(kReceiveBufferSize), packet_writer_(kTransmitBufferSize) {
|
||||
receive_buffer_.resize(kReceiveBufferSize);
|
||||
}
|
||||
|
||||
DebugClient::~DebugClient() {
|
||||
socket_->Close();
|
||||
xe::threading::Wait(thread_.get(), true);
|
||||
thread_.reset();
|
||||
}
|
||||
|
||||
bool DebugClient::Connect(std::string hostname, uint16_t port) {
|
||||
socket_ = Socket::Connect(std::move(hostname), port);
|
||||
if (!socket_) {
|
||||
XELOGE("Unable to connect to remote host");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a thread to manage the connection.
|
||||
thread_ = xe::threading::Thread::Create({}, [this]() {
|
||||
// Main loop.
|
||||
bool running = true;
|
||||
while (running) {
|
||||
auto wait_result = xe::threading::Wait(socket_->wait_handle(), true);
|
||||
switch (wait_result) {
|
||||
case xe::threading::WaitResult::kSuccess:
|
||||
// Event (read or close).
|
||||
running = HandleSocketEvent();
|
||||
continue;
|
||||
case xe::threading::WaitResult::kAbandoned:
|
||||
case xe::threading::WaitResult::kFailed:
|
||||
// Error - kill the thread.
|
||||
running = false;
|
||||
continue;
|
||||
default:
|
||||
// Eh. Continue.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Kill socket (likely already disconnected).
|
||||
socket_.reset();
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugClient::HandleSocketEvent() {
|
||||
if (!socket_->is_connected()) {
|
||||
// Known-disconnected.
|
||||
return false;
|
||||
}
|
||||
// Attempt to read into our buffer.
|
||||
size_t bytes_read =
|
||||
socket_->Receive(receive_buffer_.data(), receive_buffer_.capacity());
|
||||
if (bytes_read == -1) {
|
||||
// Disconnected.
|
||||
return false;
|
||||
} else if (bytes_read == 0) {
|
||||
// No data available. Wait again.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pass off the command processor to do with it what it wants.
|
||||
if (!ProcessBuffer(receive_buffer_.data(), bytes_read)) {
|
||||
// Error.
|
||||
XELOGE("Error processing incoming XDP command; forcing disconnect");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugClient::ProcessBuffer(const uint8_t* buffer, size_t buffer_length) {
|
||||
// Grow and append the bytes to the receive buffer.
|
||||
packet_reader_.AppendBuffer(buffer, buffer_length);
|
||||
|
||||
// Process all packets in the buffer.
|
||||
while (true) {
|
||||
auto packet = packet_reader_.Begin();
|
||||
if (!packet) {
|
||||
// Out of packets. Compact any unused space.
|
||||
packet_reader_.Compact();
|
||||
break;
|
||||
}
|
||||
|
||||
// Emit packet. Possibly dispatch to a loop, if desired.
|
||||
if (loop_) {
|
||||
auto clone = packet_reader_.ClonePacket();
|
||||
auto clone_ptr = clone.release();
|
||||
loop_->Post([this, clone_ptr]() {
|
||||
std::unique_ptr<PacketReader> clone(clone_ptr);
|
||||
auto packet = clone->Begin();
|
||||
if (!ProcessPacket(clone.get(), packet)) {
|
||||
// Failed to process packet, but there's no good way to report that.
|
||||
XELOGE("Failed to process incoming packet");
|
||||
assert_always();
|
||||
}
|
||||
clone->End();
|
||||
});
|
||||
} else {
|
||||
// Process inline.
|
||||
if (!ProcessPacket(&packet_reader_, packet)) {
|
||||
// Failed to process packet.
|
||||
XELOGE("Failed to process incoming packet");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
packet_reader_.End();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugClient::ProcessPacket(proto::PacketReader* packet_reader,
|
||||
const proto::Packet* packet) {
|
||||
// Hold lock during processing.
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
|
||||
switch (packet->packet_type) {
|
||||
case PacketType::kGenericResponse: {
|
||||
//
|
||||
} break;
|
||||
case PacketType::kExecutionNotification: {
|
||||
auto body = packet_reader->Read<ExecutionNotification>();
|
||||
switch (body->current_state) {
|
||||
case ExecutionNotification::State::kStopped: {
|
||||
// body->stop_reason
|
||||
execution_state_ = ExecutionState::kStopped;
|
||||
listener_->OnExecutionStateChanged(execution_state_);
|
||||
BeginUpdateAllState();
|
||||
} break;
|
||||
case ExecutionNotification::State::kRunning: {
|
||||
execution_state_ = ExecutionState::kRunning;
|
||||
listener_->OnExecutionStateChanged(execution_state_);
|
||||
} break;
|
||||
case ExecutionNotification::State::kExited: {
|
||||
execution_state_ = ExecutionState::kExited;
|
||||
listener_->OnExecutionStateChanged(execution_state_);
|
||||
BeginUpdateAllState();
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
case PacketType::kModuleListResponse: {
|
||||
auto body = packet_reader->Read<ModuleListResponse>();
|
||||
auto entries = packet_reader->ReadArray<ModuleListEntry>(body->count);
|
||||
listener_->OnModulesUpdated(std::move(entries));
|
||||
} break;
|
||||
case PacketType::kThreadListResponse: {
|
||||
auto body = packet_reader->Read<ThreadListResponse>();
|
||||
auto entries = packet_reader->ReadArray<ThreadListEntry>(body->count);
|
||||
listener_->OnThreadsUpdated(std::move(entries));
|
||||
} break;
|
||||
case PacketType::kThreadStatesResponse: {
|
||||
auto body = packet_reader->Read<ThreadStatesResponse>();
|
||||
for (size_t i = 0; i < body->count; ++i) {
|
||||
auto entry = packet_reader->Read<ThreadStateEntry>();
|
||||
auto frames =
|
||||
packet_reader->ReadArray<ThreadCallStackFrame>(entry->frame_count);
|
||||
listener_->OnThreadStateUpdated(entry->thread_handle, entry,
|
||||
std::move(frames));
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
XELOGE("Unknown incoming packet type");
|
||||
return false;
|
||||
} break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebugClient::Flush() {
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
if (!packet_writer_.buffer_offset()) {
|
||||
return;
|
||||
}
|
||||
socket_->Send(packet_writer_.buffer(), packet_writer_.buffer_offset());
|
||||
packet_writer_.Reset();
|
||||
}
|
||||
|
||||
void DebugClient::Continue() {
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
packet_writer_.Begin(PacketType::kExecutionRequest);
|
||||
auto body = packet_writer_.Append<ExecutionRequest>();
|
||||
body->action = ExecutionRequest::Action::kContinue;
|
||||
packet_writer_.End();
|
||||
Flush();
|
||||
}
|
||||
|
||||
void DebugClient::StepOne(uint32_t thread_id) {
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
packet_writer_.Begin(PacketType::kExecutionRequest);
|
||||
auto body = packet_writer_.Append<ExecutionRequest>();
|
||||
body->action = ExecutionRequest::Action::kStep;
|
||||
body->step_mode = ExecutionRequest::StepMode::kStepOne;
|
||||
body->thread_id = thread_id;
|
||||
packet_writer_.End();
|
||||
Flush();
|
||||
}
|
||||
|
||||
void DebugClient::StepTo(uint32_t thread_id, uint32_t target_pc) {
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
packet_writer_.Begin(PacketType::kExecutionRequest);
|
||||
auto body = packet_writer_.Append<ExecutionRequest>();
|
||||
body->action = ExecutionRequest::Action::kStep;
|
||||
body->step_mode = ExecutionRequest::StepMode::kStepTo;
|
||||
body->thread_id = thread_id;
|
||||
body->target_pc = target_pc;
|
||||
packet_writer_.End();
|
||||
Flush();
|
||||
}
|
||||
|
||||
void DebugClient::Interrupt() {
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
packet_writer_.Begin(PacketType::kExecutionRequest);
|
||||
auto body = packet_writer_.Append<ExecutionRequest>();
|
||||
body->action = ExecutionRequest::Action::kInterrupt;
|
||||
packet_writer_.End();
|
||||
Flush();
|
||||
}
|
||||
|
||||
void DebugClient::Exit() {
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
packet_writer_.Begin(PacketType::kExecutionRequest);
|
||||
auto body = packet_writer_.Append<ExecutionRequest>();
|
||||
body->action = ExecutionRequest::Action::kExit;
|
||||
packet_writer_.End();
|
||||
Flush();
|
||||
}
|
||||
|
||||
void DebugClient::BeginUpdateAllState() {
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
|
||||
// Request all module infos.
|
||||
packet_writer_.Begin(PacketType::kModuleListRequest);
|
||||
packet_writer_.End();
|
||||
// Request all thread infos.
|
||||
packet_writer_.Begin(PacketType::kThreadListRequest);
|
||||
packet_writer_.End();
|
||||
// Request the full call stacks for all threads.
|
||||
packet_writer_.Begin(PacketType::kThreadStatesRequest);
|
||||
packet_writer_.End();
|
||||
|
||||
Flush();
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -1,106 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_DEBUG_CLIENT_H_
|
||||
#define XENIA_DEBUG_DEBUG_CLIENT_H_
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/socket.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/debug/proto/packet_reader.h"
|
||||
#include "xenia/debug/proto/packet_writer.h"
|
||||
#include "xenia/debug/proto/xdp_protocol.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
class Loop;
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
using proto::ModuleListEntry;
|
||||
using proto::ThreadCallStackFrame;
|
||||
using proto::ThreadListEntry;
|
||||
using proto::ThreadStateEntry;
|
||||
|
||||
enum class ExecutionState {
|
||||
kRunning,
|
||||
kStopped,
|
||||
kExited,
|
||||
};
|
||||
|
||||
// NOTE: all callbacks are issued on the client thread and should be marshalled
|
||||
// to the UI thread. All other methods can be called from any thread.
|
||||
|
||||
class DebugClientListener {
|
||||
public:
|
||||
virtual ~DebugClientListener() = default;
|
||||
|
||||
virtual void OnExecutionStateChanged(ExecutionState execution_state) = 0;
|
||||
virtual void OnModulesUpdated(
|
||||
std::vector<const ModuleListEntry*> entries) = 0;
|
||||
virtual void OnThreadsUpdated(
|
||||
std::vector<const ThreadListEntry*> entries) = 0;
|
||||
virtual void OnThreadStateUpdated(
|
||||
uint32_t thread_handle, const ThreadStateEntry* entry,
|
||||
std::vector<const ThreadCallStackFrame*> frames) = 0;
|
||||
};
|
||||
|
||||
class DebugClient {
|
||||
public:
|
||||
DebugClient();
|
||||
~DebugClient();
|
||||
|
||||
Socket* socket() const { return socket_.get(); }
|
||||
void set_listener(DebugClientListener* listener) { listener_ = listener; }
|
||||
void set_loop(xe::ui::Loop* loop) { loop_ = loop; }
|
||||
|
||||
ExecutionState execution_state() const { return execution_state_; }
|
||||
|
||||
bool Connect(std::string hostname, uint16_t port);
|
||||
|
||||
void Continue();
|
||||
void StepOne(uint32_t thread_id);
|
||||
void StepTo(uint32_t thread_id, uint32_t target_pc);
|
||||
void Interrupt();
|
||||
void Exit();
|
||||
|
||||
private:
|
||||
bool HandleSocketEvent();
|
||||
bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length);
|
||||
bool ProcessPacket(proto::PacketReader* packet_reader,
|
||||
const proto::Packet* packet);
|
||||
void Flush();
|
||||
|
||||
void BeginUpdateAllState();
|
||||
|
||||
std::recursive_mutex mutex_;
|
||||
std::unique_ptr<Socket> socket_;
|
||||
std::unique_ptr<xe::threading::Thread> thread_;
|
||||
std::vector<uint8_t> receive_buffer_;
|
||||
|
||||
proto::PacketReader packet_reader_;
|
||||
proto::PacketWriter packet_writer_;
|
||||
|
||||
DebugClientListener* listener_ = nullptr;
|
||||
xe::ui::Loop* loop_ = nullptr;
|
||||
|
||||
ExecutionState execution_state_ = ExecutionState::kStopped;
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_DEBUG_CLIENT_H_
|
|
@ -1,406 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/debug_server.h"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/cpu/stack_walker.h"
|
||||
#include "xenia/debug/debugger.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/kernel/xmodule.h"
|
||||
#include "xenia/kernel/xthread.h"
|
||||
|
||||
DEFINE_int32(debug_server_port, 9002, "Debugger XDP server TCP port.");
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
using namespace xe::debug::proto; // NOLINT(build/namespaces)
|
||||
|
||||
using xe::kernel::XModule;
|
||||
using xe::kernel::XObject;
|
||||
using xe::kernel::XThread;
|
||||
|
||||
constexpr size_t kReceiveBufferSize = 32 * 1024;
|
||||
constexpr size_t kReadBufferSize = 1 * 1024 * 1024;
|
||||
constexpr size_t kWriteBufferSize = 1 * 1024 * 1024;
|
||||
|
||||
DebugServer::DebugServer(Debugger* debugger)
|
||||
: debugger_(debugger),
|
||||
packet_reader_(kReadBufferSize),
|
||||
packet_writer_(kWriteBufferSize) {
|
||||
receive_buffer_.resize(kReceiveBufferSize);
|
||||
}
|
||||
|
||||
DebugServer::~DebugServer() = default;
|
||||
|
||||
bool DebugServer::Initialize() {
|
||||
post_event_ = xe::threading::Event::CreateAutoResetEvent(false);
|
||||
|
||||
socket_server_ = SocketServer::Create(uint16_t(FLAGS_debug_server_port),
|
||||
[this](std::unique_ptr<Socket> client) {
|
||||
AcceptClient(std::move(client));
|
||||
});
|
||||
if (!socket_server_) {
|
||||
XELOGE("Unable to create XDP socket server - port in use?");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebugServer::PostSynchronous(std::function<void()> fn) {
|
||||
xe::threading::Fence fence;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(post_mutex_);
|
||||
post_queue_.push_back([&fence, fn]() {
|
||||
fn();
|
||||
fence.Signal();
|
||||
});
|
||||
}
|
||||
post_event_->Set();
|
||||
fence.Wait();
|
||||
}
|
||||
|
||||
void DebugServer::AcceptClient(std::unique_ptr<Socket> client) {
|
||||
// If we have an existing client, kill it and join its thread.
|
||||
if (client_) {
|
||||
// TODO(benvanik): XDP say goodbye?
|
||||
|
||||
client_->Close();
|
||||
xe::threading::Wait(client_thread_.get(), true);
|
||||
client_thread_.reset();
|
||||
}
|
||||
|
||||
// Take ownership of the new one.
|
||||
client_ = std::move(client);
|
||||
|
||||
// Create a thread to manage the connection.
|
||||
client_thread_ = xe::threading::Thread::Create({}, [this]() {
|
||||
xe::threading::set_name("XDP Debug Server");
|
||||
|
||||
// Let the debugger know we are present.
|
||||
debugger_->set_attached(true);
|
||||
|
||||
// Notify the client of the current state.
|
||||
if (debugger_->execution_state() == ExecutionState::kRunning) {
|
||||
OnExecutionContinued();
|
||||
} else {
|
||||
OnExecutionInterrupted();
|
||||
}
|
||||
|
||||
// Main loop.
|
||||
bool running = true;
|
||||
while (running) {
|
||||
xe::threading::WaitHandle* wait_handles[] = {
|
||||
client_->wait_handle(), post_event_.get(),
|
||||
};
|
||||
auto wait_result = xe::threading::WaitMultiple(
|
||||
wait_handles, xe::countof(wait_handles), false, true);
|
||||
switch (wait_result.first) {
|
||||
case xe::threading::WaitResult::kSuccess:
|
||||
// Event (read or close).
|
||||
switch (wait_result.second) {
|
||||
case 0: {
|
||||
running = HandleClientEvent();
|
||||
} break;
|
||||
case 1: {
|
||||
bool has_remaining = true;
|
||||
while (has_remaining) {
|
||||
std::function<void()> fn;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(post_mutex_);
|
||||
fn = std::move(post_queue_.front());
|
||||
post_queue_.pop_front();
|
||||
has_remaining = !post_queue_.empty();
|
||||
if (!has_remaining) {
|
||||
post_event_->Reset();
|
||||
}
|
||||
}
|
||||
fn();
|
||||
}
|
||||
} break;
|
||||
}
|
||||
continue;
|
||||
case xe::threading::WaitResult::kAbandoned:
|
||||
case xe::threading::WaitResult::kFailed:
|
||||
// Error - kill the thread.
|
||||
running = false;
|
||||
continue;
|
||||
default:
|
||||
// Eh. Continue.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Kill client (likely already disconnected).
|
||||
client_.reset();
|
||||
|
||||
// Notify debugger we are no longer attached.
|
||||
debugger_->set_attached(false);
|
||||
});
|
||||
}
|
||||
|
||||
bool DebugServer::HandleClientEvent() {
|
||||
if (!client_->is_connected()) {
|
||||
// Known-disconnected.
|
||||
return false;
|
||||
}
|
||||
// Attempt to read into our buffer.
|
||||
size_t bytes_read =
|
||||
client_->Receive(receive_buffer_.data(), receive_buffer_.capacity());
|
||||
if (bytes_read == -1) {
|
||||
// Disconnected.
|
||||
return false;
|
||||
} else if (bytes_read == 0) {
|
||||
// No data available. Wait again.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pass off the command processor to do with it what it wants.
|
||||
if (!ProcessBuffer(receive_buffer_.data(), bytes_read)) {
|
||||
// Error.
|
||||
XELOGE("Error processing incoming XDP command; forcing disconnect");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
bool DebugServer::ProcessBuffer(const uint8_t* buffer, size_t buffer_length) {
|
||||
// Grow and append the bytes to the receive buffer.
|
||||
packet_reader_.AppendBuffer(buffer, buffer_length);
|
||||
|
||||
// Process all packets in the buffer.
|
||||
while (true) {
|
||||
auto packet = packet_reader_.Begin();
|
||||
if (!packet) {
|
||||
// Out of packets. Compact any unused space.
|
||||
packet_reader_.Compact();
|
||||
break;
|
||||
}
|
||||
|
||||
// Emit packet.
|
||||
if (!ProcessPacket(packet)) {
|
||||
// Failed to process packet.
|
||||
XELOGE("Failed to process incoming packet");
|
||||
return false;
|
||||
}
|
||||
|
||||
packet_reader_.End();
|
||||
}
|
||||
|
||||
Flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DebugServer::ProcessPacket(const proto::Packet* packet) {
|
||||
auto emulator = debugger()->emulator();
|
||||
auto kernel_state = emulator->kernel_state();
|
||||
auto object_table = kernel_state->object_table();
|
||||
|
||||
switch (packet->packet_type) {
|
||||
case PacketType::kExecutionRequest: {
|
||||
auto body = packet_reader_.Read<ExecutionRequest>();
|
||||
switch (body->action) {
|
||||
case ExecutionRequest::Action::kContinue: {
|
||||
debugger_->Continue();
|
||||
} break;
|
||||
case ExecutionRequest::Action::kStep: {
|
||||
switch (body->step_mode) {
|
||||
case ExecutionRequest::StepMode::kStepOne: {
|
||||
debugger_->StepOne(body->thread_id);
|
||||
} break;
|
||||
case ExecutionRequest::StepMode::kStepTo: {
|
||||
debugger_->StepTo(body->thread_id, body->target_pc);
|
||||
} break;
|
||||
default:
|
||||
assert_unhandled_case(body->step_mode);
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
case ExecutionRequest::Action::kInterrupt: {
|
||||
debugger_->Interrupt();
|
||||
} break;
|
||||
case ExecutionRequest::Action::kExit: {
|
||||
XELOGI("Debug client requested exit");
|
||||
exit(1);
|
||||
} break;
|
||||
}
|
||||
SendSuccess(packet->request_id);
|
||||
} break;
|
||||
case PacketType::kModuleListRequest: {
|
||||
packet_writer_.Begin(PacketType::kModuleListResponse);
|
||||
auto body = packet_writer_.Append<ModuleListResponse>();
|
||||
auto modules =
|
||||
object_table->GetObjectsByType<XModule>(XObject::Type::kTypeModule);
|
||||
body->count = uint32_t(modules.size());
|
||||
for (auto& module : modules) {
|
||||
auto entry = packet_writer_.Append<ModuleListEntry>();
|
||||
entry->module_handle = module->handle();
|
||||
entry->module_ptr = module->hmodule_ptr();
|
||||
entry->is_kernel_module =
|
||||
module->module_type() == XModule::ModuleType::kKernelModule;
|
||||
std::strncpy(entry->path, module->path().c_str(),
|
||||
xe::countof(entry->path));
|
||||
std::strncpy(entry->name, module->name().c_str(),
|
||||
xe::countof(entry->name));
|
||||
}
|
||||
packet_writer_.End();
|
||||
} break;
|
||||
case PacketType::kThreadListRequest: {
|
||||
packet_writer_.Begin(PacketType::kThreadListResponse);
|
||||
auto body = packet_writer_.Append<ThreadListResponse>();
|
||||
auto threads =
|
||||
object_table->GetObjectsByType<XThread>(XObject::Type::kTypeThread);
|
||||
body->count = uint32_t(threads.size());
|
||||
for (auto& thread : threads) {
|
||||
auto entry = packet_writer_.Append<ThreadListEntry>();
|
||||
entry->thread_handle = thread->handle();
|
||||
entry->thread_id = thread->thread_id();
|
||||
entry->is_host_thread = !thread->is_guest_thread();
|
||||
std::strncpy(entry->name, thread->name().c_str(),
|
||||
xe::countof(entry->name));
|
||||
entry->exit_code;
|
||||
entry->priority = thread->priority();
|
||||
entry->xapi_thread_startup =
|
||||
thread->creation_params()->xapi_thread_startup;
|
||||
entry->start_address = thread->creation_params()->start_address;
|
||||
entry->start_context = thread->creation_params()->start_context;
|
||||
entry->creation_flags = thread->creation_params()->creation_flags;
|
||||
entry->stack_address = thread->thread_state()->stack_address();
|
||||
entry->stack_size = thread->thread_state()->stack_size();
|
||||
entry->stack_base = thread->thread_state()->stack_base();
|
||||
entry->stack_limit = thread->thread_state()->stack_limit();
|
||||
entry->pcr_address = thread->pcr_ptr();
|
||||
entry->tls_address = thread->tls_ptr();
|
||||
}
|
||||
packet_writer_.End();
|
||||
} break;
|
||||
case PacketType::kThreadStatesRequest: {
|
||||
packet_writer_.Begin(PacketType::kThreadStatesResponse);
|
||||
auto body = packet_writer_.Append<ThreadStatesResponse>();
|
||||
auto stack_walker = emulator->processor()->stack_walker();
|
||||
auto threads =
|
||||
emulator->kernel_state()->object_table()->GetObjectsByType<XThread>(
|
||||
XObject::kTypeThread);
|
||||
body->count = uint32_t(threads.size());
|
||||
uint64_t frame_host_pcs[64];
|
||||
cpu::StackFrame frames[64];
|
||||
for (auto& thread : threads) {
|
||||
auto thread_entry_body = packet_writer_.Append<ThreadStateEntry>();
|
||||
thread_entry_body->thread_handle = thread->handle();
|
||||
|
||||
// Grab PPC context.
|
||||
// Note that this is only up to date if --store_all_context_values is
|
||||
// enabled (or --debug).
|
||||
if (thread->is_guest_thread()) {
|
||||
std::memcpy(&thread_entry_body->guest_context,
|
||||
thread->thread_state()->context(),
|
||||
sizeof(thread_entry_body->guest_context));
|
||||
} else {
|
||||
std::memset(&thread_entry_body->guest_context, 0,
|
||||
sizeof(thread_entry_body->guest_context));
|
||||
}
|
||||
|
||||
// Grab stack trace and X64 context then resolve all symbols.
|
||||
uint64_t hash;
|
||||
size_t count = stack_walker->CaptureStackTrace(
|
||||
thread->GetWaitHandle()->native_handle(), frame_host_pcs, 0, 64,
|
||||
&thread_entry_body->host_context, &hash);
|
||||
stack_walker->ResolveStack(frame_host_pcs, frames, count);
|
||||
|
||||
// Populate stack frames with additional information.
|
||||
thread_entry_body->frame_count = uint32_t(count);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
auto& frame = frames[i];
|
||||
auto frame_body = packet_writer_.Append<ThreadCallStackFrame>();
|
||||
frame_body->host_pc = frame.host_pc;
|
||||
frame_body->host_function_address = frame.host_symbol.address;
|
||||
frame_body->guest_pc = frame.guest_pc;
|
||||
frame_body->guest_function_address = 0;
|
||||
auto function = frame.guest_symbol.function;
|
||||
if (frame.type == cpu::StackFrame::Type::kGuest && function) {
|
||||
frame_body->guest_function_address = function->address();
|
||||
std::strncpy(frame_body->name, function->name().c_str(),
|
||||
xe::countof(frame_body->name));
|
||||
} else {
|
||||
std::strncpy(frame_body->name, frame.host_symbol.name,
|
||||
xe::countof(frame_body->name));
|
||||
}
|
||||
}
|
||||
}
|
||||
packet_writer_.End();
|
||||
} break;
|
||||
default: {
|
||||
XELOGE("Unknown packet type");
|
||||
SendError(packet->request_id, "Unknown packet type");
|
||||
return false;
|
||||
} break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebugServer::OnExecutionContinued() {
|
||||
packet_writer_.Begin(PacketType::kExecutionNotification);
|
||||
auto body = packet_writer_.Append<ExecutionNotification>();
|
||||
body->current_state = ExecutionNotification::State::kRunning;
|
||||
packet_writer_.End();
|
||||
Flush();
|
||||
}
|
||||
|
||||
void DebugServer::OnExecutionInterrupted() {
|
||||
packet_writer_.Begin(PacketType::kExecutionNotification);
|
||||
auto body = packet_writer_.Append<ExecutionNotification>();
|
||||
body->current_state = ExecutionNotification::State::kStopped;
|
||||
body->stop_reason = ExecutionNotification::Reason::kInterrupt;
|
||||
packet_writer_.End();
|
||||
Flush();
|
||||
}
|
||||
|
||||
void DebugServer::Flush() {
|
||||
if (!packet_writer_.buffer_offset()) {
|
||||
return;
|
||||
}
|
||||
client_->Send(packet_writer_.buffer(), packet_writer_.buffer_offset());
|
||||
packet_writer_.Reset();
|
||||
}
|
||||
|
||||
void DebugServer::SendSuccess(proto::request_id_t request_id) {
|
||||
packet_writer_.Begin(PacketType::kGenericResponse, request_id);
|
||||
auto body = packet_writer_.Append<GenericResponse>();
|
||||
body->code = GenericResponse::Code::kSuccess;
|
||||
packet_writer_.End();
|
||||
Flush();
|
||||
}
|
||||
|
||||
void DebugServer::SendError(proto::request_id_t request_id,
|
||||
const char* error_message) {
|
||||
packet_writer_.Begin(PacketType::kGenericResponse, request_id);
|
||||
auto body = packet_writer_.Append<GenericResponse>();
|
||||
body->code = GenericResponse::Code::kError;
|
||||
packet_writer_.AppendString(error_message ? error_message : "");
|
||||
packet_writer_.End();
|
||||
Flush();
|
||||
}
|
||||
|
||||
void DebugServer::SendError(proto::request_id_t request_id,
|
||||
const std::string& error_message) {
|
||||
packet_writer_.Begin(PacketType::kGenericResponse, request_id);
|
||||
auto body = packet_writer_.Append<GenericResponse>();
|
||||
body->code = GenericResponse::Code::kError;
|
||||
packet_writer_.AppendString(error_message);
|
||||
packet_writer_.End();
|
||||
Flush();
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_DEBUG_SERVER_H_
|
||||
#define XENIA_DEBUG_DEBUG_SERVER_H_
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/socket.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/debug/breakpoint.h"
|
||||
#include "xenia/debug/proto/packet_reader.h"
|
||||
#include "xenia/debug/proto/packet_writer.h"
|
||||
#include "xenia/debug/proto/xdp_protocol.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
class Debugger;
|
||||
|
||||
class DebugServer {
|
||||
public:
|
||||
explicit DebugServer(Debugger* debugger);
|
||||
~DebugServer();
|
||||
|
||||
Debugger* debugger() const { return debugger_; }
|
||||
Socket* client() const { return client_.get(); }
|
||||
|
||||
bool Initialize();
|
||||
|
||||
void PostSynchronous(std::function<void()> fn);
|
||||
|
||||
// TODO(benvanik): better thread type (XThread?)
|
||||
// void OnThreadCreated(ThreadState* thread_state);
|
||||
// void OnThreadDestroyed(ThreadState* thread_state);
|
||||
|
||||
void OnExecutionContinued();
|
||||
void OnExecutionInterrupted();
|
||||
/*void OnBreakpointHit(xe::cpu::ThreadState* thread_state,
|
||||
Breakpoint* breakpoint);*/
|
||||
|
||||
private:
|
||||
void AcceptClient(std::unique_ptr<Socket> client);
|
||||
bool HandleClientEvent();
|
||||
|
||||
bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length);
|
||||
bool ProcessPacket(const proto::Packet* packet);
|
||||
|
||||
void Flush();
|
||||
void SendSuccess(proto::request_id_t request_id);
|
||||
void SendError(proto::request_id_t request_id,
|
||||
const char* error_message = nullptr);
|
||||
void SendError(proto::request_id_t request_id,
|
||||
const std::string& error_message);
|
||||
|
||||
Debugger* debugger_ = nullptr;
|
||||
|
||||
std::unique_ptr<SocketServer> socket_server_;
|
||||
|
||||
std::unique_ptr<Socket> client_;
|
||||
std::unique_ptr<xe::threading::Thread> client_thread_;
|
||||
|
||||
std::mutex post_mutex_;
|
||||
std::unique_ptr<xe::threading::Event> post_event_;
|
||||
std::list<std::function<void()>> post_queue_;
|
||||
|
||||
std::vector<uint8_t> receive_buffer_;
|
||||
proto::PacketReader packet_reader_;
|
||||
proto::PacketWriter packet_writer_;
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_DEBUG_SERVER_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -19,6 +19,7 @@
|
|||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/exception_handler.h"
|
||||
#include "xenia/base/mapped_memory.h"
|
||||
#include "xenia/base/mutex.h"
|
||||
#include "xenia/base/threading.h"
|
||||
|
@ -38,11 +39,127 @@ class XThread;
|
|||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
class DebugServer;
|
||||
|
||||
// Describes the current state of the debug target as known to the debugger.
|
||||
// This determines which state the debugger is in and what operations are
|
||||
// allowed.
|
||||
enum class ExecutionState {
|
||||
// Target is running; the debugger is not waiting for any events.
|
||||
kRunning,
|
||||
kStopped,
|
||||
// Target is running in stepping mode with the debugger waiting for feedback.
|
||||
kStepping,
|
||||
// Target is paused for debugging.
|
||||
kPaused,
|
||||
// Target has been stopped and cannot be restarted (crash, etc).
|
||||
kEnded,
|
||||
};
|
||||
|
||||
// Per-XThread structure holding debugger state and a cache of the sampled call
|
||||
// stack.
|
||||
//
|
||||
// In most cases debug consumers should rely only on data in this structure as
|
||||
// it is never removed (even when a thread is destroyed) and always available
|
||||
// even when running.
|
||||
struct ThreadExecutionInfo {
|
||||
ThreadExecutionInfo();
|
||||
~ThreadExecutionInfo();
|
||||
|
||||
enum class State {
|
||||
// Thread is alive and running.
|
||||
kAlive,
|
||||
// Thread is in a wait state.
|
||||
kWaiting,
|
||||
// Thread has exited but not yet been killed.
|
||||
kExited,
|
||||
// Thread has been killed.
|
||||
kZombie,
|
||||
};
|
||||
|
||||
// XThread::handle() of the thread.
|
||||
uint32_t thread_handle = 0;
|
||||
// Target XThread, if it has not been destroyed.
|
||||
// TODO(benvanik): hold a ref here to keep zombie threads around?
|
||||
kernel::XThread* thread = nullptr;
|
||||
// Current state of the thread.
|
||||
State state = State::kAlive;
|
||||
// Whether the debugger has forcefully suspended this thread.
|
||||
bool suspended = false;
|
||||
|
||||
// A breakpoint managed by the stepping system, installed as required to
|
||||
// trigger a break at the next instruction.
|
||||
std::unique_ptr<StepBreakpoint> step_breakpoint;
|
||||
// A breakpoint managed by the stepping system, installed as required to
|
||||
// trigger after a step over a disabled breakpoint.
|
||||
// When this breakpoint is hit the breakpoint referenced in
|
||||
// restore_original_breakpoint will be reinstalled.
|
||||
// Breakpoint restore_step_breakpoint;
|
||||
// If the thread is stepping over a disabled breakpoint this will point to
|
||||
// that breakpoint so it can be restored.
|
||||
// Breakpoint* restore_original_breakpoint = nullptr;
|
||||
|
||||
// Last-sampled PPC context.
|
||||
// This is updated whenever the debugger stops.
|
||||
xe::cpu::frontend::PPCContext guest_context;
|
||||
// Last-sampled host x64 context.
|
||||
// This is updated whenever the debugger stops and must be used instead of any
|
||||
// value taken from the StackWalker as it properly respects exception stacks.
|
||||
X64Context host_context;
|
||||
|
||||
// A single frame in a call stack.
|
||||
struct Frame {
|
||||
// PC of the current instruction in host code.
|
||||
uint64_t host_pc = 0;
|
||||
// Base of the function the current host_pc is located within.
|
||||
uint64_t host_function_address = 0;
|
||||
// PC of the current instruction in guest code.
|
||||
// 0 if not a guest address or not known.
|
||||
uint32_t guest_pc = 0;
|
||||
// Base of the function the current guest_pc is located within.
|
||||
uint32_t guest_function_address = 0;
|
||||
// Function the current guest_pc is located within.
|
||||
cpu::Function* guest_function = nullptr;
|
||||
// Name of the function, if known.
|
||||
// TODO(benvanik): string table?
|
||||
char name[256] = {0};
|
||||
};
|
||||
|
||||
// Last-sampled call stack.
|
||||
// This is updated whenever the debugger stops.
|
||||
std::vector<Frame> frames;
|
||||
};
|
||||
|
||||
// Debug event listener interface.
|
||||
// Implementations will receive calls from arbitrary threads and must marshal
|
||||
// them as required.
|
||||
class DebugListener {
|
||||
public:
|
||||
// Handles request for debugger focus (such as when the user requests the
|
||||
// debugger be brought to the front).
|
||||
virtual void OnFocus() = 0;
|
||||
|
||||
// Handles the debugger detaching from the target.
|
||||
// This will be called on shutdown or when the user requests the debug session
|
||||
// end.
|
||||
virtual void OnDetached() = 0;
|
||||
|
||||
// Handles execution being interrupted and transitioning to
|
||||
// ExceutionState::kPaused.
|
||||
virtual void OnExecutionPaused() = 0;
|
||||
// Handles execution continuing when transitioning to
|
||||
// ExecutionState::kRunning or ExecutionState::kStepping.
|
||||
virtual void OnExecutionContinued() = 0;
|
||||
// Handles execution ending when transitioning to ExecutionState::kEnded.
|
||||
virtual void OnExecutionEnded() = 0;
|
||||
|
||||
// Handles step completion events when a requested step operation completes.
|
||||
// The thread is the one that hit its step target. Note that because multiple
|
||||
// threads could be stepping simultaneously (such as a run-to-cursor) use the
|
||||
// thread passed instead of keeping any other state.
|
||||
virtual void OnStepCompleted(xe::kernel::XThread* thread) = 0;
|
||||
|
||||
// Handles breakpoint events as they are hit per-thread.
|
||||
// Breakpoints may be hit during stepping.
|
||||
virtual void OnBreakpointHit(Breakpoint* breakpoint,
|
||||
xe::kernel::XThread* thread) = 0;
|
||||
};
|
||||
|
||||
class Debugger {
|
||||
|
@ -52,63 +169,147 @@ class Debugger {
|
|||
|
||||
Emulator* emulator() const { return emulator_; }
|
||||
|
||||
// True if a debug listener is attached and the debugger is active.
|
||||
bool is_attached() const { return !!debug_listener_; }
|
||||
// Gets the active debug listener, if any.
|
||||
DebugListener* debug_listener() const { return debug_listener_; }
|
||||
// Sets the active debug listener, if any.
|
||||
// This can be used to detach the listener.
|
||||
void set_debug_listener(DebugListener* debug_listener);
|
||||
// Sets a handler that will be called from a random thread when a debugger
|
||||
// listener is required (such as on a breakpoint hit/etc).
|
||||
// Will only be called if the debug listener has not already been specified
|
||||
// with set_debug_listener.
|
||||
void set_debug_listener_request_handler(
|
||||
std::function<DebugListener*(Debugger*)> handler) {
|
||||
debug_listener_handler_ = std::move(handler);
|
||||
}
|
||||
|
||||
// The current execution state of the target.
|
||||
ExecutionState execution_state() const { return execution_state_; }
|
||||
|
||||
// TODO(benvanik): remove/cleanup.
|
||||
bool StartSession();
|
||||
void PreLaunch();
|
||||
void StopSession();
|
||||
void FlushSession();
|
||||
|
||||
// TODO(benvanik): hide.
|
||||
uint8_t* AllocateFunctionData(size_t size);
|
||||
uint8_t* AllocateFunctionTraceData(size_t size);
|
||||
|
||||
bool is_attached() const { return is_attached_; }
|
||||
void set_attached(bool attached);
|
||||
// Returns a list of debugger info for all threads that have ever existed.
|
||||
// This is the preferred way to sample thread state vs. attempting to ask
|
||||
// the kernel.
|
||||
std::vector<ThreadExecutionInfo*> QueryThreadExecutionInfos();
|
||||
// Returns the debugger info for the given thread.
|
||||
ThreadExecutionInfo* QueryThreadExecutionInfo(uint32_t thread_handle);
|
||||
|
||||
ExecutionState execution_state() const { return execution_state_; }
|
||||
|
||||
void DumpThreadStacks();
|
||||
|
||||
int AddBreakpoint(Breakpoint* breakpoint);
|
||||
int RemoveBreakpoint(Breakpoint* breakpoint);
|
||||
void FindBreakpoints(uint32_t address,
|
||||
std::vector<Breakpoint*>* out_breakpoints);
|
||||
// Adds a breakpoint to the debugger and activates it (if enabled).
|
||||
// The given breakpoint will not be owned by the debugger and must remain
|
||||
// allocated so long as it is added.
|
||||
void AddBreakpoint(Breakpoint* breakpoint);
|
||||
// Removes a breakpoint from the debugger and deactivates it.
|
||||
void RemoveBreakpoint(Breakpoint* breakpoint);
|
||||
// Returns all currently registered breakpoints.
|
||||
const std::vector<Breakpoint*>& breakpoints() const { return breakpoints_; }
|
||||
|
||||
// TODO(benvanik): utility functions for modification (make function ignored,
|
||||
// etc).
|
||||
|
||||
void Interrupt();
|
||||
void Continue();
|
||||
void StepOne(uint32_t thread_id);
|
||||
void StepTo(uint32_t thread_id, uint32_t target_pc);
|
||||
// Shows the debug listener, focusing it if it already exists.
|
||||
void Show();
|
||||
|
||||
// Pauses target execution by suspending all threads.
|
||||
// The debug listener will be requested if it has not been attached.
|
||||
void Pause();
|
||||
|
||||
// Continues target execution from wherever it is.
|
||||
// This will cancel any active step operations and resume all threads.
|
||||
void Continue();
|
||||
|
||||
// Steps the given thread a single PPC guest instruction.
|
||||
// If the step is over a branch the branch will be followed.
|
||||
void StepGuestInstruction(uint32_t thread_handle);
|
||||
// Steps the given thread a single x64 host instruction.
|
||||
// If the step is over a branch the branch will be followed.
|
||||
void StepHostInstruction(uint32_t thread_handle);
|
||||
|
||||
public:
|
||||
// TODO(benvanik): hide.
|
||||
void OnThreadCreated(xe::kernel::XThread* thread);
|
||||
void OnThreadExit(xe::kernel::XThread* thread);
|
||||
void OnThreadDestroyed(xe::kernel::XThread* thread);
|
||||
void OnThreadEnteringWait(xe::kernel::XThread* thread);
|
||||
void OnThreadLeavingWait(xe::kernel::XThread* thread);
|
||||
|
||||
void OnFunctionDefined(cpu::Function* function);
|
||||
void OnFunctionDefined(xe::cpu::Function* function);
|
||||
|
||||
void OnBreakpointHit(xe::kernel::XThread* thread, Breakpoint* breakpoint);
|
||||
bool OnUnhandledException(Exception* ex);
|
||||
|
||||
private:
|
||||
// Synchronously demands a debug listener.
|
||||
void DemandDebugListener();
|
||||
|
||||
// Suspends all known threads (except the caller).
|
||||
bool SuspendAllThreads();
|
||||
bool ResumeThread(uint32_t thread_id);
|
||||
// Resumes the given thread.
|
||||
bool ResumeThread(uint32_t thread_handle);
|
||||
// Resumes all known threads (except the caller).
|
||||
bool ResumeAllThreads();
|
||||
// Updates all cached thread execution info (state, call stacks, etc).
|
||||
// The given override thread handle and context will be used in place of
|
||||
// sampled values for that thread.
|
||||
void UpdateThreadExecutionStates(uint32_t override_handle = 0,
|
||||
X64Context* override_context = nullptr);
|
||||
|
||||
// Suspends all breakpoints, uninstalling them as required.
|
||||
// No breakpoints will be triggered until they are resumed.
|
||||
void SuspendAllBreakpoints();
|
||||
// Resumes all breakpoints, re-installing them if required.
|
||||
void ResumeAllBreakpoints();
|
||||
|
||||
static bool ExceptionCallbackThunk(Exception* ex, void* data);
|
||||
bool ExceptionCallback(Exception* ex);
|
||||
void OnStepCompleted(ThreadExecutionInfo* thread_info);
|
||||
void OnBreakpointHit(ThreadExecutionInfo* thread_info,
|
||||
Breakpoint* breakpoint);
|
||||
|
||||
// Calculates the next guest instruction based on the current thread state and
|
||||
// current PC. This will look for branches and other control flow
|
||||
// instructions.
|
||||
uint32_t CalculateNextGuestInstruction(ThreadExecutionInfo* thread_info,
|
||||
uint32_t current_pc);
|
||||
// Calculates the next host instruction based on the current thread state and
|
||||
// current PC. This will look for branches and other control flow
|
||||
// instructions.
|
||||
uint64_t CalculateNextHostInstruction(ThreadExecutionInfo* thread_info,
|
||||
uint64_t current_pc);
|
||||
|
||||
Emulator* emulator_ = nullptr;
|
||||
|
||||
std::unique_ptr<DebugServer> server_;
|
||||
bool is_attached_ = false;
|
||||
xe::threading::Fence attach_fence_;
|
||||
// TODO(benvanik): move this to the CPU backend and remove x64-specific stuff?
|
||||
uintptr_t capstone_handle_ = 0;
|
||||
|
||||
std::function<DebugListener*(Debugger*)> debug_listener_handler_;
|
||||
DebugListener* debug_listener_ = nullptr;
|
||||
|
||||
// TODO(benvanik): cleanup - maybe remove now that in-process?
|
||||
std::wstring functions_path_;
|
||||
std::unique_ptr<ChunkedMappedMemoryWriter> functions_file_;
|
||||
std::wstring functions_trace_path_;
|
||||
std::unique_ptr<ChunkedMappedMemoryWriter> functions_trace_file_;
|
||||
|
||||
std::recursive_mutex mutex_;
|
||||
ExecutionState execution_state_ = ExecutionState::kStopped;
|
||||
|
||||
xe::global_critical_region global_critical_region_;
|
||||
std::multimap<uint32_t, Breakpoint*> breakpoints_;
|
||||
|
||||
ExecutionState execution_state_ = ExecutionState::kPaused;
|
||||
|
||||
// Maps thread ID to state. Updated on thread create, and threads are never
|
||||
// removed.
|
||||
std::map<uint32_t, std::unique_ptr<ThreadExecutionInfo>>
|
||||
thread_execution_infos_;
|
||||
// TODO(benvanik): cleanup/change structures.
|
||||
std::vector<Breakpoint*> breakpoints_;
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
|
|
|
@ -16,4 +16,3 @@ project("xenia-debug")
|
|||
project_root.."/build_tools/third_party/gflags/src",
|
||||
})
|
||||
local_platform_files()
|
||||
recursive_platform_files("proto")
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_PROTO_PACKET_READER_H_
|
||||
#define XENIA_DEBUG_PROTO_PACKET_READER_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/debug/proto/varint.h"
|
||||
#include "xenia/debug/proto/xdp_protocol.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace proto {
|
||||
|
||||
class PacketReader {
|
||||
public:
|
||||
explicit PacketReader(size_t buffer_capacity) : buffer_(buffer_capacity) {}
|
||||
|
||||
const uint8_t* buffer() { return buffer_.data(); }
|
||||
size_t buffer_capacity() const { return buffer_.size(); }
|
||||
size_t buffer_offset() const { return buffer_offset_; }
|
||||
|
||||
void Reset() {
|
||||
buffer_offset_ = 0;
|
||||
buffer_size_ = 0;
|
||||
}
|
||||
|
||||
void AppendBuffer(const uint8_t* buffer, size_t buffer_length) {
|
||||
if (buffer_size_ + buffer_length > buffer_.size()) {
|
||||
size_t new_size =
|
||||
std::max(buffer_.size() * 2,
|
||||
xe::round_up(buffer_size_ + buffer_length, 16 * 1024));
|
||||
buffer_.resize(new_size);
|
||||
}
|
||||
std::memcpy(buffer_.data() + buffer_size_, buffer, buffer_length);
|
||||
buffer_size_ += buffer_length;
|
||||
}
|
||||
|
||||
void Compact() {
|
||||
assert_null(current_packet_);
|
||||
if (!buffer_offset_) {
|
||||
return;
|
||||
}
|
||||
std::memmove(buffer_.data(), buffer_.data() + buffer_offset_,
|
||||
buffer_size_ - buffer_offset_);
|
||||
buffer_size_ -= buffer_offset_;
|
||||
buffer_offset_ = 0;
|
||||
}
|
||||
|
||||
const Packet* Begin() {
|
||||
assert_null(current_packet_);
|
||||
size_t bytes_remaining = buffer_size_ - buffer_offset_;
|
||||
if (bytes_remaining < sizeof(Packet)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto packet =
|
||||
reinterpret_cast<const Packet*>(buffer_.data() + buffer_offset_);
|
||||
if (bytes_remaining < sizeof(Packet) + packet->body_length) {
|
||||
return nullptr;
|
||||
}
|
||||
packet_offset_ = buffer_offset_ + sizeof(Packet);
|
||||
buffer_offset_ += sizeof(Packet) + packet->body_length;
|
||||
current_packet_ = packet;
|
||||
return packet;
|
||||
}
|
||||
|
||||
void End() {
|
||||
assert_not_null(current_packet_);
|
||||
current_packet_ = nullptr;
|
||||
packet_offset_ = 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<PacketReader> ClonePacket() {
|
||||
assert_not_null(current_packet_);
|
||||
size_t length = sizeof(Packet) + current_packet_->body_length;
|
||||
auto clone = std::make_unique<PacketReader>(length);
|
||||
std::memcpy(clone->buffer_.data(), buffer_.data() + buffer_offset_ - length,
|
||||
length);
|
||||
clone->buffer_size_ = length;
|
||||
return clone;
|
||||
}
|
||||
|
||||
const uint8_t* Read(size_t length) {
|
||||
// Can't read into next packet/off of end.
|
||||
assert_not_null(current_packet_);
|
||||
assert_true(packet_offset_ + length <= buffer_offset_);
|
||||
auto ptr = buffer_.data() + packet_offset_;
|
||||
packet_offset_ += length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* Read() {
|
||||
return reinterpret_cast<const T*>(Read(sizeof(T)));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<const T*> ReadArray(size_t count) {
|
||||
std::vector<const T*> entries;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
entries.push_back(Read<T>());
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
void Read(void* buffer, size_t buffer_length) {
|
||||
assert_not_null(current_packet_);
|
||||
auto src = Read(buffer_length);
|
||||
std::memcpy(buffer, src, buffer_length);
|
||||
}
|
||||
|
||||
void ReadString(char* buffer, size_t buffer_length) {
|
||||
assert_not_null(current_packet_);
|
||||
varint_t length;
|
||||
auto src = length.Read(buffer_.data() + packet_offset_);
|
||||
assert_not_null(src);
|
||||
assert_true(length < buffer_length);
|
||||
assert_true(length.size() + length < (buffer_offset_ - packet_offset_));
|
||||
std::memcpy(buffer, src, length);
|
||||
packet_offset_ += length;
|
||||
}
|
||||
|
||||
std::string ReadString() {
|
||||
assert_not_null(current_packet_);
|
||||
varint_t length;
|
||||
auto src = length.Read(buffer_.data() + packet_offset_);
|
||||
assert_not_null(src);
|
||||
assert_true(length.size() + length < (buffer_offset_ - packet_offset_));
|
||||
std::string value;
|
||||
value.resize(length.value());
|
||||
std::memcpy(const_cast<char*>(value.data()), src, length);
|
||||
packet_offset_ += length;
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> buffer_;
|
||||
size_t buffer_offset_ = 0;
|
||||
size_t buffer_size_ = 0;
|
||||
|
||||
const Packet* current_packet_ = nullptr;
|
||||
size_t packet_offset_ = 0;
|
||||
};
|
||||
|
||||
} // namespace proto
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_PROTO_PACKET_READER_H_
|
|
@ -1,98 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_PROTO_PACKET_WRITER_H_
|
||||
#define XENIA_DEBUG_PROTO_PACKET_WRITER_H_
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/debug/proto/varint.h"
|
||||
#include "xenia/debug/proto/xdp_protocol.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace proto {
|
||||
|
||||
class PacketWriter {
|
||||
public:
|
||||
explicit PacketWriter(size_t buffer_capacity) : buffer_(buffer_capacity) {}
|
||||
|
||||
uint8_t* buffer() { return buffer_.data(); }
|
||||
size_t buffer_capacity() const { return buffer_.size(); }
|
||||
size_t buffer_offset() const { return buffer_offset_; }
|
||||
|
||||
void Reset() { buffer_offset_ = 0; }
|
||||
|
||||
void Begin(PacketType packet_type, request_id_t request_id = 0) {
|
||||
assert_null(current_packet_);
|
||||
current_packet_ = Append<Packet>();
|
||||
current_packet_->packet_type = packet_type;
|
||||
current_packet_->request_id = request_id;
|
||||
current_packet_->body_length = 0;
|
||||
}
|
||||
|
||||
void End() {
|
||||
assert_not_null(current_packet_);
|
||||
current_packet_->body_length =
|
||||
uint32_t((buffer_.data() + buffer_offset_) -
|
||||
reinterpret_cast<uint8_t*>(current_packet_) - sizeof(Packet));
|
||||
current_packet_ = nullptr;
|
||||
}
|
||||
|
||||
uint8_t* Append(size_t length) {
|
||||
if (buffer_offset_ + length > buffer_.size()) {
|
||||
size_t new_size = std::max(
|
||||
xe::round_up(buffer_offset_ + length, 16 * 1024), buffer_.size() * 2);
|
||||
buffer_.resize(new_size);
|
||||
}
|
||||
auto ptr = buffer_.data() + buffer_offset_;
|
||||
buffer_offset_ += length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T* Append() {
|
||||
return reinterpret_cast<T*>(Append(sizeof(T)));
|
||||
}
|
||||
|
||||
void Append(const void* buffer, size_t buffer_length) {
|
||||
auto dest = Append(buffer_length);
|
||||
std::memcpy(dest, buffer, buffer_length);
|
||||
}
|
||||
|
||||
void AppendString(const char* value) {
|
||||
size_t value_length = std::strlen(value);
|
||||
varint_t length(value_length);
|
||||
auto dest = Append(length.size() + value_length);
|
||||
dest = length.Write(dest);
|
||||
std::memcpy(dest, value, value_length);
|
||||
}
|
||||
void AppendString(const std::string& value) {
|
||||
varint_t length(value.size());
|
||||
auto dest = Append(length.size() + value.size());
|
||||
dest = length.Write(dest);
|
||||
std::memcpy(dest, value.data(), value.size());
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8_t> buffer_;
|
||||
size_t buffer_offset_ = 0;
|
||||
|
||||
Packet* current_packet_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace proto
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_PROTO_PACKET_WRITER_H_
|
|
@ -1,74 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_PROTO_VARINT_H_
|
||||
#define XENIA_DEBUG_PROTO_VARINT_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace proto {
|
||||
|
||||
struct varint_t {
|
||||
varint_t() = default;
|
||||
varint_t(uint64_t value) : value_(value) {} // NOLINT(runtime/explicit)
|
||||
varint_t(const varint_t& other) : value_(other.value_) {}
|
||||
|
||||
operator uint64_t() const { return value_; }
|
||||
uint64_t value() const { return value_; }
|
||||
|
||||
size_t size() const {
|
||||
uint64_t value = value_;
|
||||
size_t i = 0;
|
||||
do {
|
||||
value >>= 7;
|
||||
++i;
|
||||
} while (value >= 0x80);
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
const uint8_t* Read(const uint8_t* src) {
|
||||
if (!(src[0] & 0x80)) {
|
||||
value_ = src[0];
|
||||
return src + 1;
|
||||
}
|
||||
uint64_t r = src[0] & 0x7F;
|
||||
size_t i;
|
||||
for (i = 1; i < 10; ++i) {
|
||||
r |= (src[i] & 0x7F) << (7 * i);
|
||||
if (!(src[i] & 0x80)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
value_ = r;
|
||||
return src + i + 1;
|
||||
}
|
||||
|
||||
uint8_t* Write(uint8_t* dest) {
|
||||
uint64_t value = value_;
|
||||
size_t i = 0;
|
||||
do {
|
||||
dest[i] = uint8_t(value | 0x80);
|
||||
value >>= 7;
|
||||
++i;
|
||||
} while (value >= 0x80);
|
||||
dest[i] = uint8_t(value);
|
||||
return dest + i + 1;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t value_ = 0;
|
||||
};
|
||||
|
||||
} // namespace proto
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_PROTO_VARINT_H_
|
|
@ -1,210 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_PROTO_XDP_PROTOCOL_H_
|
||||
#define XENIA_DEBUG_PROTO_XDP_PROTOCOL_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "xenia/base/vec128.h"
|
||||
#include "xenia/cpu/frontend/ppc_context.h"
|
||||
#include "xenia/cpu/stack_walker.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace proto {
|
||||
|
||||
enum class PacketType : uint16_t {
|
||||
kUnknown = 0,
|
||||
kGenericResponse = 1,
|
||||
|
||||
kExecutionRequest = 10,
|
||||
kExecutionNotification = 11,
|
||||
|
||||
kModuleListRequest = 20,
|
||||
kModuleListResponse = 21,
|
||||
|
||||
kThreadListRequest = 30,
|
||||
kThreadListResponse = 31,
|
||||
kThreadStatesRequest = 32,
|
||||
kThreadStatesResponse = 33,
|
||||
};
|
||||
|
||||
using request_id_t = uint16_t;
|
||||
|
||||
struct Packet {
|
||||
PacketType packet_type;
|
||||
request_id_t request_id;
|
||||
uint32_t body_length;
|
||||
uint8_t* body() { return reinterpret_cast<uint8_t*>(this) + sizeof(this); }
|
||||
};
|
||||
static_assert(sizeof(Packet) == 8, "Fixed packet size");
|
||||
|
||||
// Generic response to requests (S->C).
|
||||
// Used for any request type that doesn't define its own response.
|
||||
struct GenericResponse {
|
||||
static const PacketType type = PacketType::kGenericResponse;
|
||||
|
||||
enum class Code : uint32_t {
|
||||
// Command was received and successfully acted upon.
|
||||
// If the request is handled asynchronously this only indicates that the
|
||||
// command was recognized.
|
||||
kSuccess = 0,
|
||||
// Command was invalid or could not be executed.
|
||||
// A string will follow this response containing an error message.
|
||||
kError = 1,
|
||||
};
|
||||
|
||||
Code code;
|
||||
|
||||
bool is_success() const { return code == Code::kSuccess; }
|
||||
};
|
||||
|
||||
// Requests execution state changes (C->S).
|
||||
// Response is a GenericResponse, and as execution changes one or more
|
||||
// ExecutionNotification packets will be sent S->C.
|
||||
struct ExecutionRequest {
|
||||
static const PacketType type = PacketType::kExecutionRequest;
|
||||
|
||||
enum class Action : uint32_t {
|
||||
// Continues execution until the next breakpoint.
|
||||
kContinue,
|
||||
// Steps execution by the given step_mode.
|
||||
kStep,
|
||||
// Interrupts execution and breaks.
|
||||
kInterrupt,
|
||||
// Stops execution and causes the server to exit.
|
||||
kExit,
|
||||
};
|
||||
|
||||
enum class StepMode : uint32_t {
|
||||
// Steps a single statement, following branches.
|
||||
kStepOne,
|
||||
// Runs until the given target_pc.
|
||||
kStepTo,
|
||||
};
|
||||
|
||||
Action action;
|
||||
StepMode step_mode;
|
||||
uint32_t thread_id;
|
||||
uint32_t target_pc;
|
||||
};
|
||||
|
||||
// Notifies clients of changes in execution state (S->C).
|
||||
// This is fired asynchronously from any request and possibly due to events
|
||||
// external to the client requested ones.
|
||||
// After stopping clients should query execution state.
|
||||
struct ExecutionNotification {
|
||||
static const PacketType type = PacketType::kExecutionNotification;
|
||||
|
||||
enum class State : uint32_t {
|
||||
// Execution has stopped for stop_reason.
|
||||
kStopped,
|
||||
// Execution has resumed and the target is now running.
|
||||
kRunning,
|
||||
// The target has exited.
|
||||
kExited,
|
||||
};
|
||||
|
||||
enum class Reason : uint32_t {
|
||||
// Stopped because the client requested it with Action::kInterrupt.
|
||||
kInterrupt,
|
||||
// Stopped due to a completed step.
|
||||
kStep,
|
||||
// Stopped at a breakpoint.
|
||||
kBreakpoint,
|
||||
// TODO(benvanik): others? catches? library loads? etc?
|
||||
};
|
||||
|
||||
State current_state;
|
||||
Reason stop_reason;
|
||||
};
|
||||
|
||||
struct ModuleListRequest {
|
||||
static const PacketType type = PacketType::kModuleListRequest;
|
||||
};
|
||||
struct ModuleListResponse {
|
||||
static const PacketType type = PacketType::kModuleListResponse;
|
||||
|
||||
uint32_t count;
|
||||
// ModuleListEntry[count]
|
||||
};
|
||||
struct ModuleListEntry {
|
||||
uint32_t module_handle;
|
||||
uint32_t module_ptr;
|
||||
bool is_kernel_module;
|
||||
char path[256];
|
||||
char name[256];
|
||||
};
|
||||
|
||||
struct ThreadListRequest {
|
||||
static const PacketType type = PacketType::kThreadListRequest;
|
||||
};
|
||||
struct ThreadListResponse {
|
||||
static const PacketType type = PacketType::kThreadListResponse;
|
||||
|
||||
uint32_t count;
|
||||
// ThreadListEntry[count]
|
||||
};
|
||||
struct ThreadListEntry {
|
||||
uint32_t thread_handle;
|
||||
uint32_t thread_id;
|
||||
bool is_host_thread;
|
||||
char name[64];
|
||||
uint32_t exit_code;
|
||||
int32_t priority;
|
||||
uint32_t affinity;
|
||||
uint32_t xapi_thread_startup;
|
||||
uint32_t start_address;
|
||||
uint32_t start_context;
|
||||
uint32_t creation_flags;
|
||||
uint32_t stack_address;
|
||||
uint32_t stack_size;
|
||||
uint32_t stack_base;
|
||||
uint32_t stack_limit;
|
||||
uint32_t pcr_address;
|
||||
uint32_t tls_address;
|
||||
};
|
||||
|
||||
struct ThreadStatesRequest {
|
||||
static const PacketType type = PacketType::kThreadStatesRequest;
|
||||
};
|
||||
struct ThreadStatesResponse {
|
||||
static const PacketType type = PacketType::kThreadStatesResponse;
|
||||
|
||||
uint32_t count;
|
||||
// ThreadStateEntry[count]
|
||||
};
|
||||
struct ThreadStateEntry {
|
||||
uint32_t thread_handle;
|
||||
cpu::frontend::PPCContext guest_context;
|
||||
cpu::X64Context host_context;
|
||||
uint32_t frame_count;
|
||||
// ThreadCallStackFrame[frame_count]
|
||||
};
|
||||
struct ThreadCallStackFrame {
|
||||
// PC of the current instruction in host code.
|
||||
uint64_t host_pc;
|
||||
// Base of the function the current host_pc is located within.
|
||||
uint64_t host_function_address;
|
||||
// PC of the current instruction in guest code.
|
||||
// 0 if not a guest address or not known.
|
||||
uint32_t guest_pc;
|
||||
// Base of the function the current guest_pc is located within.
|
||||
uint32_t guest_function_address;
|
||||
// Name of the function, if known.
|
||||
// TODO(benvanik): string table?
|
||||
char name[256];
|
||||
};
|
||||
|
||||
} // namespace proto
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_PROTO_XDP_PROTOCOL_H_
|
|
@ -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.
|
|
@ -1,88 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/ui/application.h"
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/debug/ui/main_window.h"
|
||||
#include "xenia/profiling.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
Application* current_application_ = nullptr;
|
||||
|
||||
Application* Application::current() {
|
||||
assert_not_null(current_application_);
|
||||
return current_application_;
|
||||
}
|
||||
|
||||
Application::Application() {
|
||||
current_application_ = this;
|
||||
loop_ = xe::ui::Loop::Create();
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
assert_true(current_application_ == this);
|
||||
current_application_ = nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Application> Application::Create() {
|
||||
std::unique_ptr<Application> app(new Application());
|
||||
|
||||
xe::threading::Fence fence;
|
||||
app->loop()->Post([&app, &fence]() {
|
||||
xe::threading::set_name("Win32 Loop");
|
||||
xe::Profiler::ThreadEnter("Win32 Loop");
|
||||
|
||||
if (!app->Initialize()) {
|
||||
xe::FatalError("Failed to initialize application");
|
||||
return;
|
||||
}
|
||||
|
||||
fence.Signal();
|
||||
});
|
||||
fence.Wait();
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
bool Application::Initialize() {
|
||||
// Bind the object model to the client so it'll maintain state.
|
||||
system_ = std::make_unique<model::System>(loop(), &client_);
|
||||
|
||||
// TODO(benvanik): flags and such.
|
||||
if (!client_.Connect("localhost", 9002)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
main_window_ = std::make_unique<MainWindow>(this);
|
||||
if (!main_window_->Initialize()) {
|
||||
XELOGE("Unable to initialize main window");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Application::Quit() {
|
||||
loop_->Quit();
|
||||
|
||||
// TODO(benvanik): proper exit.
|
||||
XELOGI("User-initiated death!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -1,55 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_APPLICATION_H_
|
||||
#define XENIA_DEBUG_UI_APPLICATION_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "xenia/debug/debug_client.h"
|
||||
#include "xenia/debug/ui/model/system.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
class MainWindow;
|
||||
|
||||
class Application {
|
||||
public:
|
||||
~Application();
|
||||
|
||||
static std::unique_ptr<Application> Create();
|
||||
static Application* current();
|
||||
|
||||
xe::ui::Loop* loop() { return loop_.get(); }
|
||||
MainWindow* main_window() const { return main_window_.get(); }
|
||||
DebugClient* client() { return &client_; }
|
||||
model::System* system() const { return system_.get(); }
|
||||
|
||||
void Quit();
|
||||
|
||||
private:
|
||||
Application();
|
||||
|
||||
bool Initialize();
|
||||
|
||||
std::unique_ptr<xe::ui::Loop> loop_;
|
||||
std::unique_ptr<MainWindow> main_window_;
|
||||
DebugClient client_;
|
||||
|
||||
std::unique_ptr<model::System> system_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_APPLICATION_H_
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_CONTROL_H_
|
||||
#define XENIA_DEBUG_UI_CONTROL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "el/elements.h"
|
||||
#include "el/event_handler.h"
|
||||
#include "xenia/debug/ui/application.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
class Control {
|
||||
public:
|
||||
virtual ~Control() = default;
|
||||
|
||||
el::LayoutBox* root_element() { return &root_element_; }
|
||||
xe::ui::Loop* loop() const { return Application::current()->loop(); }
|
||||
DebugClient* client() const { return client_; }
|
||||
model::System* system() const { return Application::current()->system(); }
|
||||
|
||||
virtual el::Element* BuildUI() = 0;
|
||||
|
||||
virtual void Setup(xe::debug::DebugClient* client) = 0;
|
||||
|
||||
protected:
|
||||
Control() = default;
|
||||
|
||||
el::LayoutBox root_element_;
|
||||
std::unique_ptr<el::EventHandler> handler_;
|
||||
xe::debug::DebugClient* client_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_CONTROL_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,153 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_DEBUG_WINDOW_H_
|
||||
#define XENIA_DEBUG_UI_DEBUG_WINDOW_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/x64_context.h"
|
||||
#include "xenia/debug/debugger.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
#include "xenia/ui/menu_item.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
class ImGuiRenderer;
|
||||
|
||||
class DebugWindow : public DebugListener {
|
||||
public:
|
||||
~DebugWindow();
|
||||
|
||||
static std::unique_ptr<DebugWindow> Create(Emulator* emulator,
|
||||
xe::ui::Loop* loop);
|
||||
|
||||
Emulator* emulator() const { return emulator_; }
|
||||
xe::ui::Loop* loop() const { return loop_; }
|
||||
xe::ui::Window* window() const { return window_.get(); }
|
||||
|
||||
void OnFocus() override;
|
||||
void OnDetached() override;
|
||||
void OnExecutionPaused() override;
|
||||
void OnExecutionContinued() override;
|
||||
void OnExecutionEnded() override;
|
||||
void OnStepCompleted(xe::kernel::XThread* thread) override;
|
||||
void OnBreakpointHit(Breakpoint* breakpoint,
|
||||
xe::kernel::XThread* thread) override;
|
||||
|
||||
private:
|
||||
explicit DebugWindow(Emulator* emulator, xe::ui::Loop* loop);
|
||||
bool Initialize();
|
||||
|
||||
void DrawFrame();
|
||||
void DrawToolbar();
|
||||
void DrawFunctionsPane();
|
||||
void DrawSourcePane();
|
||||
void DrawGuestFunctionSource();
|
||||
void DrawMachineCodeSource(const uint8_t* ptr, size_t length);
|
||||
void DrawBreakpointGutterButton(bool has_breakpoint,
|
||||
CodeBreakpoint::AddressType address_type,
|
||||
uint64_t address);
|
||||
void ScrollToSourceIfPcChanged();
|
||||
void DrawRegistersPane();
|
||||
bool DrawRegisterTextBox(int id, uint32_t* value);
|
||||
bool DrawRegisterTextBox(int id, uint64_t* value);
|
||||
bool DrawRegisterTextBox(int id, double* value);
|
||||
bool DrawRegisterTextBoxes(int id, float* value);
|
||||
void DrawThreadsPane();
|
||||
void DrawMemoryPane();
|
||||
void DrawBreakpointsPane();
|
||||
void DrawLogPane();
|
||||
|
||||
void SelectThreadStackFrame(kernel::XThread* thread, size_t stack_frame_index,
|
||||
bool always_navigate);
|
||||
void NavigateToFunction(cpu::Function* function, uint32_t guest_pc = 0,
|
||||
uint64_t host_pc = 0);
|
||||
// void NavigateToMemory(uint64_t address, uint64_t length = 0);
|
||||
// void ToggleLogThreadFocus(thread | nullptr);
|
||||
|
||||
void UpdateCache();
|
||||
|
||||
void CreateCodeBreakpoint(CodeBreakpoint::AddressType address_type,
|
||||
uint64_t address);
|
||||
void DeleteCodeBreakpoint(CodeBreakpoint* breakpoint);
|
||||
void DeleteBreakpoint(Breakpoint* breakpoint);
|
||||
CodeBreakpoint* LookupBreakpointAtAddress(
|
||||
CodeBreakpoint::AddressType address_type, uint64_t address);
|
||||
|
||||
Emulator* emulator_ = nullptr;
|
||||
Debugger* debugger_ = nullptr;
|
||||
xe::ui::Loop* loop_ = nullptr;
|
||||
std::unique_ptr<xe::ui::Window> window_;
|
||||
std::unique_ptr<ImGuiRenderer> imgui_renderer_;
|
||||
uint64_t last_draw_tick_count_ = 0;
|
||||
|
||||
uintptr_t capstone_handle_ = 0;
|
||||
|
||||
// Cached debugger data, updated on every break before a frame is drawn.
|
||||
// Prefer putting stuff here that will be queried either each frame or
|
||||
// multiple times per frame to avoid expensive redundant work.
|
||||
struct ImDataCache {
|
||||
bool is_running = false;
|
||||
std::vector<kernel::object_ref<kernel::XModule>> modules;
|
||||
std::vector<ThreadExecutionInfo*> thread_execution_infos;
|
||||
} cache_;
|
||||
|
||||
enum class RegisterGroup {
|
||||
kGuestGeneral,
|
||||
kGuestFloat,
|
||||
kGuestVector,
|
||||
kHostGeneral,
|
||||
kHostVector,
|
||||
};
|
||||
|
||||
// The current state of the UI. Use this to synchronize multiple parts of the
|
||||
// UI.
|
||||
struct ImState {
|
||||
static const int kRightPaneThreads = 0;
|
||||
static const int kRightPaneMemory = 1;
|
||||
int right_pane_tab = kRightPaneThreads;
|
||||
|
||||
xe::kernel::XThread* thread = nullptr;
|
||||
ThreadExecutionInfo* thread_info = nullptr;
|
||||
size_t thread_stack_frame_index = 0;
|
||||
bool has_changed_thread = false;
|
||||
|
||||
xe::cpu::Function* function = nullptr;
|
||||
uint64_t last_host_pc = 0;
|
||||
bool has_changed_pc = false;
|
||||
int source_display_mode = 3;
|
||||
|
||||
RegisterGroup register_group = RegisterGroup::kGuestGeneral;
|
||||
bool register_input_hex = true;
|
||||
|
||||
struct {
|
||||
char kernel_call_filter[64] = {0};
|
||||
std::vector<std::unique_ptr<Breakpoint>> all_breakpoints;
|
||||
std::unordered_map<uint32_t, CodeBreakpoint*>
|
||||
code_breakpoints_by_guest_address;
|
||||
std::unordered_map<uintptr_t, CodeBreakpoint*>
|
||||
code_breakpoints_by_host_address;
|
||||
} breakpoints;
|
||||
|
||||
xe::kernel::XThread* isolated_log_thread = nullptr;
|
||||
} state_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_DEBUG_WINDOW_H_
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/debug/ui/application.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
int main(const std::vector<std::wstring>& args) {
|
||||
auto app = Application::Create();
|
||||
app->loop()->AwaitQuit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
DEFINE_ENTRY_POINT(L"xenia-debug-ui", L"xenia-debug-ui ??",
|
||||
xe::debug::ui::main);
|
|
@ -1,6 +0,0 @@
|
|||
//{{NO_DEPENDENCIES}}
|
||||
|
||||
#include "third_party\\elemental-forms\\resources.rc"
|
||||
|
||||
//IDR_xe_debug_ui_resources_skin_bg_tile_png RCDATA ".\\resources\\skin\\bg_tile.png"
|
||||
IDR_xe_debug_ui_resources_smaller_skin_skin_tb_txt RCDATA ".\\resources\\smaller_skin\\skin.tb.txt"
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_IMGUI_RENDERER_H_
|
||||
#define XENIA_DEBUG_UI_IMGUI_RENDERER_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "third_party/imgui/imgui.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/ui/gl/circular_buffer.h"
|
||||
#include "xenia/ui/gl/gl_context.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
class ImGuiRenderer {
|
||||
public:
|
||||
ImGuiRenderer(xe::ui::Window* window, xe::ui::GraphicsContext* context);
|
||||
~ImGuiRenderer();
|
||||
|
||||
private:
|
||||
static ImGuiRenderer* global_renderer_;
|
||||
|
||||
void Initialize();
|
||||
void InitializeShaders();
|
||||
void InitializeFontTextures();
|
||||
void Shutdown();
|
||||
|
||||
void RenderDrawLists(ImDrawData* data);
|
||||
|
||||
xe::ui::Window* window_ = nullptr;
|
||||
xe::ui::GraphicsContext* context_ = nullptr;
|
||||
|
||||
GLuint program_ = 0;
|
||||
GLuint vao_ = 0;
|
||||
xe::ui::gl::CircularBuffer vertex_buffer_;
|
||||
xe::ui::gl::CircularBuffer index_buffer_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_IMGUI_RENDERER_H_
|
|
@ -1,197 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/ui/main_window.h"
|
||||
|
||||
#include "el/animation_manager.h"
|
||||
#include "el/io/file_manager.h"
|
||||
#include "el/util/debug.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/ui/gl/gl_context.h"
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
#include "el/io/win32_res_file_system_win.h"
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
using xe::ui::MenuItem;
|
||||
|
||||
const std::wstring kBaseTitle = L"xenia debugger";
|
||||
|
||||
MainWindow::MainWindow(Application* app) : app_(app) {}
|
||||
|
||||
MainWindow::~MainWindow() = default;
|
||||
|
||||
bool MainWindow::Initialize() {
|
||||
client_ = app_->client();
|
||||
|
||||
window_ = xe::ui::Window::Create(app()->loop(), kBaseTitle);
|
||||
if (!window_) {
|
||||
return false;
|
||||
}
|
||||
window_->Initialize();
|
||||
window_->set_context(xe::ui::gl::GLContext::Create(window_.get()));
|
||||
|
||||
#if XE_PLATFORM_WIN32
|
||||
el::io::FileManager::RegisterFileSystem(
|
||||
std::make_unique<el::io::Win32ResFileSystem>(
|
||||
"IDR_xe_debug_ui_resources_"));
|
||||
#endif // XE_PLATFORM_WIN32
|
||||
window_->LoadSkin("smaller_skin/skin.tb.txt");
|
||||
|
||||
window_->on_closed.AddListener(std::bind(&MainWindow::OnClose, this));
|
||||
|
||||
window_->on_key_down.AddListener([this](xe::ui::KeyEvent* e) {
|
||||
bool handled = true;
|
||||
switch (e->key_code()) {
|
||||
case 0x1B: { // VK_ESCAPE
|
||||
// Allow users to escape fullscreen (but not enter it).
|
||||
if (window_->is_fullscreen()) {
|
||||
window_->ToggleFullscreen(false);
|
||||
}
|
||||
} break;
|
||||
|
||||
case 0x70: { // VK_F1
|
||||
LaunchBrowser("http://xenia.jp");
|
||||
} break;
|
||||
|
||||
default: { handled = false; } break;
|
||||
}
|
||||
e->set_handled(handled);
|
||||
});
|
||||
|
||||
// Main menu.
|
||||
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
|
||||
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&File");
|
||||
{
|
||||
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, L"E&xit",
|
||||
L"Alt+F4",
|
||||
[this]() { window_->Close(); }));
|
||||
}
|
||||
main_menu->AddChild(std::move(file_menu));
|
||||
|
||||
// Help menu.
|
||||
auto help_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&Help");
|
||||
{
|
||||
help_menu->AddChild(
|
||||
MenuItem::Create(MenuItem::Type::kString, L"&Website...", L"F1",
|
||||
[]() { LaunchBrowser("http://xenia.jp"); }));
|
||||
help_menu->AddChild(
|
||||
MenuItem::Create(MenuItem::Type::kString, L"&About...",
|
||||
[]() { LaunchBrowser("http://xenia.jp/about/"); }));
|
||||
}
|
||||
main_menu->AddChild(std::move(help_menu));
|
||||
|
||||
window_->set_main_menu(std::move(main_menu));
|
||||
|
||||
window_->Resize(1440, 1200);
|
||||
|
||||
BuildUI();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWindow::BuildUI() {
|
||||
using namespace el::dsl; // NOLINT(build/namespaces)
|
||||
el::AnimationBlocker animation_blocker;
|
||||
|
||||
auto root_element = window_->root_element();
|
||||
form_ = std::make_unique<el::Form>();
|
||||
form_->set_settings(el::FormSettings::kFullScreen);
|
||||
root_element->AddChild(form_.get());
|
||||
|
||||
auto root_node =
|
||||
LayoutBoxNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.distribution(LayoutDistribution::kAvailable)
|
||||
.axis(Axis::kY)
|
||||
.child(
|
||||
LayoutBoxNode()
|
||||
.id("toolbar_box")
|
||||
.gravity(Gravity::kTop | Gravity::kLeftRight)
|
||||
.distribution(LayoutDistribution::kGravity)
|
||||
.distribution_position(LayoutDistributionPosition::kLeftTop)
|
||||
.child(ButtonNode("Pause").id("pause_button"))
|
||||
.child(ButtonNode("Continue").id("resume_button")))
|
||||
.child(
|
||||
SplitContainerNode()
|
||||
.id("split_container")
|
||||
.gravity(Gravity::kAll)
|
||||
.axis(Axis::kX)
|
||||
.fixed_pane(FixedPane::kSecond)
|
||||
.min(128)
|
||||
.value(250)
|
||||
.pane(TabContainerNode()
|
||||
.id("tab_container")
|
||||
.gravity(Gravity::kAll)
|
||||
.align(Align::kTop))
|
||||
.pane(LayoutBoxNode().id("log_box").gravity(Gravity::kAll)));
|
||||
|
||||
form_->LoadNodeTree(root_node);
|
||||
form_->GetElementsById({
|
||||
{TBIDC("split_container"), &ui_.split_container},
|
||||
{TBIDC("toolbar_box"), &ui_.toolbar_box},
|
||||
{TBIDC("tab_container"), &ui_.tab_container},
|
||||
});
|
||||
|
||||
handler_ = std::make_unique<el::EventHandler>(form_.get());
|
||||
handler_->Listen(el::EventType::kClick, TBIDC("pause_button"),
|
||||
[this](const el::Event& ev) {
|
||||
client_->Interrupt();
|
||||
return true;
|
||||
});
|
||||
handler_->Listen(el::EventType::kClick, TBIDC("resume_button"),
|
||||
[this](const el::Event& ev) {
|
||||
client_->Continue();
|
||||
return true;
|
||||
});
|
||||
|
||||
system()->on_execution_state_changed.AddListener(
|
||||
[this]() { UpdateElementState(); });
|
||||
|
||||
ui_.tab_container->tab_bar()->LoadNodeTree(ButtonNode(cpu_view_.name()));
|
||||
ui_.tab_container->content_root()->AddChild(cpu_view_.BuildUI());
|
||||
cpu_view_.Setup(Application::current()->client());
|
||||
|
||||
ui_.tab_container->tab_bar()->LoadNodeTree(ButtonNode(gpu_view_.name()));
|
||||
ui_.tab_container->content_root()->AddChild(gpu_view_.BuildUI());
|
||||
gpu_view_.Setup(Application::current()->client());
|
||||
|
||||
UpdateElementState();
|
||||
|
||||
el::util::ShowDebugInfoSettingsForm(root_element);
|
||||
}
|
||||
|
||||
void MainWindow::UpdateElementState() {
|
||||
bool is_running = client_->execution_state() == ExecutionState::kRunning;
|
||||
|
||||
el::TabContainer* tab_container;
|
||||
el::Button* pause_button;
|
||||
el::Button* resume_button;
|
||||
form_->GetElementsById({
|
||||
{TBIDC("tab_container"), &tab_container},
|
||||
{TBIDC("pause_button"), &pause_button},
|
||||
{TBIDC("resume_button"), &resume_button},
|
||||
});
|
||||
tab_container->set_enabled(!is_running);
|
||||
pause_button->set_enabled(is_running);
|
||||
resume_button->set_enabled(!is_running);
|
||||
}
|
||||
|
||||
void MainWindow::OnClose() { app_->Quit(); }
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -1,63 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_MAIN_WINDOW_H_
|
||||
#define XENIA_DEBUG_UI_MAIN_WINDOW_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "el/elements.h"
|
||||
#include "xenia/debug/ui/application.h"
|
||||
#include "xenia/debug/ui/views/cpu/cpu_view.h"
|
||||
#include "xenia/debug/ui/views/gpu/gpu_view.h"
|
||||
#include "xenia/ui/window.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
class MainWindow {
|
||||
public:
|
||||
explicit MainWindow(Application* app);
|
||||
~MainWindow();
|
||||
|
||||
Application* app() const { return app_; }
|
||||
xe::ui::Loop* loop() const { return app_->loop(); }
|
||||
model::System* system() const { return app_->system(); }
|
||||
|
||||
bool Initialize();
|
||||
|
||||
// void NavigateToCpuView(uint32_t address);
|
||||
|
||||
private:
|
||||
void BuildUI();
|
||||
void UpdateElementState();
|
||||
|
||||
void OnClose();
|
||||
|
||||
Application* app_ = nullptr;
|
||||
xe::debug::DebugClient* client_ = nullptr;
|
||||
|
||||
std::unique_ptr<xe::ui::Window> window_;
|
||||
std::unique_ptr<el::Form> form_;
|
||||
struct {
|
||||
el::SplitContainer* split_container;
|
||||
el::LayoutBox* toolbar_box;
|
||||
el::TabContainer* tab_container;
|
||||
} ui_ = {0};
|
||||
std::unique_ptr<el::EventHandler> handler_;
|
||||
views::cpu::CpuView cpu_view_;
|
||||
views::gpu::GpuView gpu_view_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_MAIN_WINDOW_H_
|
|
@ -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
|
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_MODEL_FUNCTION_H_
|
||||
#define XENIA_DEBUG_UI_MODEL_FUNCTION_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace model {
|
||||
|
||||
class Function {
|
||||
public:
|
||||
};
|
||||
|
||||
} // namespace model
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_MODEL_FUNCTION_H_
|
|
@ -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
|
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_MODEL_MODULE_H_
|
||||
#define XENIA_DEBUG_UI_MODEL_MODULE_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/debug/proto/xdp_protocol.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace model {
|
||||
|
||||
class System;
|
||||
|
||||
class Module {
|
||||
public:
|
||||
explicit Module(System* system) : system_(system) {}
|
||||
|
||||
bool is_dead() const { return is_dead_; }
|
||||
void set_dead(bool is_dead) { is_dead_ = is_dead; }
|
||||
|
||||
uint32_t module_handle() const { return entry_.module_handle; }
|
||||
bool is_kernel_module() const { return entry_.is_kernel_module; }
|
||||
std::string name() const { return entry_.name; }
|
||||
const proto::ModuleListEntry* entry() const { return &entry_; }
|
||||
|
||||
void Update(const proto::ModuleListEntry* entry);
|
||||
|
||||
private:
|
||||
System* system_ = nullptr;
|
||||
bool is_dead_ = false;
|
||||
proto::ModuleListEntry entry_ = {0};
|
||||
};
|
||||
|
||||
} // namespace model
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_MODEL_MODULE_H_
|
|
@ -1,128 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/ui/model/system.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace model {
|
||||
|
||||
using namespace xe::debug::proto; // NOLINT(build/namespaces)
|
||||
|
||||
System::System(xe::ui::Loop* loop, DebugClient* client)
|
||||
: loop_(loop), client_(client) {
|
||||
client_->set_listener(this);
|
||||
client_->set_loop(loop);
|
||||
}
|
||||
|
||||
System::~System() { client_->set_listener(nullptr); }
|
||||
|
||||
ExecutionState System::execution_state() { return client_->execution_state(); }
|
||||
|
||||
std::vector<Module*> System::modules() {
|
||||
std::vector<Module*> result;
|
||||
for (auto& module : modules_) {
|
||||
result.push_back(module.get());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<Thread*> System::threads() {
|
||||
std::vector<Thread*> result;
|
||||
for (auto& thread : threads_) {
|
||||
result.push_back(thread.get());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Module* System::GetModuleByHandle(uint32_t module_handle) {
|
||||
auto it = modules_by_handle_.find(module_handle);
|
||||
return it != modules_by_handle_.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
Thread* System::GetThreadByHandle(uint32_t thread_handle) {
|
||||
auto it = threads_by_handle_.find(thread_handle);
|
||||
return it != threads_by_handle_.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
void System::OnExecutionStateChanged(ExecutionState execution_state) {
|
||||
on_execution_state_changed();
|
||||
}
|
||||
|
||||
void System::OnModulesUpdated(std::vector<const ModuleListEntry*> entries) {
|
||||
std::unordered_set<uint32_t> extra_modules;
|
||||
for (size_t i = 0; i < modules_.size(); ++i) {
|
||||
extra_modules.emplace(modules_[i]->module_handle());
|
||||
}
|
||||
for (auto entry : entries) {
|
||||
auto existing_module = modules_by_handle_.find(entry->module_handle);
|
||||
if (existing_module == modules_by_handle_.end()) {
|
||||
auto module = std::make_unique<Module>(this);
|
||||
module->Update(entry);
|
||||
modules_by_handle_.emplace(entry->module_handle, module.get());
|
||||
modules_.emplace_back(std::move(module));
|
||||
} else {
|
||||
existing_module->second->Update(entry);
|
||||
extra_modules.erase(existing_module->first);
|
||||
}
|
||||
}
|
||||
for (auto module_handle : extra_modules) {
|
||||
auto module = modules_by_handle_.find(module_handle);
|
||||
if (module != modules_by_handle_.end()) {
|
||||
module->second->set_dead(true);
|
||||
}
|
||||
}
|
||||
|
||||
on_modules_updated();
|
||||
}
|
||||
|
||||
void System::OnThreadsUpdated(std::vector<const ThreadListEntry*> entries) {
|
||||
std::unordered_set<uint32_t> extra_threads;
|
||||
for (size_t i = 0; i < threads_.size(); ++i) {
|
||||
extra_threads.emplace(threads_[i]->thread_handle());
|
||||
}
|
||||
for (auto entry : entries) {
|
||||
auto existing_thread = threads_by_handle_.find(entry->thread_handle);
|
||||
if (existing_thread == threads_by_handle_.end()) {
|
||||
auto thread = std::make_unique<Thread>(this);
|
||||
thread->Update(entry);
|
||||
threads_by_handle_.emplace(entry->thread_handle, thread.get());
|
||||
threads_.emplace_back(std::move(thread));
|
||||
} else {
|
||||
existing_thread->second->Update(entry);
|
||||
extra_threads.erase(existing_thread->first);
|
||||
}
|
||||
}
|
||||
for (auto thread_handle : extra_threads) {
|
||||
auto thread = threads_by_handle_.find(thread_handle);
|
||||
if (thread != threads_by_handle_.end()) {
|
||||
thread->second->set_dead(true);
|
||||
}
|
||||
}
|
||||
|
||||
on_threads_updated();
|
||||
}
|
||||
|
||||
void System::OnThreadStateUpdated(
|
||||
uint32_t thread_handle, const ThreadStateEntry* entry,
|
||||
std::vector<const ThreadCallStackFrame*> frames) {
|
||||
auto thread = threads_by_handle_[thread_handle];
|
||||
if (thread != nullptr) {
|
||||
thread->UpdateState(entry, std::move(frames));
|
||||
on_thread_state_updated(thread);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace model
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -1,75 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_MODEL_SYSTEM_H_
|
||||
#define XENIA_DEBUG_UI_MODEL_SYSTEM_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/delegate.h"
|
||||
#include "xenia/debug/debug_client.h"
|
||||
#include "xenia/debug/ui/model/function.h"
|
||||
#include "xenia/debug/ui/model/module.h"
|
||||
#include "xenia/debug/ui/model/thread.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace model {
|
||||
|
||||
class System : public DebugClientListener {
|
||||
public:
|
||||
System(xe::ui::Loop* loop, DebugClient* client);
|
||||
~System() override;
|
||||
|
||||
xe::ui::Loop* loop() const { return loop_; }
|
||||
DebugClient* client() const { return client_; }
|
||||
|
||||
ExecutionState execution_state();
|
||||
|
||||
std::vector<Module*> modules();
|
||||
std::vector<Thread*> threads();
|
||||
|
||||
Module* GetModuleByHandle(uint32_t module_handle);
|
||||
Thread* GetThreadByHandle(uint32_t thread_handle);
|
||||
|
||||
Delegate<void> on_execution_state_changed;
|
||||
Delegate<void> on_modules_updated;
|
||||
Delegate<void> on_threads_updated;
|
||||
Delegate<Thread*> on_thread_state_updated;
|
||||
|
||||
private:
|
||||
void OnExecutionStateChanged(ExecutionState execution_state) override;
|
||||
void OnModulesUpdated(
|
||||
std::vector<const proto::ModuleListEntry*> entries) override;
|
||||
void OnThreadsUpdated(
|
||||
std::vector<const proto::ThreadListEntry*> entries) override;
|
||||
void OnThreadStateUpdated(
|
||||
uint32_t thread_handle, const ThreadStateEntry* entry,
|
||||
std::vector<const ThreadCallStackFrame*> frames) override;
|
||||
|
||||
xe::ui::Loop* loop_ = nullptr;
|
||||
DebugClient* client_ = nullptr;
|
||||
|
||||
std::vector<std::unique_ptr<Module>> modules_;
|
||||
std::unordered_map<uint32_t, Module*> modules_by_handle_;
|
||||
std::vector<std::unique_ptr<Thread>> threads_;
|
||||
std::unordered_map<uint32_t, Thread*> threads_by_handle_;
|
||||
};
|
||||
|
||||
} // namespace model
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_MODEL_SYSTEM_H_
|
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/ui/model/thread.h"
|
||||
|
||||
#include "xenia/debug/ui/model/system.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace model {
|
||||
|
||||
Thread::Thread(System* system) : system_(system) {
|
||||
state_ = memory::AlignedAlloc<proto::ThreadStateEntry>(64);
|
||||
}
|
||||
|
||||
Thread::~Thread() { memory::AlignedFree(state_); }
|
||||
|
||||
std::string Thread::to_string() {
|
||||
std::string value = entry_.name;
|
||||
if (is_host_thread()) {
|
||||
value += " (host)";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void Thread::Update(const proto::ThreadListEntry* entry) {
|
||||
std::memcpy(&entry_, entry, sizeof(entry_));
|
||||
}
|
||||
|
||||
void Thread::UpdateState(
|
||||
const proto::ThreadStateEntry* entry,
|
||||
std::vector<const proto::ThreadCallStackFrame*> frames) {
|
||||
std::memcpy(state_, entry, sizeof(*state_));
|
||||
call_stack_.resize(frames.size());
|
||||
for (size_t i = 0; i < frames.size(); ++i) {
|
||||
std::memcpy(call_stack_.data() + i, frames[i], sizeof(Frame));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace model
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -1,69 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_MODEL_THREAD_H_
|
||||
#define XENIA_DEBUG_UI_MODEL_THREAD_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/debug/proto/xdp_protocol.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace model {
|
||||
|
||||
class System;
|
||||
|
||||
class Thread {
|
||||
public:
|
||||
using Frame = proto::ThreadCallStackFrame;
|
||||
|
||||
explicit Thread(System* system);
|
||||
~Thread();
|
||||
|
||||
bool is_dead() const { return is_dead_; }
|
||||
void set_dead(bool is_dead) { is_dead_ = is_dead; }
|
||||
|
||||
const proto::ThreadListEntry* entry() const { return &entry_; }
|
||||
const proto::ThreadStateEntry* state() const { return state_; }
|
||||
|
||||
uint32_t thread_handle() const { return entry_.thread_handle; }
|
||||
uint32_t thread_id() const { return entry_.thread_id; }
|
||||
bool is_host_thread() const { return entry_.is_host_thread; }
|
||||
std::string name() const { return entry_.name; }
|
||||
|
||||
const cpu::frontend::PPCContext* guest_context() const {
|
||||
return &state_->guest_context;
|
||||
}
|
||||
const cpu::X64Context* host_context() const { return &state_->host_context; }
|
||||
const std::vector<Frame>& call_stack() const { return call_stack_; }
|
||||
|
||||
std::string to_string();
|
||||
|
||||
void Update(const proto::ThreadListEntry* entry);
|
||||
void UpdateState(const proto::ThreadStateEntry* entry,
|
||||
std::vector<const proto::ThreadCallStackFrame*> frames);
|
||||
|
||||
private:
|
||||
System* system_ = nullptr;
|
||||
bool is_dead_ = false;
|
||||
proto::ThreadListEntry entry_ = {0};
|
||||
proto::ThreadStateEntry* state_ = nullptr;
|
||||
std::vector<Frame> call_stack_;
|
||||
};
|
||||
|
||||
} // namespace model
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_MODEL_THREAD_H_
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_VIEW_H_
|
||||
#define XENIA_DEBUG_UI_VIEW_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "el/elements.h"
|
||||
#include "el/event_handler.h"
|
||||
#include "xenia/debug/ui/application.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
class View {
|
||||
public:
|
||||
virtual ~View() = default;
|
||||
|
||||
std::string name() const { return name_; }
|
||||
el::LayoutBox* root_element() { return &root_element_; }
|
||||
xe::ui::Loop* loop() const { return Application::current()->loop(); }
|
||||
DebugClient* client() const { return client_; }
|
||||
model::System* system() const { return Application::current()->system(); }
|
||||
|
||||
virtual el::Element* BuildUI() = 0;
|
||||
|
||||
virtual void Setup(xe::debug::DebugClient* client) = 0;
|
||||
|
||||
protected:
|
||||
explicit View(std::string name) : name_(name) {}
|
||||
|
||||
std::string name_;
|
||||
el::LayoutBox root_element_;
|
||||
std::unique_ptr<el::EventHandler> handler_;
|
||||
xe::debug::DebugClient* client_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_VIEW_H_
|
|
@ -1,152 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/ui/views/cpu/call_stack_control.h"
|
||||
|
||||
#include "el/animation_manager.h"
|
||||
#include "xenia/base/string_buffer.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace views {
|
||||
namespace cpu {
|
||||
|
||||
class CallStackItem : public el::GenericStringItem {
|
||||
public:
|
||||
CallStackItem(size_t ordinal, const ThreadCallStackFrame* frame)
|
||||
: GenericStringItem(""), ordinal_(ordinal), frame_(frame) {
|
||||
StringBuffer sb;
|
||||
if (frame_->guest_pc) {
|
||||
sb.AppendFormat(" %.2lld %.16llX %.8X %s", ordinal_, frame_->host_pc,
|
||||
frame_->guest_pc, frame_->name);
|
||||
} else {
|
||||
sb.AppendFormat(" %.2lld %.16llX %s", ordinal_, frame_->host_pc,
|
||||
frame_->name);
|
||||
}
|
||||
str = sb.to_string();
|
||||
}
|
||||
|
||||
private:
|
||||
size_t ordinal_ = 0;
|
||||
const ThreadCallStackFrame* frame_ = nullptr;
|
||||
};
|
||||
|
||||
class CallStackItemElement : public el::LayoutBox {
|
||||
public:
|
||||
CallStackItemElement(CallStackItem* item, CallStackItemSource* source,
|
||||
el::ListItemObserver* source_viewer, size_t index)
|
||||
: item_(item),
|
||||
source_(source),
|
||||
source_viewer_(source_viewer),
|
||||
index_(index) {
|
||||
set_background_skin(TBIDC("ListItem"));
|
||||
set_axis(el::Axis::kY);
|
||||
set_gravity(el::Gravity::kAll);
|
||||
set_layout_position(el::LayoutPosition::kLeftTop);
|
||||
set_layout_distribution(el::LayoutDistribution::kAvailable);
|
||||
set_layout_distribution_position(el::LayoutDistributionPosition::kLeftTop);
|
||||
set_layout_size(el::LayoutSize::kAvailable);
|
||||
set_paint_overflow_fadeout(false);
|
||||
|
||||
using namespace el::dsl; // NOLINT(build/namespaces)
|
||||
el::AnimationBlocker animation_blocker;
|
||||
|
||||
auto node = LabelNode(item_->str.c_str())
|
||||
.gravity(Gravity::kLeft)
|
||||
.ignore_input(true)
|
||||
.text_align(el::TextAlign::kLeft);
|
||||
content_root()->LoadNodeTree(node);
|
||||
}
|
||||
|
||||
bool OnEvent(const el::Event& ev) override {
|
||||
return el::LayoutBox::OnEvent(ev);
|
||||
}
|
||||
|
||||
private:
|
||||
CallStackItem* item_ = nullptr;
|
||||
CallStackItemSource* source_ = nullptr;
|
||||
el::ListItemObserver* source_viewer_ = nullptr;
|
||||
size_t index_ = 0;
|
||||
};
|
||||
|
||||
class CallStackItemSource : public el::ListItemSourceList<CallStackItem> {
|
||||
public:
|
||||
el::Element* CreateItemElement(size_t index,
|
||||
el::ListItemObserver* viewer) override {
|
||||
return new CallStackItemElement(at(index), this, viewer, index);
|
||||
}
|
||||
};
|
||||
|
||||
CallStackControl::CallStackControl()
|
||||
: item_source_(new CallStackItemSource()) {}
|
||||
|
||||
CallStackControl::~CallStackControl() = default;
|
||||
|
||||
el::Element* CallStackControl::BuildUI() {
|
||||
using namespace el::dsl; // NOLINT(build/namespaces)
|
||||
el::AnimationBlocker animation_blocker;
|
||||
|
||||
auto node =
|
||||
LayoutBoxNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.distribution(LayoutDistribution::kAvailable)
|
||||
.child(ListBoxNode().id("stack_listbox").gravity(Gravity::kAll));
|
||||
|
||||
root_element_.set_gravity(Gravity::kAll);
|
||||
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
|
||||
root_element_.LoadNodeTree(node);
|
||||
|
||||
auto stack_listbox =
|
||||
root_element_.GetElementById<el::ListBox>(TBIDC("stack_listbox"));
|
||||
stack_listbox->set_source(item_source_.get());
|
||||
stack_listbox->scroll_container()->set_scroll_mode(
|
||||
el::ScrollMode::kAutoXAutoY);
|
||||
|
||||
handler_ = std::make_unique<el::EventHandler>(&root_element_);
|
||||
|
||||
return &root_element_;
|
||||
}
|
||||
|
||||
void CallStackControl::Setup(DebugClient* client) {
|
||||
client_ = client;
|
||||
|
||||
system()->on_thread_state_updated.AddListener([this](model::Thread* thread) {
|
||||
if (thread == thread_) {
|
||||
InvalidateCallStack();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CallStackControl::set_thread(model::Thread* thread) {
|
||||
thread_ = thread;
|
||||
InvalidateCallStack();
|
||||
}
|
||||
|
||||
void CallStackControl::InvalidateCallStack() {
|
||||
item_source_->clear();
|
||||
if (!thread_) {
|
||||
return;
|
||||
}
|
||||
auto& call_stack = thread_->call_stack();
|
||||
for (size_t i = 0; i < call_stack.size(); ++i) {
|
||||
auto& frame = call_stack[i];
|
||||
size_t ordinal = call_stack.size() - i - 1;
|
||||
auto item = std::make_unique<CallStackItem>(ordinal, &frame);
|
||||
item->tag.set_integer(static_cast<int>(i));
|
||||
item_source_->push_back(std::move(item));
|
||||
}
|
||||
item_source_->InvokeAllItemsRemoved();
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace views
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_
|
||||
#define XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/debug/ui/control.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace views {
|
||||
namespace cpu {
|
||||
|
||||
class CallStackItemSource;
|
||||
|
||||
class CallStackControl : public Control {
|
||||
public:
|
||||
CallStackControl();
|
||||
~CallStackControl() override;
|
||||
|
||||
el::Element* BuildUI() override;
|
||||
|
||||
void Setup(xe::debug::DebugClient* client) override;
|
||||
|
||||
model::Thread* thread() const { return thread_; }
|
||||
void set_thread(model::Thread* thread);
|
||||
|
||||
private:
|
||||
void InvalidateCallStack();
|
||||
|
||||
model::Thread* thread_ = nullptr;
|
||||
std::unique_ptr<CallStackItemSource> item_source_;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace views
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_
|
|
@ -1,278 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/ui/views/cpu/cpu_view.h"
|
||||
|
||||
#include "el/animation_manager.h"
|
||||
#include "xenia/base/string_buffer.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace views {
|
||||
namespace cpu {
|
||||
|
||||
CpuView::CpuView()
|
||||
: View("CPU"),
|
||||
gr_registers_control_(RegisterSet::kGeneral),
|
||||
fr_registers_control_(RegisterSet::kFloat),
|
||||
vr_registers_control_(RegisterSet::kVector),
|
||||
host_registers_control_(RegisterSet::kHost) {}
|
||||
|
||||
CpuView::~CpuView() = default;
|
||||
|
||||
el::Element* CpuView::BuildUI() {
|
||||
using namespace el::dsl; // NOLINT(build/namespaces)
|
||||
el::AnimationBlocker animation_blocker;
|
||||
|
||||
auto functions_node =
|
||||
LayoutBoxNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.distribution(LayoutDistribution::kAvailable)
|
||||
.axis(Axis::kY)
|
||||
.child(LayoutBoxNode()
|
||||
.gravity(Gravity::kTop | Gravity::kLeftRight)
|
||||
.distribution(LayoutDistribution::kAvailable)
|
||||
.axis(Axis::kX)
|
||||
.skin("button_group")
|
||||
.child(DropDownButtonNode().id("module_dropdown")))
|
||||
.child(ListBoxNode().id("function_listbox").gravity(Gravity::kAll))
|
||||
.child(LayoutBoxNode()
|
||||
.gravity(Gravity::kBottom | Gravity::kLeftRight)
|
||||
.distribution(LayoutDistribution::kAvailable)
|
||||
.axis(Axis::kX)
|
||||
.child(TextBoxNode()
|
||||
.type(EditType::kSearch)
|
||||
.placeholder("Filter")));
|
||||
|
||||
auto register_list_node = ListBoxNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.item("A")
|
||||
.item("A")
|
||||
.item("A")
|
||||
.item("A");
|
||||
auto source_registers_node =
|
||||
TabContainerNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.tab(ButtonNode("GR"), LabelNode("<register list control>")
|
||||
.id("gr_registers_placeholder"))
|
||||
.tab(ButtonNode("FR"), LabelNode("<register list control>")
|
||||
.id("fr_registers_placeholder"))
|
||||
.tab(ButtonNode("VR"), LabelNode("<register list control>")
|
||||
.id("vr_registers_placeholder"))
|
||||
.tab(ButtonNode("X64"), LabelNode("<register list control>")
|
||||
.id("host_registers_placeholder"));
|
||||
|
||||
auto source_tools_node =
|
||||
TabContainerNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.align(Align::kLeft)
|
||||
.tab(ButtonNode("Stack"),
|
||||
LabelNode("<callstack control>").id("call_stack_placeholder"))
|
||||
.tab(ButtonNode("BPs"), LabelNode("BREAKPOINTS"));
|
||||
|
||||
auto source_pane_node =
|
||||
LayoutBoxNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.distribution(LayoutDistribution::kAvailable)
|
||||
.axis(Axis::kY)
|
||||
.child(
|
||||
LayoutBoxNode()
|
||||
.id("source_toolbar")
|
||||
.gravity(Gravity::kTop | Gravity::kLeftRight)
|
||||
.distribution(LayoutDistribution::kGravity)
|
||||
.distribution_position(LayoutDistributionPosition::kLeftTop)
|
||||
.axis(Axis::kX)
|
||||
.child(DropDownButtonNode().id("thread_dropdown")))
|
||||
.child(LayoutBoxNode()
|
||||
.id("source_content")
|
||||
.gravity(Gravity::kAll)
|
||||
.distribution(LayoutDistribution::kAvailable)
|
||||
.child(SplitContainerNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.axis(Axis::kX)
|
||||
.fixed_pane(FixedPane::kSecond)
|
||||
.value(250)
|
||||
.pane(SplitContainerNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.axis(Axis::kY)
|
||||
.fixed_pane(FixedPane::kSecond)
|
||||
.value(240)
|
||||
.pane(LabelNode("<source control>")
|
||||
.id("source_placeholder"))
|
||||
.pane(source_registers_node))
|
||||
.pane(source_tools_node)));
|
||||
|
||||
auto memory_pane_node = LabelNode("MEMORY");
|
||||
|
||||
auto node = SplitContainerNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.axis(Axis::kY)
|
||||
.min(128)
|
||||
.max(512)
|
||||
.value(128)
|
||||
.pane(functions_node)
|
||||
.pane(SplitContainerNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.axis(Axis::kY)
|
||||
.value(800)
|
||||
.pane(source_pane_node)
|
||||
.pane(memory_pane_node));
|
||||
|
||||
root_element_.set_gravity(Gravity::kAll);
|
||||
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
|
||||
root_element_.LoadNodeTree(node);
|
||||
|
||||
el::Label* source_placeholder;
|
||||
el::Label* gr_registers_placeholder;
|
||||
el::Label* fr_registers_placeholder;
|
||||
el::Label* vr_registers_placeholder;
|
||||
el::Label* host_registers_placeholder;
|
||||
el::Label* call_stack_placeholder;
|
||||
root_element_.GetElementsById({
|
||||
{TBIDC("source_placeholder"), &source_placeholder},
|
||||
{TBIDC("gr_registers_placeholder"), &gr_registers_placeholder},
|
||||
{TBIDC("fr_registers_placeholder"), &fr_registers_placeholder},
|
||||
{TBIDC("vr_registers_placeholder"), &vr_registers_placeholder},
|
||||
{TBIDC("host_registers_placeholder"), &host_registers_placeholder},
|
||||
{TBIDC("call_stack_placeholder"), &call_stack_placeholder},
|
||||
});
|
||||
source_placeholder->parent()->ReplaceChild(source_placeholder,
|
||||
source_control_.BuildUI());
|
||||
source_control_.Setup(client_);
|
||||
gr_registers_placeholder->parent()->ReplaceChild(
|
||||
gr_registers_placeholder, gr_registers_control_.BuildUI());
|
||||
gr_registers_control_.Setup(client_);
|
||||
fr_registers_placeholder->parent()->ReplaceChild(
|
||||
fr_registers_placeholder, fr_registers_control_.BuildUI());
|
||||
fr_registers_control_.Setup(client_);
|
||||
vr_registers_placeholder->parent()->ReplaceChild(
|
||||
vr_registers_placeholder, vr_registers_control_.BuildUI());
|
||||
vr_registers_control_.Setup(client_);
|
||||
host_registers_placeholder->parent()->ReplaceChild(
|
||||
host_registers_placeholder, host_registers_control_.BuildUI());
|
||||
host_registers_control_.Setup(client_);
|
||||
call_stack_placeholder->parent()->ReplaceChild(call_stack_placeholder,
|
||||
call_stack_control_.BuildUI());
|
||||
call_stack_control_.Setup(client_);
|
||||
|
||||
handler_ = std::make_unique<el::EventHandler>(&root_element_);
|
||||
|
||||
handler_->Listen(el::EventType::kChanged, TBIDC("module_dropdown"),
|
||||
[this](const el::Event& ev) {
|
||||
UpdateFunctionList();
|
||||
return true;
|
||||
});
|
||||
handler_->Listen(
|
||||
el::EventType::kChanged, TBIDC("thread_dropdown"),
|
||||
[this](const el::Event& ev) {
|
||||
auto thread_dropdown = root_element_.GetElementById<el::DropDownButton>(
|
||||
TBIDC("thread_dropdown"));
|
||||
auto thread_handle = uint32_t(thread_dropdown->selected_item_id());
|
||||
if (thread_handle) {
|
||||
current_thread_ = system()->GetThreadByHandle(thread_handle);
|
||||
} else {
|
||||
current_thread_ = nullptr;
|
||||
}
|
||||
SwitchCurrentThread(current_thread_);
|
||||
return true;
|
||||
});
|
||||
|
||||
return &root_element_;
|
||||
}
|
||||
|
||||
void CpuView::Setup(DebugClient* client) {
|
||||
client_ = client;
|
||||
|
||||
system()->on_execution_state_changed.AddListener(
|
||||
[this]() { UpdateElementState(); });
|
||||
system()->on_modules_updated.AddListener([this]() { UpdateModuleList(); });
|
||||
system()->on_threads_updated.AddListener([this]() { UpdateThreadList(); });
|
||||
}
|
||||
|
||||
void CpuView::UpdateElementState() {
|
||||
root_element_.GetElementsById({
|
||||
//
|
||||
});
|
||||
}
|
||||
|
||||
void CpuView::UpdateModuleList() {
|
||||
el::DropDownButton* module_dropdown;
|
||||
root_element_.GetElementsById({
|
||||
{TBIDC("module_dropdown"), &module_dropdown},
|
||||
});
|
||||
auto module_items = module_dropdown->default_source();
|
||||
auto modules = system()->modules();
|
||||
bool is_first = module_items->size() == 0;
|
||||
for (size_t i = module_items->size(); i < modules.size(); ++i) {
|
||||
auto module = modules[i];
|
||||
auto item = std::make_unique<el::GenericStringItem>(module->name());
|
||||
item->id = module->module_handle();
|
||||
module_items->push_back(std::move(item));
|
||||
}
|
||||
if (is_first) {
|
||||
module_dropdown->set_value(static_cast<int>(module_items->size() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
void CpuView::UpdateFunctionList() {
|
||||
el::DropDownButton* module_dropdown;
|
||||
el::ListBox* function_listbox;
|
||||
root_element_.GetElementsById({
|
||||
{TBIDC("module_dropdown"), &module_dropdown},
|
||||
{TBIDC("function_listbox"), &function_listbox},
|
||||
});
|
||||
auto module_handle = module_dropdown->selected_item_id();
|
||||
auto module = system()->GetModuleByHandle(module_handle);
|
||||
if (!module) {
|
||||
function_listbox->default_source()->clear();
|
||||
return;
|
||||
}
|
||||
// TODO(benvanik): fetch list.
|
||||
}
|
||||
|
||||
void CpuView::UpdateThreadList() {
|
||||
el::DropDownButton* thread_dropdown;
|
||||
root_element_.GetElementsById({
|
||||
{TBIDC("thread_dropdown"), &thread_dropdown},
|
||||
});
|
||||
auto thread_items = thread_dropdown->default_source();
|
||||
auto threads = system()->threads();
|
||||
bool is_first = thread_items->size() == 0;
|
||||
for (size_t i = 0; i < thread_items->size(); ++i) {
|
||||
auto thread = threads[i];
|
||||
auto item = thread_items->at(i);
|
||||
item->str = thread->to_string();
|
||||
}
|
||||
for (size_t i = thread_items->size(); i < threads.size(); ++i) {
|
||||
auto thread = threads[i];
|
||||
auto item = std::make_unique<el::GenericStringItem>(thread->to_string());
|
||||
item->id = thread->thread_handle();
|
||||
thread_items->push_back(std::move(item));
|
||||
}
|
||||
if (is_first) {
|
||||
thread_dropdown->set_value(static_cast<int>(thread_items->size() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
void CpuView::SwitchCurrentThread(model::Thread* thread) {
|
||||
current_thread_ = thread;
|
||||
source_control_.set_thread(thread);
|
||||
gr_registers_control_.set_thread(thread);
|
||||
fr_registers_control_.set_thread(thread);
|
||||
vr_registers_control_.set_thread(thread);
|
||||
host_registers_control_.set_thread(thread);
|
||||
call_stack_control_.set_thread(thread);
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace views
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -1,61 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_VIEWS_CPU_CPU_VIEW_H_
|
||||
#define XENIA_DEBUG_UI_VIEWS_CPU_CPU_VIEW_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/debug/ui/view.h"
|
||||
#include "xenia/debug/ui/views/cpu/call_stack_control.h"
|
||||
#include "xenia/debug/ui/views/cpu/register_list_control.h"
|
||||
#include "xenia/debug/ui/views/cpu/source_control.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace views {
|
||||
namespace cpu {
|
||||
|
||||
class CpuView : public View {
|
||||
public:
|
||||
CpuView();
|
||||
~CpuView() override;
|
||||
|
||||
el::Element* BuildUI() override;
|
||||
|
||||
void Setup(xe::debug::DebugClient* client) override;
|
||||
|
||||
protected:
|
||||
void UpdateElementState();
|
||||
void UpdateModuleList();
|
||||
void UpdateFunctionList();
|
||||
void UpdateThreadList();
|
||||
|
||||
void SwitchCurrentThread(model::Thread* thread);
|
||||
|
||||
// TODO(benvanik): better state machine.
|
||||
model::Thread* current_thread_ = nullptr;
|
||||
|
||||
SourceControl source_control_;
|
||||
RegisterListControl gr_registers_control_;
|
||||
RegisterListControl fr_registers_control_;
|
||||
RegisterListControl vr_registers_control_;
|
||||
RegisterListControl host_registers_control_;
|
||||
CallStackControl call_stack_control_;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace views
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_VIEWS_CPU_CPU_VIEW_H_
|
|
@ -1,325 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/ui/views/cpu/register_list_control.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "el/animation_manager.h"
|
||||
#include "xenia/base/string_buffer.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/cpu/x64_context.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace views {
|
||||
namespace cpu {
|
||||
|
||||
using xe::cpu::frontend::PPCRegister;
|
||||
using xe::cpu::X64Register;
|
||||
|
||||
enum class RegisterType {
|
||||
kControl,
|
||||
kInteger,
|
||||
kFloat,
|
||||
kVector,
|
||||
};
|
||||
|
||||
class RegisterItem : public el::GenericStringItem {
|
||||
public:
|
||||
const char* name() { return name_.c_str(); }
|
||||
|
||||
virtual void set_thread(model::Thread* thread) { thread_ = thread; }
|
||||
|
||||
protected:
|
||||
RegisterItem(RegisterSet set, RegisterType type, int ordinal)
|
||||
: GenericStringItem(""), set_(set), type_(type), ordinal_(ordinal) {
|
||||
tag.set_integer(ordinal);
|
||||
}
|
||||
|
||||
RegisterSet set_;
|
||||
RegisterType type_;
|
||||
int ordinal_;
|
||||
model::Thread* thread_ = nullptr;
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
class GuestRegisterItem : public RegisterItem {
|
||||
public:
|
||||
GuestRegisterItem(RegisterSet set, RegisterType type, PPCRegister reg)
|
||||
: RegisterItem(set, type, static_cast<int>(reg)), reg_(reg) {
|
||||
name_ = xe::cpu::frontend::PPCContext::GetRegisterName(reg_);
|
||||
}
|
||||
|
||||
void set_thread(model::Thread* thread) override {
|
||||
RegisterItem::set_thread(thread);
|
||||
if (thread_) {
|
||||
str = thread_->guest_context()->GetStringFromValue(reg_);
|
||||
} else {
|
||||
str = "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
PPCRegister reg_;
|
||||
};
|
||||
|
||||
class HostRegisterItem : public RegisterItem {
|
||||
public:
|
||||
HostRegisterItem(RegisterSet set, RegisterType type, X64Register reg)
|
||||
: RegisterItem(set, type, static_cast<int>(reg)), reg_(reg) {
|
||||
name_ = xe::cpu::X64Context::GetRegisterName(reg_);
|
||||
}
|
||||
|
||||
void set_thread(model::Thread* thread) override {
|
||||
RegisterItem::set_thread(thread);
|
||||
if (thread_) {
|
||||
str = thread_->host_context()->GetStringFromValue(reg_);
|
||||
} else {
|
||||
str = "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
X64Register reg_;
|
||||
};
|
||||
|
||||
class RegisterItemElement : public el::LayoutBox {
|
||||
public:
|
||||
RegisterItemElement(RegisterItem* item, RegisterItemSource* source,
|
||||
el::ListItemObserver* source_viewer, size_t index)
|
||||
: item_(item),
|
||||
source_(source),
|
||||
source_viewer_(source_viewer),
|
||||
index_(index) {
|
||||
set_background_skin(TBIDC("ListItem"));
|
||||
set_axis(el::Axis::kY);
|
||||
set_gravity(el::Gravity::kAll);
|
||||
set_layout_position(el::LayoutPosition::kLeftTop);
|
||||
set_layout_distribution(el::LayoutDistribution::kAvailable);
|
||||
set_layout_distribution_position(el::LayoutDistributionPosition::kLeftTop);
|
||||
set_layout_size(el::LayoutSize::kAvailable);
|
||||
set_paint_overflow_fadeout(false);
|
||||
|
||||
using namespace el::dsl; // NOLINT(build/namespaces)
|
||||
el::AnimationBlocker animation_blocker;
|
||||
|
||||
auto node = LayoutBoxNode()
|
||||
.axis(Axis::kX)
|
||||
.gravity(Gravity::kLeft)
|
||||
.distribution(LayoutDistribution::kAvailable)
|
||||
.child(LabelNode(item_->name())
|
||||
.ignore_input(true)
|
||||
.width(45_px)
|
||||
.gravity(Gravity::kLeft))
|
||||
.child(TextBoxNode(item_->str.c_str())
|
||||
.gravity(Gravity::kLeftRight));
|
||||
content_root()->LoadNodeTree(node);
|
||||
}
|
||||
|
||||
bool OnEvent(const el::Event& ev) override {
|
||||
return el::LayoutBox::OnEvent(ev);
|
||||
}
|
||||
|
||||
private:
|
||||
RegisterItem* item_ = nullptr;
|
||||
RegisterItemSource* source_ = nullptr;
|
||||
el::ListItemObserver* source_viewer_ = nullptr;
|
||||
size_t index_ = 0;
|
||||
};
|
||||
|
||||
class RegisterItemSource : public el::ListItemSourceList<RegisterItem> {
|
||||
public:
|
||||
el::Element* CreateItemElement(size_t index,
|
||||
el::ListItemObserver* viewer) override {
|
||||
return new RegisterItemElement(at(index), this, viewer, index);
|
||||
}
|
||||
};
|
||||
|
||||
void DefineRegisterItem(RegisterItemSource* item_source, RegisterSet set,
|
||||
RegisterType type, PPCRegister reg) {
|
||||
auto item = std::make_unique<GuestRegisterItem>(set, type, reg);
|
||||
item->tag.set_integer(static_cast<int>(reg));
|
||||
item_source->push_back(std::move(item));
|
||||
}
|
||||
void DefineRegisterItem(RegisterItemSource* item_source, RegisterSet set,
|
||||
RegisterType type, X64Register reg) {
|
||||
auto item = std::make_unique<HostRegisterItem>(set, type, reg);
|
||||
item->tag.set_integer(static_cast<int>(reg));
|
||||
item_source->push_back(std::move(item));
|
||||
}
|
||||
|
||||
RegisterListControl::RegisterListControl(RegisterSet set)
|
||||
: set_(set), item_source_(new RegisterItemSource()) {
|
||||
switch (set_) {
|
||||
case RegisterSet::kGeneral: {
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
PPCRegister::kLR);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
PPCRegister::kCTR);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl,
|
||||
PPCRegister::kXER);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl,
|
||||
PPCRegister::kCR);
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
static_cast<PPCRegister>(
|
||||
static_cast<size_t>(PPCRegister::kR0) + i));
|
||||
}
|
||||
} break;
|
||||
case RegisterSet::kFloat: {
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl,
|
||||
PPCRegister::kFPSCR);
|
||||
for (size_t i = 0; i < 32; ++i) {
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kFloat,
|
||||
static_cast<PPCRegister>(
|
||||
static_cast<size_t>(PPCRegister::kFR0) + i));
|
||||
}
|
||||
} break;
|
||||
case RegisterSet::kVector: {
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl,
|
||||
PPCRegister::kVSCR);
|
||||
for (size_t i = 0; i < 128; ++i) {
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
static_cast<PPCRegister>(
|
||||
static_cast<size_t>(PPCRegister::kVR0) + i));
|
||||
}
|
||||
} break;
|
||||
case RegisterSet::kHost: {
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kRip);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl,
|
||||
X64Register::kEflags);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kRax);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kRcx);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kRdx);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kRbx);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kRsp);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kRbp);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kRsi);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kRdi);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kR8);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kR9);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kR10);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kR11);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kR12);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kR13);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kR14);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
|
||||
X64Register::kR15);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm0);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm1);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm2);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm3);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm4);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm5);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm6);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm7);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm8);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm9);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm10);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm11);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm12);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm13);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm14);
|
||||
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
|
||||
X64Register::kXmm15);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
RegisterListControl::~RegisterListControl() = default;
|
||||
|
||||
el::Element* RegisterListControl::BuildUI() {
|
||||
using namespace el::dsl; // NOLINT(build/namespaces)
|
||||
el::AnimationBlocker animation_blocker;
|
||||
|
||||
auto node =
|
||||
LayoutBoxNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.distribution(LayoutDistribution::kAvailable)
|
||||
.child(ListBoxNode().id("register_listbox").gravity(Gravity::kAll));
|
||||
|
||||
root_element_.set_gravity(Gravity::kAll);
|
||||
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
|
||||
root_element_.LoadNodeTree(node);
|
||||
|
||||
auto register_listbox =
|
||||
root_element_.GetElementById<el::ListBox>(TBIDC("register_listbox"));
|
||||
register_listbox->set_source(item_source_.get());
|
||||
register_listbox->scroll_container()->set_scroll_mode(
|
||||
el::ScrollMode::kAutoXAutoY);
|
||||
|
||||
handler_ = std::make_unique<el::EventHandler>(&root_element_);
|
||||
|
||||
return &root_element_;
|
||||
}
|
||||
|
||||
void RegisterListControl::Setup(DebugClient* client) {
|
||||
client_ = client;
|
||||
|
||||
system()->on_thread_state_updated.AddListener([this](model::Thread* thread) {
|
||||
if (thread == thread_) {
|
||||
InvalidateRegisters();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void RegisterListControl::set_thread(model::Thread* thread) {
|
||||
thread_ = thread;
|
||||
InvalidateRegisters();
|
||||
}
|
||||
|
||||
void RegisterListControl::InvalidateRegisters() {
|
||||
for (size_t i = 0; i < item_source_->size(); ++i) {
|
||||
item_source_->at(i)->set_thread(thread_);
|
||||
}
|
||||
|
||||
auto register_listbox =
|
||||
root_element_.GetElementById<el::ListBox>(TBIDC("register_listbox"));
|
||||
register_listbox->InvalidateList();
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace views
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -1,59 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_VIEWS_CPU_REGISTER_LIST_CONTROL_H_
|
||||
#define XENIA_DEBUG_UI_VIEWS_CPU_REGISTER_LIST_CONTROL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/debug/ui/control.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace views {
|
||||
namespace cpu {
|
||||
|
||||
class RegisterItemSource;
|
||||
|
||||
enum class RegisterSet {
|
||||
kGeneral,
|
||||
kFloat,
|
||||
kVector,
|
||||
kHost,
|
||||
};
|
||||
|
||||
class RegisterListControl : public Control {
|
||||
public:
|
||||
explicit RegisterListControl(RegisterSet set);
|
||||
~RegisterListControl() override;
|
||||
|
||||
el::Element* BuildUI() override;
|
||||
|
||||
void Setup(xe::debug::DebugClient* client) override;
|
||||
|
||||
model::Thread* thread() const { return thread_; }
|
||||
void set_thread(model::Thread* thread);
|
||||
|
||||
private:
|
||||
void InvalidateRegisters();
|
||||
|
||||
RegisterSet set_;
|
||||
model::Thread* thread_ = nullptr;
|
||||
std::unique_ptr<RegisterItemSource> item_source_;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace views
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_VIEWS_CPU_REGISTER_LIST_CONTROL_H_
|
|
@ -1,75 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/ui/views/cpu/source_control.h"
|
||||
|
||||
#include "el/animation_manager.h"
|
||||
#include "xenia/base/string_buffer.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace views {
|
||||
namespace cpu {
|
||||
|
||||
SourceControl::SourceControl() = default;
|
||||
|
||||
SourceControl::~SourceControl() = default;
|
||||
|
||||
el::Element* SourceControl::BuildUI() {
|
||||
using namespace el::dsl; // NOLINT(build/namespaces)
|
||||
el::AnimationBlocker animation_blocker;
|
||||
|
||||
auto node = LayoutBoxNode()
|
||||
.gravity(Gravity::kAll)
|
||||
.distribution(LayoutDistribution::kAvailable)
|
||||
.axis(Axis::kY)
|
||||
.child(LayoutBoxNode()
|
||||
.gravity(Gravity::kTop | Gravity::kLeftRight)
|
||||
.distribution(LayoutDistribution::kGravity)
|
||||
.distribution_position(
|
||||
LayoutDistributionPosition::kLeftTop)
|
||||
.axis(Axis::kX)
|
||||
.child(ButtonNode("A")))
|
||||
.child(TextBoxNode("source!")
|
||||
.id("source_textbox")
|
||||
.gravity(Gravity::kAll)
|
||||
.is_multiline(true)
|
||||
.is_read_only(true));
|
||||
|
||||
root_element_.set_gravity(Gravity::kAll);
|
||||
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
|
||||
root_element_.LoadNodeTree(node);
|
||||
|
||||
handler_ = std::make_unique<el::EventHandler>(&root_element_);
|
||||
|
||||
return &root_element_;
|
||||
}
|
||||
|
||||
void SourceControl::Setup(DebugClient* client) {
|
||||
client_ = client;
|
||||
|
||||
// system()->on_thread_state_updated.AddListener([this](model::Thread* thread)
|
||||
// {
|
||||
// if (thread == thread_) {
|
||||
// InvalidateCallStack();
|
||||
// }
|
||||
//});
|
||||
}
|
||||
|
||||
void SourceControl::set_thread(model::Thread* thread) {
|
||||
thread_ = thread;
|
||||
// InvalidateCallStack();
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace views
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -1,46 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_VIEWS_CPU_SOURCE_CONTROL_H_
|
||||
#define XENIA_DEBUG_UI_VIEWS_CPU_SOURCE_CONTROL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/debug/ui/control.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace views {
|
||||
namespace cpu {
|
||||
|
||||
class SourceControl : public Control {
|
||||
public:
|
||||
SourceControl();
|
||||
~SourceControl() override;
|
||||
|
||||
el::Element* BuildUI() override;
|
||||
|
||||
void Setup(xe::debug::DebugClient* client) override;
|
||||
|
||||
model::Thread* thread() const { return thread_; }
|
||||
void set_thread(model::Thread* thread);
|
||||
|
||||
private:
|
||||
model::Thread* thread_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace views
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_VIEWS_CPU_SOURCE_CONTROL_H_
|
|
@ -1,47 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "el/animation_manager.h"
|
||||
#include "xenia/debug/ui/views/gpu/gpu_view.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace views {
|
||||
namespace gpu {
|
||||
|
||||
GpuView::GpuView() : View("GPU") {}
|
||||
|
||||
GpuView::~GpuView() = default;
|
||||
|
||||
el::Element* GpuView::BuildUI() {
|
||||
using namespace el::dsl; // NOLINT(build/namespaces)
|
||||
el::AnimationBlocker animation_blocker;
|
||||
|
||||
auto node = LabelNode("TODO");
|
||||
|
||||
root_element_.set_gravity(Gravity::kAll);
|
||||
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
|
||||
root_element_.LoadNodeTree(node);
|
||||
root_element_.GetElementsById({
|
||||
//
|
||||
});
|
||||
handler_ = std::make_unique<el::EventHandler>(&root_element_);
|
||||
return &root_element_;
|
||||
}
|
||||
|
||||
void GpuView::Setup(xe::debug::DebugClient* client) {
|
||||
//
|
||||
}
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace views
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
|
@ -1,42 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_UI_VIEWS_GPU_GPU_VIEW_H_
|
||||
#define XENIA_DEBUG_UI_VIEWS_GPU_GPU_VIEW_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/debug/ui/view.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
namespace views {
|
||||
namespace gpu {
|
||||
|
||||
class GpuView : public View {
|
||||
public:
|
||||
GpuView();
|
||||
~GpuView() override;
|
||||
|
||||
el::Element* BuildUI() override;
|
||||
|
||||
void Setup(xe::debug::DebugClient* client) override;
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
} // namespace gpu
|
||||
} // namespace views
|
||||
} // namespace ui
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_UI_VIEWS_GPU_GPU_VIEW_H_
|
|
@ -1,101 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{C5BA52F0-C86B-4817-921C-CCA257FC04BE}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>xedebugui</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v140</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v140</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).Common.props" />
|
||||
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).$(Configuration).props" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).Common.props" />
|
||||
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).$(Configuration).props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>libgflags.lib;libglew.lib;libxenia.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>libgflags.lib;libglew.lib;libxenia.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\base\main.h" />
|
||||
<ClInclude Include="application.h" />
|
||||
<ClInclude Include="main_window.h" />
|
||||
<ClInclude Include="view.h" />
|
||||
<ClInclude Include="views\cpu\cpu_view.h" />
|
||||
<ClInclude Include="views\gpu\gpu_view.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\base\main_win.cc" />
|
||||
<ClCompile Include="application.cc" />
|
||||
<ClCompile Include="main_window.cc" />
|
||||
<ClCompile Include="views\cpu\cpu_view.cc" />
|
||||
<ClCompile Include="views\gpu\gpu_view.cc" />
|
||||
<ClCompile Include="xe-debug-ui.cc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="resources.rc">
|
||||
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)third_party/elemental-forms;$(SolutionDir);.</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir)third_party/elemental-forms;$(SolutionDir);.</AdditionalIncludeDirectories>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
|
@ -1,77 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="src">
|
||||
<UniqueIdentifier>{52d1c314-7464-4640-bee4-59b30d2b2df4}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="src\xenia">
|
||||
<UniqueIdentifier>{f8d1867c-424d-4942-ba97-a7b29fa8c87c}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="src\xenia\debug">
|
||||
<UniqueIdentifier>{1b70e26e-0024-410f-b27f-ceaba190d5a9}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="src\xenia\debug\ui">
|
||||
<UniqueIdentifier>{9b5a4bce-210a-4488-863a-9175f0543d00}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="src\xenia\base">
|
||||
<UniqueIdentifier>{9a5724c2-5473-4d53-93b4-26531201f38d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="src\xenia\debug\ui\elements">
|
||||
<UniqueIdentifier>{f0ac4999-4700-4b41-b73d-c6dc8b23c5e6}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="src\xenia\debug\ui\views">
|
||||
<UniqueIdentifier>{5601b0a2-a720-4d89-9fca-df1637fce0b1}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="src\xenia\debug\ui\views\cpu">
|
||||
<UniqueIdentifier>{5b186e9a-1fef-42f5-b06b-2ac0b10a8a5b}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="src\xenia\debug\ui\views\gpu">
|
||||
<UniqueIdentifier>{c66e9a6b-0947-4610-9b2b-29db99c08e91}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\base\main.h">
|
||||
<Filter>src\xenia\base</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="main_window.h">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="application.h">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="view.h">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="views\gpu\gpu_view.h">
|
||||
<Filter>src\xenia\debug\ui\views\gpu</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="views\cpu\cpu_view.h">
|
||||
<Filter>src\xenia\debug\ui\views\cpu</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\base\main_win.cc">
|
||||
<Filter>src\xenia\base</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="xe-debug-ui.cc">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="main_window.cc">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="application.cc">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="views\gpu\gpu_view.cc">
|
||||
<Filter>src\xenia\debug\ui\views\gpu</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="views\cpu\cpu_view.cc">
|
||||
<Filter>src\xenia\debug\ui\views\cpu</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="resources.rc">
|
||||
<Filter>src\xenia\debug\ui</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "third_party/elemental-forms/src/el/elements.h"
|
||||
#include "xenia/apu/audio_system.h"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/clock.h"
|
||||
|
@ -30,8 +31,6 @@
|
|||
#include "xenia/vfs/devices/stfs_container_device.h"
|
||||
#include "xenia/vfs/virtual_file_system.h"
|
||||
|
||||
#include "el/elements/message_form.h"
|
||||
|
||||
DEFINE_double(time_scalar, 1.0,
|
||||
"Scalar used to speed or slow time (1x, 2x, 1/2x, etc).");
|
||||
|
||||
|
@ -64,6 +63,8 @@ Emulator::~Emulator() {
|
|||
debugger_.reset();
|
||||
|
||||
export_resolver_.reset();
|
||||
|
||||
ExceptionHandler::Uninstall(Emulator::ExceptionCallbackThunk, this);
|
||||
}
|
||||
|
||||
X_STATUS Emulator::Setup(ui::Window* display_window) {
|
||||
|
@ -157,6 +158,9 @@ X_STATUS Emulator::Setup(ui::Window* display_window) {
|
|||
kernel_state_->LoadKernelModule<kernel::xboxkrnl::XboxkrnlModule>();
|
||||
kernel_state_->LoadKernelModule<kernel::xam::XamModule>();
|
||||
|
||||
// Initialize emulator fallback exception handling last.
|
||||
ExceptionHandler::Install(Emulator::ExceptionCallbackThunk, this);
|
||||
|
||||
// Finish initializing the display.
|
||||
display_window_->loop()->PostSynchronous([this]() {
|
||||
xe::ui::GraphicsContextLock context_lock(display_window_->context());
|
||||
|
@ -164,11 +168,6 @@ X_STATUS Emulator::Setup(ui::Window* display_window) {
|
|||
Profiler::set_display(std::move(profiler_display));
|
||||
});
|
||||
|
||||
// Install an exception handler.
|
||||
ExceptionHandler::Initialize();
|
||||
ExceptionHandler::Install(
|
||||
std::bind(&Emulator::ExceptionCallback, this, std::placeholders::_1));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -272,61 +271,65 @@ X_STATUS Emulator::LaunchStfsContainer(std::wstring path) {
|
|||
return CompleteLaunch(path, "game:\\default.xex");
|
||||
}
|
||||
|
||||
bool Emulator::ExceptionCallback(ExceptionHandler::Info* info) {
|
||||
bool Emulator::ExceptionCallbackThunk(Exception* ex, void* data) {
|
||||
return reinterpret_cast<Emulator*>(data)->ExceptionCallback(ex);
|
||||
}
|
||||
|
||||
bool Emulator::ExceptionCallback(Exception* ex) {
|
||||
// Check to see if the exception occurred in guest code.
|
||||
auto code_cache = processor()->backend()->code_cache();
|
||||
auto code_base = code_cache->base_address();
|
||||
auto code_end = code_base + code_cache->total_size();
|
||||
|
||||
if (!debugger()->is_attached() && debugging::IsDebuggerAttached()) {
|
||||
if (!debugger() ||
|
||||
!debugger()->is_attached() && debugging::IsDebuggerAttached()) {
|
||||
// If Xenia's debugger isn't attached but another one is, pass it to that
|
||||
// debugger.
|
||||
return false;
|
||||
} else if (debugger() && debugger()->is_attached()) {
|
||||
// Let the debugger handle this exception. It may decide to continue past it
|
||||
// (if it was a stepping breakpoint, etc).
|
||||
return debugger()->OnUnhandledException(ex);
|
||||
}
|
||||
|
||||
if (info->pc >= code_base && info->pc < code_end) {
|
||||
using namespace xe::kernel;
|
||||
auto global_lock = global_critical_region::AcquireDirect();
|
||||
|
||||
// Within range. Pause the emulator and eat the exception.
|
||||
auto threads = kernel_state()->object_table()->GetObjectsByType<XThread>(
|
||||
XObject::kTypeThread);
|
||||
auto cur_thread = XThread::GetCurrentThread();
|
||||
for (auto thread : threads) {
|
||||
if (!thread->is_guest_thread()) {
|
||||
// Don't pause host threads.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cur_thread == thread.get()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
thread->Suspend(nullptr);
|
||||
}
|
||||
|
||||
if (debugger()->is_attached()) {
|
||||
// TODO: Send the exception to Xenia's debugger
|
||||
}
|
||||
|
||||
// Display a dialog telling the user the guest has crashed.
|
||||
display_window()->loop()->PostSynchronous([&]() {
|
||||
auto msgbox = new el::elements::MessageForm(
|
||||
display_window()->root_element(), "none");
|
||||
msgbox->Show("Uh-oh!",
|
||||
"The guest has crashed.\n\n"
|
||||
"Xenia has now paused itself.");
|
||||
});
|
||||
|
||||
// Now suspend ourself (we should be a guest thread).
|
||||
cur_thread->Suspend(nullptr);
|
||||
|
||||
// We should not arrive here!
|
||||
assert_always();
|
||||
if (!(ex->pc() >= code_base && ex->pc() < code_end)) {
|
||||
// Didn't occur in guest code. Let it pass.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Didn't occur in guest code. Let it pass.
|
||||
auto global_lock = global_critical_region::AcquireDirect();
|
||||
|
||||
// Within range. Pause the emulator and eat the exception.
|
||||
auto threads =
|
||||
kernel_state()->object_table()->GetObjectsByType<kernel::XThread>(
|
||||
kernel::XObject::kTypeThread);
|
||||
auto current_thread = kernel::XThread::GetCurrentThread();
|
||||
for (auto thread : threads) {
|
||||
if (!thread->is_guest_thread()) {
|
||||
// Don't pause host threads.
|
||||
continue;
|
||||
}
|
||||
if (current_thread == thread.get()) {
|
||||
continue;
|
||||
}
|
||||
thread->Suspend(nullptr);
|
||||
}
|
||||
|
||||
// Display a dialog telling the user the guest has crashed.
|
||||
display_window()->loop()->PostSynchronous([&]() {
|
||||
auto message_form = new el::MessageForm(display_window()->root_element(),
|
||||
TBIDC("crash_form"));
|
||||
message_form->Show("Uh-oh!",
|
||||
"The guest has crashed.\n\n"
|
||||
"Xenia has now paused itself.");
|
||||
});
|
||||
|
||||
// Now suspend ourself (we should be a guest thread).
|
||||
assert_true(current_thread->is_guest_thread());
|
||||
current_thread->Suspend(nullptr);
|
||||
|
||||
// We should not arrive here!
|
||||
assert_always();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
{
|
||||
|
|
|
@ -515,8 +515,6 @@ bool DisasmPacketType3(const uint8_t* base_ptr, uint32_t packet,
|
|||
return result;
|
||||
}
|
||||
bool DisasmPacket(const uint8_t* base_ptr, PacketInfo* out_info) {
|
||||
std::memset(out_info, 0, sizeof(PacketInfo));
|
||||
|
||||
const uint32_t packet = xe::load_and_swap<uint32_t>(base_ptr);
|
||||
const uint32_t packet_type = packet >> 30;
|
||||
switch (packet_type) {
|
||||
|
@ -977,7 +975,7 @@ void DrawCommandListUI(xe::ui::Window* window, TracePlayer& player,
|
|||
if (ImGui::Selectable(label, &is_selected)) {
|
||||
player.SeekCommand(i);
|
||||
}
|
||||
ImGui::SameLine(column_width - 60);
|
||||
ImGui::SameLine(column_width - 60.0f);
|
||||
ImGui::Text("%d", i);
|
||||
ImGui::PopID();
|
||||
if (did_seek && target_command == i) {
|
||||
|
@ -2098,7 +2096,7 @@ void DrawPacketDisassemblerUI(xe::ui::Window* window, TracePlayer& player,
|
|||
auto cmd = reinterpret_cast<const PacketEndCommand*>(trace_ptr);
|
||||
trace_ptr += sizeof(*cmd);
|
||||
if (pending_packet) {
|
||||
PacketInfo packet_info;
|
||||
PacketInfo packet_info = {0};
|
||||
if (DisasmPacket(reinterpret_cast<const uint8_t*>(pending_packet) +
|
||||
sizeof(PacketStartCommand),
|
||||
&packet_info)) {
|
||||
|
@ -2107,7 +2105,7 @@ void DrawPacketDisassemblerUI(xe::ui::Window* window, TracePlayer& player,
|
|||
}
|
||||
ImGui::BulletText(packet_info.type_info->name);
|
||||
ImGui::TreePush((const char*)0);
|
||||
for (auto& action : packet_info.actions) {
|
||||
for (auto action : packet_info.actions) {
|
||||
switch (action.type) {
|
||||
case PacketAction::Type::kRegisterWrite: {
|
||||
auto register_info = xe::gpu::RegisterFile::GetRegisterInfo(
|
||||
|
@ -2343,7 +2341,7 @@ static int texture_location, proj_mtx_location;
|
|||
static int position_location, uv_location, colour_location;
|
||||
static size_t vbo_max_size = 20000;
|
||||
static unsigned int vbo_handle, vao_handle;
|
||||
void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count);
|
||||
void ImImpl_RenderDrawLists(ImDrawData* data);
|
||||
void ImImpl_Setup() {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
|
@ -2483,7 +2481,7 @@ void ImImpl_Setup() {
|
|||
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
|
||||
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
|
||||
io.KeyMap[ImGuiKey_UpArrow] = VK_UP;
|
||||
io.KeyMap[ImGuiKey_DownArrow] = VK_UP;
|
||||
io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN;
|
||||
io.KeyMap[ImGuiKey_Home] = VK_HOME;
|
||||
io.KeyMap[ImGuiKey_End] = VK_END;
|
||||
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
|
||||
|
@ -2510,8 +2508,8 @@ void ImImpl_Shutdown() {
|
|||
glDeleteTextures(1, &tex_id);
|
||||
ImGui::Shutdown();
|
||||
}
|
||||
void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) {
|
||||
if (cmd_lists_count == 0) return;
|
||||
void ImImpl_RenderDrawLists(ImDrawData* data) {
|
||||
if (data->CmdListsCount == 0) return;
|
||||
|
||||
// Setup render state: alpha-blending enabled, no face culling, no depth
|
||||
// testing, scissor enabled
|
||||
|
@ -2537,52 +2535,52 @@ void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) {
|
|||
&ortho_projection[0][0]);
|
||||
|
||||
// Grow our buffer according to what we need
|
||||
size_t total_vtx_count = 0;
|
||||
for (int n = 0; n < cmd_lists_count; n++)
|
||||
total_vtx_count += cmd_lists[n]->vtx_buffer.size();
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_handle);
|
||||
size_t neededBufferSize = total_vtx_count * sizeof(ImDrawVert);
|
||||
size_t neededBufferSize = data->TotalVtxCount * sizeof(ImDrawVert);
|
||||
if (neededBufferSize > vbo_max_size) {
|
||||
vbo_max_size = neededBufferSize + 5000; // Grow buffer
|
||||
glBufferData(GL_ARRAY_BUFFER, vbo_max_size, NULL, GL_STREAM_DRAW);
|
||||
}
|
||||
|
||||
// Copy and convert all vertices into a single contiguous buffer
|
||||
unsigned char* buffer_data =
|
||||
(unsigned char*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
|
||||
if (!buffer_data) return;
|
||||
for (int n = 0; n < cmd_lists_count; n++) {
|
||||
const ImDrawList* cmd_list = cmd_lists[n];
|
||||
memcpy(buffer_data, &cmd_list->vtx_buffer[0],
|
||||
cmd_list->vtx_buffer.size() * sizeof(ImDrawVert));
|
||||
buffer_data += cmd_list->vtx_buffer.size() * sizeof(ImDrawVert);
|
||||
}
|
||||
glUnmapBuffer(GL_ARRAY_BUFFER);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(vao_handle);
|
||||
glUseProgram(shader_handle);
|
||||
|
||||
int cmd_offset = 0;
|
||||
GLuint ib;
|
||||
glGenBuffers(1, &ib);
|
||||
|
||||
ImTextureID prev_texture_id = 0;
|
||||
for (int n = 0; n < cmd_lists_count; n++) {
|
||||
const ImDrawList* cmd_list = cmd_lists[n];
|
||||
int vtx_offset = cmd_offset;
|
||||
const ImDrawCmd* pcmd_end = cmd_list->commands.end();
|
||||
for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end;
|
||||
pcmd++) {
|
||||
if (pcmd->texture_id != prev_texture_id) {
|
||||
glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->texture_id);
|
||||
prev_texture_id = pcmd->texture_id;
|
||||
for (int n = 0; n < data->CmdListsCount; n++) {
|
||||
const ImDrawList* cmd_list = data->CmdLists[n];
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
(GLsizeiptr)cmd_list->IdxBuffer.size() * sizeof(ImDrawIdx),
|
||||
(GLvoid*)&cmd_list->IdxBuffer.front(), GL_STREAM_DRAW);
|
||||
// Copy and convert all vertices into a single contiguous buffer
|
||||
unsigned char* buffer_data =
|
||||
(unsigned char*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
|
||||
memcpy(buffer_data, &cmd_list->VtxBuffer[0],
|
||||
cmd_list->VtxBuffer.size() * sizeof(ImDrawVert));
|
||||
glUnmapBuffer(GL_ARRAY_BUFFER);
|
||||
const ImDrawIdx* idx_buffer_offset = 0;
|
||||
for (auto cmd = cmd_list->CmdBuffer.begin();
|
||||
cmd != cmd_list->CmdBuffer.end(); ++cmd) {
|
||||
if (cmd->TextureId != prev_texture_id) {
|
||||
glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)cmd->TextureId);
|
||||
prev_texture_id = cmd->TextureId;
|
||||
}
|
||||
glScissor((int)pcmd->clip_rect.x, (int)(height - pcmd->clip_rect.w),
|
||||
(int)(pcmd->clip_rect.z - pcmd->clip_rect.x),
|
||||
(int)(pcmd->clip_rect.w - pcmd->clip_rect.y));
|
||||
glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count);
|
||||
vtx_offset += pcmd->vtx_count;
|
||||
glScissor((int)cmd->ClipRect.x, (int)(height - cmd->ClipRect.w),
|
||||
(int)(cmd->ClipRect.z - cmd->ClipRect.x),
|
||||
(int)(cmd->ClipRect.w - cmd->ClipRect.y));
|
||||
glDrawElements(GL_TRIANGLES, (GLsizei)cmd->ElemCount, GL_UNSIGNED_SHORT,
|
||||
idx_buffer_offset);
|
||||
idx_buffer_offset += cmd->ElemCount;
|
||||
}
|
||||
cmd_offset = vtx_offset;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
glDeleteBuffers(1, &ib);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
// Restore modified state
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -636,6 +636,10 @@ void XThread::SetActiveCpu(uint32_t cpu_index) {
|
|||
xe::store_and_swap<uint8_t>(pcr + 0x10C, cpu_index);
|
||||
}
|
||||
|
||||
uint32_t XThread::suspend_count() {
|
||||
return guest_object<X_KTHREAD>()->suspend_count;
|
||||
}
|
||||
|
||||
X_STATUS XThread::Resume(uint32_t* out_suspend_count) {
|
||||
--guest_object<X_KTHREAD>()->suspend_count;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -39,6 +39,11 @@ Loop::~Loop() {
|
|||
}
|
||||
|
||||
void Loop::PostSynchronous(std::function<void()> fn) {
|
||||
if (is_on_loop_thread()) {
|
||||
// Prevent deadlock if we are executing on ourselves.
|
||||
fn();
|
||||
return;
|
||||
}
|
||||
xe::threading::Fence fence;
|
||||
Post([&fn, &fence]() {
|
||||
fn();
|
||||
|
|
|
@ -31,6 +31,9 @@ class Loop {
|
|||
Loop();
|
||||
virtual ~Loop();
|
||||
|
||||
// Returns true if the currently executing code is within the loop thread.
|
||||
virtual bool is_on_loop_thread() = 0;
|
||||
|
||||
virtual void Post(std::function<void()> fn) = 0;
|
||||
virtual void PostDelayed(std::function<void()> fn, uint64_t delay_millis) = 0;
|
||||
void PostSynchronous(std::function<void()> fn);
|
||||
|
|
|
@ -87,6 +87,10 @@ void Win32Loop::ThreadMain() {
|
|||
on_quit(&e);
|
||||
}
|
||||
|
||||
bool Win32Loop::is_on_loop_thread() {
|
||||
return thread_id_ == GetCurrentThreadId();
|
||||
}
|
||||
|
||||
void Win32Loop::Post(std::function<void()> fn) {
|
||||
assert_true(thread_id_ != 0);
|
||||
if (!PostThreadMessage(
|
||||
|
|
|
@ -26,6 +26,8 @@ class Win32Loop : public Loop {
|
|||
Win32Loop();
|
||||
~Win32Loop() override;
|
||||
|
||||
bool is_on_loop_thread() override;
|
||||
|
||||
void Post(std::function<void()> fn) override;
|
||||
void PostDelayed(std::function<void()> fn, uint64_t delay_millis) override;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -320,6 +320,10 @@ LRESULT CALLBACK Win32Window::WndProcThunk(HWND hWnd, UINT message,
|
|||
|
||||
LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||
LPARAM lParam) {
|
||||
if (hWnd != hwnd_) {
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
}
|
||||
|
||||
if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) {
|
||||
if (HandleMouse(message, wParam, lParam)) {
|
||||
return 0;
|
||||
|
@ -328,7 +332,6 @@ LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
|||
}
|
||||
} else if (message >= WM_KEYFIRST && message <= WM_KEYLAST) {
|
||||
if (HandleKeyboard(message, wParam, lParam)) {
|
||||
SetFocus(hwnd_);
|
||||
return 0;
|
||||
} else {
|
||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||
|
@ -363,10 +366,14 @@ LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
|||
|
||||
case WM_PAINT: {
|
||||
ValidateRect(hwnd_, nullptr);
|
||||
auto e = UIEvent(this);
|
||||
OnPaint(&e);
|
||||
static bool in_paint = false;
|
||||
if (!in_paint) {
|
||||
in_paint = true;
|
||||
auto e = UIEvent(this);
|
||||
OnPaint(&e);
|
||||
in_paint = false;
|
||||
}
|
||||
return 0; // Ignored because of custom paint.
|
||||
break;
|
||||
}
|
||||
case WM_ERASEBKGND:
|
||||
return 0; // Ignored because of custom paint.
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit b6906423349431e54bc646be61b7c64b13c442c3
|
||||
Subproject commit f0f692cb72e0d603d526db803d45f32ffabfe8bb
|
|
@ -1 +1 @@
|
|||
Subproject commit 14f189b2f62ec2f79875e7c897a42415fe96fde6
|
||||
Subproject commit 127f44c12bc4b1e5b6d7eb90b9a934668b01e8c2
|
|
@ -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.
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue