diff --git a/src/xenia/base/delegate.h b/src/xenia/base/delegate.h index aa0590cd8..8ecc5cf74 100644 --- a/src/xenia/base/delegate.h +++ b/src/xenia/base/delegate.h @@ -45,6 +45,33 @@ class Delegate { std::vector listeners_; }; +template <> +class Delegate { + public: + typedef std::function Listener; + + void AddListener(Listener const& listener) { + std::lock_guard guard(lock_); + listeners_.push_back(listener); + } + + void RemoveAllListeners() { + std::lock_guard guard(lock_); + listeners_.clear(); + } + + void operator()() { + std::lock_guard guard(lock_); + for (auto& listener : listeners_) { + listener(); + } + } + + private: + std::mutex lock_; + std::vector listeners_; +}; + } // namespace xe #endif // XENIA_BASE_DELEGATE_H_ diff --git a/src/xenia/base/socket.h b/src/xenia/base/socket.h index 85afc0ada..449824bbf 100644 --- a/src/xenia/base/socket.h +++ b/src/xenia/base/socket.h @@ -28,7 +28,8 @@ namespace xe { // wait handle should be waited on until new data arrives. class Socket { public: - // TODO(benvanik): client socket static Connect method. + // Synchronously connects to the given hostname:port. + static std::unique_ptr Connect(std::string hostname, uint16_t port); virtual ~Socket() = default; @@ -84,6 +85,12 @@ class Socket { auto buffer_list = std::make_pair(buffer.data(), buffer.size()); return Send(&buffer_list, 1); } + + // Asynchronously sends a string buffer. + // Returns false if the socket is disconnected or the data cannot be sent. + bool Send(const std::string& value) { + return Send(value.data(), value.size()); + } }; // Runs a socket server on the specified local port. diff --git a/src/xenia/base/socket_win.cc b/src/xenia/base/socket_win.cc index 83efeaf14..a718ed652 100644 --- a/src/xenia/base/socket_win.cc +++ b/src/xenia/base/socket_win.cc @@ -36,6 +36,51 @@ class Win32Socket : public Socket { Win32Socket() = default; ~Win32Socket() override { Close(); } + bool Connect(std::string hostname, uint16_t port) { + addrinfo hints = {0}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + // Resolve the server address and port. + // May return multiple results, so attempt to connect to an address until + // one succeeds. + addrinfo* result = nullptr; + auto port_string = std::to_string(port); + int ret = + getaddrinfo(hostname.c_str(), port_string.c_str(), &hints, &result); + if (ret != 0) { + XELOGE("getaddrinfo failed with error: %d", ret); + return false; + } + SOCKET try_socket = INVALID_SOCKET; + for (addrinfo* ptr = result; ptr; ptr = ptr->ai_next) { + // Create a SOCKET for connecting to server. + try_socket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol); + if (try_socket == INVALID_SOCKET) { + XELOGE("socket failed with error: %ld", WSAGetLastError()); + freeaddrinfo(result); + return false; + } + // Connect to server. + ret = connect(try_socket, ptr->ai_addr, (int)ptr->ai_addrlen); + if (ret == SOCKET_ERROR) { + closesocket(try_socket); + try_socket = INVALID_SOCKET; + continue; + } + break; + } + freeaddrinfo(result); + if (try_socket == INVALID_SOCKET) { + XELOGE("Unable to connect to server"); + return false; + } + + // Piggyback to setup the socket. + return Accept(try_socket); + } + bool Accept(SOCKET socket) { std::lock_guard lock(mutex_); @@ -142,6 +187,16 @@ class Win32Socket : public Socket { std::unique_ptr event_; }; +std::unique_ptr Socket::Connect(std::string hostname, uint16_t port) { + InitializeWinsock(); + + auto socket = std::make_unique(); + if (!socket->Connect(std::move(hostname), port)) { + return nullptr; + } + return std::unique_ptr(socket.release()); +} + class Win32SocketServer : public SocketServer { public: Win32SocketServer( diff --git a/src/xenia/cpu/thread_state.cc b/src/xenia/cpu/thread_state.cc index 5bc5cc4a0..d0c1f3846 100644 --- a/src/xenia/cpu/thread_state.cc +++ b/src/xenia/cpu/thread_state.cc @@ -104,17 +104,9 @@ ThreadState::ThreadState(Processor* processor, uint32_t thread_id, // Set initial registers. context_->r[1] = stack_base_; context_->r[13] = pcr_address_; - - if (processor_->debugger()) { - processor_->debugger()->OnThreadCreated(this); - } } ThreadState::~ThreadState() { - if (processor_->debugger()) { - processor_->debugger()->OnThreadDestroyed(this); - } - if (backend_data_) { processor_->backend()->FreeThreadData(backend_data_); } diff --git a/src/xenia/debug/client/xdp/xdp_client.cc b/src/xenia/debug/client/xdp/xdp_client.cc new file mode 100644 index 000000000..57a292661 --- /dev/null +++ b/src/xenia/debug/client/xdp/xdp_client.cc @@ -0,0 +1,243 @@ +/** + ****************************************************************************** + * 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/client/xdp/xdp_client.h" + +#include "xenia/base/logging.h" +#include "xenia/debug/debugger.h" + +namespace xe { +namespace debug { +namespace client { +namespace xdp { + +using namespace xe::debug::proto; + +constexpr size_t kReceiveBufferSize = 1 * 1024 * 1024; +constexpr size_t kTransmitBufferSize = 1 * 1024 * 1024; + +XdpClient::XdpClient() + : packet_reader_(kReceiveBufferSize), packet_writer_(kTransmitBufferSize) { + receive_buffer_.resize(kReceiveBufferSize); +} + +XdpClient::~XdpClient() { + socket_->Close(); + xe::threading::Wait(thread_.get(), true); + thread_.reset(); +} + +bool XdpClient::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 XdpClient::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 XdpClient::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(); + } + + return true; +} + +bool XdpClient::ProcessPacket(const proto::Packet* packet) { + // Hold lock during processing. + std::lock_guard lock(mutex_); + + switch (packet->packet_type) { + case PacketType::kGenericResponse: { + // + } break; + case PacketType::kExecutionNotification: { + auto body = packet_reader_.Read(); + switch (body->current_state) { + case ExecutionNotification::State::kStopped: { + // body->stop_reason + execution_state_ = ExecutionState::kStopped; + listener_->OnExecutionStateChanged(execution_state_); + BeginUpdateAllState(); + } break; + case ExecutionNotification::State::kRunning: { + execution_state_ = ExecutionState::kRunning; + listener_->OnExecutionStateChanged(execution_state_); + } break; + case ExecutionNotification::State::kExited: { + execution_state_ = ExecutionState::kExited; + listener_->OnExecutionStateChanged(execution_state_); + BeginUpdateAllState(); + } break; + } + } break; + case PacketType::kModuleListResponse: { + auto body = packet_reader_.Read(); + auto entries = packet_reader_.ReadArray(body->count); + listener_->OnModulesUpdated(entries); + } break; + case PacketType::kThreadListResponse: { + auto body = packet_reader_.Read(); + auto entries = packet_reader_.ReadArray(body->count); + listener_->OnThreadsUpdated(entries); + } break; + default: { + XELOGE("Unknown incoming packet type"); + return false; + } break; + } + return true; +} + +void XdpClient::Flush() { + std::lock_guard lock(mutex_); + if (!packet_writer_.buffer_offset()) { + return; + } + socket_->Send(packet_writer_.buffer(), packet_writer_.buffer_offset()); + packet_writer_.Reset(); +} + +void XdpClient::Continue() { + std::lock_guard lock(mutex_); + packet_writer_.Begin(PacketType::kExecutionRequest); + auto body = packet_writer_.Append(); + body->action = ExecutionRequest::Action::kContinue; + packet_writer_.End(); + Flush(); +} + +void XdpClient::StepOne(uint32_t thread_id) { + std::lock_guard lock(mutex_); + packet_writer_.Begin(PacketType::kExecutionRequest); + auto body = packet_writer_.Append(); + body->action = ExecutionRequest::Action::kStep; + body->step_mode = ExecutionRequest::StepMode::kStepOne; + body->thread_id = thread_id; + packet_writer_.End(); + Flush(); +} + +void XdpClient::StepTo(uint32_t thread_id, uint32_t target_pc) { + std::lock_guard lock(mutex_); + packet_writer_.Begin(PacketType::kExecutionRequest); + auto body = packet_writer_.Append(); + body->action = ExecutionRequest::Action::kStep; + body->step_mode = ExecutionRequest::StepMode::kStepTo; + body->thread_id = thread_id; + body->target_pc = target_pc; + packet_writer_.End(); + Flush(); +} + +void XdpClient::Interrupt() { + std::lock_guard lock(mutex_); + packet_writer_.Begin(PacketType::kExecutionRequest); + auto body = packet_writer_.Append(); + body->action = ExecutionRequest::Action::kInterrupt; + packet_writer_.End(); + Flush(); +} + +void XdpClient::Exit() { + std::lock_guard lock(mutex_); + packet_writer_.Begin(PacketType::kExecutionRequest); + auto body = packet_writer_.Append(); + body->action = ExecutionRequest::Action::kExit; + packet_writer_.End(); + Flush(); +} + +void XdpClient::BeginUpdateAllState() { + std::lock_guard lock(mutex_); + + packet_writer_.Begin(PacketType::kModuleListRequest); + packet_writer_.End(); + packet_writer_.Begin(PacketType::kThreadListRequest); + packet_writer_.End(); + + Flush(); +} + +} // namespace xdp +} // namespace client +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/client/xdp/xdp_client.h b/src/xenia/debug/client/xdp/xdp_client.h new file mode 100644 index 000000000..9d3cbc65a --- /dev/null +++ b/src/xenia/debug/client/xdp/xdp_client.h @@ -0,0 +1,92 @@ +/** + ****************************************************************************** + * 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_CLIENT_XDP_XDP_CLIENT_H_ +#define XENIA_DEBUG_CLIENT_XDP_XDP_CLIENT_H_ + +#include +#include + +#include "xenia/base/socket.h" +#include "xenia/base/threading.h" +#include "xenia/debug/proto/packet_reader.h" +#include "xenia/debug/proto/packet_writer.h" +#include "xenia/debug/proto/xdp_protocol.h" + +namespace xe { +namespace debug { +namespace client { +namespace xdp { + +using proto::ModuleListEntry; +using proto::ThreadListEntry; + +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 ClientListener { + public: + virtual void OnExecutionStateChanged(ExecutionState execution_state) = 0; + virtual void OnModulesUpdated( + std::vector entries) = 0; + virtual void OnThreadsUpdated( + std::vector entries) = 0; +}; + +class XdpClient { + public: + XdpClient(); + ~XdpClient(); + + Socket* socket() const { return socket_.get(); } + void set_listener(ClientListener* listener) { listener_ = listener; } + + 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(const proto::Packet* packet); + void Flush(); + + void BeginUpdateAllState(); + + std::recursive_mutex mutex_; + std::unique_ptr socket_; + std::unique_ptr thread_; + std::vector receive_buffer_; + + proto::PacketReader packet_reader_; + proto::PacketWriter packet_writer_; + + ClientListener* listener_ = nullptr; + + ExecutionState execution_state_ = ExecutionState::kStopped; +}; + +} // namespace xdp +} // namespace client +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_CLIENT_XDP_XDP_CLIENT_H_ diff --git a/src/xenia/debug/client/xdp/xdp_command_processor.cc b/src/xenia/debug/client/xdp/xdp_command_processor.cc new file mode 100644 index 000000000..6725a64a7 --- /dev/null +++ b/src/xenia/debug/client/xdp/xdp_command_processor.cc @@ -0,0 +1,34 @@ +/** + ****************************************************************************** + * 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/client/xdp/xdp_command_processor.h" + +#include "xenia/base/logging.h" +#include "xenia/base/string_buffer.h" +#include "xenia/debug/client/xdp/xdp_client.h" + +namespace xe { +namespace debug { +namespace client { +namespace xdp { + +using namespace xe::debug::proto; + +constexpr size_t kReceiveBufferSize = 1 * 1024 * 1024; +constexpr size_t kTransmitBufferSize = 1 * 1024 * 1024; + +XdpCommandProcessor::XdpCommandProcessor(XdpClient* client) + : client_(client), + packet_reader_(kReceiveBufferSize), + packet_writer_(kTransmitBufferSize) {} + +} // namespace xdp +} // namespace client +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/client/xdp/xdp_command_processor.h b/src/xenia/debug/client/xdp/xdp_command_processor.h new file mode 100644 index 000000000..c7c09bacb --- /dev/null +++ b/src/xenia/debug/client/xdp/xdp_command_processor.h @@ -0,0 +1,46 @@ +/** + ****************************************************************************** + * 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_CLIENT_XDP_XDP_COMMAND_PROCESSOR_H_ +#define XENIA_DEBUG_CLIENT_XDP_XDP_COMMAND_PROCESSOR_H_ + +#include + +#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 { +namespace client { +namespace xdp { + +class XdpClient; + +class XdpCommandProcessor { + public: + XdpCommandProcessor(XdpClient* client); + ~XdpCommandProcessor() = default; + + bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length); + + private: + bool ProcessPacket(const proto::Packet* packet); + + void Flush(); + + XdpClient* client_ = nullptr; +}; + +} // namespace xdp +} // namespace client +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_CLIENT_XDP_XDP_COMMAND_PROCESSOR_H_ diff --git a/src/xenia/debug/transport.h b/src/xenia/debug/debug_server.h similarity index 78% rename from src/xenia/debug/transport.h rename to src/xenia/debug/debug_server.h index e4d22542f..07da439eb 100644 --- a/src/xenia/debug/transport.h +++ b/src/xenia/debug/debug_server.h @@ -7,8 +7,8 @@ ****************************************************************************** */ -#ifndef XENIA_DEBUG_TRANSPORT_H_ -#define XENIA_DEBUG_TRANSPORT_H_ +#ifndef XENIA_DEBUG_DEBUG_SERVER_H_ +#define XENIA_DEBUG_DEBUG_SERVER_H_ #include "xenia/cpu/function.h" #include "xenia/cpu/processor.h" @@ -19,25 +19,25 @@ namespace debug { class Debugger; -class Transport { +class DebugServer { public: - virtual ~Transport() = default; + virtual ~DebugServer() = default; Debugger* debugger() const { return debugger_; } virtual bool Initialize() = 0; - // break/resume state - // TODO(benvanik): better thread type (XThread?) // virtual void OnThreadCreated(ThreadState* thread_state) = 0; // virtual void OnThreadDestroyed(ThreadState* thread_state) = 0; + virtual void OnExecutionContinued() {} + virtual void OnExecutionInterrupted() {} /*virtual void OnBreakpointHit(xe::cpu::ThreadState* thread_state, Breakpoint* breakpoint) = 0;*/ protected: - Transport(Debugger* debugger) : debugger_(debugger) {} + DebugServer(Debugger* debugger) : debugger_(debugger) {} Debugger* debugger_ = nullptr; }; @@ -45,4 +45,4 @@ class Transport { } // namespace debug } // namespace xe -#endif // XENIA_DEBUG_TRANSPORT_H_ +#endif // XENIA_DEBUG_DEBUG_SERVER_H_ diff --git a/src/xenia/debug/debugger.cc b/src/xenia/debug/debugger.cc index 54f26b799..6a2bae882 100644 --- a/src/xenia/debug/debugger.cc +++ b/src/xenia/debug/debugger.cc @@ -20,7 +20,9 @@ #include "xenia/cpu/backend/code_cache.h" #include "xenia/cpu/function.h" #include "xenia/cpu/processor.h" -#include "xenia/debug/transport/gdb/gdb_transport.h" +#include "xenia/debug/server/gdb/gdb_server.h" +#include "xenia/debug/server/mi/mi_server.h" +#include "xenia/debug/server/xdp/xdp_server.h" #include "xenia/emulator.h" #include "xenia/kernel/objects/xkernel_module.h" #include "xenia/kernel/objects/xmodule.h" @@ -40,6 +42,8 @@ DEFINE_bool(wait_for_debugger, false, "Waits for a debugger to attach before starting the game."); DEFINE_bool(exit_with_debugger, true, "Exit whe the debugger disconnects."); +DEFINE_string(debug_server, "xdp", "Debug server protocol [gdb, mi, xdp]."); + namespace xe { namespace debug { @@ -86,13 +90,24 @@ bool Debugger::StartSession() { functions_trace_file_ = ChunkedMappedMemoryWriter::Open( functions_trace_path_, 32 * 1024 * 1024, true); - // Add default transports. - auto gdb_transport = - std::make_unique(this); - if (gdb_transport->Initialize()) { - transports_.emplace_back(std::move(gdb_transport)); + if (FLAGS_debug_server == "gdb") { + server_ = std::make_unique(this); + if (!server_->Initialize()) { + XELOGE("Unable to initialize GDB debug server"); + return false; + } + } else if (FLAGS_debug_server == "gdb") { + server_ = std::make_unique(this); + if (!server_->Initialize()) { + XELOGE("Unable to initialize MI debug server"); + return false; + } } else { - XELOGE("Unable to initialize GDB debugger transport"); + server_ = std::make_unique(this); + if (!server_->Initialize()) { + XELOGE("Unable to initialize XDP debug server"); + return false; + } } return true; @@ -103,17 +118,24 @@ void Debugger::PreLaunch() { // Wait for the first client. XELOGI("Waiting for debugger because of --wait_for_debugger..."); attach_fence_.Wait(); - XELOGI("Debugger attached, continuing..."); - } + XELOGI("Debugger attached, breaking and waiting for continue..."); - // TODO(benvanik): notify transports? + // Start paused. + execution_state_ = ExecutionState::kRunning; + Interrupt(); + } else { + // Start running. + execution_state_ = ExecutionState::kRunning; + } } void Debugger::StopSession() { + std::lock_guard lock(mutex_); + FlushSession(); - // Kill all transports. - transports_.clear(); + // Kill debug server. + server_.reset(); functions_file_.reset(); functions_trace_file_.reset(); @@ -142,6 +164,78 @@ uint8_t* Debugger::AllocateFunctionTraceData(size_t size) { return functions_trace_file_->Allocate(size); } +int Debugger::AddBreakpoint(Breakpoint* breakpoint) { + // Add to breakpoints map. + { + std::lock_guard lock(mutex_); + breakpoints_.insert( + std::pair(breakpoint->address(), breakpoint)); + } + + // Find all functions that contain the breakpoint address. + auto fns = + emulator_->processor()->FindFunctionsWithAddress(breakpoint->address()); + + // Add. + for (auto fn : fns) { + if (fn->AddBreakpoint(breakpoint)) { + return 1; + } + } + + return 0; +} + +int Debugger::RemoveBreakpoint(Breakpoint* breakpoint) { + // Remove from breakpoint map. + { + std::lock_guard lock(mutex_); + auto range = breakpoints_.equal_range(breakpoint->address()); + if (range.first == range.second) { + return 1; + } + bool found = false; + for (auto it = range.first; it != range.second; ++it) { + if (it->second == breakpoint) { + breakpoints_.erase(it); + found = true; + break; + } + } + if (!found) { + return 1; + } + } + + // Find all functions that have the breakpoint set. + auto fns = + emulator_->processor()->FindFunctionsWithAddress(breakpoint->address()); + + // Remove. + for (auto fn : fns) { + fn->RemoveBreakpoint(breakpoint); + } + + return 0; +} + +void Debugger::FindBreakpoints(uint32_t address, + std::vector& out_breakpoints) { + std::lock_guard lock(mutex_); + + out_breakpoints.clear(); + + auto range = breakpoints_.equal_range(address); + if (range.first == range.second) { + return; + } + + for (auto it = range.first; it != range.second; ++it) { + Breakpoint* breakpoint = it->second; + out_breakpoints.push_back(breakpoint); + } +} + bool Debugger::SuspendAllThreads() { auto threads = emulator_->kernel_state()->object_table()->GetObjectsByType( @@ -174,83 +268,43 @@ bool Debugger::ResumeAllThreads() { return true; } -int Debugger::AddBreakpoint(Breakpoint* breakpoint) { - // Add to breakpoints map. - { - std::lock_guard guard(breakpoints_lock_); - breakpoints_.insert( - std::pair(breakpoint->address(), breakpoint)); - } - - // Find all functions that contain the breakpoint address. - auto fns = - emulator_->processor()->FindFunctionsWithAddress(breakpoint->address()); - - // Add. - for (auto fn : fns) { - if (fn->AddBreakpoint(breakpoint)) { - return 1; - } - } - - return 0; +void Debugger::Interrupt() { + std::lock_guard lock(mutex_); + assert_true(execution_state_ == ExecutionState::kRunning); + SuspendAllThreads(); + execution_state_ = ExecutionState::kStopped; + server_->OnExecutionInterrupted(); } -int Debugger::RemoveBreakpoint(Breakpoint* breakpoint) { - // Remove from breakpoint map. - { - std::lock_guard guard(breakpoints_lock_); - auto range = breakpoints_.equal_range(breakpoint->address()); - if (range.first == range.second) { - return 1; - } - bool found = false; - for (auto it = range.first; it != range.second; ++it) { - if (it->second == breakpoint) { - breakpoints_.erase(it); - found = true; - break; - } - } - if (!found) { - return 1; - } - } - - // Find all functions that have the breakpoint set. - auto fns = - emulator_->processor()->FindFunctionsWithAddress(breakpoint->address()); - - // Remove. - for (auto fn : fns) { - fn->RemoveBreakpoint(breakpoint); - } - - return 0; +void Debugger::Continue() { + std::lock_guard lock(mutex_); + assert_true(execution_state_ == ExecutionState::kStopped); + ResumeAllThreads(); + execution_state_ = ExecutionState::kRunning; + server_->OnExecutionContinued(); } -void Debugger::FindBreakpoints(uint32_t address, - std::vector& out_breakpoints) { - std::lock_guard guard(breakpoints_lock_); - - out_breakpoints.clear(); - - auto range = breakpoints_.equal_range(address); - if (range.first == range.second) { - return; - } - - for (auto it = range.first; it != range.second; ++it) { - Breakpoint* breakpoint = it->second; - out_breakpoints.push_back(breakpoint); - } +void Debugger::StepOne(uint32_t thread_id) { + std::lock_guard lock(mutex_); + assert_true(execution_state_ == ExecutionState::kStopped); + // } -void Debugger::OnThreadCreated(ThreadState* thread_state) { +void Debugger::StepTo(uint32_t thread_id, uint32_t target_pc) { + std::lock_guard lock(mutex_); + assert_true(execution_state_ == ExecutionState::kStopped); + // +} + +void Debugger::OnThreadCreated(xe::kernel::XThread* thread) { // TODO(benvanik): notify transports. } -void Debugger::OnThreadDestroyed(ThreadState* thread_state) { +void Debugger::OnThreadExit(xe::kernel::XThread* thread) { + // TODO(benvanik): notify transports. +} + +void Debugger::OnThreadDestroyed(xe::kernel::XThread* thread) { // TODO(benvanik): notify transports. } @@ -259,7 +313,7 @@ void Debugger::OnFunctionDefined(cpu::FunctionInfo* symbol_info, // Man, I'd love not to take this lock. std::vector breakpoints; { - std::lock_guard guard(breakpoints_lock_); + std::lock_guard lock(mutex_); for (uint32_t address = symbol_info->address(); address <= symbol_info->end_address(); address += 4) { auto range = breakpoints_.equal_range(address); @@ -281,7 +335,7 @@ void Debugger::OnFunctionDefined(cpu::FunctionInfo* symbol_info, } } -void Debugger::OnBreakpointHit(ThreadState* thread_state, +void Debugger::OnBreakpointHit(xe::kernel::XThread* thread, Breakpoint* breakpoint) { // Suspend all threads immediately. SuspendAllThreads(); diff --git a/src/xenia/debug/debugger.h b/src/xenia/debug/debugger.h index 843cc8a5e..82f044e7d 100644 --- a/src/xenia/debug/debugger.h +++ b/src/xenia/debug/debugger.h @@ -29,12 +29,20 @@ DECLARE_bool(debug); namespace xe { class Emulator; +namespace kernel { +class XThread; +} // namespace kernel } // namespace xe namespace xe { namespace debug { -class Transport; +class DebugServer; + +enum class ExecutionState { + kRunning, + kStopped, +}; class Debugger { public: @@ -54,9 +62,7 @@ class Debugger { bool is_attached() const { return is_attached_; } void set_attached(bool attached); - bool SuspendAllThreads(); - bool ResumeThread(uint32_t thread_id); - bool ResumeAllThreads(); + ExecutionState execution_state() const { return execution_state_; } int AddBreakpoint(Breakpoint* breakpoint); int RemoveBreakpoint(Breakpoint* breakpoint); @@ -66,19 +72,28 @@ class Debugger { // TODO(benvanik): utility functions for modification (make function ignored, // etc). - void OnThreadCreated(cpu::ThreadState* thread_state); - void OnThreadDestroyed(cpu::ThreadState* thread_state); + void Interrupt(); + void Continue(); + void StepOne(uint32_t thread_id); + void StepTo(uint32_t thread_id, uint32_t target_pc); + + void OnThreadCreated(xe::kernel::XThread* thread); + void OnThreadExit(xe::kernel::XThread* thread); + void OnThreadDestroyed(xe::kernel::XThread* thread); + void OnFunctionDefined(cpu::FunctionInfo* symbol_info, cpu::Function* function); - void OnBreakpointHit(cpu::ThreadState* thread_state, Breakpoint* breakpoint); + void OnBreakpointHit(xe::kernel::XThread* thread, Breakpoint* breakpoint); private: - void OnMessage(std::vector buffer); + bool SuspendAllThreads(); + bool ResumeThread(uint32_t thread_id); + bool ResumeAllThreads(); Emulator* emulator_ = nullptr; - std::vector> transports_; + std::unique_ptr server_; bool is_attached_ = false; xe::threading::Fence attach_fence_; @@ -87,7 +102,9 @@ class Debugger { std::wstring functions_trace_path_; std::unique_ptr functions_trace_file_; - std::mutex breakpoints_lock_; + std::recursive_mutex mutex_; + ExecutionState execution_state_ = ExecutionState::kStopped; + std::multimap breakpoints_; }; diff --git a/src/xenia/debug/premake5.lua b/src/xenia/debug/premake5.lua index a1133046d..7fd45380b 100644 --- a/src/xenia/debug/premake5.lua +++ b/src/xenia/debug/premake5.lua @@ -16,4 +16,6 @@ project("xenia-debug") project_root.."/third_party/flatbuffers/include" }) local_platform_files() - recursive_platform_files("transport") + recursive_platform_files("client") + recursive_platform_files("proto") + recursive_platform_files("server") diff --git a/src/xenia/debug/proto/packet_reader.h b/src/xenia/debug/proto/packet_reader.h new file mode 100644 index 000000000..97b95703b --- /dev/null +++ b/src/xenia/debug/proto/packet_reader.h @@ -0,0 +1,151 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_PROTO_PACKET_READER_H_ +#define XENIA_DEBUG_PROTO_PACKET_READER_H_ + +#include +#include +#include +#include + +#include "xenia/base/assert.h" +#include "xenia/base/math.h" +#include "xenia/debug/proto/varint.h" +#include "xenia/debug/proto/xdp_protocol.h" + +namespace xe { +namespace debug { +namespace proto { + +class PacketReader { + public: + PacketReader(size_t buffer_capacity) : buffer_(buffer_capacity) {} + + const uint8_t* buffer() { return buffer_.data(); } + size_t buffer_capacity() const { return buffer_.size(); } + size_t buffer_offset() const { return buffer_offset_; } + + void Reset() { + buffer_offset_ = 0; + buffer_size_ = 0; + } + + void AppendBuffer(const uint8_t* buffer, size_t buffer_length) { + if (buffer_size_ + buffer_length > buffer_.size()) { + size_t new_size = + std::max(buffer_.size() * 2, + xe::round_up(buffer_size_ + buffer_length, 16 * 1024)); + buffer_.resize(new_size); + } + std::memcpy(buffer_.data() + buffer_size_, buffer, buffer_length); + buffer_size_ += buffer_length; + } + + void Compact() { + assert_null(current_packet_); + if (!buffer_offset_) { + return; + } + std::memmove(buffer_.data(), buffer_.data() + buffer_offset_, + buffer_size_ - buffer_offset_); + buffer_size_ -= buffer_offset_; + buffer_offset_ = 0; + } + + const Packet* Begin() { + assert_null(current_packet_); + size_t bytes_remaining = buffer_size_ - buffer_offset_; + if (bytes_remaining < sizeof(Packet)) { + return nullptr; + } + auto packet = + reinterpret_cast(buffer_.data() + buffer_offset_); + if (bytes_remaining < sizeof(Packet) + packet->body_length) { + return nullptr; + } + packet_offset_ = buffer_offset_ + sizeof(Packet); + buffer_offset_ += sizeof(Packet) + packet->body_length; + current_packet_ = packet; + return packet; + } + + void End() { + assert_not_null(current_packet_); + current_packet_ = nullptr; + packet_offset_ = 0; + } + + const uint8_t* Read(size_t length) { + // Can't read into next packet/off of end. + assert_not_null(current_packet_); + assert_true(packet_offset_ + length <= buffer_offset_); + auto ptr = buffer_.data() + packet_offset_; + packet_offset_ += length; + return ptr; + } + + template + const T* Read() { + return reinterpret_cast(Read(sizeof(T))); + } + + template + std::vector ReadArray(size_t count) { + std::vector entries; + for (size_t i = 0; i < count; ++i) { + entries.push_back(Read()); + } + return entries; + } + + void Read(void* buffer, size_t buffer_length) { + assert_not_null(current_packet_); + auto src = Read(buffer_length); + std::memcpy(buffer, src, buffer_length); + } + + void ReadString(char* buffer, size_t buffer_length) { + assert_not_null(current_packet_); + varint_t length; + auto src = length.Read(buffer_.data() + packet_offset_); + assert_not_null(src); + assert_true(length < buffer_length); + assert_true(length.size() + length < (buffer_offset_ - packet_offset_)); + std::memcpy(buffer, src, length); + packet_offset_ += length; + } + + std::string ReadString() { + assert_not_null(current_packet_); + varint_t length; + auto src = length.Read(buffer_.data() + packet_offset_); + assert_not_null(src); + assert_true(length.size() + length < (buffer_offset_ - packet_offset_)); + std::string value; + value.resize(length.value()); + std::memcpy(const_cast(value.data()), src, length); + packet_offset_ += length; + return value; + } + + private: + std::vector buffer_; + size_t buffer_offset_ = 0; + size_t buffer_size_ = 0; + + const Packet* current_packet_ = nullptr; + size_t packet_offset_ = 0; +}; + +} // namespace proto +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_PROTO_PACKET_READER_H_ diff --git a/src/xenia/debug/proto/packet_writer.h b/src/xenia/debug/proto/packet_writer.h new file mode 100644 index 000000000..c5c854234 --- /dev/null +++ b/src/xenia/debug/proto/packet_writer.h @@ -0,0 +1,98 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_PROTO_PACKET_WRITER_H_ +#define XENIA_DEBUG_PROTO_PACKET_WRITER_H_ + +#include +#include +#include + +#include "xenia/base/assert.h" +#include "xenia/base/math.h" +#include "xenia/debug/proto/varint.h" +#include "xenia/debug/proto/xdp_protocol.h" + +namespace xe { +namespace debug { +namespace proto { + +class PacketWriter { + public: + PacketWriter(size_t buffer_capacity) : buffer_(buffer_capacity) {} + + uint8_t* buffer() { return buffer_.data(); } + size_t buffer_capacity() const { return buffer_.size(); } + size_t buffer_offset() const { return buffer_offset_; } + + void Reset() { buffer_offset_ = 0; } + + void Begin(PacketType packet_type, request_id_t request_id = 0) { + assert_null(current_packet_); + current_packet_ = Append(); + current_packet_->packet_type = packet_type; + current_packet_->request_id = request_id; + current_packet_->body_length = 0; + } + + void End() { + assert_not_null(current_packet_); + current_packet_->body_length = + uint32_t((buffer_.data() + buffer_offset_) - + reinterpret_cast(current_packet_) - sizeof(Packet)); + current_packet_ = nullptr; + } + + uint8_t* Append(size_t length) { + if (buffer_offset_ + length > buffer_.size()) { + size_t new_size = std::max( + xe::round_up(buffer_offset_ + length, 16 * 1024), buffer_.size() * 2); + buffer_.resize(new_size); + } + auto ptr = buffer_.data() + buffer_offset_; + buffer_offset_ += length; + return ptr; + } + + template + T* Append() { + return reinterpret_cast(Append(sizeof(T))); + } + + void Append(const void* buffer, size_t buffer_length) { + auto dest = Append(buffer_length); + std::memcpy(dest, buffer, buffer_length); + } + + void AppendString(const char* value) { + size_t value_length = std::strlen(value); + varint_t length(value_length); + auto dest = Append(length.size() + value_length); + dest = length.Write(dest); + std::memcpy(dest, value, value_length); + } + void AppendString(const std::string& value) { + varint_t length(value.size()); + auto dest = Append(length.size() + value.size()); + dest = length.Write(dest); + std::memcpy(dest, value.data(), value.size()); + } + + private: + std::vector buffer_; + size_t buffer_offset_ = 0; + + Packet* current_packet_ = nullptr; +}; + +} // namespace proto +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_PROTO_PACKET_WRITER_H_ diff --git a/src/xenia/debug/proto/varint.h b/src/xenia/debug/proto/varint.h new file mode 100644 index 000000000..c6e5658a7 --- /dev/null +++ b/src/xenia/debug/proto/varint.h @@ -0,0 +1,74 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_PROTO_VARINT_H_ +#define XENIA_DEBUG_PROTO_VARINT_H_ + +#include + +namespace xe { +namespace debug { +namespace proto { + +struct varint_t { + varint_t() = default; + varint_t(uint64_t value) : value_(value) {} + varint_t(const varint_t& other) : value_(other.value_) {} + + operator uint64_t() const { return value_; } + uint64_t value() const { return value_; } + + size_t size() const { + uint64_t value = value_; + size_t i = 0; + do { + value >>= 7; + ++i; + } while (value >= 0x80); + return i + 1; + } + + const uint8_t* Read(const uint8_t* src) { + if (!(src[0] & 0x80)) { + value_ = src[0]; + return src + 1; + } + uint64_t r = src[0] & 0x7F; + size_t i; + for (i = 1; i < 10; ++i) { + r |= (src[i] & 0x7F) << (7 * i); + if (!(src[i] & 0x80)) { + break; + } + } + value_ = r; + return src + i + 1; + } + + uint8_t* Write(uint8_t* dest) { + uint64_t value = value_; + size_t i = 0; + do { + dest[i] = uint8_t(value | 0x80); + value >>= 7; + ++i; + } while (value >= 0x80); + dest[i] = uint8_t(value); + return dest + i + 1; + } + + private: + uint64_t value_ = 0; +}; + +} // namespace proto +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_PROTO_VARINT_H_ diff --git a/src/xenia/debug/proto/xdp_protocol.h b/src/xenia/debug/proto/xdp_protocol.h new file mode 100644 index 000000000..1a5409794 --- /dev/null +++ b/src/xenia/debug/proto/xdp_protocol.h @@ -0,0 +1,173 @@ +/** + ****************************************************************************** + * 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 + +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, +}; + +using request_id_t = uint16_t; + +struct Packet { + PacketType packet_type; + request_id_t request_id; + uint32_t body_length; + uint8_t* body() { return reinterpret_cast(this) + sizeof(this); } +}; +static_assert(sizeof(Packet) == 8, "Fixed packet size"); + +// Generic response to requests (S->C). +// Used for any request type that doesn't define its own response. +struct GenericResponse { + static const PacketType type = PacketType::kGenericResponse; + + enum class Code : uint32_t { + // Command was received and successfully acted upon. + // If the request is handled asynchronously this only indicates that the + // command was recognized. + kSuccess = 0, + // Command was invalid or could not be executed. + // A string will follow this response containing an error message. + kError = 1, + }; + + Code code; + + bool is_success() const { return code == Code::kSuccess; } +}; + +// Requests execution state changes (C->S). +// Response is a GenericResponse, and as execution changes one or more +// ExecutionNotification packets will be sent S->C. +struct ExecutionRequest { + static const PacketType type = PacketType::kExecutionRequest; + + enum class Action : uint32_t { + // Continues execution until the next breakpoint. + kContinue, + // Steps execution by the given step_mode. + kStep, + // Interrupts execution and breaks. + kInterrupt, + // Stops execution and causes the server to exit. + kExit, + }; + + enum class StepMode : uint32_t { + // Steps a single statement, following branches. + kStepOne, + // Runs until the given target_pc. + kStepTo, + }; + + Action action; + StepMode step_mode; + uint32_t thread_id; + uint32_t target_pc; +}; + +// Notifies clients of changes in execution state (S->C). +// This is fired asynchronously from any request and possibly due to events +// external to the client requested ones. +// After stopping clients should query execution state. +struct ExecutionNotification { + static const PacketType type = PacketType::kExecutionNotification; + + enum class State : uint32_t { + // Execution has stopped for stop_reason. + kStopped, + // Execution has resumed and the target is now running. + kRunning, + // The target has exited. + kExited, + }; + + enum class Reason : uint32_t { + // Stopped because the client requested it with Action::kInterrupt. + kInterrupt, + // Stopped due to a completed step. + kStep, + // Stopped at a breakpoint. + kBreakpoint, + // TODO(benvanik): others? catches? library loads? etc? + }; + + State current_state; + Reason stop_reason; +}; + +struct ModuleListRequest { + static const PacketType type = PacketType::kModuleListRequest; +}; +struct ModuleListResponse { + static const PacketType type = PacketType::kModuleListResponse; + + uint32_t count; + // ModuleListEntry[count] +}; +struct ModuleListEntry { + uint32_t module_handle; + uint32_t module_ptr; + bool is_kernel_module; + char path[256]; + char name[256]; +}; + +struct ThreadListRequest { + static const PacketType type = PacketType::kThreadListRequest; +}; +struct ThreadListResponse { + static const PacketType type = PacketType::kThreadListResponse; + + uint32_t count; + // ThreadListEntry[count] +}; +struct ThreadListEntry { + uint32_t thread_handle; + uint32_t thread_id; + bool is_host_thread; + char name[64]; + uint32_t exit_code; + int32_t priority; + uint32_t affinity; + uint32_t xapi_thread_startup; + uint32_t start_address; + uint32_t start_context; + uint32_t creation_flags; + uint32_t stack_address; + uint32_t stack_size; + uint32_t stack_base; + uint32_t stack_limit; + uint32_t pcr_address; + uint32_t tls_address; +}; + +} // namespace proto +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_PROTO_XDP_PROTOCOL_H_ diff --git a/src/xenia/debug/proto/xdp_protocol.md b/src/xenia/debug/proto/xdp_protocol.md new file mode 100644 index 000000000..f8f8b9c4c --- /dev/null +++ b/src/xenia/debug/proto/xdp_protocol.md @@ -0,0 +1,44 @@ +XDP: Xenia Debug Protocol +-------------------------- + +## Overview + +### General + +* Packets flow in both directions (client and server). +* Each packet is length-prefixed (exclusive) and tagged with a type. +* The entire protocol is fully asynchronous, and supports both request/response + style RPCs as well as notifications. +* Request/response RPCs are matched on ID and multiple are allowed to be in + flight at a time. +* Notifications may come in any order. + +### Server + +The debug server is single threaded and processes commands as a FIFO. This +means that all requests will be responded to in the order they are received. + +### Client + +TODO + +## Packet Structure + +Each packet consists of a length, type, and request ID. The length denotes +the total number of bytes within the packet body, excluding the packet header. +The request ID is an opaque value sent by the client when making a request and +the server returns whatever the ID is when it responds to that request. The ID +is unused for notifications. + +``` +struct Packet { + uint16_t packet_type; + uint16_t request_id; + uint32_t body_length; + uint8_t body[body_length]; +}; +``` + +## Packets + +TODO(benvanik): document the various types, etc. diff --git a/src/xenia/debug/server/gdb/gdb_command_processor.cc b/src/xenia/debug/server/gdb/gdb_command_processor.cc new file mode 100644 index 000000000..1fe486f36 --- /dev/null +++ b/src/xenia/debug/server/gdb/gdb_command_processor.cc @@ -0,0 +1,319 @@ +/** + ****************************************************************************** + * 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/server/gdb/gdb_command_processor.h" + +#include "xenia/base/logging.h" +#include "xenia/base/string_buffer.h" +#include "xenia/debug/server/gdb/gdb_server.h" + +namespace xe { +namespace debug { +namespace server { +namespace gdb { + +constexpr size_t kReceiveBufferSize = 1 * 1024 * 1024; +constexpr size_t kTransmitBufferSize = 1 * 1024 * 1024; + +GdbCommandProcessor::GdbCommandProcessor(GdbServer* server, Debugger* debugger) + : server_(server), debugger_(debugger) { + receive_buffer_.resize(kReceiveBufferSize); + transmit_buffer_.resize(kTransmitBufferSize); +} + +bool GdbCommandProcessor::ProcessBuffer(const uint8_t* buffer, + size_t buffer_length) { + // Grow and append the bytes to the receive buffer. + while (receive_offset_ + buffer_length > receive_buffer_.capacity()) { + receive_buffer_.resize(receive_buffer_.size() * 2); + } + std::memcpy(receive_buffer_.data() + receive_offset_, buffer, buffer_length); + receive_offset_ += buffer_length; + + // While there are bytes pending in the buffer we scan through looking for end + // markers. When we find one, we emit the packet and move on. + size_t process_offset = 0; + while (process_offset < receive_offset_) { + // Look for an end marker. + size_t end_offset = -1; + size_t buffer_end_offset = -1; + for (size_t i = process_offset; i < receive_offset_; ++i) { + if (receive_buffer_[i] == '#') { + end_offset = i - 1; + buffer_end_offset = i + 2; + break; + } + } + if (end_offset == -1) { + // No end marker found - break out for now. + break; + } + + // Emit packet. + if (!ProcessPacket((const char*)(receive_buffer_.data() + process_offset), + end_offset - process_offset)) { + // Failed to process line. + std::string line(receive_buffer_.data() + process_offset, + receive_buffer_.data() + end_offset); + XELOGE("Failed to process incoming GDB line: %s", line.c_str()); + return false; + } + process_offset = buffer_end_offset + 1; + } + + // If we have leftover unprocessed bytes move them to the front of the buffer. + if (process_offset < receive_offset_) { + size_t remaining_bytes = receive_offset_ - process_offset; + std::memmove(receive_buffer_.data(), + receive_buffer_.data() + process_offset, remaining_bytes); + receive_offset_ = remaining_bytes; + } else { + receive_offset_ = 0; + } + + return true; +} + +bool GdbCommandProcessor::ProcessPacket(const char* buffer, + size_t buffer_length) { + // There may be any number of leading +'s or -'s. + size_t offset = 0; + for (; offset < buffer_length; ++offset) { + if (buffer[offset] == '$') { + // Command start. + ++offset; + break; + } else if (buffer[offset] == '+') { + // Ack. + } else if (buffer[offset] == '-') { + // No good - means transmission error. + XELOGE("GDB client remorted transmission error"); + return false; + } + } + + std::string line((const char*)(buffer + offset), buffer_length - 1); + XELOGI("GDB -> %s", line.c_str()); + + // Immediately send ACK. + if (!no_ack_mode_) { + server_->client()->Send("+", 1); + } + + const char* buffer_ptr = buffer + offset; + bool handled = false; + switch (buffer[offset]) { + case '!': + // Enable extended mode. + SendResponse("OK"); + handled = true; + break; + case 'v': + // Verbose packets. + if (std::strstr(buffer_ptr, "vRun") == buffer_ptr) { + SendResponse("S05"); + handled = true; + } else if (std::strstr(buffer_ptr, "vCont") == buffer_ptr) { + SendResponse("S05"); + handled = true; + } + break; + case 'q': + // Query packets. + switch (buffer_ptr[1]) { + case 'C': + // Get current thread ID. + SendResponse("QC01"); + handled = true; + break; + case 'R': + // Command. + SendResponse("OK"); + handled = true; + break; + default: + if (std::strstr(buffer_ptr, "qSupported") == buffer_ptr) { + // Get/set feature support. + handled = Process_qSupported(line); + } else if (std::strstr(buffer_ptr, "qAttached") == buffer_ptr) { + // Check attach mode; 0 = new process, 1 = existing process. + SendResponse("0"); + handled = true; + } else if (std::strstr(buffer_ptr, "qfThreadInfo") == buffer_ptr) { + // Start of thread list request. + SendResponse("m01"); + handled = true; + } else if (std::strstr(buffer_ptr, "qsThreadInfo") == buffer_ptr) { + // Continuation of thread list request. + SendResponse("l"); // l = last. + handled = true; + } + break; + } + break; + case 'Q': + // Set packets. + switch (buffer_ptr[1]) { + default: + if (std::strstr(buffer_ptr, "QStartNoAckMode") == buffer_ptr) { + no_ack_mode_ = true; + SendResponse("OK"); + handled = true; + } + break; + } + break; + case 'H': + // Set thread for subsequent operations. + SendResponse("OK"); + handled = true; + break; + case 'g': + // Read all registers. + handled = ProcessReadRegisters(line); + break; + case 'G': + // Write all registers. + handled = true; + break; + case 'p': + // Read register. + handled = true; + break; + case 'P': + // Write register. + handled = true; + break; + case 'm': + // Read memory. + handled = true; + break; + case 'M': + // Write memory. + handled = true; + break; + case 'Z': + // Insert breakpoint. + handled = true; + break; + case 'z': + // Remove breakpoint. + handled = true; + break; + case '?': + // Query halt reason. + SendResponse("S05"); + handled = true; + break; + case 'c': + // Continue (vCont should be used instead). + // NOTE: reply is sent on halt, not right now. + SendResponse("S05"); + handled = true; + break; + case 's': + // Single step. + // NOTE: reply is sent on halt, not right now. + SendResponse("S05"); + handled = true; + break; + } + if (!handled) { + XELOGE("Unknown GDB packet: %s", buffer_ptr); + SendResponse(""); + } + return true; +} + +void GdbCommandProcessor::SendResponse(const char* value, size_t length) { + XELOGI("GDB <- %s", value); + uint8_t checksum = 0; + for (size_t i = 0; i < length; ++i) { + uint8_t c = uint8_t(value[i]); + if (!c) { + break; + } + checksum += c; + } + char crc[4]; + sprintf(crc, "#%.2X", checksum); + std::pair buffers[] = { + {"$", 1}, {value, length}, {crc, 3}, + }; + server_->client()->Send(buffers, xe::countof(buffers)); +} + +bool GdbCommandProcessor::Process_qSupported(const std::string& line) { + StringBuffer response; + + // Read in the features the client supports. + // qSupported[:gdbfeature[;gdbfeature]...] + size_t feature_offset = line.find(':'); + while (feature_offset != std::string::npos) { + size_t next_offset = line.find(';', feature_offset + 1); + std::string feature = + line.substr(feature_offset + 1, next_offset - feature_offset - 1); + feature_offset = next_offset; + if (feature.find("multiprocess") == 0) { + bool is_supported = feature[12] == '+'; + } else if (feature.find("xmlRegisters") == 0) { + // xmlRegisters=A,B,C + } else if (feature.find("qRelocInsn") == 0) { + bool is_supported = feature[10] == '+'; + } else if (feature.find("swbreak") == 0) { + bool is_supported = feature[7] == '+'; + } else if (feature.find("hwbreak") == 0) { + bool is_supported = feature[7] == '+'; + } else { + XELOGW("Unknown GDB client support feature: %s", feature.c_str()); + } + } + + response.Append("PacketSize=4000;"); + response.Append("QStartNoAckMode+;"); + response.Append("qRelocInsn-;"); + response.Append("multiprocess-;"); + response.Append("ConditionalBreakpoints-;"); + response.Append("ConditionalTracepoints-;"); + response.Append("ReverseContinue-;"); + response.Append("ReverseStep-;"); + response.Append("swbreak+;"); + response.Append("hwbreak+;"); + + SendResponse(response.GetString()); + return true; +} + +bool GdbCommandProcessor::ProcessReadRegisters(const std::string& line) { + StringBuffer response; + + for (int32_t n = 0; n < 32; n++) { + // gpr + response.AppendFormat("%08X", n); + } + for (int64_t n = 0; n < 32; n++) { + // fpr + response.AppendFormat("%016llX", n); + } + response.AppendFormat("%08X", 0x8202FB40); // pc + response.AppendFormat("%08X", 65); // msr + response.AppendFormat("%08X", 66); // cr + response.AppendFormat("%08X", 67); // lr + response.AppendFormat("%08X", 68); // ctr + response.AppendFormat("%08X", 69); // xer + response.AppendFormat("%08X", 70); // fpscr + + SendResponse(response.GetString()); + return true; +} + +} // namespace gdb +} // namespace server +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/server/gdb/gdb_command_processor.h b/src/xenia/debug/server/gdb/gdb_command_processor.h new file mode 100644 index 000000000..634e0c8f6 --- /dev/null +++ b/src/xenia/debug/server/gdb/gdb_command_processor.h @@ -0,0 +1,59 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_SERVER_GDB_GDB_COMMAND_PROCESSOR_H_ +#define XENIA_DEBUG_SERVER_GDB_GDB_COMMAND_PROCESSOR_H_ + +#include +#include + +#include "xenia/debug/debugger.h" + +namespace xe { +namespace debug { +namespace server { +namespace gdb { + +class GdbServer; + +class GdbCommandProcessor { + public: + GdbCommandProcessor(GdbServer* server, Debugger* debugger); + ~GdbCommandProcessor() = default; + + bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length); + + private: + bool ProcessPacket(const char* buffer, size_t buffer_length); + void SendResponse(const char* value, size_t length); + void SendResponse(const char* value) { + SendResponse(value, std::strlen(value)); + } + void SendResponse(std::string value) { + SendResponse(value.data(), value.size()); + } + + bool Process_qSupported(const std::string& line); + bool ProcessReadRegisters(const std::string& line); + + GdbServer* server_ = nullptr; + Debugger* debugger_ = nullptr; + std::vector receive_buffer_; + size_t receive_offset_ = 0; + std::vector transmit_buffer_; + + bool no_ack_mode_ = false; +}; + +} // namespace gdb +} // namespace server +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_SERVER_GDB_GDB_COMMAND_PROCESSOR_H_ diff --git a/src/xenia/debug/transport/gdb/gdb_transport.cc b/src/xenia/debug/server/gdb/gdb_server.cc similarity index 74% rename from src/xenia/debug/transport/gdb/gdb_transport.cc rename to src/xenia/debug/server/gdb/gdb_server.cc index bb8313715..d17448242 100644 --- a/src/xenia/debug/transport/gdb/gdb_transport.cc +++ b/src/xenia/debug/server/gdb/gdb_server.cc @@ -7,30 +7,32 @@ ****************************************************************************** */ -#include "xenia/debug/transport/gdb/gdb_transport.h" +#include "xenia/debug/server/gdb/gdb_server.h" #include #include "xenia/base/logging.h" #include "xenia/debug/debugger.h" +#include "xenia/debug/server/gdb/gdb_command_processor.h" -DEFINE_int32(gdb_port, 9000, "Debugger gdbserver TCP port."); +DEFINE_int32(gdb_server_port, 9000, "Debugger gdbserver TCP port."); namespace xe { namespace debug { -namespace transport { +namespace server { namespace gdb { -constexpr size_t kReceiveBufferSize = 2 * 1024 * 1024; +constexpr size_t kReceiveBufferSize = 32 * 1024; -GdbTransport::GdbTransport(Debugger* debugger) : Transport(debugger) { +GdbServer::GdbServer(Debugger* debugger) : DebugServer(debugger) { receive_buffer_.resize(kReceiveBufferSize); + command_processor_ = std::make_unique(this, debugger); } -GdbTransport::~GdbTransport() = default; +GdbServer::~GdbServer() = default; -bool GdbTransport::Initialize() { - socket_server_ = SocketServer::Create(uint16_t(FLAGS_gdb_port), +bool GdbServer::Initialize() { + socket_server_ = SocketServer::Create(uint16_t(FLAGS_gdb_server_port), [this](std::unique_ptr client) { AcceptClient(std::move(client)); }); @@ -42,7 +44,7 @@ bool GdbTransport::Initialize() { return true; } -void GdbTransport::AcceptClient(std::unique_ptr client) { +void GdbServer::AcceptClient(std::unique_ptr client) { // If we have an existing client, kill it and join its thread. if (client_) { // TODO(benvanik): GDB say goodbye? @@ -63,6 +65,9 @@ void GdbTransport::AcceptClient(std::unique_ptr client) { // Let the debugger know we are present. debugger_->set_attached(true); + // Junk just to poke the remote client. + client_->Send("(gdb)\n"); + // Main loop. bool running = true; while (running) { @@ -91,7 +96,7 @@ void GdbTransport::AcceptClient(std::unique_ptr client) { }); } -bool GdbTransport::HandleClientEvent() { +bool GdbServer::HandleClientEvent() { if (!client_->is_connected()) { // Known-disconnected. return false; @@ -107,12 +112,17 @@ bool GdbTransport::HandleClientEvent() { return true; } - // TODO(benvanik): process incoming command. + // Pass off the command processor to do with it what it wants. + if (!command_processor_->ProcessBuffer(receive_buffer_.data(), bytes_read)) { + // Error. + XELOGE("Error processing incoming GDB command; forcing disconnect"); + return false; + } return true; } } // namespace gdb -} // namespace transport +} // namespace server } // namespace debug } // namespace xe diff --git a/src/xenia/debug/transport/gdb/gdb_transport.h b/src/xenia/debug/server/gdb/gdb_server.h similarity index 68% rename from src/xenia/debug/transport/gdb/gdb_transport.h rename to src/xenia/debug/server/gdb/gdb_server.h index 1dc7f3098..2e6b3019e 100644 --- a/src/xenia/debug/transport/gdb/gdb_transport.h +++ b/src/xenia/debug/server/gdb/gdb_server.h @@ -7,24 +7,28 @@ ****************************************************************************** */ -#ifndef XENIA_DEBUG_TRANSPORT_GDB_GDB_TRANSPORT_H_ -#define XENIA_DEBUG_TRANSPORT_GDB_GDB_TRANSPORT_H_ +#ifndef XENIA_DEBUG_SERVER_GDB_GDB_SERVER_H_ +#define XENIA_DEBUG_SERVER_GDB_GDB_SERVER_H_ #include #include "xenia/base/socket.h" #include "xenia/base/threading.h" -#include "xenia/debug/transport.h" +#include "xenia/debug/debug_server.h" namespace xe { namespace debug { -namespace transport { +namespace server { namespace gdb { -class GdbTransport : public Transport { +class GdbCommandProcessor; + +class GdbServer : public DebugServer { public: - GdbTransport(Debugger* debugger); - ~GdbTransport() override; + GdbServer(Debugger* debugger); + ~GdbServer() override; + + Socket* client() const { return client_.get(); } bool Initialize() override; @@ -37,11 +41,13 @@ class GdbTransport : public Transport { std::unique_ptr client_; std::unique_ptr client_thread_; std::vector receive_buffer_; + + std::unique_ptr command_processor_; }; } // namespace gdb -} // namespace transport +} // namespace server } // namespace debug } // namespace xe -#endif // XENIA_DEBUG_TRANSPORT_GDB_GDB_TRANSPORT_H_ +#endif // XENIA_DEBUG_SERVER_GDB_GDB_SERVER_H_ diff --git a/src/xenia/debug/server/mi/mi_command_processor.cc b/src/xenia/debug/server/mi/mi_command_processor.cc new file mode 100644 index 000000000..0f9326595 --- /dev/null +++ b/src/xenia/debug/server/mi/mi_command_processor.cc @@ -0,0 +1,232 @@ +/** + ****************************************************************************** + * 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/server/mi/mi_command_processor.h" + +#include "xenia/base/logging.h" +#include "xenia/base/string_buffer.h" +#include "xenia/debug/server/mi/mi_reader.h" +#include "xenia/debug/server/mi/mi_server.h" +#include "xenia/debug/server/mi/mi_writer.h" + +namespace xe { +namespace debug { +namespace server { +namespace mi { + +constexpr size_t kReceiveBufferSize = 1 * 1024 * 1024; +constexpr size_t kTransmitBufferSize = 1 * 1024 * 1024; + +MICommandProcessor::MICommandProcessor(MIServer* server, Debugger* debugger) + : server_(server), debugger_(debugger) { + receive_buffer_.resize(kReceiveBufferSize); + transmit_buffer_.resize(kTransmitBufferSize); +} + +bool MICommandProcessor::ProcessBuffer(const uint8_t* buffer, + size_t buffer_length) { + // Grow and append the bytes to the receive buffer. + while (receive_offset_ + buffer_length > receive_buffer_.capacity()) { + receive_buffer_.resize(receive_buffer_.size() * 2); + } + std::memcpy(receive_buffer_.data() + receive_offset_, buffer, buffer_length); + receive_offset_ += buffer_length; + + // While there are bytes pending in the buffer we scan through looking for end + // markers. When we find one, we emit the packet and move on. + size_t process_offset = 0; + while (process_offset < receive_offset_) { + // Look for an end marker. + size_t end_offset = -1; + size_t buffer_end_offset = -1; + for (size_t i = process_offset; i < receive_offset_; ++i) { + if (receive_buffer_[i] == '\r') { + end_offset = i - 1; + buffer_end_offset = i + 1; + break; + } else if (receive_buffer_[i] == '\n') { + end_offset = i - 1; + buffer_end_offset = i; + } + } + if (end_offset == -1) { + // No end marker found - break out for now. + break; + } + + // Emit packet. + if (!ProcessPacket((const char*)(receive_buffer_.data() + process_offset), + end_offset - process_offset)) { + // Failed to process line. + std::string line(receive_buffer_.data() + process_offset, + receive_buffer_.data() + end_offset); + XELOGE("Failed to process incoming MI line: %s", line.c_str()); + return false; + } + process_offset = buffer_end_offset + 1; + } + + // If we have leftover unprocessed bytes move them to the front of the buffer. + if (process_offset < receive_offset_) { + size_t remaining_bytes = receive_offset_ - process_offset; + std::memmove(receive_buffer_.data(), + receive_buffer_.data() + process_offset, remaining_bytes); + receive_offset_ = remaining_bytes; + } else { + receive_offset_ = 0; + } + + return true; +} + +bool MICommandProcessor::ProcessPacket(const char* buffer, + size_t buffer_length) { + // Will likely have a request prefix, like '#####-....'. + auto buffer_ptr = std::strchr(buffer, '-'); + if (!buffer_ptr) { + XELOGE("Malformed MI command"); + return false; + } + std::string token; + if (buffer_ptr != buffer) { + token = std::string(buffer, buffer_ptr); + } + ++buffer_ptr; // Skip leading '-'. + + std::string line((const char*)buffer_ptr, + buffer_length - (buffer_ptr - buffer) + 1); + XELOGI("MI -> %s", line.c_str()); + + auto command = line.substr(0, line.find(' ')); + auto args = + command.size() == line.size() ? "" : line.substr(command.size() + 1); + + bool handled = false; + if (command == "gdb-set") { + auto key = args.substr(0, args.find(' ')); + auto value = args.substr(args.find(' ') + 1); + if (key == "mi-async" || key == "target-async") { + bool is_enabled = value == "1" || value == "on"; + SendResult(token, ResultClass::kDone); + handled = true; + } else if (key == "stop-on-solib-events") { + bool is_enabled = value == "1" || value == "on"; + SendResult(token, ResultClass::kDone); + handled = true; + } + } else if (command == "file-exec-and-symbols") { + // args=foo + SendResult(token, ResultClass::kDone); + handled = true; + } else if (command == "break-insert") { + // args=main + SendResult(token, ResultClass::kDone); + handled = true; + } else if (command == "exec-run") { + exec_token_ = token; + SendResult(token, ResultClass::kRunning); + handled = true; + + MIWriter writer(transmit_buffer_.data(), transmit_buffer_.size()); + writer.AppendRaw(exec_token_); + writer.BeginAsyncNotifyRecord("library-loaded"); + writer.AppendString("id=123"); + writer.AppendString("target-name=\"foo.xex\""); + writer.AppendString("host-name=\"foo.xex\""); + writer.EndRecord(); + server_->client()->Send(transmit_buffer_.data(), writer.buffer_offset()); + } else if (command == "exec-continue") { + exec_token_ = token; + SendResult(token, ResultClass::kRunning); + handled = true; + } else if (command == "exec-interrupt") { + SendResult(token, ResultClass::kDone); + handled = true; + + // later: + MIWriter writer(transmit_buffer_.data(), transmit_buffer_.size()); + writer.AppendRaw(exec_token_); + writer.BeginAsyncExecRecord("stopped"); + writer.AppendString("reason=\"signal-received\""); + writer.AppendString("signal-name=\"SIGINT\""); + writer.AppendString("signal-meaning=\"Interrupt\""); + writer.EndRecord(); + server_->client()->Send(transmit_buffer_.data(), writer.buffer_offset()); + } else if (command == "exec-abort") { + XELOGI("Debug client requested abort"); + SendResult(token, ResultClass::kDone); + exit(1); + handled = true; + } else if (command == "gdb-exit") { + // TODO(benvanik): die better? + XELOGI("Debug client requested exit"); + exit(1); + SendResult(token, ResultClass::kDone); + handled = true; + } + if (!handled) { + XELOGE("Unknown GDB packet: %s", buffer_ptr); + SendErrorResult(token, "Unknown/unimplemented"); + } + return true; +} + +void MICommandProcessor::SendConsoleStreamOutput(const char* value) { + MIWriter writer(transmit_buffer_.data(), transmit_buffer_.size()); + writer.BeginConsoleStreamOutput(); + writer.AppendString(value); + writer.EndRecord(); + server_->client()->Send(transmit_buffer_.data(), writer.buffer_offset()); +} + +void MICommandProcessor::SendTargetStreamOutput(const char* value) { + MIWriter writer(transmit_buffer_.data(), transmit_buffer_.size()); + writer.BeginTargetStreamOutput(); + writer.AppendString(value); + writer.EndRecord(); + server_->client()->Send(transmit_buffer_.data(), writer.buffer_offset()); +} + +void MICommandProcessor::SendLogStreamOutput(const char* value) { + MIWriter writer(transmit_buffer_.data(), transmit_buffer_.size()); + writer.BeginLogStreamOutput(); + writer.AppendString(value); + writer.EndRecord(); + server_->client()->Send(transmit_buffer_.data(), writer.buffer_offset()); +} + +void MICommandProcessor::SendResult(const std::string& prefix, + ResultClass result_class) { + MIWriter writer(transmit_buffer_.data(), transmit_buffer_.size()); + writer.AppendRaw(prefix); + writer.BeginResultRecord(result_class); + writer.EndRecord(); + server_->client()->Send(transmit_buffer_.data(), writer.buffer_offset()); +} + +void MICommandProcessor::SendErrorResult(const std::string& prefix, + const char* message, + const char* code) { + MIWriter writer(transmit_buffer_.data(), transmit_buffer_.size()); + writer.AppendRaw(prefix); + writer.BeginResultRecord(ResultClass::kError); + if (message) { + writer.AppendResult("msg", message); + } + if (code) { + writer.AppendResult("code", code); + } + writer.EndRecord(); + server_->client()->Send(transmit_buffer_.data(), writer.buffer_offset()); +} + +} // namespace mi +} // namespace server +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/server/mi/mi_command_processor.h b/src/xenia/debug/server/mi/mi_command_processor.h new file mode 100644 index 000000000..8d144d398 --- /dev/null +++ b/src/xenia/debug/server/mi/mi_command_processor.h @@ -0,0 +1,57 @@ +/** + ****************************************************************************** + * 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_SERVER_MI_MI_COMMAND_PROCESSOR_H_ +#define XENIA_DEBUG_SERVER_MI_MI_COMMAND_PROCESSOR_H_ + +#include +#include + +#include "xenia/debug/debugger.h" +#include "xenia/debug/server/mi/mi_protocol.h" + +namespace xe { +namespace debug { +namespace server { +namespace mi { + +class MIServer; + +class MICommandProcessor { + public: + MICommandProcessor(MIServer* server, Debugger* debugger); + ~MICommandProcessor() = default; + + bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length); + + private: + bool ProcessPacket(const char* buffer, size_t buffer_length); + void SendConsoleStreamOutput(const char* value); + void SendTargetStreamOutput(const char* value); + void SendLogStreamOutput(const char* value); + void SendResult(const std::string& prefix, ResultClass result_class); + void SendErrorResult(const std::string& prefix, const char* message = nullptr, + const char* code = nullptr); + + MIServer* server_ = nullptr; + Debugger* debugger_ = nullptr; + std::vector receive_buffer_; + size_t receive_offset_ = 0; + std::vector transmit_buffer_; + + // Token of the last -exec-* command. Sent back on stops. + std::string exec_token_; +}; + +} // namespace mi +} // namespace server +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_SERVER_MI_MI_COMMAND_PROCESSOR_H_ diff --git a/src/xenia/debug/server/mi/mi_protocol.h b/src/xenia/debug/server/mi/mi_protocol.h new file mode 100644 index 000000000..82a2ad8f8 --- /dev/null +++ b/src/xenia/debug/server/mi/mi_protocol.h @@ -0,0 +1,46 @@ +/** + ****************************************************************************** + * 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_SERVER_MI_MI_PROTOCOL_H_ +#define XENIA_DEBUG_SERVER_MI_MI_PROTOCOL_H_ + +#include + +namespace xe { +namespace debug { +namespace server { +namespace mi { + +enum class RecordToken : char { + kResult = '^', + kAsyncExec = '*', + kAsyncStatus = '+', + kAsyncNotify = '=', +}; + +enum class StreamToken : char { + kConsole = '~', + kTarget = '@', + kLog = '&', +}; + +enum class ResultClass { + kDone, + kRunning, + kConnected, + kError, + kExit, +}; + +} // namespace mi +} // namespace server +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_SERVER_MI_MI_PROTOCOL_H_ diff --git a/src/xenia/debug/server/mi/mi_reader.cc b/src/xenia/debug/server/mi/mi_reader.cc new file mode 100644 index 000000000..6f8fb76be --- /dev/null +++ b/src/xenia/debug/server/mi/mi_reader.cc @@ -0,0 +1,22 @@ +/** + ****************************************************************************** + * 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/server/mi/mi_reader.h" + +namespace xe { +namespace debug { +namespace server { +namespace mi { + +// + +} // namespace mi +} // namespace server +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/server/mi/mi_reader.h b/src/xenia/debug/server/mi/mi_reader.h new file mode 100644 index 000000000..0252f46f9 --- /dev/null +++ b/src/xenia/debug/server/mi/mi_reader.h @@ -0,0 +1,31 @@ +/** + ****************************************************************************** + * 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_SERVER_MI_MI_READER_H_ +#define XENIA_DEBUG_SERVER_MI_MI_READER_H_ + +#include + +#include "xenia/debug/server/mi/mi_protocol.h" + +namespace xe { +namespace debug { +namespace server { +namespace mi { + +class MIReader { + public: +}; + +} // namespace mi +} // namespace server +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_SERVER_MI_MI_READER_H_ diff --git a/src/xenia/debug/server/mi/mi_server.cc b/src/xenia/debug/server/mi/mi_server.cc new file mode 100644 index 000000000..19e0f5fd6 --- /dev/null +++ b/src/xenia/debug/server/mi/mi_server.cc @@ -0,0 +1,130 @@ +/** + ****************************************************************************** + * 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/server/mi/mi_server.h" + +#include + +#include "xenia/base/logging.h" +#include "xenia/debug/debugger.h" +#include "xenia/debug/server/mi/mi_command_processor.h" + +DEFINE_int32(mi_server_port, 9001, "Debugger MI server TCP port."); + +namespace xe { +namespace debug { +namespace server { +namespace mi { + +constexpr size_t kReceiveBufferSize = 32 * 1024; + +MIServer::MIServer(Debugger* debugger) : DebugServer(debugger) { + receive_buffer_.resize(kReceiveBufferSize); + command_processor_ = std::make_unique(this, debugger); +} + +MIServer::~MIServer() = default; + +bool MIServer::Initialize() { + socket_server_ = SocketServer::Create(uint16_t(FLAGS_mi_server_port), + [this](std::unique_ptr client) { + AcceptClient(std::move(client)); + }); + if (!socket_server_) { + XELOGE("Unable to create MI socket server - port in use?"); + return false; + } + + return true; +} + +void MIServer::AcceptClient(std::unique_ptr client) { + // If we have an existing client, kill it and join its thread. + if (client_) { + // TODO(benvanik): GDB 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]() { + // TODO(benvanik): GDB protocol stuff? Do we say hi? + // TODO(benvanik): move hello to thread + + // Let the debugger know we are present. + debugger_->set_attached(true); + + // Junk just to poke the remote client. + // The Microsoft MI engine seems to wait for *something* to come through on + // connection. + client_->Send("(gdb)\n"); + + // Main loop. + bool running = true; + while (running) { + auto wait_result = xe::threading::Wait(client_->wait_handle(), true); + switch (wait_result) { + case xe::threading::WaitResult::kSuccess: + // Event (read or close). + running = HandleClientEvent(); + 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 MIServer::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 (!command_processor_->ProcessBuffer(receive_buffer_.data(), bytes_read)) { + // Error. + XELOGE("Error processing incoming GDB command; forcing disconnect"); + return false; + } + + return true; +} + +} // namespace mi +} // namespace server +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/server/mi/mi_server.h b/src/xenia/debug/server/mi/mi_server.h new file mode 100644 index 000000000..9d109d1eb --- /dev/null +++ b/src/xenia/debug/server/mi/mi_server.h @@ -0,0 +1,53 @@ +/** + ****************************************************************************** + * 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_SERVER_MI_MI_SERVER_H_ +#define XENIA_DEBUG_SERVER_MI_MI_SERVER_H_ + +#include + +#include "xenia/base/socket.h" +#include "xenia/base/threading.h" +#include "xenia/debug/debug_server.h" + +namespace xe { +namespace debug { +namespace server { +namespace mi { + +class MICommandProcessor; + +class MIServer : public DebugServer { + public: + MIServer(Debugger* debugger); + ~MIServer() override; + + Socket* client() const { return client_.get(); } + + bool Initialize() override; + + private: + void AcceptClient(std::unique_ptr client); + bool HandleClientEvent(); + + std::unique_ptr socket_server_; + + std::unique_ptr client_; + std::unique_ptr client_thread_; + std::vector receive_buffer_; + + std::unique_ptr command_processor_; +}; + +} // namespace mi +} // namespace server +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_SERVER_MI_MI_SERVER_H_ diff --git a/src/xenia/debug/server/mi/mi_writer.cc b/src/xenia/debug/server/mi/mi_writer.cc new file mode 100644 index 000000000..b2b98cd5d --- /dev/null +++ b/src/xenia/debug/server/mi/mi_writer.cc @@ -0,0 +1,137 @@ +/** + ****************************************************************************** + * 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/server/mi/mi_writer.h" + +#include "xenia/base/assert.h" + +namespace xe { +namespace debug { +namespace server { +namespace mi { + +void MIWriter::AppendRaw(const char* value, size_t length) { + assert_true(buffer_offset_ + length < buffer_capacity_); + std::memcpy(buffer_ + buffer_offset_, value, length); + buffer_offset_ += length; +} + +void MIWriter::BeginRecord(RecordToken token, const char* record_class) { + assert_false(in_record_); + in_record_ = true; + size_t record_class_length = std::strlen(record_class); + assert_true(buffer_offset_ + 1 + record_class_length < buffer_capacity_); + buffer_[buffer_offset_++] = char(token); + std::memcpy(buffer_ + buffer_offset_, record_class, record_class_length); + buffer_offset_ += record_class_length; +} + +void MIWriter::BeginResultRecord(ResultClass result_class) { + const char* record_class = "error"; + switch (result_class) { + case ResultClass::kDone: + record_class = "done"; + break; + case ResultClass::kRunning: + record_class = "running"; + break; + case ResultClass::kConnected: + record_class = "connected"; + break; + case ResultClass::kError: + record_class = "error"; + break; + case ResultClass::kExit: + record_class = "exit"; + break; + } + BeginRecord(RecordToken::kResult, record_class); +} + +void MIWriter::BeginStreamRecord(StreamToken token) { + assert_false(in_record_); + in_record_ = true; + assert_true(buffer_offset_ + 1 < buffer_capacity_); + buffer_[buffer_offset_++] = char(token); +} + +void MIWriter::EndRecord() { + assert_true(in_record_); + in_record_ = false; + assert_true(buffer_offset_ + 1 < buffer_capacity_); + buffer_[buffer_offset_++] = '\n'; +} + +void MIWriter::AppendArraySeparator() { + if (array_depth_) { + if (array_count_[array_depth_]) { + assert_true(buffer_offset_ + 1 < buffer_capacity_); + buffer_[buffer_offset_++] = ','; + } + ++array_count_[array_depth_]; + } +} + +void MIWriter::PushTuple() { + assert_true(in_record_); + AppendArraySeparator(); + ++array_depth_; + assert_true(array_depth_ < kMaxArrayDepth); + array_count_[array_depth_] = 0; + assert_true(buffer_offset_ + 1 < buffer_capacity_); + buffer_[buffer_offset_++] = '{'; +} + +void MIWriter::PopTuple() { + --array_depth_; + assert_true(buffer_offset_ + 1 < buffer_capacity_); + buffer_[buffer_offset_++] = '}'; +} + +void MIWriter::PushList() { + assert_true(in_record_); + AppendArraySeparator(); + ++array_depth_; + assert_true(array_depth_ < kMaxArrayDepth); + array_count_[array_depth_] = 0; + assert_true(buffer_offset_ + 1 < buffer_capacity_); + buffer_[buffer_offset_++] = '['; +} + +void MIWriter::PopList() { + --array_depth_; + assert_true(buffer_offset_ + 1 < buffer_capacity_); + buffer_[buffer_offset_++] = ']'; +} + +void MIWriter::AppendString(const char* value, size_t length) { + assert_true(in_record_); + AppendArraySeparator(); + assert_true(buffer_offset_ + length < buffer_capacity_); + std::memcpy(buffer_ + buffer_offset_, value, length); + buffer_offset_ += length; +} + +void MIWriter::AppendResult(const char* variable, const char* value, + size_t length) { + assert_true(in_record_); + AppendArraySeparator(); + size_t variable_length = std::strlen(variable); + assert_true(buffer_offset_ + 1 + variable_length + length < buffer_capacity_); + std::memcpy(buffer_ + buffer_offset_, variable, variable_length); + buffer_offset_ += variable_length; + buffer_[buffer_offset_++] = '='; + std::memcpy(buffer_ + buffer_offset_, value, length); + buffer_offset_ += length; +} + +} // namespace mi +} // namespace server +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/server/mi/mi_writer.h b/src/xenia/debug/server/mi/mi_writer.h new file mode 100644 index 000000000..905cf353a --- /dev/null +++ b/src/xenia/debug/server/mi/mi_writer.h @@ -0,0 +1,101 @@ +/** + ****************************************************************************** + * 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_SERVER_MI_MI_WRITER_H_ +#define XENIA_DEBUG_SERVER_MI_MI_WRITER_H_ + +#include +#include + +#include "xenia/debug/server/mi/mi_protocol.h" + +namespace xe { +namespace debug { +namespace server { +namespace mi { + +// https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Output-Syntax.html#GDB_002fMI-Output-Syntax +class MIWriter { + public: + MIWriter(uint8_t* buffer, size_t buffer_capacity) + : buffer_(buffer), buffer_capacity_(buffer_capacity) {} + + uint8_t* buffer() const { return buffer_; } + size_t buffer_capacity() const { return buffer_capacity_; } + size_t buffer_offset() const { return buffer_offset_; } + + void AppendRaw(const char* value, size_t length); + void AppendRaw(const char* value) { AppendRaw(value, std::strlen(value)); } + void AppendRaw(const std::string& value) { + AppendRaw(value.c_str(), value.size()); + } + + void BeginResultRecord(ResultClass result_class); + void BeginAsyncExecRecord(const char* async_class) { + BeginRecord(RecordToken::kAsyncExec, async_class); + array_depth_ = 1; + array_count_[array_depth_] = 1; + } + void BeginAsyncStatusRecord(const char* async_class) { + BeginRecord(RecordToken::kAsyncStatus, async_class); + array_depth_ = 1; + array_count_[array_depth_] = 1; + } + void BeginAsyncNotifyRecord(const char* async_class) { + BeginRecord(RecordToken::kAsyncNotify, async_class); + array_depth_ = 1; + array_count_[array_depth_] = 1; + } + void BeginConsoleStreamOutput() { BeginStreamRecord(StreamToken::kConsole); } + void BeginTargetStreamOutput() { BeginStreamRecord(StreamToken::kTarget); } + void BeginLogStreamOutput() { BeginStreamRecord(StreamToken::kLog); } + void EndRecord(); + + void PushTuple(); + void PopTuple(); + void PushList(); + void PopList(); + + void AppendString(const char* value, size_t length); + void AppendString(const char* value) { + AppendString(value, std::strlen(value)); + } + void AppendString(const std::string& value) { + AppendString(value.c_str(), value.size()); + } + + void AppendResult(const char* variable, const char* value, size_t length); + void AppendResult(const char* variable, const char* value) { + AppendResult(variable, value, std::strlen(value)); + } + void AppendResult(const char* variable, const std::string& value) { + AppendResult(variable, value.c_str(), value.size()); + } + + private: + void BeginRecord(RecordToken token, const char* record_class); + void BeginStreamRecord(StreamToken token); + void AppendArraySeparator(); + + uint8_t* buffer_ = nullptr; + size_t buffer_capacity_ = 0; + size_t buffer_offset_ = 0; + + static const int kMaxArrayDepth = 32; + bool in_record_ = false; + int array_depth_ = 0; + int array_count_[kMaxArrayDepth]; +}; + +} // namespace mi +} // namespace server +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_SERVER_MI_MI_WRITER_H_ diff --git a/src/xenia/debug/server/xdp/xdp_server.cc b/src/xenia/debug/server/xdp/xdp_server.cc new file mode 100644 index 000000000..2b6bc386a --- /dev/null +++ b/src/xenia/debug/server/xdp/xdp_server.cc @@ -0,0 +1,313 @@ +/** + ****************************************************************************** + * 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/server/xdp/xdp_server.h" + +#include + +#include "xenia/base/logging.h" +#include "xenia/debug/debugger.h" +#include "xenia/emulator.h" +#include "xenia/kernel/kernel_state.h" +#include "xenia/kernel/objects/xmodule.h" +#include "xenia/kernel/objects/xthread.h" + +DEFINE_int32(xdp_server_port, 9002, "Debugger XDP server TCP port."); + +namespace xe { +namespace debug { +namespace server { +namespace xdp { + +using namespace xe::debug::proto; +using namespace xe::kernel; + +constexpr size_t kReceiveBufferSize = 32 * 1024; +constexpr size_t kReadBufferSize = 1 * 1024 * 1024; +constexpr size_t kWriteBufferSize = 1 * 1024 * 1024; + +XdpServer::XdpServer(Debugger* debugger) + : DebugServer(debugger), + packet_reader_(kReadBufferSize), + packet_writer_(kWriteBufferSize) { + receive_buffer_.resize(kReceiveBufferSize); +} + +XdpServer::~XdpServer() = default; + +bool XdpServer::Initialize() { + socket_server_ = SocketServer::Create(uint16_t(FLAGS_xdp_server_port), + [this](std::unique_ptr client) { + AcceptClient(std::move(client)); + }); + if (!socket_server_) { + XELOGE("Unable to create XDP socket server - port in use?"); + return false; + } + + return true; +} + +void XdpServer::AcceptClient(std::unique_ptr client) { + // If we have an existing client, kill it and join its thread. + if (client_) { + // TODO(benvanik): XDP say goodbye? + + client_->Close(); + xe::threading::Wait(client_thread_.get(), true); + client_thread_.reset(); + } + + // Take ownership of the new one. + client_ = std::move(client); + + // Create a thread to manage the connection. + client_thread_ = xe::threading::Thread::Create({}, [this]() { + // TODO(benvanik): GDB protocol stuff? Do we say hi? + // TODO(benvanik): move hello to thread + + // 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) { + auto wait_result = xe::threading::Wait(client_->wait_handle(), true); + switch (wait_result) { + case xe::threading::WaitResult::kSuccess: + // Event (read or close). + running = HandleClientEvent(); + 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 XdpServer::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 XdpServer::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 XdpServer::ProcessPacket(const proto::Packet* packet) { + auto emulator = debugger()->emulator(); + auto kernel_state = emulator->kernel_state(); + auto object_table = kernel_state->object_table(); + + switch (packet->packet_type) { + case PacketType::kExecutionRequest: { + auto body = packet_reader_.Read(); + switch (body->action) { + case ExecutionRequest::Action::kContinue: { + debugger_->Continue(); + } break; + case ExecutionRequest::Action::kStep: { + switch (body->step_mode) { + case ExecutionRequest::StepMode::kStepOne: { + debugger_->StepOne(body->thread_id); + } break; + case ExecutionRequest::StepMode::kStepTo: { + debugger_->StepTo(body->thread_id, body->target_pc); + } break; + default: + assert_unhandled_case(body->step_mode); + return false; + } + } break; + case ExecutionRequest::Action::kInterrupt: { + debugger_->Interrupt(); + } break; + case ExecutionRequest::Action::kExit: { + XELOGI("Debug client requested exit"); + exit(1); + } break; + } + SendSuccess(packet->request_id); + } break; + case PacketType::kModuleListRequest: { + packet_writer_.Begin(PacketType::kModuleListResponse); + auto body = packet_writer_.Append(); + auto modules = + object_table->GetObjectsByType(XObject::Type::kTypeModule); + body->count = uint32_t(modules.size()); + for (auto& module : modules) { + auto entry = packet_writer_.Append(); + entry->module_handle = module->handle(); + entry->module_ptr = module->hmodule_ptr(); + entry->is_kernel_module = + module->module_type() == XModule::ModuleType::kKernelModule; + std::strncpy(entry->path, module->path().c_str(), + xe::countof(entry->path)); + std::strncpy(entry->name, module->name().c_str(), + xe::countof(entry->name)); + } + packet_writer_.End(); + } break; + case PacketType::kThreadListRequest: { + packet_writer_.Begin(PacketType::kThreadListResponse); + auto body = packet_writer_.Append(); + auto threads = + object_table->GetObjectsByType(XObject::Type::kTypeThread); + body->count = uint32_t(threads.size()); + for (auto& thread : threads) { + auto entry = packet_writer_.Append(); + entry->thread_handle = thread->handle(); + entry->thread_id = thread->thread_id(); + entry->is_host_thread = !thread->is_guest_thread(); + std::strncpy(entry->name, thread->name().c_str(), + xe::countof(entry->name)); + entry->exit_code; + entry->priority = thread->priority(); + entry->xapi_thread_startup = + thread->creation_params()->xapi_thread_startup; + entry->start_address = thread->creation_params()->start_address; + entry->start_context = thread->creation_params()->start_context; + entry->creation_flags = thread->creation_params()->creation_flags; + entry->stack_address = thread->thread_state()->stack_address(); + entry->stack_size = thread->thread_state()->stack_size(); + entry->stack_base = thread->thread_state()->stack_base(); + entry->stack_limit = thread->thread_state()->stack_limit(); + entry->pcr_address = thread->pcr_ptr(); + entry->tls_address = thread->tls_ptr(); + } + packet_writer_.End(); + } break; + default: { + XELOGE("Unknown packet type"); + SendError(packet->request_id, "Unknown packet type"); + return false; + } break; + } + return true; +} + +void XdpServer::OnExecutionContinued() { + packet_writer_.Begin(PacketType::kExecutionNotification); + auto body = packet_writer_.Append(); + body->current_state = ExecutionNotification::State::kRunning; + packet_writer_.End(); + Flush(); +} + +void XdpServer::OnExecutionInterrupted() { + packet_writer_.Begin(PacketType::kExecutionNotification); + auto body = packet_writer_.Append(); + body->current_state = ExecutionNotification::State::kStopped; + body->stop_reason = ExecutionNotification::Reason::kInterrupt; + packet_writer_.End(); + Flush(); +} + +void XdpServer::Flush() { + if (!packet_writer_.buffer_offset()) { + return; + } + client_->Send(packet_writer_.buffer(), packet_writer_.buffer_offset()); + packet_writer_.Reset(); +} + +void XdpServer::SendSuccess(proto::request_id_t request_id) { + packet_writer_.Begin(PacketType::kGenericResponse, request_id); + auto body = packet_writer_.Append(); + body->code = GenericResponse::Code::kSuccess; + packet_writer_.End(); + Flush(); +} + +void XdpServer::SendError(proto::request_id_t request_id, + const char* error_message) { + packet_writer_.Begin(PacketType::kGenericResponse, request_id); + auto body = packet_writer_.Append(); + body->code = GenericResponse::Code::kError; + packet_writer_.AppendString(error_message ? error_message : ""); + packet_writer_.End(); + Flush(); +} + +void XdpServer::SendError(proto::request_id_t request_id, + const std::string& error_message) { + packet_writer_.Begin(PacketType::kGenericResponse, request_id); + auto body = packet_writer_.Append(); + body->code = GenericResponse::Code::kError; + packet_writer_.AppendString(error_message); + packet_writer_.End(); + Flush(); +} + +} // namespace xdp +} // namespace server +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/server/xdp/xdp_server.h b/src/xenia/debug/server/xdp/xdp_server.h new file mode 100644 index 000000000..c5f5db4da --- /dev/null +++ b/src/xenia/debug/server/xdp/xdp_server.h @@ -0,0 +1,67 @@ +/** + ****************************************************************************** + * 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_SERVER_XDP_XDP_SERVER_H_ +#define XENIA_DEBUG_SERVER_XDP_XDP_SERVER_H_ + +#include + +#include "xenia/base/socket.h" +#include "xenia/base/threading.h" +#include "xenia/debug/debug_server.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 { +namespace server { +namespace xdp { + +class XdpServer : public DebugServer { + public: + XdpServer(Debugger* debugger); + ~XdpServer() override; + + Socket* client() const { return client_.get(); } + + bool Initialize() override; + + void OnExecutionContinued() override; + void OnExecutionInterrupted() override; + + private: + void AcceptClient(std::unique_ptr client); + bool HandleClientEvent(); + + bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length); + bool ProcessPacket(const proto::Packet* packet); + + void Flush(); + void SendSuccess(proto::request_id_t request_id); + void SendError(proto::request_id_t request_id, + const char* error_message = nullptr); + void SendError(proto::request_id_t request_id, + const std::string& error_message); + + std::unique_ptr socket_server_; + + std::unique_ptr client_; + std::unique_ptr client_thread_; + std::vector receive_buffer_; + proto::PacketReader packet_reader_; + proto::PacketWriter packet_writer_; +}; + +} // namespace xdp +} // namespace server +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_SERVER_XDP_XDP_SERVER_H_ diff --git a/src/xenia/debug/ui/application.cc b/src/xenia/debug/ui/application.cc index 15ccf21aa..f0b0d1477 100644 --- a/src/xenia/debug/ui/application.cc +++ b/src/xenia/debug/ui/application.cc @@ -58,6 +58,15 @@ std::unique_ptr Application::Create() { } bool Application::Initialize() { + // Bind the object model to the client so it'll maintain state. + system_ = std::make_unique(loop(), &client_); + client_.set_listener(system_.get()); + + // TODO(benvanik): flags and such. + if (!client_.Connect("localhost", 9002)) { + return false; + } + main_window_ = std::make_unique(this); if (!main_window_->Initialize()) { XELOGE("Unable to initialize main window"); diff --git a/src/xenia/debug/ui/application.h b/src/xenia/debug/ui/application.h index 72b4a3634..0c31f580e 100644 --- a/src/xenia/debug/ui/application.h +++ b/src/xenia/debug/ui/application.h @@ -12,6 +12,8 @@ #include +#include "xenia/debug/client/xdp/xdp_client.h" +#include "xenia/debug/ui/model/system.h" #include "xenia/ui/loop.h" namespace xe { @@ -29,6 +31,8 @@ class Application { xe::ui::Loop* loop() { return loop_.get(); } MainWindow* main_window() const { return main_window_.get(); } + client::xdp::XdpClient* client() { return &client_; } + model::System* system() const { return system_.get(); } void Quit(); @@ -39,6 +43,9 @@ class Application { std::unique_ptr loop_; std::unique_ptr main_window_; + client::xdp::XdpClient client_; + + std::unique_ptr system_; }; } // namespace ui diff --git a/src/xenia/debug/ui/main_window.cc b/src/xenia/debug/ui/main_window.cc index b4aa45ab9..f4ecb5c5a 100644 --- a/src/xenia/debug/ui/main_window.cc +++ b/src/xenia/debug/ui/main_window.cc @@ -21,6 +21,8 @@ namespace xe { namespace debug { namespace ui { +using namespace xe::debug::client::xdp; + using xe::ui::MenuItem; const std::wstring kBaseTitle = L"xenia debugger"; @@ -30,6 +32,8 @@ MainWindow::MainWindow(Application* app) : app_(app) {} MainWindow::~MainWindow() = default; bool MainWindow::Initialize() { + client_ = app_->client(); + platform_window_ = xe::ui::Window::Create(app()->loop(), kBaseTitle); if (!platform_window_) { return false; @@ -106,11 +110,14 @@ void MainWindow::BuildUI() { .gravity(Gravity::kAll) .distribution(LayoutDistribution::kAvailable) .axis(Axis::kY) - .child(LayoutBoxNode() - .id("toolbar_box") - .gravity(Gravity::kTop | Gravity::kLeftRight) - .distribution(LayoutDistribution::kAvailable) - .child(LabelNode("toolbar"))) + .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") @@ -132,15 +139,50 @@ void MainWindow::BuildUI() { {TBIDC("tab_container"), &ui_.tab_container}, }); + handler_ = std::make_unique(window_.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::ShowDebugInfoSettingsWindow(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; + window_->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 diff --git a/src/xenia/debug/ui/main_window.h b/src/xenia/debug/ui/main_window.h index c51117a42..e99bd1e14 100644 --- a/src/xenia/debug/ui/main_window.h +++ b/src/xenia/debug/ui/main_window.h @@ -28,6 +28,8 @@ class MainWindow { ~MainWindow(); Application* app() const { return app_; } + xe::ui::Loop* loop() const { return app_->loop(); } + model::System* system() const { return app_->system(); } bool Initialize(); @@ -35,10 +37,12 @@ class MainWindow { private: void BuildUI(); + void UpdateElementState(); void OnClose(); Application* app_ = nullptr; + xe::debug::client::xdp::XdpClient* client_ = nullptr; std::unique_ptr platform_window_; std::unique_ptr window_; @@ -47,6 +51,7 @@ class MainWindow { el::LayoutBox* toolbar_box; el::TabContainer* tab_container; } ui_ = {0}; + std::unique_ptr handler_; views::cpu::CpuView cpu_view_; views::gpu::GpuView gpu_view_; }; diff --git a/src/xenia/debug/ui/model/function.cc b/src/xenia/debug/ui/model/function.cc new file mode 100644 index 000000000..9808fff1a --- /dev/null +++ b/src/xenia/debug/ui/model/function.cc @@ -0,0 +1,22 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/debug/ui/model/function.h" + +namespace xe { +namespace debug { +namespace ui { +namespace model { + +// + +} // namespace model +} // namespace ui +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/ui/model/function.h b/src/xenia/debug/ui/model/function.h new file mode 100644 index 000000000..a31f7b9ea --- /dev/null +++ b/src/xenia/debug/ui/model/function.h @@ -0,0 +1,29 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_UI_MODEL_FUNCTION_H_ +#define XENIA_DEBUG_UI_MODEL_FUNCTION_H_ + +#include + +namespace xe { +namespace debug { +namespace ui { +namespace model { + +class Function { + public: +}; + +} // namespace model +} // namespace ui +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_UI_MODEL_FUNCTION_H_ diff --git a/src/xenia/debug/ui/model/module.cc b/src/xenia/debug/ui/model/module.cc new file mode 100644 index 000000000..675df0647 --- /dev/null +++ b/src/xenia/debug/ui/model/module.cc @@ -0,0 +1,32 @@ +/** + ****************************************************************************** + * 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) { + if (!entry_.module_handle) { + std::memcpy(&entry_, entry, sizeof(entry_)); + } else { + std::memcpy(&temp_entry_, entry, sizeof(temp_entry_)); + system_->loop()->Post( + [this]() { std::memcpy(&entry_, &temp_entry_, sizeof(temp_entry_)); }); + } +} + +} // namespace model +} // namespace ui +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/ui/model/module.h b/src/xenia/debug/ui/model/module.h new file mode 100644 index 000000000..3ff3dd3a2 --- /dev/null +++ b/src/xenia/debug/ui/model/module.h @@ -0,0 +1,51 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_UI_MODEL_MODULE_H_ +#define XENIA_DEBUG_UI_MODEL_MODULE_H_ + +#include +#include + +#include "xenia/debug/proto/xdp_protocol.h" + +namespace xe { +namespace debug { +namespace ui { +namespace model { + +class System; + +class Module { + public: + 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}; + proto::ModuleListEntry temp_entry_ = {0}; +}; + +} // namespace model +} // namespace ui +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_UI_MODEL_MODULE_H_ diff --git a/src/xenia/debug/ui/model/system.cc b/src/xenia/debug/ui/model/system.cc new file mode 100644 index 000000000..3d15782cf --- /dev/null +++ b/src/xenia/debug/ui/model/system.cc @@ -0,0 +1,127 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/debug/ui/model/system.h" + +#include + +namespace xe { +namespace debug { +namespace ui { +namespace model { + +using namespace xe::debug::proto; + +System::System(xe::ui::Loop* loop, client::xdp::XdpClient* client) + : loop_(loop), client_(client) {} + +ExecutionState System::execution_state() { + std::lock_guard lock(mutex_); + return client_->execution_state(); +} + +std::vector System::modules() { + std::lock_guard lock(mutex_); + std::vector result; + for (auto& module : modules_) { + result.push_back(module.get()); + } + return result; +} + +std::vector System::threads() { + std::lock_guard lock(mutex_); + std::vector result; + for (auto& thread : threads_) { + result.push_back(thread.get()); + } + return result; +} + +Module* System::GetModuleByHandle(uint32_t module_handle) { + std::lock_guard lock(mutex_); + auto it = modules_by_handle_.find(module_handle); + return it != modules_by_handle_.end() ? it->second : nullptr; +} + +Thread* System::GetThreadByHandle(uint32_t thread_handle) { + std::lock_guard lock(mutex_); + auto it = threads_by_handle_.find(thread_handle); + return it != threads_by_handle_.end() ? it->second : nullptr; +} + +void System::OnExecutionStateChanged(ExecutionState execution_state) { + loop_->Post([this]() { + std::lock_guard lock(mutex_); + on_execution_state_changed(); + }); +} + +void System::OnModulesUpdated(std::vector entries) { + { + std::lock_guard lock(mutex_); + std::unordered_set extra_modules; + for (size_t i = 0; i < modules_.size(); ++i) { + extra_modules.emplace(modules_[i]->module_handle()); + } + for (auto entry : entries) { + auto existing_module = modules_by_handle_.find(entry->module_handle); + if (existing_module == modules_by_handle_.end()) { + auto module = std::make_unique(this); + module->Update(entry); + modules_by_handle_.emplace(entry->module_handle, module.get()); + modules_.emplace_back(std::move(module)); + } else { + existing_module->second->Update(entry); + extra_modules.erase(existing_module->first); + } + } + for (auto module_handle : extra_modules) { + modules_by_handle_[module_handle]->set_dead(true); + } + } + loop_->Post([this]() { + std::lock_guard lock(mutex_); + on_modules_updated(); + }); +} + +void System::OnThreadsUpdated(std::vector entries) { + { + std::lock_guard lock(mutex_); + std::unordered_set extra_threads; + for (size_t i = 0; i < threads_.size(); ++i) { + extra_threads.emplace(threads_[i]->thread_handle()); + } + for (auto entry : entries) { + auto existing_thread = threads_by_handle_.find(entry->thread_handle); + if (existing_thread == threads_by_handle_.end()) { + auto thread = std::make_unique(this); + thread->Update(entry); + threads_by_handle_.emplace(entry->thread_handle, thread.get()); + threads_.emplace_back(std::move(thread)); + } else { + existing_thread->second->Update(entry); + extra_threads.erase(existing_thread->first); + } + } + for (auto thread_handle : extra_threads) { + modules_by_handle_[thread_handle]->set_dead(true); + } + } + loop_->Post([this]() { + std::lock_guard lock(mutex_); + on_threads_updated(); + }); +} + +} // namespace model +} // namespace ui +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/ui/model/system.h b/src/xenia/debug/ui/model/system.h new file mode 100644 index 000000000..00c436ac2 --- /dev/null +++ b/src/xenia/debug/ui/model/system.h @@ -0,0 +1,74 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_UI_MODEL_SYSTEM_H_ +#define XENIA_DEBUG_UI_MODEL_SYSTEM_H_ + +#include +#include +#include +#include +#include + +#include "xenia/base/delegate.h" +#include "xenia/debug/client/xdp/xdp_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 { + +using xe::debug::client::xdp::ExecutionState; + +class System : public client::xdp::ClientListener { + public: + System(xe::ui::Loop* loop, client::xdp::XdpClient* client); + + xe::ui::Loop* loop() const { return loop_; } + client::xdp::XdpClient* client() const { return client_; } + + ExecutionState execution_state(); + + std::vector modules(); + std::vector threads(); + + Module* GetModuleByHandle(uint32_t module_handle); + Thread* GetThreadByHandle(uint32_t thread_handle); + + Delegate on_execution_state_changed; + Delegate on_modules_updated; + Delegate on_threads_updated; + + private: + void OnExecutionStateChanged(ExecutionState execution_state) override; + void OnModulesUpdated( + std::vector entries) override; + void OnThreadsUpdated( + std::vector entries) override; + + xe::ui::Loop* loop_ = nullptr; + client::xdp::XdpClient* client_ = nullptr; + + std::recursive_mutex mutex_; + std::vector> modules_; + std::unordered_map modules_by_handle_; + std::vector> threads_; + std::unordered_map threads_by_handle_; +}; + +} // namespace model +} // namespace ui +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_UI_MODEL_SYSTEM_H_ diff --git a/src/xenia/debug/ui/model/thread.cc b/src/xenia/debug/ui/model/thread.cc new file mode 100644 index 000000000..0635acd0b --- /dev/null +++ b/src/xenia/debug/ui/model/thread.cc @@ -0,0 +1,40 @@ +/** + ****************************************************************************** + * 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 { + +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) { + if (!entry_.thread_handle) { + std::memcpy(&entry_, entry, sizeof(entry_)); + } else { + std::memcpy(&temp_entry_, entry, sizeof(temp_entry_)); + system_->loop()->Post( + [this]() { std::memcpy(&entry_, &temp_entry_, sizeof(temp_entry_)); }); + } +} + +} // namespace model +} // namespace ui +} // namespace debug +} // namespace xe diff --git a/src/xenia/debug/ui/model/thread.h b/src/xenia/debug/ui/model/thread.h new file mode 100644 index 000000000..f60ddd5d6 --- /dev/null +++ b/src/xenia/debug/ui/model/thread.h @@ -0,0 +1,54 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_UI_MODEL_THREAD_H_ +#define XENIA_DEBUG_UI_MODEL_THREAD_H_ + +#include +#include + +#include "xenia/debug/proto/xdp_protocol.h" + +namespace xe { +namespace debug { +namespace ui { +namespace model { + +class System; + +class Thread { + public: + Thread(System* system) : system_(system) {} + + bool is_dead() const { return is_dead_; } + void set_dead(bool is_dead) { is_dead_ = is_dead; } + + 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 proto::ThreadListEntry* entry() const { return &entry_; } + + std::string to_string(); + + void Update(const proto::ThreadListEntry* entry); + + private: + System* system_ = nullptr; + bool is_dead_ = false; + proto::ThreadListEntry entry_ = {0}; + proto::ThreadListEntry temp_entry_ = {0}; +}; + +} // namespace model +} // namespace ui +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_UI_MODEL_THREAD_H_ diff --git a/src/xenia/debug/ui/view.h b/src/xenia/debug/ui/view.h index fb36ae6d3..9105bedb4 100644 --- a/src/xenia/debug/ui/view.h +++ b/src/xenia/debug/ui/view.h @@ -14,6 +14,8 @@ #include #include "el/elements.h" +#include "el/event_handler.h" +#include "xenia/debug/ui/application.h" namespace xe { namespace debug { @@ -25,14 +27,21 @@ class View { std::string name() const { return name_; } el::LayoutBox* root_element() { return &root_element_; } + xe::ui::Loop* loop() const { return Application::current()->loop(); } + client::xdp::XdpClient* client() const { return client_; } + model::System* system() const { return Application::current()->system(); } virtual el::Element* BuildUI() = 0; + virtual void Setup(xe::debug::client::xdp::XdpClient* client) = 0; + protected: View(std::string name) : name_(name) {} std::string name_; el::LayoutBox root_element_; + std::unique_ptr handler_; + xe::debug::client::xdp::XdpClient* client_ = nullptr; }; } // namespace ui diff --git a/src/xenia/debug/ui/views/cpu/cpu_view.cc b/src/xenia/debug/ui/views/cpu/cpu_view.cc index cf634ebf3..a8e5169d1 100644 --- a/src/xenia/debug/ui/views/cpu/cpu_view.cc +++ b/src/xenia/debug/ui/views/cpu/cpu_view.cc @@ -16,6 +16,8 @@ namespace ui { namespace views { namespace cpu { +using namespace xe::debug::client::xdp; + CpuView::CpuView() : View("CPU") {} CpuView::~CpuView() = default; @@ -29,22 +31,13 @@ el::Element* CpuView::BuildUI() { .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(ButtonNode("?")) - .child( - DropDownButtonNode().item("Module").item("Module").item( - "Module"))) - .child(ListBoxNode() - .gravity(Gravity::kAll) - .item("fn") - .item("fn") - .item("fn") - .item("fn")) + .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) @@ -102,10 +95,9 @@ el::Element* CpuView::BuildUI() { .distribution(LayoutDistribution::kGravity) .distribution_position(LayoutDistributionPosition::kLeftTop) .axis(Axis::kX) - .child(ButtonNode("button")) - .child(ButtonNode("button")) - .child(ButtonNode("button"))) + .child(DropDownButtonNode().id("thread_dropdown"))) .child(LayoutBoxNode() + .id("source_content") .gravity(Gravity::kAll) .distribution(LayoutDistribution::kAvailable) .child(SplitContainerNode() @@ -144,9 +136,91 @@ el::Element* CpuView::BuildUI() { root_element_.GetElementsById({ // }); + handler_ = std::make_unique(&root_element_); + + handler_->Listen(el::EventType::kChanged, TBIDC("module_dropdown"), + [this](const el::Event& ev) { + UpdateFunctionList(); + return true; + }); + return &root_element_; } +void CpuView::Setup(XdpClient* client) { + client_ = client; + + system()->on_execution_state_changed.AddListener( + [this]() { UpdateElementState(); }); + system()->on_modules_updated.AddListener([this]() { UpdateModuleList(); }); + system()->on_threads_updated.AddListener([this]() { UpdateThreadList(); }); +} + +void CpuView::UpdateElementState() { + root_element_.GetElementsById({ + // + }); +} + +void CpuView::UpdateModuleList() { + el::DropDownButton* module_dropdown; + root_element_.GetElementsById({ + {TBIDC("module_dropdown"), &module_dropdown}, + }); + auto module_items = module_dropdown->default_source(); + auto modules = system()->modules(); + bool is_first = module_items->size() == 0; + for (size_t i = module_items->size(); i < modules.size(); ++i) { + auto module = modules[i]; + auto item = std::make_unique(module->name()); + item->id = module->module_handle(); + module_items->push_back(std::move(item)); + } + if (is_first) { + module_dropdown->set_value(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(thread->to_string()); + item->id = thread->thread_handle(); + thread_items->push_back(std::move(item)); + } + if (is_first) { + thread_dropdown->set_value(int(thread_items->size() - 1)); + } +} + } // namespace cpu } // namespace views } // namespace ui diff --git a/src/xenia/debug/ui/views/cpu/cpu_view.h b/src/xenia/debug/ui/views/cpu/cpu_view.h index 1962eee77..b727fc492 100644 --- a/src/xenia/debug/ui/views/cpu/cpu_view.h +++ b/src/xenia/debug/ui/views/cpu/cpu_view.h @@ -28,7 +28,13 @@ class CpuView : public View { el::Element* BuildUI() override; + void Setup(xe::debug::client::xdp::XdpClient* client) override; + protected: + void UpdateElementState(); + void UpdateModuleList(); + void UpdateFunctionList(); + void UpdateThreadList(); }; } // namespace cpu diff --git a/src/xenia/debug/ui/views/gpu/gpu_view.cc b/src/xenia/debug/ui/views/gpu/gpu_view.cc index f91dbbbdb..382478b3d 100644 --- a/src/xenia/debug/ui/views/gpu/gpu_view.cc +++ b/src/xenia/debug/ui/views/gpu/gpu_view.cc @@ -32,9 +32,14 @@ el::Element* GpuView::BuildUI() { root_element_.GetElementsById({ // }); + handler_ = std::make_unique(&root_element_); return &root_element_; } +void GpuView::Setup(xe::debug::client::xdp::XdpClient* client) { + // +} + } // namespace gpu } // namespace views } // namespace ui diff --git a/src/xenia/debug/ui/views/gpu/gpu_view.h b/src/xenia/debug/ui/views/gpu/gpu_view.h index d53263a92..9d4f5c136 100644 --- a/src/xenia/debug/ui/views/gpu/gpu_view.h +++ b/src/xenia/debug/ui/views/gpu/gpu_view.h @@ -28,6 +28,8 @@ class GpuView : public View { el::Element* BuildUI() override; + void Setup(xe::debug::client::xdp::XdpClient* client) override; + protected: }; diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index cb97337da..ee865a423 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -353,7 +353,7 @@ void KernelState::TerminateTitle(bool from_guest_thread) { // Second: Kill all guest threads. for (auto it = threads_by_id_.begin(); it != threads_by_id_.end();) { - if (it->second->guest_thread()) { + if (it->second->is_guest_thread()) { auto thread = it->second; if (from_guest_thread && XThread::IsInThread(thread)) { @@ -362,7 +362,7 @@ void KernelState::TerminateTitle(bool from_guest_thread) { continue; } - if (thread->running()) { + if (thread->is_running()) { thread->Terminate(0); } @@ -457,6 +457,10 @@ void KernelState::OnThreadExit(XThread* thread) { xe::countof(args)); } } + + if (emulator()->debugger()) { + emulator()->debugger()->OnThreadExit(thread); + } } object_ref KernelState::GetThreadByID(uint32_t thread_id) { diff --git a/src/xenia/kernel/objects/xthread.cc b/src/xenia/kernel/objects/xthread.cc index 0b3828439..4af7524ff 100644 --- a/src/xenia/kernel/objects/xthread.cc +++ b/src/xenia/kernel/objects/xthread.cc @@ -19,6 +19,7 @@ #include "xenia/base/mutex.h" #include "xenia/base/threading.h" #include "xenia/cpu/processor.h" +#include "xenia/emulator.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/native_list.h" #include "xenia/kernel/objects/xevent.h" @@ -70,6 +71,10 @@ XThread::~XThread() { // Unregister first to prevent lookups while deleting. kernel_state_->UnregisterThread(this); + if (emulator()->debugger()) { + emulator()->debugger()->OnThreadDestroyed(this); + } + delete apc_list_; thread_.reset(); @@ -329,6 +334,10 @@ X_STATUS XThread::Create() { thread_->set_priority(creation_params_.creation_flags & 0x20 ? 1 : 0); } + if (emulator()->debugger()) { + emulator()->debugger()->OnThreadCreated(this); + } + if ((creation_params_.creation_flags & X_CREATE_SUSPENDED) == 0) { // Start the thread now that we're all setup. thread_->Resume(); @@ -344,17 +353,21 @@ X_STATUS XThread::Exit(int exit_code) { // TODO(benvanik); dispatch events? waiters? etc? RundownAPCs(); + // Set exit code. + X_KTHREAD* thread = guest_object(); + thread->header.signal_state = 1; + thread->exit_status = exit_code; + kernel_state()->OnThreadExit(this); + if (emulator()->debugger()) { + emulator()->debugger()->OnThreadExit(this); + } + // NOTE: unless PlatformExit fails, expect it to never return! current_thread_tls = nullptr; xe::Profiler::ThreadExit(); - // Set exit code - X_KTHREAD* thread = guest_object(); - thread->header.signal_state = 1; - thread->exit_status = exit_code; - running_ = false; Release(); ReleaseHandle(); @@ -367,11 +380,15 @@ X_STATUS XThread::Exit(int exit_code) { X_STATUS XThread::Terminate(int exit_code) { // TODO: Inform the profiler that this thread is exiting. - // Set exit code + // Set exit code. X_KTHREAD* thread = guest_object(); thread->header.signal_state = 1; thread->exit_status = exit_code; + if (emulator()->debugger()) { + emulator()->debugger()->OnThreadExit(this); + } + running_ = false; Release(); ReleaseHandle(); diff --git a/src/xenia/kernel/objects/xthread.h b/src/xenia/kernel/objects/xthread.h index d052b53a0..f770501c6 100644 --- a/src/xenia/kernel/objects/xthread.h +++ b/src/xenia/kernel/objects/xthread.h @@ -103,6 +103,14 @@ static_assert_size(X_KTHREAD, 0xAB0); class XThread : public XObject { public: + struct CreationParams { + uint32_t stack_size; + uint32_t xapi_thread_startup; + uint32_t start_address; + uint32_t start_context; + uint32_t creation_flags; + }; + XThread(KernelState* kernel_state, uint32_t stack_size, uint32_t xapi_thread_startup, uint32_t start_address, uint32_t start_context, uint32_t creation_flags, bool guest_thread); @@ -114,10 +122,11 @@ class XThread : public XObject { static uint32_t GetCurrentThreadHandle(); static uint32_t GetCurrentThreadId(); + const CreationParams* creation_params() const { return &creation_params_; } uint32_t tls_ptr() const { return tls_address_; } uint32_t pcr_ptr() const { return pcr_address_; } - bool guest_thread() const { return guest_thread_; } - bool running() const { return running_; } + bool is_guest_thread() const { return guest_thread_; } + bool is_running() const { return running_; } cpu::ThreadState* thread_state() const { return thread_state_; } uint32_t thread_id() const { return thread_id_; } @@ -163,13 +172,7 @@ class XThread : public XObject { void DeliverAPCs(); void RundownAPCs(); - struct { - uint32_t stack_size; - uint32_t xapi_thread_startup; - uint32_t start_address; - uint32_t start_context; - uint32_t creation_flags; - } creation_params_ = {0}; + CreationParams creation_params_ = {0}; uint32_t thread_id_ = 0; std::unique_ptr thread_;