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
|
By default logs are written to a file with the name of the executable. You can
|
||||||
override this with `--log_file=log.txt`.
|
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
|
||||||
|
|
||||||
Linux support is extremely experimental and incomplete.
|
Linux support is extremely experimental and incomplete.
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "xenia/app/emulator_window.h"
|
#include "xenia/app/emulator_window.h"
|
||||||
|
|
||||||
|
#include "third_party/elemental-forms/src/el/elements.h"
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
|
@ -105,6 +106,10 @@ bool EmulatorWindow::Initialize() {
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
case 0x13: { // VK_PAUSE
|
||||||
|
CpuBreakIntoDebugger();
|
||||||
|
} break;
|
||||||
|
|
||||||
case 0x70: { // VK_F1
|
case 0x70: { // VK_F1
|
||||||
ShowHelpWebsite();
|
ShowHelpWebsite();
|
||||||
} break;
|
} break;
|
||||||
|
@ -147,6 +152,12 @@ bool EmulatorWindow::Initialize() {
|
||||||
L"&Pause/Resume Profiler", L"`",
|
L"&Pause/Resume Profiler", L"`",
|
||||||
[]() { Profiler::TogglePause(); }));
|
[]() { 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));
|
main_menu->AddChild(std::move(cpu_menu));
|
||||||
|
|
||||||
// GPU menu.
|
// GPU menu.
|
||||||
|
@ -207,6 +218,25 @@ void EmulatorWindow::CpuTimeScalarSetDouble() {
|
||||||
UpdateTitle();
|
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() {
|
void EmulatorWindow::GpuTraceFrame() {
|
||||||
emulator()->graphics_system()->RequestFrameTrace();
|
emulator()->graphics_system()->RequestFrameTrace();
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ class EmulatorWindow {
|
||||||
void CpuTimeScalarReset();
|
void CpuTimeScalarReset();
|
||||||
void CpuTimeScalarSetHalf();
|
void CpuTimeScalarSetHalf();
|
||||||
void CpuTimeScalarSetDouble();
|
void CpuTimeScalarSetDouble();
|
||||||
|
void CpuBreakIntoDebugger();
|
||||||
void GpuTraceFrame();
|
void GpuTraceFrame();
|
||||||
void GpuClearCaches();
|
void GpuClearCaches();
|
||||||
void ToggleFullscreen();
|
void ToggleFullscreen();
|
||||||
|
|
|
@ -10,6 +10,7 @@ project("xenia-app")
|
||||||
links({
|
links({
|
||||||
"elemental-forms",
|
"elemental-forms",
|
||||||
"gflags",
|
"gflags",
|
||||||
|
"imgui",
|
||||||
"xenia-apu",
|
"xenia-apu",
|
||||||
"xenia-apu-nop",
|
"xenia-apu-nop",
|
||||||
"xenia-base",
|
"xenia-base",
|
||||||
|
@ -17,6 +18,7 @@ project("xenia-app")
|
||||||
"xenia-cpu",
|
"xenia-cpu",
|
||||||
"xenia-cpu-backend-x64",
|
"xenia-cpu-backend-x64",
|
||||||
"xenia-debug",
|
"xenia-debug",
|
||||||
|
"xenia-debug-ui",
|
||||||
"xenia-gpu",
|
"xenia-gpu",
|
||||||
"xenia-gpu-gl4",
|
"xenia-gpu-gl4",
|
||||||
"xenia-hid-nop",
|
"xenia-hid-nop",
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "xenia/app/emulator_window.h"
|
#include "xenia/app/emulator_window.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main.h"
|
#include "xenia/base/main.h"
|
||||||
|
#include "xenia/debug/ui/debug_window.h"
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
#include "xenia/profiling.h"
|
#include "xenia/profiling.h"
|
||||||
#include "xenia/ui/file_picker.h"
|
#include "xenia/ui/file_picker.h"
|
||||||
|
@ -39,6 +40,27 @@ int xenia_main(const std::vector<std::wstring>& args) {
|
||||||
return 1;
|
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.
|
// Grab path from the flag or unnamed argument.
|
||||||
std::wstring path;
|
std::wstring path;
|
||||||
if (!FLAGS_target.empty() || args.size() >= 2) {
|
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();
|
emulator->display_window()->loop()->AwaitQuit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug_window.reset();
|
||||||
emulator.reset();
|
emulator.reset();
|
||||||
emulator_window.reset();
|
emulator_window.reset();
|
||||||
|
|
||||||
|
|
|
@ -13,37 +13,61 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/x64_context.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
class ExceptionHandler {
|
|
||||||
|
class Exception {
|
||||||
public:
|
public:
|
||||||
struct Info {
|
enum class Code {
|
||||||
enum {
|
kInvalidException = 0,
|
||||||
kInvalidException = 0,
|
kAccessViolation,
|
||||||
kAccessViolation,
|
kIllegalInstruction,
|
||||||
} 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.
|
|
||||||
};
|
};
|
||||||
typedef std::function<bool(Info* ex_info)> Handler;
|
|
||||||
|
|
||||||
// Static initialization. Only call this once!
|
void InitializeAccessViolation(X64Context* thread_context,
|
||||||
static bool Initialize();
|
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
|
Code code() const { return code_; }
|
||||||
// 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);
|
|
||||||
|
|
||||||
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:
|
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/exception_handler.h"
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/platform_win.h"
|
#include "xenia/base/platform_win.h"
|
||||||
|
|
||||||
namespace xe {
|
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) {
|
LONG CALLBACK ExceptionHandlerCallback(PEXCEPTION_POINTERS ex_info) {
|
||||||
// Visual Studio SetThreadName
|
// Visual Studio SetThreadName.
|
||||||
if (ex_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
|
if (ex_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto code = ex_info->ExceptionRecord->ExceptionCode;
|
// TODO(benvanik): avoid this by mapping X64Context virtual?
|
||||||
ExceptionHandler::Info info;
|
X64Context thread_context;
|
||||||
info.pc = ex_info->ContextRecord->Rip;
|
thread_context.rip = ex_info->ContextRecord->Rip;
|
||||||
info.thread_context = ex_info->ContextRecord;
|
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) {
|
// http://msdn.microsoft.com/en-us/library/ms679331(v=vs.85).aspx
|
||||||
case STATUS_ACCESS_VIOLATION:
|
// http://msdn.microsoft.com/en-us/library/aa363082(v=vs.85).aspx
|
||||||
info.code = ExceptionHandler::Info::kAccessViolation;
|
Exception ex;
|
||||||
info.fault_address = ex_info->ExceptionRecord->ExceptionInformation[1];
|
switch (ex_info->ExceptionRecord->ExceptionCode) {
|
||||||
|
case STATUS_ILLEGAL_INSTRUCTION:
|
||||||
|
ex.InitializeIllegalInstruction(&thread_context);
|
||||||
break;
|
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.
|
for (size_t i = 0; i < xe::countof(handlers_) && handlers_[i].first; ++i) {
|
||||||
if (info.code != ExceptionHandler::Info::kInvalidException) {
|
if (handlers_[i].first(&ex, handlers_[i].second)) {
|
||||||
for (auto handler : ExceptionHandler::handlers()) {
|
// Exception handled.
|
||||||
if (handler(&info)) {
|
// TODO(benvanik): update all thread state? Dirty flags?
|
||||||
// Exception handled.
|
ex_info->ContextRecord->Rip = thread_context.rip;
|
||||||
return EXCEPTION_CONTINUE_EXECUTION;
|
return EXCEPTION_CONTINUE_EXECUTION;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return EXCEPTION_CONTINUE_SEARCH;
|
return EXCEPTION_CONTINUE_SEARCH;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExceptionHandler::Initialize() {
|
void ExceptionHandler::Install(Handler fn, void* data) {
|
||||||
AddVectoredExceptionHandler(0, ExceptionHandlerCallback);
|
if (!veh_handle_) {
|
||||||
|
veh_handle_ = AddVectoredExceptionHandler(1, ExceptionHandlerCallback);
|
||||||
|
|
||||||
// TODO: Do we need a continue handler if a debugger is attached?
|
if (IsDebuggerPresent()) {
|
||||||
return true;
|
// 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) {
|
void ExceptionHandler::Uninstall(Handler fn, void* data) {
|
||||||
handlers_.push_back(fn);
|
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!
|
bool has_any = false;
|
||||||
return 0;
|
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
|
#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.
|
// Utilities for SSE values.
|
||||||
template <int N>
|
template <int N>
|
||||||
float m128_f32(const __m128& v) {
|
float m128_f32(const __m128& v) {
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <codecvt>
|
#include <codecvt>
|
||||||
#endif // XE_PLATFORM_LINUX
|
#endif // XE_PLATFORM_LINUX
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <locale>
|
#include <locale>
|
||||||
|
|
||||||
|
@ -25,7 +26,7 @@ std::string to_string(const std::wstring& source) {
|
||||||
#if NO_CODECVT
|
#if NO_CODECVT
|
||||||
return std::string(source.begin(), source.end());
|
return std::string(source.begin(), source.end());
|
||||||
#else
|
#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);
|
return converter.to_bytes(source);
|
||||||
#endif // XE_PLATFORM_LINUX
|
#endif // XE_PLATFORM_LINUX
|
||||||
}
|
}
|
||||||
|
@ -34,7 +35,7 @@ std::wstring to_wstring(const std::string& source) {
|
||||||
#if NO_CODECVT
|
#if NO_CODECVT
|
||||||
return std::wstring(source.begin(), source.end());
|
return std::wstring(source.begin(), source.end());
|
||||||
#else
|
#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);
|
return converter.from_bytes(source);
|
||||||
#endif // XE_PLATFORM_LINUX
|
#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
|
} // namespace xe
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "xenia/base/platform.h"
|
#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,
|
std::wstring find_base_path(const std::wstring& path,
|
||||||
wchar_t sep = xe::kPathSeparator);
|
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
|
} // namespace xe
|
||||||
|
|
||||||
#endif // XENIA_BASE_STRING_H_
|
#endif // XENIA_BASE_STRING_H_
|
||||||
|
|
|
@ -19,6 +19,7 @@ namespace xe {
|
||||||
StringBuffer::StringBuffer(size_t initial_capacity) {
|
StringBuffer::StringBuffer(size_t initial_capacity) {
|
||||||
buffer_capacity_ = std::max(initial_capacity, static_cast<size_t>(16 * 1024));
|
buffer_capacity_ = std::max(initial_capacity, static_cast<size_t>(16 * 1024));
|
||||||
buffer_ = reinterpret_cast<char*>(malloc(buffer_capacity_));
|
buffer_ = reinterpret_cast<char*>(malloc(buffer_capacity_));
|
||||||
|
buffer_[0] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuffer::~StringBuffer() {
|
StringBuffer::~StringBuffer() {
|
||||||
|
|
|
@ -33,6 +33,15 @@ inline std::string to_hex_string(uint64_t value) {
|
||||||
return std::string(buffer);
|
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) {
|
inline std::string to_hex_string(double value) {
|
||||||
union {
|
union {
|
||||||
uint64_t ui;
|
uint64_t ui;
|
||||||
|
@ -57,12 +66,46 @@ inline std::string to_hex_string(const __m128& value) {
|
||||||
return std::string(buffer);
|
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>
|
template <typename T>
|
||||||
inline T from_string(const char* value);
|
inline T from_string(const char* value, bool force_hex = false);
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline uint64_t from_string<uint64_t>(const char* value) {
|
inline int32_t from_string<int32_t>(const char* value, bool force_hex) {
|
||||||
if (std::strchr(value, 'h') != nullptr) {
|
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);
|
return std::strtoull(value, nullptr, 16);
|
||||||
} else {
|
} else {
|
||||||
return std::strtoull(value, nullptr, 0);
|
return std::strtoull(value, nullptr, 0);
|
||||||
|
@ -70,23 +113,38 @@ inline uint64_t from_string<uint64_t>(const char* value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline double from_string<double>(const char* value) {
|
inline float from_string<float>(const char* value, bool force_hex) {
|
||||||
if (std::strstr(value, "0x") == value || std::strchr(value, 'h') != nullptr) {
|
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 {
|
union {
|
||||||
uint64_t ui;
|
uint64_t ui;
|
||||||
double dbl;
|
double dbl;
|
||||||
} v;
|
} v;
|
||||||
v.ui = from_string<uint64_t>(value);
|
v.ui = from_string<uint64_t>(value, force_hex);
|
||||||
return v.dbl;
|
return v.dbl;
|
||||||
}
|
}
|
||||||
return std::strtod(value, nullptr);
|
return std::strtod(value, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
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;
|
vec128_t v;
|
||||||
char* p = const_cast<char*>(value);
|
char* p = const_cast<char*>(value);
|
||||||
bool hex_mode = false;
|
bool hex_mode = force_hex;
|
||||||
if (*p == '[') {
|
if (*p == '[') {
|
||||||
hex_mode = true;
|
hex_mode = true;
|
||||||
++p;
|
++p;
|
||||||
|
@ -119,10 +177,10 @@ inline vec128_t from_string<vec128_t>(const char* value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
inline __m128 from_string<__m128>(const char* value) {
|
inline __m128 from_string<__m128>(const char* value, bool force_hex) {
|
||||||
__m128 v;
|
__m128 v;
|
||||||
char* p = const_cast<char*>(value);
|
char* p = const_cast<char*>(value);
|
||||||
bool hex_mode = false;
|
bool hex_mode = force_hex;
|
||||||
if (*p == '[') {
|
if (*p == '[') {
|
||||||
hex_mode = true;
|
hex_mode = true;
|
||||||
++p;
|
++p;
|
||||||
|
@ -155,8 +213,8 @@ inline __m128 from_string<__m128>(const char* value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
inline T from_string(const std::string& value) {
|
inline T from_string(const std::string& value, bool force_hex = false) {
|
||||||
return from_string<T>(value.c_str());
|
return from_string<T>(value.c_str(), force_hex);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace string_util
|
} // 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/assert.h"
|
||||||
#include "xenia/base/string_util.h"
|
#include "xenia/base/string_util.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
|
||||||
|
|
||||||
// NOTE: this order matches 1:1 with the X64Register enum.
|
// NOTE: this order matches 1:1 with the X64Register enum.
|
||||||
static const char* kRegisterNames[] = {
|
static const char* kRegisterNames[] = {
|
||||||
|
@ -28,25 +27,26 @@ const char* X64Context::GetRegisterName(X64Register reg) {
|
||||||
return kRegisterNames[static_cast<int>(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) {
|
switch (reg) {
|
||||||
case X64Register::kRip:
|
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:
|
case X64Register::kEflags:
|
||||||
return string_util::to_hex_string(eflags);
|
return hex ? string_util::to_hex_string(eflags) : std::to_string(eflags);
|
||||||
default:
|
default:
|
||||||
if (static_cast<int>(reg) >= static_cast<int>(X64Register::kRax) &&
|
if (static_cast<int>(reg) >= static_cast<int>(X64Register::kRax) &&
|
||||||
static_cast<int>(reg) <= static_cast<int>(X64Register::kR15)) {
|
static_cast<int>(reg) <= static_cast<int>(X64Register::kR15)) {
|
||||||
return string_util::to_hex_string(
|
auto value = int_registers[static_cast<int>(reg) -
|
||||||
int_registers.values[static_cast<int>(reg) -
|
static_cast<int>(X64Register::kRax)];
|
||||||
static_cast<int>(X64Register::kRax)]);
|
return hex ? string_util::to_hex_string(value) : std::to_string(value);
|
||||||
} else if (static_cast<int>(reg) >=
|
} else if (static_cast<int>(reg) >=
|
||||||
static_cast<int>(X64Register::kXmm0) &&
|
static_cast<int>(X64Register::kXmm0) &&
|
||||||
static_cast<int>(reg) <=
|
static_cast<int>(reg) <=
|
||||||
static_cast<int>(X64Register::kXmm15)) {
|
static_cast<int>(X64Register::kXmm15)) {
|
||||||
return string_util::to_hex_string(
|
auto value = xmm_registers[static_cast<int>(reg) -
|
||||||
xmm_registers.values[static_cast<int>(reg) -
|
static_cast<int>(X64Register::kXmm0)];
|
||||||
static_cast<int>(X64Register::kXmm0)]);
|
return hex ? string_util::to_hex_string(value)
|
||||||
|
: string_util::to_string(value);
|
||||||
} else {
|
} else {
|
||||||
assert_unhandled_case(reg);
|
assert_unhandled_case(reg);
|
||||||
return "";
|
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.
|
// TODO(benvanik): set value from string.
|
||||||
assert_always(false);
|
assert_always(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace cpu
|
|
||||||
} // namespace xe
|
} // namespace xe
|
|
@ -7,14 +7,13 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef XENIA_CPU_X64_CONTEXT_H_
|
#ifndef XENIA_BASE_X64_CONTEXT_H_
|
||||||
#define XENIA_CPU_X64_CONTEXT_H_
|
#define XENIA_BASE_X64_CONTEXT_H_
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
|
||||||
|
|
||||||
enum class X64Register {
|
enum class X64Register {
|
||||||
// NOTE: this order matches 1:1 with the order in the X64Context.
|
// NOTE: this order matches 1:1 with the order in the X64Context.
|
||||||
|
@ -55,8 +54,8 @@ enum class X64Register {
|
||||||
kXmm15,
|
kXmm15,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct X64Context {
|
class X64Context {
|
||||||
// TODO(benvanik): x64 registers.
|
public:
|
||||||
uint64_t rip;
|
uint64_t rip;
|
||||||
uint32_t eflags;
|
uint32_t eflags;
|
||||||
union {
|
union {
|
||||||
|
@ -78,8 +77,9 @@ struct X64Context {
|
||||||
uint64_t r14;
|
uint64_t r14;
|
||||||
uint64_t r15;
|
uint64_t r15;
|
||||||
};
|
};
|
||||||
uint64_t values[16];
|
uint64_t int_registers[16];
|
||||||
} int_registers;
|
};
|
||||||
|
|
||||||
union {
|
union {
|
||||||
struct {
|
struct {
|
||||||
__m128 xmm0;
|
__m128 xmm0;
|
||||||
|
@ -99,15 +99,14 @@ struct X64Context {
|
||||||
__m128 xmm14;
|
__m128 xmm14;
|
||||||
__m128 xmm15;
|
__m128 xmm15;
|
||||||
};
|
};
|
||||||
__m128 values[16];
|
__m128 xmm_registers[16];
|
||||||
} xmm_registers;
|
};
|
||||||
|
|
||||||
static const char* GetRegisterName(X64Register reg);
|
static const char* GetRegisterName(X64Register reg);
|
||||||
std::string GetStringFromValue(X64Register reg) const;
|
std::string GetStringFromValue(X64Register reg, bool hex) const;
|
||||||
void SetValueFromString(X64Register reg, std::string value);
|
void SetValueFromString(X64Register reg, std::string value, bool hex);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cpu
|
|
||||||
} // namespace xe
|
} // 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");
|
"Log debugprint traps to the active debugger");
|
||||||
DEFINE_bool(ignore_undefined_externs, true,
|
DEFINE_bool(ignore_undefined_externs, true,
|
||||||
"Don't exit when an undefined extern is called.");
|
"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 xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
|
@ -239,7 +241,7 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t* out_stack_size) {
|
||||||
add(rsp, (uint32_t)stack_size);
|
add(rsp, (uint32_t)stack_size);
|
||||||
ret();
|
ret();
|
||||||
|
|
||||||
if (FLAGS_debug) {
|
if (FLAGS_emit_source_annotations) {
|
||||||
nop();
|
nop();
|
||||||
nop();
|
nop();
|
||||||
nop();
|
nop();
|
||||||
|
@ -254,9 +256,9 @@ void X64Emitter::MarkSourceOffset(const Instr* i) {
|
||||||
auto entry = source_map_arena_.Alloc<SourceMapEntry>();
|
auto entry = source_map_arena_.Alloc<SourceMapEntry>();
|
||||||
entry->source_offset = static_cast<uint32_t>(i->src1.offset);
|
entry->source_offset = static_cast<uint32_t>(i->src1.offset);
|
||||||
entry->hir_offset = uint32_t(i->block->ordinal << 16) | i->ordinal;
|
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();
|
||||||
nop();
|
nop();
|
||||||
mov(eax, entry->source_offset);
|
mov(eax, entry->source_offset);
|
||||||
|
|
|
@ -15,50 +15,84 @@
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
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;
|
||||||
|
|
||||||
ExportResolver::~ExportResolver() = default;
|
ExportResolver::~ExportResolver() = default;
|
||||||
|
|
||||||
void ExportResolver::RegisterTable(
|
void ExportResolver::RegisterTable(
|
||||||
const std::string& library_name,
|
const char* module_name, const std::vector<xe::cpu::Export*>* exports) {
|
||||||
const std::vector<xe::cpu::Export*>* exports) {
|
tables_.emplace_back(module_name, exports);
|
||||||
tables_.emplace_back(library_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) {
|
uint16_t ordinal) {
|
||||||
for (const auto& table : tables_) {
|
for (const auto& table : tables_) {
|
||||||
if (table.name == library_name || table.simple_name == library_name) {
|
if (std::strncmp(module_name, table.module_name(),
|
||||||
if (ordinal > table.exports->size()) {
|
std::strlen(table.module_name())) == 0) {
|
||||||
|
if (ordinal > table.exports_by_ordinal().size()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return table.exports->at(ordinal);
|
return table.exports_by_ordinal().at(ordinal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExportResolver::SetVariableMapping(const std::string& library_name,
|
void ExportResolver::SetVariableMapping(const char* module_name,
|
||||||
uint16_t ordinal, uint32_t value) {
|
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);
|
assert_not_null(export_entry);
|
||||||
export_entry->tags |= ExportTag::kImplemented;
|
export_entry->tags |= ExportTag::kImplemented;
|
||||||
export_entry->variable_ptr = value;
|
export_entry->variable_ptr = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExportResolver::SetFunctionMapping(const std::string& library_name,
|
void ExportResolver::SetFunctionMapping(const char* module_name,
|
||||||
uint16_t ordinal,
|
uint16_t ordinal,
|
||||||
xe_kernel_export_shim_fn shim) {
|
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);
|
assert_not_null(export_entry);
|
||||||
export_entry->tags |= ExportTag::kImplemented;
|
export_entry->tags |= ExportTag::kImplemented;
|
||||||
export_entry->function_data.shim = shim;
|
export_entry->function_data.shim = shim;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExportResolver::SetFunctionMapping(const std::string& library_name,
|
void ExportResolver::SetFunctionMapping(const char* module_name,
|
||||||
uint16_t ordinal,
|
uint16_t ordinal,
|
||||||
ExportTrampoline trampoline) {
|
ExportTrampoline trampoline) {
|
||||||
auto export_entry = GetExportByOrdinal(library_name, ordinal);
|
auto export_entry = GetExportByOrdinal(module_name, ordinal);
|
||||||
assert_not_null(export_entry);
|
assert_not_null(export_entry);
|
||||||
export_entry->tags |= ExportTag::kImplemented;
|
export_entry->tags |= ExportTag::kImplemented;
|
||||||
export_entry->function_data.trampoline = trampoline;
|
export_entry->function_data.trampoline = trampoline;
|
||||||
|
|
|
@ -99,37 +99,46 @@ class Export {
|
||||||
|
|
||||||
class ExportResolver {
|
class ExportResolver {
|
||||||
public:
|
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();
|
||||||
~ExportResolver();
|
~ExportResolver();
|
||||||
|
|
||||||
void RegisterTable(const std::string& library_name,
|
void RegisterTable(const char* module_name,
|
||||||
const std::vector<Export*>* exports);
|
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);
|
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);
|
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);
|
ExportTrampoline trampoline);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ExportTable {
|
std::vector<Table> tables_;
|
||||||
std::string name;
|
std::vector<Export*> all_exports_by_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_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cpu
|
} // namespace cpu
|
||||||
|
|
|
@ -246,7 +246,7 @@ enum class PPCRegister {
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma pack(push, 8)
|
#pragma pack(push, 8)
|
||||||
typedef struct alignas(64) PPCContext_s {
|
typedef struct PPCContext_s {
|
||||||
// Must be stored at 0x0 for now.
|
// Must be stored at 0x0 for now.
|
||||||
// TODO(benvanik): find a nice way to describe this to the JIT.
|
// TODO(benvanik): find a nice way to describe this to the JIT.
|
||||||
ThreadState* thread_state;
|
ThreadState* thread_state;
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
|
|
||||||
using xe::debug::Breakpoint;
|
|
||||||
|
|
||||||
Function::Function(Module* module, uint32_t address)
|
Function::Function(Module* module, uint32_t address)
|
||||||
: Symbol(Symbol::Type::kFunction, module, 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];
|
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) {
|
bool GuestFunction::Call(ThreadState* thread_state, uint32_t return_address) {
|
||||||
// SCOPE_profile_cpu_f("cpu");
|
// SCOPE_profile_cpu_f("cpu");
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
#include "xenia/cpu/frontend/ppc_context.h"
|
#include "xenia/cpu/frontend/ppc_context.h"
|
||||||
#include "xenia/cpu/symbol.h"
|
#include "xenia/cpu/symbol.h"
|
||||||
#include "xenia/cpu/thread_state.h"
|
#include "xenia/cpu/thread_state.h"
|
||||||
#include "xenia/debug/breakpoint.h"
|
|
||||||
#include "xenia/debug/function_trace_data.h"
|
#include "xenia/debug/function_trace_data.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -111,6 +110,9 @@ class GuestFunction : public Function {
|
||||||
const SourceMapEntry* LookupHIROffset(uint32_t offset) const;
|
const SourceMapEntry* LookupHIROffset(uint32_t offset) const;
|
||||||
const SourceMapEntry* LookupCodeOffset(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;
|
bool Call(ThreadState* thread_state, uint32_t return_address) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/byte_order.h"
|
#include "xenia/base/byte_order.h"
|
||||||
|
#include "xenia/base/exception_handler.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
|
@ -20,33 +21,28 @@ namespace cpu {
|
||||||
|
|
||||||
MMIOHandler* MMIOHandler::global_handler_ = nullptr;
|
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,
|
std::unique_ptr<MMIOHandler> MMIOHandler::Install(uint8_t* virtual_membase,
|
||||||
uint8_t* physical_membase,
|
uint8_t* physical_membase,
|
||||||
uint8_t* memory_end) {
|
uint8_t* membase_end) {
|
||||||
// There can be only one handler at a time.
|
// There can be only one handler at a time.
|
||||||
assert_null(global_handler_);
|
assert_null(global_handler_);
|
||||||
if (global_handler_) {
|
if (global_handler_) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the platform-specific handler.
|
auto handler = std::unique_ptr<MMIOHandler>(
|
||||||
auto handler = CreateMMIOHandler(virtual_membase, physical_membase);
|
new MMIOHandler(virtual_membase, physical_membase, membase_end));
|
||||||
|
|
||||||
// Platform-specific initialization for the handler.
|
// Install the exception handler directed at the MMIOHandler.
|
||||||
if (!handler->Initialize()) {
|
ExceptionHandler::Install(ExceptionCallbackThunk, handler.get());
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
handler->memory_end_ = memory_end;
|
|
||||||
global_handler_ = handler.get();
|
global_handler_ = handler.get();
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
MMIOHandler::~MMIOHandler() {
|
MMIOHandler::~MMIOHandler() {
|
||||||
|
ExceptionHandler::Uninstall(ExceptionCallbackThunk, this);
|
||||||
|
|
||||||
assert_true(global_handler_ == this);
|
assert_true(global_handler_ == this);
|
||||||
global_handler_ = nullptr;
|
global_handler_ = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -166,7 +162,8 @@ void MMIOHandler::CancelWriteWatch(uintptr_t watch_handle) {
|
||||||
delete entry;
|
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);
|
uint32_t physical_address = uint32_t(fault_address);
|
||||||
if (physical_address > 0x1FFFFFFF) {
|
if (physical_address > 0x1FFFFFFF) {
|
||||||
physical_address &= 0x1FFFFFFF;
|
physical_address &= 0x1FFFFFFF;
|
||||||
|
@ -364,10 +361,16 @@ bool TryDecodeMov(const uint8_t* p, DecodedMov* mov) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MMIOHandler::HandleAccessFault(void* thread_state,
|
bool MMIOHandler::ExceptionCallbackThunk(Exception* ex, void* data) {
|
||||||
uint64_t fault_address) {
|
return reinterpret_cast<MMIOHandler*>(data)->ExceptionCallback(ex);
|
||||||
if (fault_address < uint64_t(virtual_membase_) ||
|
}
|
||||||
fault_address > uint64_t(memory_end_)) {
|
|
||||||
|
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.
|
// Quick kill anything outside our mapping.
|
||||||
return false;
|
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.
|
// 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.
|
// Only check if in the virtual range, as we only support virtual ranges.
|
||||||
const MMIORange* range = nullptr;
|
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_) {
|
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.
|
// Address is within the range of this mapping.
|
||||||
range = &test_range;
|
range = &test_range;
|
||||||
break;
|
break;
|
||||||
|
@ -387,10 +391,10 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
|
||||||
if (!range) {
|
if (!range) {
|
||||||
// Access is not found within any range, so fail and let the caller handle
|
// Access is not found within any range, so fail and let the caller handle
|
||||||
// it (likely by aborting).
|
// 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);
|
auto p = reinterpret_cast<const uint8_t*>(rip);
|
||||||
DecodedMov mov = {0};
|
DecodedMov mov = {0};
|
||||||
bool decoded = TryDecodeMov(p, &mov);
|
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
|
// Load of a memory value - read from range, swap, and store in the
|
||||||
// register.
|
// register.
|
||||||
uint32_t value = range->read(nullptr, range->callback_context,
|
uint32_t value = range->read(nullptr, range->callback_context,
|
||||||
fault_address & 0xFFFFFFFF);
|
static_cast<uint32_t>(ex->fault_address()));
|
||||||
uint64_t* reg_ptr = GetThreadStateRegPtr(thread_state, mov.value_reg);
|
uint64_t* reg_ptr = &ex->thread_context()->int_registers[mov.value_reg];
|
||||||
if (!mov.byte_swap) {
|
if (!mov.byte_swap) {
|
||||||
// We swap only if it's not a movbe, as otherwise we are swapping twice.
|
// We swap only if it's not a movbe, as otherwise we are swapping twice.
|
||||||
value = xe::byte_swap(value);
|
value = xe::byte_swap(value);
|
||||||
|
@ -417,19 +421,19 @@ bool MMIOHandler::HandleAccessFault(void* thread_state,
|
||||||
if (mov.is_constant) {
|
if (mov.is_constant) {
|
||||||
value = uint32_t(mov.constant);
|
value = uint32_t(mov.constant);
|
||||||
} else {
|
} 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);
|
value = static_cast<uint32_t>(*reg_ptr);
|
||||||
if (!mov.byte_swap) {
|
if (!mov.byte_swap) {
|
||||||
// We swap only if it's not a movbe, as otherwise we are swapping twice.
|
// We swap only if it's not a movbe, as otherwise we are swapping twice.
|
||||||
value = xe::byte_swap(static_cast<uint32_t>(value));
|
value = xe::byte_swap(static_cast<uint32_t>(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
range->write(nullptr, range->callback_context, fault_address & 0xFFFFFFFF,
|
range->write(nullptr, range->callback_context,
|
||||||
value);
|
static_cast<uint32_t>(ex->fault_address()), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Advance RIP to the next instruction so that we resume properly.
|
// 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,11 @@
|
||||||
|
|
||||||
#include "xenia/base/mutex.h"
|
#include "xenia/base/mutex.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
class Exception;
|
||||||
|
class X64Context;
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
|
|
||||||
|
@ -59,9 +64,6 @@ class MMIOHandler {
|
||||||
void* callback_context, void* callback_data);
|
void* callback_context, void* callback_data);
|
||||||
void CancelWriteWatch(uintptr_t watch_handle);
|
void CancelWriteWatch(uintptr_t watch_handle);
|
||||||
|
|
||||||
public:
|
|
||||||
bool HandleAccessFault(void* thread_state, uint64_t fault_address);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct WriteWatchEntry {
|
struct WriteWatchEntry {
|
||||||
uint32_t address;
|
uint32_t address;
|
||||||
|
@ -71,19 +73,17 @@ class MMIOHandler {
|
||||||
void* callback_data;
|
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),
|
: 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);
|
void ClearWriteWatch(WriteWatchEntry* entry);
|
||||||
bool CheckWriteWatch(void* thread_state, uint64_t fault_address);
|
bool CheckWriteWatch(X64Context* thread_context, 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;
|
|
||||||
|
|
||||||
uint8_t* virtual_membase_;
|
uint8_t* virtual_membase_;
|
||||||
uint8_t* physical_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 <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "xenia/base/x64_context.h"
|
||||||
#include "xenia/cpu/function.h"
|
#include "xenia/cpu/function.h"
|
||||||
#include "xenia/cpu/x64_context.h"
|
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
|
@ -82,6 +82,7 @@ class StackWalker {
|
||||||
virtual size_t CaptureStackTrace(void* thread_handle,
|
virtual size_t CaptureStackTrace(void* thread_handle,
|
||||||
uint64_t* frame_host_pcs,
|
uint64_t* frame_host_pcs,
|
||||||
size_t frame_offset, size_t frame_count,
|
size_t frame_offset, size_t frame_count,
|
||||||
|
const X64Context* in_host_context,
|
||||||
X64Context* out_host_context,
|
X64Context* out_host_context,
|
||||||
uint64_t* out_stack_hash = nullptr) = 0;
|
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 CaptureStackTrace(void* thread_handle, uint64_t* frame_host_pcs,
|
||||||
size_t frame_offset, size_t frame_count,
|
size_t frame_offset, size_t frame_count,
|
||||||
|
const X64Context* in_host_context,
|
||||||
X64Context* out_host_context,
|
X64Context* out_host_context,
|
||||||
uint64_t* out_stack_hash) override {
|
uint64_t* out_stack_hash) override {
|
||||||
// TODO(benvanik): use xstate?
|
// 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
|
// Need at least CONTEXT_CONTROL (for rip and rsp) and CONTEXT_INTEGER (for
|
||||||
// rbp).
|
// rbp).
|
||||||
CONTEXT thread_context;
|
CONTEXT thread_context;
|
||||||
thread_context.ContextFlags = CONTEXT_FULL;
|
if (!in_host_context) {
|
||||||
if (!GetThreadContext(thread_handle, &thread_context)) {
|
// If not given a context we need to ask for it.
|
||||||
XELOGE("Unable to read thread context for stack walk");
|
// This gets the state of the thread exactly where it was when suspended.
|
||||||
return 0;
|
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) {
|
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->rip = thread_context.Rip;
|
||||||
out_host_context->eflags = thread_context.EFlags;
|
out_host_context->eflags = thread_context.EFlags;
|
||||||
std::memcpy(&out_host_context->int_registers.values, &thread_context.Rax,
|
std::memcpy(out_host_context->int_registers, &thread_context.Rax,
|
||||||
sizeof(out_host_context->int_registers.values));
|
sizeof(out_host_context->int_registers));
|
||||||
std::memcpy(&out_host_context->xmm_registers.values, &thread_context.Xmm0,
|
std::memcpy(out_host_context->xmm_registers, &thread_context.Xmm0,
|
||||||
sizeof(out_host_context->xmm_registers.values));
|
sizeof(out_host_context->xmm_registers));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the frame for walking.
|
// 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_
|
#ifndef XENIA_DEBUG_BREAKPOINT_H_
|
||||||
#define XENIA_DEBUG_BREAKPOINT_H_
|
#define XENIA_DEBUG_BREAKPOINT_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/cpu/function.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace debug {
|
namespace debug {
|
||||||
|
|
||||||
|
class Debugger;
|
||||||
|
|
||||||
class Breakpoint {
|
class Breakpoint {
|
||||||
public:
|
public:
|
||||||
enum Type {
|
enum class Type {
|
||||||
TEMP_TYPE,
|
kStepping,
|
||||||
CODE_TYPE,
|
kCode,
|
||||||
|
kData,
|
||||||
|
kExport,
|
||||||
|
kGpu,
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
Breakpoint(Debugger* debugger, Type type)
|
||||||
Breakpoint(Type type, uint32_t address);
|
: debugger_(debugger), type_(type) {}
|
||||||
~Breakpoint();
|
|
||||||
|
|
||||||
|
virtual ~Breakpoint() { assert_false(installed_); }
|
||||||
|
|
||||||
|
Debugger* debugger() const { return debugger_; }
|
||||||
Type type() const { return type_; }
|
Type type() const { return type_; }
|
||||||
uint32_t address() const { return address_; }
|
|
||||||
|
|
||||||
const char* id() const { return id_.c_str(); }
|
// Whether the breakpoint has been enabled by the user.
|
||||||
void set_id(const char* id) { id_ = std::string(id); }
|
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:
|
private:
|
||||||
Type type_;
|
friend class Debugger;
|
||||||
uint32_t address_;
|
// 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
|
} // 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 <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/exception_handler.h"
|
||||||
#include "xenia/base/mapped_memory.h"
|
#include "xenia/base/mapped_memory.h"
|
||||||
#include "xenia/base/mutex.h"
|
#include "xenia/base/mutex.h"
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
|
@ -38,11 +39,127 @@ class XThread;
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace debug {
|
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 {
|
enum class ExecutionState {
|
||||||
|
// Target is running; the debugger is not waiting for any events.
|
||||||
kRunning,
|
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 {
|
class Debugger {
|
||||||
|
@ -52,63 +169,147 @@ class Debugger {
|
||||||
|
|
||||||
Emulator* emulator() const { return emulator_; }
|
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();
|
bool StartSession();
|
||||||
void PreLaunch();
|
void PreLaunch();
|
||||||
void StopSession();
|
void StopSession();
|
||||||
void FlushSession();
|
void FlushSession();
|
||||||
|
|
||||||
|
// TODO(benvanik): hide.
|
||||||
uint8_t* AllocateFunctionData(size_t size);
|
uint8_t* AllocateFunctionData(size_t size);
|
||||||
uint8_t* AllocateFunctionTraceData(size_t size);
|
uint8_t* AllocateFunctionTraceData(size_t size);
|
||||||
|
|
||||||
bool is_attached() const { return is_attached_; }
|
// Returns a list of debugger info for all threads that have ever existed.
|
||||||
void set_attached(bool attached);
|
// 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_; }
|
// Adds a breakpoint to the debugger and activates it (if enabled).
|
||||||
|
// The given breakpoint will not be owned by the debugger and must remain
|
||||||
void DumpThreadStacks();
|
// allocated so long as it is added.
|
||||||
|
void AddBreakpoint(Breakpoint* breakpoint);
|
||||||
int AddBreakpoint(Breakpoint* breakpoint);
|
// Removes a breakpoint from the debugger and deactivates it.
|
||||||
int RemoveBreakpoint(Breakpoint* breakpoint);
|
void RemoveBreakpoint(Breakpoint* breakpoint);
|
||||||
void FindBreakpoints(uint32_t address,
|
// Returns all currently registered breakpoints.
|
||||||
std::vector<Breakpoint*>* out_breakpoints);
|
const std::vector<Breakpoint*>& breakpoints() const { return breakpoints_; }
|
||||||
|
|
||||||
// TODO(benvanik): utility functions for modification (make function ignored,
|
// TODO(benvanik): utility functions for modification (make function ignored,
|
||||||
// etc).
|
// etc).
|
||||||
|
|
||||||
void Interrupt();
|
// Shows the debug listener, focusing it if it already exists.
|
||||||
void Continue();
|
void Show();
|
||||||
void StepOne(uint32_t thread_id);
|
|
||||||
void StepTo(uint32_t thread_id, uint32_t target_pc);
|
|
||||||
|
|
||||||
|
// 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 OnThreadCreated(xe::kernel::XThread* thread);
|
||||||
void OnThreadExit(xe::kernel::XThread* thread);
|
void OnThreadExit(xe::kernel::XThread* thread);
|
||||||
void OnThreadDestroyed(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:
|
private:
|
||||||
|
// Synchronously demands a debug listener.
|
||||||
|
void DemandDebugListener();
|
||||||
|
|
||||||
|
// Suspends all known threads (except the caller).
|
||||||
bool SuspendAllThreads();
|
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();
|
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;
|
Emulator* emulator_ = nullptr;
|
||||||
|
|
||||||
std::unique_ptr<DebugServer> server_;
|
// TODO(benvanik): move this to the CPU backend and remove x64-specific stuff?
|
||||||
bool is_attached_ = false;
|
uintptr_t capstone_handle_ = 0;
|
||||||
xe::threading::Fence attach_fence_;
|
|
||||||
|
|
||||||
|
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::wstring functions_path_;
|
||||||
std::unique_ptr<ChunkedMappedMemoryWriter> functions_file_;
|
std::unique_ptr<ChunkedMappedMemoryWriter> functions_file_;
|
||||||
std::wstring functions_trace_path_;
|
std::wstring functions_trace_path_;
|
||||||
std::unique_ptr<ChunkedMappedMemoryWriter> functions_trace_file_;
|
std::unique_ptr<ChunkedMappedMemoryWriter> functions_trace_file_;
|
||||||
|
|
||||||
std::recursive_mutex mutex_;
|
|
||||||
ExecutionState execution_state_ = ExecutionState::kStopped;
|
|
||||||
|
|
||||||
xe::global_critical_region global_critical_region_;
|
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
|
} // namespace debug
|
||||||
|
|
|
@ -16,4 +16,3 @@ project("xenia-debug")
|
||||||
project_root.."/build_tools/third_party/gflags/src",
|
project_root.."/build_tools/third_party/gflags/src",
|
||||||
})
|
})
|
||||||
local_platform_files()
|
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")
|
group("src")
|
||||||
project("xenia-debug-ui")
|
project("xenia-debug-ui")
|
||||||
uuid("ed128630-8b4e-4dd7-afc9-ef00052fe3a7")
|
uuid("9193a274-f4c2-4746-bd85-93fcfc5c3e38")
|
||||||
kind("WindowedApp")
|
kind("StaticLib")
|
||||||
language("C++")
|
language("C++")
|
||||||
links({
|
links({
|
||||||
"elemental-forms",
|
"elemental-forms",
|
||||||
"gflags",
|
"glew",
|
||||||
"xenia-apu",
|
"imgui",
|
||||||
"xenia-apu-nop",
|
|
||||||
"xenia-apu-xaudio2",
|
|
||||||
"xenia-base",
|
"xenia-base",
|
||||||
"xenia-core",
|
|
||||||
"xenia-cpu",
|
"xenia-cpu",
|
||||||
"xenia-cpu-backend-x64",
|
|
||||||
"xenia-debug",
|
"xenia-debug",
|
||||||
"xenia-gpu",
|
|
||||||
"xenia-gpu-gl4",
|
|
||||||
"xenia-hid-nop",
|
|
||||||
"xenia-hid-winkey",
|
|
||||||
"xenia-hid-xinput",
|
|
||||||
"xenia-kernel",
|
|
||||||
"xenia-ui",
|
"xenia-ui",
|
||||||
"xenia-ui-gl",
|
"xenia-ui-gl",
|
||||||
"xenia-vfs",
|
|
||||||
})
|
|
||||||
flags({
|
|
||||||
"WinMain", -- Use WinMain instead of main.
|
|
||||||
})
|
})
|
||||||
defines({
|
defines({
|
||||||
|
"GLEW_STATIC=1",
|
||||||
|
"GLEW_MX=1",
|
||||||
})
|
})
|
||||||
includedirs({
|
includedirs({
|
||||||
project_root.."/third_party/elemental-forms/src",
|
|
||||||
project_root.."/build_tools/third_party/gflags/src",
|
project_root.."/build_tools/third_party/gflags/src",
|
||||||
|
project_root.."/third_party/elemental-forms/src",
|
||||||
})
|
})
|
||||||
recursive_platform_files()
|
local_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",
|
|
||||||
})
|
|
||||||
|
|
|
@ -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 <gflags/gflags.h>
|
||||||
|
|
||||||
|
#include "third_party/elemental-forms/src/el/elements.h"
|
||||||
#include "xenia/apu/audio_system.h"
|
#include "xenia/apu/audio_system.h"
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/clock.h"
|
#include "xenia/base/clock.h"
|
||||||
|
@ -30,8 +31,6 @@
|
||||||
#include "xenia/vfs/devices/stfs_container_device.h"
|
#include "xenia/vfs/devices/stfs_container_device.h"
|
||||||
#include "xenia/vfs/virtual_file_system.h"
|
#include "xenia/vfs/virtual_file_system.h"
|
||||||
|
|
||||||
#include "el/elements/message_form.h"
|
|
||||||
|
|
||||||
DEFINE_double(time_scalar, 1.0,
|
DEFINE_double(time_scalar, 1.0,
|
||||||
"Scalar used to speed or slow time (1x, 2x, 1/2x, etc).");
|
"Scalar used to speed or slow time (1x, 2x, 1/2x, etc).");
|
||||||
|
|
||||||
|
@ -64,6 +63,8 @@ Emulator::~Emulator() {
|
||||||
debugger_.reset();
|
debugger_.reset();
|
||||||
|
|
||||||
export_resolver_.reset();
|
export_resolver_.reset();
|
||||||
|
|
||||||
|
ExceptionHandler::Uninstall(Emulator::ExceptionCallbackThunk, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
X_STATUS Emulator::Setup(ui::Window* display_window) {
|
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::xboxkrnl::XboxkrnlModule>();
|
||||||
kernel_state_->LoadKernelModule<kernel::xam::XamModule>();
|
kernel_state_->LoadKernelModule<kernel::xam::XamModule>();
|
||||||
|
|
||||||
|
// Initialize emulator fallback exception handling last.
|
||||||
|
ExceptionHandler::Install(Emulator::ExceptionCallbackThunk, this);
|
||||||
|
|
||||||
// Finish initializing the display.
|
// Finish initializing the display.
|
||||||
display_window_->loop()->PostSynchronous([this]() {
|
display_window_->loop()->PostSynchronous([this]() {
|
||||||
xe::ui::GraphicsContextLock context_lock(display_window_->context());
|
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));
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,61 +271,65 @@ X_STATUS Emulator::LaunchStfsContainer(std::wstring path) {
|
||||||
return CompleteLaunch(path, "game:\\default.xex");
|
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.
|
// Check to see if the exception occurred in guest code.
|
||||||
auto code_cache = processor()->backend()->code_cache();
|
auto code_cache = processor()->backend()->code_cache();
|
||||||
auto code_base = code_cache->base_address();
|
auto code_base = code_cache->base_address();
|
||||||
auto code_end = code_base + code_cache->total_size();
|
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
|
// If Xenia's debugger isn't attached but another one is, pass it to that
|
||||||
// debugger.
|
// debugger.
|
||||||
return false;
|
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) {
|
if (!(ex->pc() >= code_base && ex->pc() < code_end)) {
|
||||||
using namespace xe::kernel;
|
// 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<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();
|
|
||||||
return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,8 @@ class Emulator {
|
||||||
X_STATUS LaunchStfsContainer(std::wstring path);
|
X_STATUS LaunchStfsContainer(std::wstring path);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool ExceptionCallback(ExceptionHandler::Info* info);
|
static bool ExceptionCallbackThunk(Exception* ex, void* data);
|
||||||
|
bool ExceptionCallback(Exception* ex);
|
||||||
|
|
||||||
X_STATUS CompleteLaunch(const std::wstring& path,
|
X_STATUS CompleteLaunch(const std::wstring& path,
|
||||||
const std::string& module_path);
|
const std::string& module_path);
|
||||||
|
|
|
@ -292,6 +292,9 @@ void GL4GraphicsSystem::MarkVblank() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GL4GraphicsSystem::Swap(xe::ui::UIEvent* e) {
|
void GL4GraphicsSystem::Swap(xe::ui::UIEvent* e) {
|
||||||
|
if (!command_processor_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Check for pending swap.
|
// Check for pending swap.
|
||||||
auto& swap_state = command_processor_->swap_state();
|
auto& swap_state = command_processor_->swap_state();
|
||||||
{
|
{
|
||||||
|
|
|
@ -515,8 +515,6 @@ bool DisasmPacketType3(const uint8_t* base_ptr, uint32_t packet,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
bool DisasmPacket(const uint8_t* base_ptr, PacketInfo* out_info) {
|
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 = xe::load_and_swap<uint32_t>(base_ptr);
|
||||||
const uint32_t packet_type = packet >> 30;
|
const uint32_t packet_type = packet >> 30;
|
||||||
switch (packet_type) {
|
switch (packet_type) {
|
||||||
|
@ -977,7 +975,7 @@ void DrawCommandListUI(xe::ui::Window* window, TracePlayer& player,
|
||||||
if (ImGui::Selectable(label, &is_selected)) {
|
if (ImGui::Selectable(label, &is_selected)) {
|
||||||
player.SeekCommand(i);
|
player.SeekCommand(i);
|
||||||
}
|
}
|
||||||
ImGui::SameLine(column_width - 60);
|
ImGui::SameLine(column_width - 60.0f);
|
||||||
ImGui::Text("%d", i);
|
ImGui::Text("%d", i);
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
if (did_seek && target_command == i) {
|
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);
|
auto cmd = reinterpret_cast<const PacketEndCommand*>(trace_ptr);
|
||||||
trace_ptr += sizeof(*cmd);
|
trace_ptr += sizeof(*cmd);
|
||||||
if (pending_packet) {
|
if (pending_packet) {
|
||||||
PacketInfo packet_info;
|
PacketInfo packet_info = {0};
|
||||||
if (DisasmPacket(reinterpret_cast<const uint8_t*>(pending_packet) +
|
if (DisasmPacket(reinterpret_cast<const uint8_t*>(pending_packet) +
|
||||||
sizeof(PacketStartCommand),
|
sizeof(PacketStartCommand),
|
||||||
&packet_info)) {
|
&packet_info)) {
|
||||||
|
@ -2107,7 +2105,7 @@ void DrawPacketDisassemblerUI(xe::ui::Window* window, TracePlayer& player,
|
||||||
}
|
}
|
||||||
ImGui::BulletText(packet_info.type_info->name);
|
ImGui::BulletText(packet_info.type_info->name);
|
||||||
ImGui::TreePush((const char*)0);
|
ImGui::TreePush((const char*)0);
|
||||||
for (auto& action : packet_info.actions) {
|
for (auto action : packet_info.actions) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case PacketAction::Type::kRegisterWrite: {
|
case PacketAction::Type::kRegisterWrite: {
|
||||||
auto register_info = xe::gpu::RegisterFile::GetRegisterInfo(
|
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 int position_location, uv_location, colour_location;
|
||||||
static size_t vbo_max_size = 20000;
|
static size_t vbo_max_size = 20000;
|
||||||
static unsigned int vbo_handle, vao_handle;
|
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() {
|
void ImImpl_Setup() {
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
|
||||||
|
@ -2483,7 +2481,7 @@ void ImImpl_Setup() {
|
||||||
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
|
io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT;
|
||||||
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
|
io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT;
|
||||||
io.KeyMap[ImGuiKey_UpArrow] = VK_UP;
|
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_Home] = VK_HOME;
|
||||||
io.KeyMap[ImGuiKey_End] = VK_END;
|
io.KeyMap[ImGuiKey_End] = VK_END;
|
||||||
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
|
io.KeyMap[ImGuiKey_Delete] = VK_DELETE;
|
||||||
|
@ -2510,8 +2508,8 @@ void ImImpl_Shutdown() {
|
||||||
glDeleteTextures(1, &tex_id);
|
glDeleteTextures(1, &tex_id);
|
||||||
ImGui::Shutdown();
|
ImGui::Shutdown();
|
||||||
}
|
}
|
||||||
void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) {
|
void ImImpl_RenderDrawLists(ImDrawData* data) {
|
||||||
if (cmd_lists_count == 0) return;
|
if (data->CmdListsCount == 0) return;
|
||||||
|
|
||||||
// Setup render state: alpha-blending enabled, no face culling, no depth
|
// Setup render state: alpha-blending enabled, no face culling, no depth
|
||||||
// testing, scissor enabled
|
// testing, scissor enabled
|
||||||
|
@ -2537,52 +2535,52 @@ void ImImpl_RenderDrawLists(ImDrawList** const cmd_lists, int cmd_lists_count) {
|
||||||
&ortho_projection[0][0]);
|
&ortho_projection[0][0]);
|
||||||
|
|
||||||
// Grow our buffer according to what we need
|
// 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);
|
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) {
|
if (neededBufferSize > vbo_max_size) {
|
||||||
vbo_max_size = neededBufferSize + 5000; // Grow buffer
|
vbo_max_size = neededBufferSize + 5000; // Grow buffer
|
||||||
glBufferData(GL_ARRAY_BUFFER, vbo_max_size, NULL, GL_STREAM_DRAW);
|
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);
|
glBindVertexArray(vao_handle);
|
||||||
glUseProgram(shader_handle);
|
glUseProgram(shader_handle);
|
||||||
|
|
||||||
int cmd_offset = 0;
|
GLuint ib;
|
||||||
|
glGenBuffers(1, &ib);
|
||||||
|
|
||||||
ImTextureID prev_texture_id = 0;
|
ImTextureID prev_texture_id = 0;
|
||||||
for (int n = 0; n < cmd_lists_count; n++) {
|
for (int n = 0; n < data->CmdListsCount; n++) {
|
||||||
const ImDrawList* cmd_list = cmd_lists[n];
|
const ImDrawList* cmd_list = data->CmdLists[n];
|
||||||
int vtx_offset = cmd_offset;
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib);
|
||||||
const ImDrawCmd* pcmd_end = cmd_list->commands.end();
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||||
for (const ImDrawCmd* pcmd = cmd_list->commands.begin(); pcmd != pcmd_end;
|
(GLsizeiptr)cmd_list->IdxBuffer.size() * sizeof(ImDrawIdx),
|
||||||
pcmd++) {
|
(GLvoid*)&cmd_list->IdxBuffer.front(), GL_STREAM_DRAW);
|
||||||
if (pcmd->texture_id != prev_texture_id) {
|
// Copy and convert all vertices into a single contiguous buffer
|
||||||
glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->texture_id);
|
unsigned char* buffer_data =
|
||||||
prev_texture_id = pcmd->texture_id;
|
(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),
|
glScissor((int)cmd->ClipRect.x, (int)(height - cmd->ClipRect.w),
|
||||||
(int)(pcmd->clip_rect.z - pcmd->clip_rect.x),
|
(int)(cmd->ClipRect.z - cmd->ClipRect.x),
|
||||||
(int)(pcmd->clip_rect.w - pcmd->clip_rect.y));
|
(int)(cmd->ClipRect.w - cmd->ClipRect.y));
|
||||||
glDrawArrays(GL_TRIANGLES, vtx_offset, pcmd->vtx_count);
|
glDrawElements(GL_TRIANGLES, (GLsizei)cmd->ElemCount, GL_UNSIGNED_SHORT,
|
||||||
vtx_offset += pcmd->vtx_count;
|
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
|
// Restore modified state
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
|
|
|
@ -28,7 +28,8 @@ KernelModule::KernelModule(KernelState* kernel_state, const char* path)
|
||||||
KernelModule::~KernelModule() {}
|
KernelModule::~KernelModule() {}
|
||||||
|
|
||||||
uint32_t KernelModule::GetProcAddressByOrdinal(uint16_t ordinal) {
|
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) {
|
if (!export_entry) {
|
||||||
// Export (or its parent library) not found.
|
// Export (or its parent library) not found.
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -636,6 +636,10 @@ void XThread::SetActiveCpu(uint32_t cpu_index) {
|
||||||
xe::store_and_swap<uint8_t>(pcr + 0x10C, 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) {
|
X_STATUS XThread::Resume(uint32_t* out_suspend_count) {
|
||||||
--guest_object<X_KTHREAD>()->suspend_count;
|
--guest_object<X_KTHREAD>()->suspend_count;
|
||||||
|
|
||||||
|
|
|
@ -161,6 +161,7 @@ class XThread : public XObject {
|
||||||
uint32_t active_cpu() const;
|
uint32_t active_cpu() const;
|
||||||
void SetActiveCpu(uint32_t cpu_index);
|
void SetActiveCpu(uint32_t cpu_index);
|
||||||
|
|
||||||
|
uint32_t suspend_count();
|
||||||
X_STATUS Resume(uint32_t* out_suspend_count = nullptr);
|
X_STATUS Resume(uint32_t* out_suspend_count = nullptr);
|
||||||
X_STATUS Suspend(uint32_t* out_suspend_count);
|
X_STATUS Suspend(uint32_t* out_suspend_count);
|
||||||
X_STATUS Delay(uint32_t processor_mode, uint32_t alertable,
|
X_STATUS Delay(uint32_t processor_mode, uint32_t alertable,
|
||||||
|
|
|
@ -39,6 +39,11 @@ Loop::~Loop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Loop::PostSynchronous(std::function<void()> fn) {
|
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;
|
xe::threading::Fence fence;
|
||||||
Post([&fn, &fence]() {
|
Post([&fn, &fence]() {
|
||||||
fn();
|
fn();
|
||||||
|
|
|
@ -31,6 +31,9 @@ class Loop {
|
||||||
Loop();
|
Loop();
|
||||||
virtual ~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 Post(std::function<void()> fn) = 0;
|
||||||
virtual void PostDelayed(std::function<void()> fn, uint64_t delay_millis) = 0;
|
virtual void PostDelayed(std::function<void()> fn, uint64_t delay_millis) = 0;
|
||||||
void PostSynchronous(std::function<void()> fn);
|
void PostSynchronous(std::function<void()> fn);
|
||||||
|
|
|
@ -87,6 +87,10 @@ void Win32Loop::ThreadMain() {
|
||||||
on_quit(&e);
|
on_quit(&e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Win32Loop::is_on_loop_thread() {
|
||||||
|
return thread_id_ == GetCurrentThreadId();
|
||||||
|
}
|
||||||
|
|
||||||
void Win32Loop::Post(std::function<void()> fn) {
|
void Win32Loop::Post(std::function<void()> fn) {
|
||||||
assert_true(thread_id_ != 0);
|
assert_true(thread_id_ != 0);
|
||||||
if (!PostThreadMessage(
|
if (!PostThreadMessage(
|
||||||
|
|
|
@ -26,6 +26,8 @@ class Win32Loop : public Loop {
|
||||||
Win32Loop();
|
Win32Loop();
|
||||||
~Win32Loop() override;
|
~Win32Loop() override;
|
||||||
|
|
||||||
|
bool is_on_loop_thread() override;
|
||||||
|
|
||||||
void Post(std::function<void()> fn) override;
|
void Post(std::function<void()> fn) override;
|
||||||
void PostDelayed(std::function<void()> fn, uint64_t delay_millis) 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) {
|
void Window::OnKeyChar(KeyEvent* e) {
|
||||||
OnKeyPress(e, true, true);
|
OnKeyPress(e, true, true);
|
||||||
|
on_key_char(e);
|
||||||
OnKeyPress(e, false, true);
|
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,
|
LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||||
LPARAM lParam) {
|
LPARAM lParam) {
|
||||||
|
if (hWnd != hwnd_) {
|
||||||
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||||
|
}
|
||||||
|
|
||||||
if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) {
|
if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST) {
|
||||||
if (HandleMouse(message, wParam, lParam)) {
|
if (HandleMouse(message, wParam, lParam)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -328,7 +332,6 @@ LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||||
}
|
}
|
||||||
} else if (message >= WM_KEYFIRST && message <= WM_KEYLAST) {
|
} else if (message >= WM_KEYFIRST && message <= WM_KEYLAST) {
|
||||||
if (HandleKeyboard(message, wParam, lParam)) {
|
if (HandleKeyboard(message, wParam, lParam)) {
|
||||||
SetFocus(hwnd_);
|
|
||||||
return 0;
|
return 0;
|
||||||
} else {
|
} else {
|
||||||
return DefWindowProc(hWnd, message, wParam, lParam);
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
||||||
|
@ -363,10 +366,14 @@ LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam,
|
||||||
|
|
||||||
case WM_PAINT: {
|
case WM_PAINT: {
|
||||||
ValidateRect(hwnd_, nullptr);
|
ValidateRect(hwnd_, nullptr);
|
||||||
auto e = UIEvent(this);
|
static bool in_paint = false;
|
||||||
OnPaint(&e);
|
if (!in_paint) {
|
||||||
|
in_paint = true;
|
||||||
|
auto e = UIEvent(this);
|
||||||
|
OnPaint(&e);
|
||||||
|
in_paint = false;
|
||||||
|
}
|
||||||
return 0; // Ignored because of custom paint.
|
return 0; // Ignored because of custom paint.
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case WM_ERASEBKGND:
|
case WM_ERASEBKGND:
|
||||||
return 0; // Ignored because of custom paint.
|
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/imconfig.h",
|
||||||
"imgui/imgui.cpp",
|
"imgui/imgui.cpp",
|
||||||
"imgui/imgui.h",
|
"imgui/imgui.h",
|
||||||
|
"imgui/imgui_draw.cpp",
|
||||||
|
"imgui/imgui_demo.cpp",
|
||||||
|
"imgui/imgui_internal.h",
|
||||||
"imgui/stb_rect_pack.h",
|
"imgui/stb_rect_pack.h",
|
||||||
"imgui/stb_textedit.h",
|
"imgui/stb_textedit.h",
|
||||||
"imgui/stb_truetype.h",
|
"imgui/stb_truetype.h",
|
||||||
})
|
})
|
||||||
|
buildoptions({
|
||||||
|
"/wd4312", -- Ugh.
|
||||||
|
})
|
||||||
|
|
Loading…
Reference in New Issue