Debugger stuff. Lots of wasted work :/
This commit is contained in:
parent
42ef3f224a
commit
7ecc6362de
|
@ -45,6 +45,33 @@ class Delegate {
|
||||||
std::vector<Listener> listeners_;
|
std::vector<Listener> listeners_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
class Delegate<void> {
|
||||||
|
public:
|
||||||
|
typedef std::function<void()> Listener;
|
||||||
|
|
||||||
|
void AddListener(Listener const& listener) {
|
||||||
|
std::lock_guard<std::mutex> guard(lock_);
|
||||||
|
listeners_.push_back(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoveAllListeners() {
|
||||||
|
std::lock_guard<std::mutex> guard(lock_);
|
||||||
|
listeners_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()() {
|
||||||
|
std::lock_guard<std::mutex> guard(lock_);
|
||||||
|
for (auto& listener : listeners_) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex lock_;
|
||||||
|
std::vector<Listener> listeners_;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
#endif // XENIA_BASE_DELEGATE_H_
|
#endif // XENIA_BASE_DELEGATE_H_
|
||||||
|
|
|
@ -28,7 +28,8 @@ namespace xe {
|
||||||
// wait handle should be waited on until new data arrives.
|
// wait handle should be waited on until new data arrives.
|
||||||
class Socket {
|
class Socket {
|
||||||
public:
|
public:
|
||||||
// TODO(benvanik): client socket static Connect method.
|
// Synchronously connects to the given hostname:port.
|
||||||
|
static std::unique_ptr<Socket> Connect(std::string hostname, uint16_t port);
|
||||||
|
|
||||||
virtual ~Socket() = default;
|
virtual ~Socket() = default;
|
||||||
|
|
||||||
|
@ -84,6 +85,12 @@ class Socket {
|
||||||
auto buffer_list = std::make_pair(buffer.data(), buffer.size());
|
auto buffer_list = std::make_pair(buffer.data(), buffer.size());
|
||||||
return Send(&buffer_list, 1);
|
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.
|
// Runs a socket server on the specified local port.
|
||||||
|
|
|
@ -36,6 +36,51 @@ class Win32Socket : public Socket {
|
||||||
Win32Socket() = default;
|
Win32Socket() = default;
|
||||||
~Win32Socket() override { Close(); }
|
~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) {
|
bool Accept(SOCKET socket) {
|
||||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
|
||||||
|
@ -142,6 +187,16 @@ class Win32Socket : public Socket {
|
||||||
std::unique_ptr<xe::threading::Event> event_;
|
std::unique_ptr<xe::threading::Event> event_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Socket> Socket::Connect(std::string hostname, uint16_t port) {
|
||||||
|
InitializeWinsock();
|
||||||
|
|
||||||
|
auto socket = std::make_unique<Win32Socket>();
|
||||||
|
if (!socket->Connect(std::move(hostname), port)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return std::unique_ptr<Socket>(socket.release());
|
||||||
|
}
|
||||||
|
|
||||||
class Win32SocketServer : public SocketServer {
|
class Win32SocketServer : public SocketServer {
|
||||||
public:
|
public:
|
||||||
Win32SocketServer(
|
Win32SocketServer(
|
||||||
|
|
|
@ -104,17 +104,9 @@ ThreadState::ThreadState(Processor* processor, uint32_t thread_id,
|
||||||
// Set initial registers.
|
// Set initial registers.
|
||||||
context_->r[1] = stack_base_;
|
context_->r[1] = stack_base_;
|
||||||
context_->r[13] = pcr_address_;
|
context_->r[13] = pcr_address_;
|
||||||
|
|
||||||
if (processor_->debugger()) {
|
|
||||||
processor_->debugger()->OnThreadCreated(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ThreadState::~ThreadState() {
|
ThreadState::~ThreadState() {
|
||||||
if (processor_->debugger()) {
|
|
||||||
processor_->debugger()->OnThreadDestroyed(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backend_data_) {
|
if (backend_data_) {
|
||||||
processor_->backend()->FreeThreadData(backend_data_);
|
processor_->backend()->FreeThreadData(backend_data_);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<std::recursive_mutex> lock(mutex_);
|
||||||
|
|
||||||
|
switch (packet->packet_type) {
|
||||||
|
case PacketType::kGenericResponse: {
|
||||||
|
//
|
||||||
|
} break;
|
||||||
|
case PacketType::kExecutionNotification: {
|
||||||
|
auto body = packet_reader_.Read<ExecutionNotification>();
|
||||||
|
switch (body->current_state) {
|
||||||
|
case ExecutionNotification::State::kStopped: {
|
||||||
|
// body->stop_reason
|
||||||
|
execution_state_ = ExecutionState::kStopped;
|
||||||
|
listener_->OnExecutionStateChanged(execution_state_);
|
||||||
|
BeginUpdateAllState();
|
||||||
|
} break;
|
||||||
|
case ExecutionNotification::State::kRunning: {
|
||||||
|
execution_state_ = ExecutionState::kRunning;
|
||||||
|
listener_->OnExecutionStateChanged(execution_state_);
|
||||||
|
} break;
|
||||||
|
case ExecutionNotification::State::kExited: {
|
||||||
|
execution_state_ = ExecutionState::kExited;
|
||||||
|
listener_->OnExecutionStateChanged(execution_state_);
|
||||||
|
BeginUpdateAllState();
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case PacketType::kModuleListResponse: {
|
||||||
|
auto body = packet_reader_.Read<ModuleListResponse>();
|
||||||
|
auto entries = packet_reader_.ReadArray<ModuleListEntry>(body->count);
|
||||||
|
listener_->OnModulesUpdated(entries);
|
||||||
|
} break;
|
||||||
|
case PacketType::kThreadListResponse: {
|
||||||
|
auto body = packet_reader_.Read<ThreadListResponse>();
|
||||||
|
auto entries = packet_reader_.ReadArray<ThreadListEntry>(body->count);
|
||||||
|
listener_->OnThreadsUpdated(entries);
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
XELOGE("Unknown incoming packet type");
|
||||||
|
return false;
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void XdpClient::Flush() {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
if (!packet_writer_.buffer_offset()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socket_->Send(packet_writer_.buffer(), packet_writer_.buffer_offset());
|
||||||
|
packet_writer_.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XdpClient::Continue() {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
packet_writer_.Begin(PacketType::kExecutionRequest);
|
||||||
|
auto body = packet_writer_.Append<ExecutionRequest>();
|
||||||
|
body->action = ExecutionRequest::Action::kContinue;
|
||||||
|
packet_writer_.End();
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XdpClient::StepOne(uint32_t thread_id) {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
packet_writer_.Begin(PacketType::kExecutionRequest);
|
||||||
|
auto body = packet_writer_.Append<ExecutionRequest>();
|
||||||
|
body->action = ExecutionRequest::Action::kStep;
|
||||||
|
body->step_mode = ExecutionRequest::StepMode::kStepOne;
|
||||||
|
body->thread_id = thread_id;
|
||||||
|
packet_writer_.End();
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XdpClient::StepTo(uint32_t thread_id, uint32_t target_pc) {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
packet_writer_.Begin(PacketType::kExecutionRequest);
|
||||||
|
auto body = packet_writer_.Append<ExecutionRequest>();
|
||||||
|
body->action = ExecutionRequest::Action::kStep;
|
||||||
|
body->step_mode = ExecutionRequest::StepMode::kStepTo;
|
||||||
|
body->thread_id = thread_id;
|
||||||
|
body->target_pc = target_pc;
|
||||||
|
packet_writer_.End();
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XdpClient::Interrupt() {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
packet_writer_.Begin(PacketType::kExecutionRequest);
|
||||||
|
auto body = packet_writer_.Append<ExecutionRequest>();
|
||||||
|
body->action = ExecutionRequest::Action::kInterrupt;
|
||||||
|
packet_writer_.End();
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XdpClient::Exit() {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
packet_writer_.Begin(PacketType::kExecutionRequest);
|
||||||
|
auto body = packet_writer_.Append<ExecutionRequest>();
|
||||||
|
body->action = ExecutionRequest::Action::kExit;
|
||||||
|
packet_writer_.End();
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XdpClient::BeginUpdateAllState() {
|
||||||
|
std::lock_guard<std::recursive_mutex> 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
|
|
@ -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 <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#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<const ModuleListEntry*> entries) = 0;
|
||||||
|
virtual void OnThreadsUpdated(
|
||||||
|
std::vector<const ThreadListEntry*> 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> socket_;
|
||||||
|
std::unique_ptr<xe::threading::Thread> thread_;
|
||||||
|
std::vector<uint8_t> 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_
|
|
@ -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
|
|
@ -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 <cstdint>
|
||||||
|
|
||||||
|
#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_
|
|
@ -7,8 +7,8 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef XENIA_DEBUG_TRANSPORT_H_
|
#ifndef XENIA_DEBUG_DEBUG_SERVER_H_
|
||||||
#define XENIA_DEBUG_TRANSPORT_H_
|
#define XENIA_DEBUG_DEBUG_SERVER_H_
|
||||||
|
|
||||||
#include "xenia/cpu/function.h"
|
#include "xenia/cpu/function.h"
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/cpu/processor.h"
|
||||||
|
@ -19,25 +19,25 @@ namespace debug {
|
||||||
|
|
||||||
class Debugger;
|
class Debugger;
|
||||||
|
|
||||||
class Transport {
|
class DebugServer {
|
||||||
public:
|
public:
|
||||||
virtual ~Transport() = default;
|
virtual ~DebugServer() = default;
|
||||||
|
|
||||||
Debugger* debugger() const { return debugger_; }
|
Debugger* debugger() const { return debugger_; }
|
||||||
|
|
||||||
virtual bool Initialize() = 0;
|
virtual bool Initialize() = 0;
|
||||||
|
|
||||||
// break/resume state
|
|
||||||
|
|
||||||
// TODO(benvanik): better thread type (XThread?)
|
// TODO(benvanik): better thread type (XThread?)
|
||||||
// virtual void OnThreadCreated(ThreadState* thread_state) = 0;
|
// virtual void OnThreadCreated(ThreadState* thread_state) = 0;
|
||||||
// virtual void OnThreadDestroyed(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,
|
/*virtual void OnBreakpointHit(xe::cpu::ThreadState* thread_state,
|
||||||
Breakpoint* breakpoint) = 0;*/
|
Breakpoint* breakpoint) = 0;*/
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Transport(Debugger* debugger) : debugger_(debugger) {}
|
DebugServer(Debugger* debugger) : debugger_(debugger) {}
|
||||||
|
|
||||||
Debugger* debugger_ = nullptr;
|
Debugger* debugger_ = nullptr;
|
||||||
};
|
};
|
||||||
|
@ -45,4 +45,4 @@ class Transport {
|
||||||
} // namespace debug
|
} // namespace debug
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
#endif // XENIA_DEBUG_TRANSPORT_H_
|
#endif // XENIA_DEBUG_DEBUG_SERVER_H_
|
|
@ -20,7 +20,9 @@
|
||||||
#include "xenia/cpu/backend/code_cache.h"
|
#include "xenia/cpu/backend/code_cache.h"
|
||||||
#include "xenia/cpu/function.h"
|
#include "xenia/cpu/function.h"
|
||||||
#include "xenia/cpu/processor.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/emulator.h"
|
||||||
#include "xenia/kernel/objects/xkernel_module.h"
|
#include "xenia/kernel/objects/xkernel_module.h"
|
||||||
#include "xenia/kernel/objects/xmodule.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.");
|
"Waits for a debugger to attach before starting the game.");
|
||||||
DEFINE_bool(exit_with_debugger, true, "Exit whe the debugger disconnects.");
|
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 xe {
|
||||||
namespace debug {
|
namespace debug {
|
||||||
|
|
||||||
|
@ -86,13 +90,24 @@ bool Debugger::StartSession() {
|
||||||
functions_trace_file_ = ChunkedMappedMemoryWriter::Open(
|
functions_trace_file_ = ChunkedMappedMemoryWriter::Open(
|
||||||
functions_trace_path_, 32 * 1024 * 1024, true);
|
functions_trace_path_, 32 * 1024 * 1024, true);
|
||||||
|
|
||||||
// Add default transports.
|
if (FLAGS_debug_server == "gdb") {
|
||||||
auto gdb_transport =
|
server_ = std::make_unique<xe::debug::server::gdb::GdbServer>(this);
|
||||||
std::make_unique<xe::debug::transport::gdb::GdbTransport>(this);
|
if (!server_->Initialize()) {
|
||||||
if (gdb_transport->Initialize()) {
|
XELOGE("Unable to initialize GDB debug server");
|
||||||
transports_.emplace_back(std::move(gdb_transport));
|
return false;
|
||||||
|
}
|
||||||
|
} else if (FLAGS_debug_server == "gdb") {
|
||||||
|
server_ = std::make_unique<xe::debug::server::mi::MIServer>(this);
|
||||||
|
if (!server_->Initialize()) {
|
||||||
|
XELOGE("Unable to initialize MI debug server");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
XELOGE("Unable to initialize GDB debugger transport");
|
server_ = std::make_unique<xe::debug::server::xdp::XdpServer>(this);
|
||||||
|
if (!server_->Initialize()) {
|
||||||
|
XELOGE("Unable to initialize XDP debug server");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -103,17 +118,24 @@ void Debugger::PreLaunch() {
|
||||||
// Wait for the first client.
|
// Wait for the first client.
|
||||||
XELOGI("Waiting for debugger because of --wait_for_debugger...");
|
XELOGI("Waiting for debugger because of --wait_for_debugger...");
|
||||||
attach_fence_.Wait();
|
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() {
|
void Debugger::StopSession() {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
|
||||||
FlushSession();
|
FlushSession();
|
||||||
|
|
||||||
// Kill all transports.
|
// Kill debug server.
|
||||||
transports_.clear();
|
server_.reset();
|
||||||
|
|
||||||
functions_file_.reset();
|
functions_file_.reset();
|
||||||
functions_trace_file_.reset();
|
functions_trace_file_.reset();
|
||||||
|
@ -142,6 +164,78 @@ uint8_t* Debugger::AllocateFunctionTraceData(size_t size) {
|
||||||
return functions_trace_file_->Allocate(size);
|
return functions_trace_file_->Allocate(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Debugger::AddBreakpoint(Breakpoint* breakpoint) {
|
||||||
|
// Add to breakpoints map.
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
breakpoints_.insert(
|
||||||
|
std::pair<uint32_t, Breakpoint*>(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<std::recursive_mutex> 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<Breakpoint*>& out_breakpoints) {
|
||||||
|
std::lock_guard<std::recursive_mutex> 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() {
|
bool Debugger::SuspendAllThreads() {
|
||||||
auto threads =
|
auto threads =
|
||||||
emulator_->kernel_state()->object_table()->GetObjectsByType<XThread>(
|
emulator_->kernel_state()->object_table()->GetObjectsByType<XThread>(
|
||||||
|
@ -174,83 +268,43 @@ bool Debugger::ResumeAllThreads() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Debugger::AddBreakpoint(Breakpoint* breakpoint) {
|
void Debugger::Interrupt() {
|
||||||
// Add to breakpoints map.
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
{
|
assert_true(execution_state_ == ExecutionState::kRunning);
|
||||||
std::lock_guard<std::mutex> guard(breakpoints_lock_);
|
SuspendAllThreads();
|
||||||
breakpoints_.insert(
|
execution_state_ = ExecutionState::kStopped;
|
||||||
std::pair<uint32_t, Breakpoint*>(breakpoint->address(), breakpoint));
|
server_->OnExecutionInterrupted();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find all functions that contain the breakpoint address.
|
void Debugger::Continue() {
|
||||||
auto fns =
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
emulator_->processor()->FindFunctionsWithAddress(breakpoint->address());
|
assert_true(execution_state_ == ExecutionState::kStopped);
|
||||||
|
ResumeAllThreads();
|
||||||
// Add.
|
execution_state_ = ExecutionState::kRunning;
|
||||||
for (auto fn : fns) {
|
server_->OnExecutionContinued();
|
||||||
if (fn->AddBreakpoint(breakpoint)) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
void Debugger::StepOne(uint32_t thread_id) {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
assert_true(execution_state_ == ExecutionState::kStopped);
|
||||||
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
int Debugger::RemoveBreakpoint(Breakpoint* breakpoint) {
|
void Debugger::StepTo(uint32_t thread_id, uint32_t target_pc) {
|
||||||
// Remove from breakpoint map.
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
{
|
assert_true(execution_state_ == ExecutionState::kStopped);
|
||||||
std::lock_guard<std::mutex> 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.
|
void Debugger::OnThreadCreated(xe::kernel::XThread* thread) {
|
||||||
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<Breakpoint*>& out_breakpoints) {
|
|
||||||
std::lock_guard<std::mutex> 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::OnThreadCreated(ThreadState* thread_state) {
|
|
||||||
// TODO(benvanik): notify transports.
|
// 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.
|
// TODO(benvanik): notify transports.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,7 +313,7 @@ void Debugger::OnFunctionDefined(cpu::FunctionInfo* symbol_info,
|
||||||
// Man, I'd love not to take this lock.
|
// Man, I'd love not to take this lock.
|
||||||
std::vector<Breakpoint*> breakpoints;
|
std::vector<Breakpoint*> breakpoints;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(breakpoints_lock_);
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
for (uint32_t address = symbol_info->address();
|
for (uint32_t address = symbol_info->address();
|
||||||
address <= symbol_info->end_address(); address += 4) {
|
address <= symbol_info->end_address(); address += 4) {
|
||||||
auto range = breakpoints_.equal_range(address);
|
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) {
|
Breakpoint* breakpoint) {
|
||||||
// Suspend all threads immediately.
|
// Suspend all threads immediately.
|
||||||
SuspendAllThreads();
|
SuspendAllThreads();
|
||||||
|
|
|
@ -29,12 +29,20 @@ DECLARE_bool(debug);
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
class Emulator;
|
class Emulator;
|
||||||
|
namespace kernel {
|
||||||
|
class XThread;
|
||||||
|
} // namespace kernel
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace debug {
|
namespace debug {
|
||||||
|
|
||||||
class Transport;
|
class DebugServer;
|
||||||
|
|
||||||
|
enum class ExecutionState {
|
||||||
|
kRunning,
|
||||||
|
kStopped,
|
||||||
|
};
|
||||||
|
|
||||||
class Debugger {
|
class Debugger {
|
||||||
public:
|
public:
|
||||||
|
@ -54,9 +62,7 @@ class Debugger {
|
||||||
bool is_attached() const { return is_attached_; }
|
bool is_attached() const { return is_attached_; }
|
||||||
void set_attached(bool attached);
|
void set_attached(bool attached);
|
||||||
|
|
||||||
bool SuspendAllThreads();
|
ExecutionState execution_state() const { return execution_state_; }
|
||||||
bool ResumeThread(uint32_t thread_id);
|
|
||||||
bool ResumeAllThreads();
|
|
||||||
|
|
||||||
int AddBreakpoint(Breakpoint* breakpoint);
|
int AddBreakpoint(Breakpoint* breakpoint);
|
||||||
int RemoveBreakpoint(Breakpoint* breakpoint);
|
int RemoveBreakpoint(Breakpoint* breakpoint);
|
||||||
|
@ -66,19 +72,28 @@ class Debugger {
|
||||||
// TODO(benvanik): utility functions for modification (make function ignored,
|
// TODO(benvanik): utility functions for modification (make function ignored,
|
||||||
// etc).
|
// etc).
|
||||||
|
|
||||||
void OnThreadCreated(cpu::ThreadState* thread_state);
|
void Interrupt();
|
||||||
void OnThreadDestroyed(cpu::ThreadState* thread_state);
|
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,
|
void OnFunctionDefined(cpu::FunctionInfo* symbol_info,
|
||||||
cpu::Function* function);
|
cpu::Function* function);
|
||||||
|
|
||||||
void OnBreakpointHit(cpu::ThreadState* thread_state, Breakpoint* breakpoint);
|
void OnBreakpointHit(xe::kernel::XThread* thread, Breakpoint* breakpoint);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void OnMessage(std::vector<uint8_t> buffer);
|
bool SuspendAllThreads();
|
||||||
|
bool ResumeThread(uint32_t thread_id);
|
||||||
|
bool ResumeAllThreads();
|
||||||
|
|
||||||
Emulator* emulator_ = nullptr;
|
Emulator* emulator_ = nullptr;
|
||||||
|
|
||||||
std::vector<std::unique_ptr<Transport>> transports_;
|
std::unique_ptr<DebugServer> server_;
|
||||||
bool is_attached_ = false;
|
bool is_attached_ = false;
|
||||||
xe::threading::Fence attach_fence_;
|
xe::threading::Fence attach_fence_;
|
||||||
|
|
||||||
|
@ -87,7 +102,9 @@ class Debugger {
|
||||||
std::wstring functions_trace_path_;
|
std::wstring functions_trace_path_;
|
||||||
std::unique_ptr<ChunkedMappedMemoryWriter> functions_trace_file_;
|
std::unique_ptr<ChunkedMappedMemoryWriter> functions_trace_file_;
|
||||||
|
|
||||||
std::mutex breakpoints_lock_;
|
std::recursive_mutex mutex_;
|
||||||
|
ExecutionState execution_state_ = ExecutionState::kStopped;
|
||||||
|
|
||||||
std::multimap<uint32_t, Breakpoint*> breakpoints_;
|
std::multimap<uint32_t, Breakpoint*> breakpoints_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -16,4 +16,6 @@ project("xenia-debug")
|
||||||
project_root.."/third_party/flatbuffers/include"
|
project_root.."/third_party/flatbuffers/include"
|
||||||
})
|
})
|
||||||
local_platform_files()
|
local_platform_files()
|
||||||
recursive_platform_files("transport")
|
recursive_platform_files("client")
|
||||||
|
recursive_platform_files("proto")
|
||||||
|
recursive_platform_files("server")
|
||||||
|
|
|
@ -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 <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/math.h"
|
||||||
|
#include "xenia/debug/proto/varint.h"
|
||||||
|
#include "xenia/debug/proto/xdp_protocol.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace debug {
|
||||||
|
namespace proto {
|
||||||
|
|
||||||
|
class PacketReader {
|
||||||
|
public:
|
||||||
|
PacketReader(size_t buffer_capacity) : buffer_(buffer_capacity) {}
|
||||||
|
|
||||||
|
const uint8_t* buffer() { return buffer_.data(); }
|
||||||
|
size_t buffer_capacity() const { return buffer_.size(); }
|
||||||
|
size_t buffer_offset() const { return buffer_offset_; }
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
buffer_offset_ = 0;
|
||||||
|
buffer_size_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendBuffer(const uint8_t* buffer, size_t buffer_length) {
|
||||||
|
if (buffer_size_ + buffer_length > buffer_.size()) {
|
||||||
|
size_t new_size =
|
||||||
|
std::max(buffer_.size() * 2,
|
||||||
|
xe::round_up(buffer_size_ + buffer_length, 16 * 1024));
|
||||||
|
buffer_.resize(new_size);
|
||||||
|
}
|
||||||
|
std::memcpy(buffer_.data() + buffer_size_, buffer, buffer_length);
|
||||||
|
buffer_size_ += buffer_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Compact() {
|
||||||
|
assert_null(current_packet_);
|
||||||
|
if (!buffer_offset_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::memmove(buffer_.data(), buffer_.data() + buffer_offset_,
|
||||||
|
buffer_size_ - buffer_offset_);
|
||||||
|
buffer_size_ -= buffer_offset_;
|
||||||
|
buffer_offset_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Packet* Begin() {
|
||||||
|
assert_null(current_packet_);
|
||||||
|
size_t bytes_remaining = buffer_size_ - buffer_offset_;
|
||||||
|
if (bytes_remaining < sizeof(Packet)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto packet =
|
||||||
|
reinterpret_cast<const Packet*>(buffer_.data() + buffer_offset_);
|
||||||
|
if (bytes_remaining < sizeof(Packet) + packet->body_length) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
packet_offset_ = buffer_offset_ + sizeof(Packet);
|
||||||
|
buffer_offset_ += sizeof(Packet) + packet->body_length;
|
||||||
|
current_packet_ = packet;
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
void End() {
|
||||||
|
assert_not_null(current_packet_);
|
||||||
|
current_packet_ = nullptr;
|
||||||
|
packet_offset_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* Read(size_t length) {
|
||||||
|
// Can't read into next packet/off of end.
|
||||||
|
assert_not_null(current_packet_);
|
||||||
|
assert_true(packet_offset_ + length <= buffer_offset_);
|
||||||
|
auto ptr = buffer_.data() + packet_offset_;
|
||||||
|
packet_offset_ += length;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
const T* Read() {
|
||||||
|
return reinterpret_cast<const T*>(Read(sizeof(T)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
std::vector<const T*> ReadArray(size_t count) {
|
||||||
|
std::vector<const T*> entries;
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
entries.push_back(Read<T>());
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Read(void* buffer, size_t buffer_length) {
|
||||||
|
assert_not_null(current_packet_);
|
||||||
|
auto src = Read(buffer_length);
|
||||||
|
std::memcpy(buffer, src, buffer_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReadString(char* buffer, size_t buffer_length) {
|
||||||
|
assert_not_null(current_packet_);
|
||||||
|
varint_t length;
|
||||||
|
auto src = length.Read(buffer_.data() + packet_offset_);
|
||||||
|
assert_not_null(src);
|
||||||
|
assert_true(length < buffer_length);
|
||||||
|
assert_true(length.size() + length < (buffer_offset_ - packet_offset_));
|
||||||
|
std::memcpy(buffer, src, length);
|
||||||
|
packet_offset_ += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ReadString() {
|
||||||
|
assert_not_null(current_packet_);
|
||||||
|
varint_t length;
|
||||||
|
auto src = length.Read(buffer_.data() + packet_offset_);
|
||||||
|
assert_not_null(src);
|
||||||
|
assert_true(length.size() + length < (buffer_offset_ - packet_offset_));
|
||||||
|
std::string value;
|
||||||
|
value.resize(length.value());
|
||||||
|
std::memcpy(const_cast<char*>(value.data()), src, length);
|
||||||
|
packet_offset_ += length;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<uint8_t> buffer_;
|
||||||
|
size_t buffer_offset_ = 0;
|
||||||
|
size_t buffer_size_ = 0;
|
||||||
|
|
||||||
|
const Packet* current_packet_ = nullptr;
|
||||||
|
size_t packet_offset_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace proto
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_DEBUG_PROTO_PACKET_READER_H_
|
|
@ -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 <algorithm>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/math.h"
|
||||||
|
#include "xenia/debug/proto/varint.h"
|
||||||
|
#include "xenia/debug/proto/xdp_protocol.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace debug {
|
||||||
|
namespace proto {
|
||||||
|
|
||||||
|
class PacketWriter {
|
||||||
|
public:
|
||||||
|
PacketWriter(size_t buffer_capacity) : buffer_(buffer_capacity) {}
|
||||||
|
|
||||||
|
uint8_t* buffer() { return buffer_.data(); }
|
||||||
|
size_t buffer_capacity() const { return buffer_.size(); }
|
||||||
|
size_t buffer_offset() const { return buffer_offset_; }
|
||||||
|
|
||||||
|
void Reset() { buffer_offset_ = 0; }
|
||||||
|
|
||||||
|
void Begin(PacketType packet_type, request_id_t request_id = 0) {
|
||||||
|
assert_null(current_packet_);
|
||||||
|
current_packet_ = Append<Packet>();
|
||||||
|
current_packet_->packet_type = packet_type;
|
||||||
|
current_packet_->request_id = request_id;
|
||||||
|
current_packet_->body_length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void End() {
|
||||||
|
assert_not_null(current_packet_);
|
||||||
|
current_packet_->body_length =
|
||||||
|
uint32_t((buffer_.data() + buffer_offset_) -
|
||||||
|
reinterpret_cast<uint8_t*>(current_packet_) - sizeof(Packet));
|
||||||
|
current_packet_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* Append(size_t length) {
|
||||||
|
if (buffer_offset_ + length > buffer_.size()) {
|
||||||
|
size_t new_size = std::max(
|
||||||
|
xe::round_up(buffer_offset_ + length, 16 * 1024), buffer_.size() * 2);
|
||||||
|
buffer_.resize(new_size);
|
||||||
|
}
|
||||||
|
auto ptr = buffer_.data() + buffer_offset_;
|
||||||
|
buffer_offset_ += length;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T* Append() {
|
||||||
|
return reinterpret_cast<T*>(Append(sizeof(T)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Append(const void* buffer, size_t buffer_length) {
|
||||||
|
auto dest = Append(buffer_length);
|
||||||
|
std::memcpy(dest, buffer, buffer_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendString(const char* value) {
|
||||||
|
size_t value_length = std::strlen(value);
|
||||||
|
varint_t length(value_length);
|
||||||
|
auto dest = Append(length.size() + value_length);
|
||||||
|
dest = length.Write(dest);
|
||||||
|
std::memcpy(dest, value, value_length);
|
||||||
|
}
|
||||||
|
void AppendString(const std::string& value) {
|
||||||
|
varint_t length(value.size());
|
||||||
|
auto dest = Append(length.size() + value.size());
|
||||||
|
dest = length.Write(dest);
|
||||||
|
std::memcpy(dest, value.data(), value.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<uint8_t> buffer_;
|
||||||
|
size_t buffer_offset_ = 0;
|
||||||
|
|
||||||
|
Packet* current_packet_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace proto
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_DEBUG_PROTO_PACKET_WRITER_H_
|
|
@ -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 <cstdint>
|
||||||
|
|
||||||
|
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_
|
|
@ -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 <cstdint>
|
||||||
|
|
||||||
|
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<uint8_t*>(this) + sizeof(this); }
|
||||||
|
};
|
||||||
|
static_assert(sizeof(Packet) == 8, "Fixed packet size");
|
||||||
|
|
||||||
|
// Generic response to requests (S->C).
|
||||||
|
// Used for any request type that doesn't define its own response.
|
||||||
|
struct GenericResponse {
|
||||||
|
static const PacketType type = PacketType::kGenericResponse;
|
||||||
|
|
||||||
|
enum class Code : uint32_t {
|
||||||
|
// Command was received and successfully acted upon.
|
||||||
|
// If the request is handled asynchronously this only indicates that the
|
||||||
|
// command was recognized.
|
||||||
|
kSuccess = 0,
|
||||||
|
// Command was invalid or could not be executed.
|
||||||
|
// A string will follow this response containing an error message.
|
||||||
|
kError = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
Code code;
|
||||||
|
|
||||||
|
bool is_success() const { return code == Code::kSuccess; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Requests execution state changes (C->S).
|
||||||
|
// Response is a GenericResponse, and as execution changes one or more
|
||||||
|
// ExecutionNotification packets will be sent S->C.
|
||||||
|
struct ExecutionRequest {
|
||||||
|
static const PacketType type = PacketType::kExecutionRequest;
|
||||||
|
|
||||||
|
enum class Action : uint32_t {
|
||||||
|
// Continues execution until the next breakpoint.
|
||||||
|
kContinue,
|
||||||
|
// Steps execution by the given step_mode.
|
||||||
|
kStep,
|
||||||
|
// Interrupts execution and breaks.
|
||||||
|
kInterrupt,
|
||||||
|
// Stops execution and causes the server to exit.
|
||||||
|
kExit,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class StepMode : uint32_t {
|
||||||
|
// Steps a single statement, following branches.
|
||||||
|
kStepOne,
|
||||||
|
// Runs until the given target_pc.
|
||||||
|
kStepTo,
|
||||||
|
};
|
||||||
|
|
||||||
|
Action action;
|
||||||
|
StepMode step_mode;
|
||||||
|
uint32_t thread_id;
|
||||||
|
uint32_t target_pc;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Notifies clients of changes in execution state (S->C).
|
||||||
|
// This is fired asynchronously from any request and possibly due to events
|
||||||
|
// external to the client requested ones.
|
||||||
|
// After stopping clients should query execution state.
|
||||||
|
struct ExecutionNotification {
|
||||||
|
static const PacketType type = PacketType::kExecutionNotification;
|
||||||
|
|
||||||
|
enum class State : uint32_t {
|
||||||
|
// Execution has stopped for stop_reason.
|
||||||
|
kStopped,
|
||||||
|
// Execution has resumed and the target is now running.
|
||||||
|
kRunning,
|
||||||
|
// The target has exited.
|
||||||
|
kExited,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Reason : uint32_t {
|
||||||
|
// Stopped because the client requested it with Action::kInterrupt.
|
||||||
|
kInterrupt,
|
||||||
|
// Stopped due to a completed step.
|
||||||
|
kStep,
|
||||||
|
// Stopped at a breakpoint.
|
||||||
|
kBreakpoint,
|
||||||
|
// TODO(benvanik): others? catches? library loads? etc?
|
||||||
|
};
|
||||||
|
|
||||||
|
State current_state;
|
||||||
|
Reason stop_reason;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModuleListRequest {
|
||||||
|
static const PacketType type = PacketType::kModuleListRequest;
|
||||||
|
};
|
||||||
|
struct ModuleListResponse {
|
||||||
|
static const PacketType type = PacketType::kModuleListResponse;
|
||||||
|
|
||||||
|
uint32_t count;
|
||||||
|
// ModuleListEntry[count]
|
||||||
|
};
|
||||||
|
struct ModuleListEntry {
|
||||||
|
uint32_t module_handle;
|
||||||
|
uint32_t module_ptr;
|
||||||
|
bool is_kernel_module;
|
||||||
|
char path[256];
|
||||||
|
char name[256];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ThreadListRequest {
|
||||||
|
static const PacketType type = PacketType::kThreadListRequest;
|
||||||
|
};
|
||||||
|
struct ThreadListResponse {
|
||||||
|
static const PacketType type = PacketType::kThreadListResponse;
|
||||||
|
|
||||||
|
uint32_t count;
|
||||||
|
// ThreadListEntry[count]
|
||||||
|
};
|
||||||
|
struct ThreadListEntry {
|
||||||
|
uint32_t thread_handle;
|
||||||
|
uint32_t thread_id;
|
||||||
|
bool is_host_thread;
|
||||||
|
char name[64];
|
||||||
|
uint32_t exit_code;
|
||||||
|
int32_t priority;
|
||||||
|
uint32_t affinity;
|
||||||
|
uint32_t xapi_thread_startup;
|
||||||
|
uint32_t start_address;
|
||||||
|
uint32_t start_context;
|
||||||
|
uint32_t creation_flags;
|
||||||
|
uint32_t stack_address;
|
||||||
|
uint32_t stack_size;
|
||||||
|
uint32_t stack_base;
|
||||||
|
uint32_t stack_limit;
|
||||||
|
uint32_t pcr_address;
|
||||||
|
uint32_t tls_address;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace proto
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_DEBUG_PROTO_XDP_PROTOCOL_H_
|
|
@ -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.
|
|
@ -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<const void*, size_t> 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
|
|
@ -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 <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<uint8_t> receive_buffer_;
|
||||||
|
size_t receive_offset_ = 0;
|
||||||
|
std::vector<uint8_t> transmit_buffer_;
|
||||||
|
|
||||||
|
bool no_ack_mode_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gdb
|
||||||
|
} // namespace server
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_DEBUG_SERVER_GDB_GDB_COMMAND_PROCESSOR_H_
|
|
@ -7,30 +7,32 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "xenia/debug/transport/gdb/gdb_transport.h"
|
#include "xenia/debug/server/gdb/gdb_server.h"
|
||||||
|
|
||||||
#include <gflags/gflags.h>
|
#include <gflags/gflags.h>
|
||||||
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/debug/debugger.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 xe {
|
||||||
namespace debug {
|
namespace debug {
|
||||||
namespace transport {
|
namespace server {
|
||||||
namespace gdb {
|
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);
|
receive_buffer_.resize(kReceiveBufferSize);
|
||||||
|
command_processor_ = std::make_unique<GdbCommandProcessor>(this, debugger);
|
||||||
}
|
}
|
||||||
|
|
||||||
GdbTransport::~GdbTransport() = default;
|
GdbServer::~GdbServer() = default;
|
||||||
|
|
||||||
bool GdbTransport::Initialize() {
|
bool GdbServer::Initialize() {
|
||||||
socket_server_ = SocketServer::Create(uint16_t(FLAGS_gdb_port),
|
socket_server_ = SocketServer::Create(uint16_t(FLAGS_gdb_server_port),
|
||||||
[this](std::unique_ptr<Socket> client) {
|
[this](std::unique_ptr<Socket> client) {
|
||||||
AcceptClient(std::move(client));
|
AcceptClient(std::move(client));
|
||||||
});
|
});
|
||||||
|
@ -42,7 +44,7 @@ bool GdbTransport::Initialize() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GdbTransport::AcceptClient(std::unique_ptr<Socket> client) {
|
void GdbServer::AcceptClient(std::unique_ptr<Socket> client) {
|
||||||
// If we have an existing client, kill it and join its thread.
|
// If we have an existing client, kill it and join its thread.
|
||||||
if (client_) {
|
if (client_) {
|
||||||
// TODO(benvanik): GDB say goodbye?
|
// TODO(benvanik): GDB say goodbye?
|
||||||
|
@ -63,6 +65,9 @@ void GdbTransport::AcceptClient(std::unique_ptr<Socket> client) {
|
||||||
// Let the debugger know we are present.
|
// Let the debugger know we are present.
|
||||||
debugger_->set_attached(true);
|
debugger_->set_attached(true);
|
||||||
|
|
||||||
|
// Junk just to poke the remote client.
|
||||||
|
client_->Send("(gdb)\n");
|
||||||
|
|
||||||
// Main loop.
|
// Main loop.
|
||||||
bool running = true;
|
bool running = true;
|
||||||
while (running) {
|
while (running) {
|
||||||
|
@ -91,7 +96,7 @@ void GdbTransport::AcceptClient(std::unique_ptr<Socket> client) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GdbTransport::HandleClientEvent() {
|
bool GdbServer::HandleClientEvent() {
|
||||||
if (!client_->is_connected()) {
|
if (!client_->is_connected()) {
|
||||||
// Known-disconnected.
|
// Known-disconnected.
|
||||||
return false;
|
return false;
|
||||||
|
@ -107,12 +112,17 @@ bool GdbTransport::HandleClientEvent() {
|
||||||
return true;
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace gdb
|
} // namespace gdb
|
||||||
} // namespace transport
|
} // namespace server
|
||||||
} // namespace debug
|
} // namespace debug
|
||||||
} // namespace xe
|
} // namespace xe
|
|
@ -7,24 +7,28 @@
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef XENIA_DEBUG_TRANSPORT_GDB_GDB_TRANSPORT_H_
|
#ifndef XENIA_DEBUG_SERVER_GDB_GDB_SERVER_H_
|
||||||
#define XENIA_DEBUG_TRANSPORT_GDB_GDB_TRANSPORT_H_
|
#define XENIA_DEBUG_SERVER_GDB_GDB_SERVER_H_
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "xenia/base/socket.h"
|
#include "xenia/base/socket.h"
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
#include "xenia/debug/transport.h"
|
#include "xenia/debug/debug_server.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace debug {
|
namespace debug {
|
||||||
namespace transport {
|
namespace server {
|
||||||
namespace gdb {
|
namespace gdb {
|
||||||
|
|
||||||
class GdbTransport : public Transport {
|
class GdbCommandProcessor;
|
||||||
|
|
||||||
|
class GdbServer : public DebugServer {
|
||||||
public:
|
public:
|
||||||
GdbTransport(Debugger* debugger);
|
GdbServer(Debugger* debugger);
|
||||||
~GdbTransport() override;
|
~GdbServer() override;
|
||||||
|
|
||||||
|
Socket* client() const { return client_.get(); }
|
||||||
|
|
||||||
bool Initialize() override;
|
bool Initialize() override;
|
||||||
|
|
||||||
|
@ -37,11 +41,13 @@ class GdbTransport : public Transport {
|
||||||
std::unique_ptr<Socket> client_;
|
std::unique_ptr<Socket> client_;
|
||||||
std::unique_ptr<xe::threading::Thread> client_thread_;
|
std::unique_ptr<xe::threading::Thread> client_thread_;
|
||||||
std::vector<uint8_t> receive_buffer_;
|
std::vector<uint8_t> receive_buffer_;
|
||||||
|
|
||||||
|
std::unique_ptr<GdbCommandProcessor> command_processor_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace gdb
|
} // namespace gdb
|
||||||
} // namespace transport
|
} // namespace server
|
||||||
} // namespace debug
|
} // namespace debug
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
#endif // XENIA_DEBUG_TRANSPORT_GDB_GDB_TRANSPORT_H_
|
#endif // XENIA_DEBUG_SERVER_GDB_GDB_SERVER_H_
|
|
@ -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
|
|
@ -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 <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<uint8_t> receive_buffer_;
|
||||||
|
size_t receive_offset_ = 0;
|
||||||
|
std::vector<uint8_t> 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_
|
|
@ -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 <cstdint>
|
||||||
|
|
||||||
|
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_
|
|
@ -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
|
|
@ -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 <cstdint>
|
||||||
|
|
||||||
|
#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_
|
|
@ -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 <gflags/gflags.h>
|
||||||
|
|
||||||
|
#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<MICommandProcessor>(this, debugger);
|
||||||
|
}
|
||||||
|
|
||||||
|
MIServer::~MIServer() = default;
|
||||||
|
|
||||||
|
bool MIServer::Initialize() {
|
||||||
|
socket_server_ = SocketServer::Create(uint16_t(FLAGS_mi_server_port),
|
||||||
|
[this](std::unique_ptr<Socket> 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<Socket> 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
|
|
@ -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 <memory>
|
||||||
|
|
||||||
|
#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<Socket> client);
|
||||||
|
bool HandleClientEvent();
|
||||||
|
|
||||||
|
std::unique_ptr<SocketServer> socket_server_;
|
||||||
|
|
||||||
|
std::unique_ptr<Socket> client_;
|
||||||
|
std::unique_ptr<xe::threading::Thread> client_thread_;
|
||||||
|
std::vector<uint8_t> receive_buffer_;
|
||||||
|
|
||||||
|
std::unique_ptr<MICommandProcessor> command_processor_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mi
|
||||||
|
} // namespace server
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_DEBUG_SERVER_MI_MI_SERVER_H_
|
|
@ -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
|
|
@ -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 <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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_
|
|
@ -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 <gflags/gflags.h>
|
||||||
|
|
||||||
|
#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<Socket> 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<Socket> client) {
|
||||||
|
// If we have an existing client, kill it and join its thread.
|
||||||
|
if (client_) {
|
||||||
|
// TODO(benvanik): XDP say goodbye?
|
||||||
|
|
||||||
|
client_->Close();
|
||||||
|
xe::threading::Wait(client_thread_.get(), true);
|
||||||
|
client_thread_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take ownership of the new one.
|
||||||
|
client_ = std::move(client);
|
||||||
|
|
||||||
|
// Create a thread to manage the connection.
|
||||||
|
client_thread_ = xe::threading::Thread::Create({}, [this]() {
|
||||||
|
// 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<ExecutionRequest>();
|
||||||
|
switch (body->action) {
|
||||||
|
case ExecutionRequest::Action::kContinue: {
|
||||||
|
debugger_->Continue();
|
||||||
|
} break;
|
||||||
|
case ExecutionRequest::Action::kStep: {
|
||||||
|
switch (body->step_mode) {
|
||||||
|
case ExecutionRequest::StepMode::kStepOne: {
|
||||||
|
debugger_->StepOne(body->thread_id);
|
||||||
|
} break;
|
||||||
|
case ExecutionRequest::StepMode::kStepTo: {
|
||||||
|
debugger_->StepTo(body->thread_id, body->target_pc);
|
||||||
|
} break;
|
||||||
|
default:
|
||||||
|
assert_unhandled_case(body->step_mode);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case ExecutionRequest::Action::kInterrupt: {
|
||||||
|
debugger_->Interrupt();
|
||||||
|
} break;
|
||||||
|
case ExecutionRequest::Action::kExit: {
|
||||||
|
XELOGI("Debug client requested exit");
|
||||||
|
exit(1);
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
SendSuccess(packet->request_id);
|
||||||
|
} break;
|
||||||
|
case PacketType::kModuleListRequest: {
|
||||||
|
packet_writer_.Begin(PacketType::kModuleListResponse);
|
||||||
|
auto body = packet_writer_.Append<ModuleListResponse>();
|
||||||
|
auto modules =
|
||||||
|
object_table->GetObjectsByType<XModule>(XObject::Type::kTypeModule);
|
||||||
|
body->count = uint32_t(modules.size());
|
||||||
|
for (auto& module : modules) {
|
||||||
|
auto entry = packet_writer_.Append<ModuleListEntry>();
|
||||||
|
entry->module_handle = module->handle();
|
||||||
|
entry->module_ptr = module->hmodule_ptr();
|
||||||
|
entry->is_kernel_module =
|
||||||
|
module->module_type() == XModule::ModuleType::kKernelModule;
|
||||||
|
std::strncpy(entry->path, module->path().c_str(),
|
||||||
|
xe::countof(entry->path));
|
||||||
|
std::strncpy(entry->name, module->name().c_str(),
|
||||||
|
xe::countof(entry->name));
|
||||||
|
}
|
||||||
|
packet_writer_.End();
|
||||||
|
} break;
|
||||||
|
case PacketType::kThreadListRequest: {
|
||||||
|
packet_writer_.Begin(PacketType::kThreadListResponse);
|
||||||
|
auto body = packet_writer_.Append<ThreadListResponse>();
|
||||||
|
auto threads =
|
||||||
|
object_table->GetObjectsByType<XThread>(XObject::Type::kTypeThread);
|
||||||
|
body->count = uint32_t(threads.size());
|
||||||
|
for (auto& thread : threads) {
|
||||||
|
auto entry = packet_writer_.Append<ThreadListEntry>();
|
||||||
|
entry->thread_handle = thread->handle();
|
||||||
|
entry->thread_id = thread->thread_id();
|
||||||
|
entry->is_host_thread = !thread->is_guest_thread();
|
||||||
|
std::strncpy(entry->name, thread->name().c_str(),
|
||||||
|
xe::countof(entry->name));
|
||||||
|
entry->exit_code;
|
||||||
|
entry->priority = thread->priority();
|
||||||
|
entry->xapi_thread_startup =
|
||||||
|
thread->creation_params()->xapi_thread_startup;
|
||||||
|
entry->start_address = thread->creation_params()->start_address;
|
||||||
|
entry->start_context = thread->creation_params()->start_context;
|
||||||
|
entry->creation_flags = thread->creation_params()->creation_flags;
|
||||||
|
entry->stack_address = thread->thread_state()->stack_address();
|
||||||
|
entry->stack_size = thread->thread_state()->stack_size();
|
||||||
|
entry->stack_base = thread->thread_state()->stack_base();
|
||||||
|
entry->stack_limit = thread->thread_state()->stack_limit();
|
||||||
|
entry->pcr_address = thread->pcr_ptr();
|
||||||
|
entry->tls_address = thread->tls_ptr();
|
||||||
|
}
|
||||||
|
packet_writer_.End();
|
||||||
|
} break;
|
||||||
|
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<ExecutionNotification>();
|
||||||
|
body->current_state = ExecutionNotification::State::kRunning;
|
||||||
|
packet_writer_.End();
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void XdpServer::OnExecutionInterrupted() {
|
||||||
|
packet_writer_.Begin(PacketType::kExecutionNotification);
|
||||||
|
auto body = packet_writer_.Append<ExecutionNotification>();
|
||||||
|
body->current_state = ExecutionNotification::State::kStopped;
|
||||||
|
body->stop_reason = ExecutionNotification::Reason::kInterrupt;
|
||||||
|
packet_writer_.End();
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void 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<GenericResponse>();
|
||||||
|
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<GenericResponse>();
|
||||||
|
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<GenericResponse>();
|
||||||
|
body->code = GenericResponse::Code::kError;
|
||||||
|
packet_writer_.AppendString(error_message);
|
||||||
|
packet_writer_.End();
|
||||||
|
Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace xdp
|
||||||
|
} // namespace server
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
|
@ -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 <memory>
|
||||||
|
|
||||||
|
#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<Socket> client);
|
||||||
|
bool HandleClientEvent();
|
||||||
|
|
||||||
|
bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length);
|
||||||
|
bool ProcessPacket(const proto::Packet* packet);
|
||||||
|
|
||||||
|
void Flush();
|
||||||
|
void SendSuccess(proto::request_id_t request_id);
|
||||||
|
void SendError(proto::request_id_t request_id,
|
||||||
|
const char* error_message = nullptr);
|
||||||
|
void SendError(proto::request_id_t request_id,
|
||||||
|
const std::string& error_message);
|
||||||
|
|
||||||
|
std::unique_ptr<SocketServer> socket_server_;
|
||||||
|
|
||||||
|
std::unique_ptr<Socket> client_;
|
||||||
|
std::unique_ptr<xe::threading::Thread> client_thread_;
|
||||||
|
std::vector<uint8_t> 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_
|
|
@ -58,6 +58,15 @@ std::unique_ptr<Application> Application::Create() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::Initialize() {
|
bool Application::Initialize() {
|
||||||
|
// Bind the object model to the client so it'll maintain state.
|
||||||
|
system_ = std::make_unique<model::System>(loop(), &client_);
|
||||||
|
client_.set_listener(system_.get());
|
||||||
|
|
||||||
|
// TODO(benvanik): flags and such.
|
||||||
|
if (!client_.Connect("localhost", 9002)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
main_window_ = std::make_unique<MainWindow>(this);
|
main_window_ = std::make_unique<MainWindow>(this);
|
||||||
if (!main_window_->Initialize()) {
|
if (!main_window_->Initialize()) {
|
||||||
XELOGE("Unable to initialize main window");
|
XELOGE("Unable to initialize main window");
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "xenia/debug/client/xdp/xdp_client.h"
|
||||||
|
#include "xenia/debug/ui/model/system.h"
|
||||||
#include "xenia/ui/loop.h"
|
#include "xenia/ui/loop.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -29,6 +31,8 @@ class Application {
|
||||||
|
|
||||||
xe::ui::Loop* loop() { return loop_.get(); }
|
xe::ui::Loop* loop() { return loop_.get(); }
|
||||||
MainWindow* main_window() const { return main_window_.get(); }
|
MainWindow* main_window() const { return main_window_.get(); }
|
||||||
|
client::xdp::XdpClient* client() { return &client_; }
|
||||||
|
model::System* system() const { return system_.get(); }
|
||||||
|
|
||||||
void Quit();
|
void Quit();
|
||||||
|
|
||||||
|
@ -39,6 +43,9 @@ class Application {
|
||||||
|
|
||||||
std::unique_ptr<xe::ui::Loop> loop_;
|
std::unique_ptr<xe::ui::Loop> loop_;
|
||||||
std::unique_ptr<MainWindow> main_window_;
|
std::unique_ptr<MainWindow> main_window_;
|
||||||
|
client::xdp::XdpClient client_;
|
||||||
|
|
||||||
|
std::unique_ptr<model::System> system_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -21,6 +21,8 @@ namespace xe {
|
||||||
namespace debug {
|
namespace debug {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
|
|
||||||
|
using namespace xe::debug::client::xdp;
|
||||||
|
|
||||||
using xe::ui::MenuItem;
|
using xe::ui::MenuItem;
|
||||||
|
|
||||||
const std::wstring kBaseTitle = L"xenia debugger";
|
const std::wstring kBaseTitle = L"xenia debugger";
|
||||||
|
@ -30,6 +32,8 @@ MainWindow::MainWindow(Application* app) : app_(app) {}
|
||||||
MainWindow::~MainWindow() = default;
|
MainWindow::~MainWindow() = default;
|
||||||
|
|
||||||
bool MainWindow::Initialize() {
|
bool MainWindow::Initialize() {
|
||||||
|
client_ = app_->client();
|
||||||
|
|
||||||
platform_window_ = xe::ui::Window::Create(app()->loop(), kBaseTitle);
|
platform_window_ = xe::ui::Window::Create(app()->loop(), kBaseTitle);
|
||||||
if (!platform_window_) {
|
if (!platform_window_) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -106,11 +110,14 @@ void MainWindow::BuildUI() {
|
||||||
.gravity(Gravity::kAll)
|
.gravity(Gravity::kAll)
|
||||||
.distribution(LayoutDistribution::kAvailable)
|
.distribution(LayoutDistribution::kAvailable)
|
||||||
.axis(Axis::kY)
|
.axis(Axis::kY)
|
||||||
.child(LayoutBoxNode()
|
.child(
|
||||||
|
LayoutBoxNode()
|
||||||
.id("toolbar_box")
|
.id("toolbar_box")
|
||||||
.gravity(Gravity::kTop | Gravity::kLeftRight)
|
.gravity(Gravity::kTop | Gravity::kLeftRight)
|
||||||
.distribution(LayoutDistribution::kAvailable)
|
.distribution(LayoutDistribution::kGravity)
|
||||||
.child(LabelNode("toolbar")))
|
.distribution_position(LayoutDistributionPosition::kLeftTop)
|
||||||
|
.child(ButtonNode("Pause").id("pause_button"))
|
||||||
|
.child(ButtonNode("Continue").id("resume_button")))
|
||||||
.child(
|
.child(
|
||||||
SplitContainerNode()
|
SplitContainerNode()
|
||||||
.id("split_container")
|
.id("split_container")
|
||||||
|
@ -132,15 +139,50 @@ void MainWindow::BuildUI() {
|
||||||
{TBIDC("tab_container"), &ui_.tab_container},
|
{TBIDC("tab_container"), &ui_.tab_container},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
handler_ = std::make_unique<el::EventHandler>(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->tab_bar()->LoadNodeTree(ButtonNode(cpu_view_.name()));
|
||||||
ui_.tab_container->content_root()->AddChild(cpu_view_.BuildUI());
|
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->tab_bar()->LoadNodeTree(ButtonNode(gpu_view_.name()));
|
||||||
ui_.tab_container->content_root()->AddChild(gpu_view_.BuildUI());
|
ui_.tab_container->content_root()->AddChild(gpu_view_.BuildUI());
|
||||||
|
gpu_view_.Setup(Application::current()->client());
|
||||||
|
|
||||||
|
UpdateElementState();
|
||||||
|
|
||||||
el::util::ShowDebugInfoSettingsWindow(root_element);
|
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(); }
|
void MainWindow::OnClose() { app_->Quit(); }
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -28,6 +28,8 @@ class MainWindow {
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
|
|
||||||
Application* app() const { return app_; }
|
Application* app() const { return app_; }
|
||||||
|
xe::ui::Loop* loop() const { return app_->loop(); }
|
||||||
|
model::System* system() const { return app_->system(); }
|
||||||
|
|
||||||
bool Initialize();
|
bool Initialize();
|
||||||
|
|
||||||
|
@ -35,10 +37,12 @@ class MainWindow {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void BuildUI();
|
void BuildUI();
|
||||||
|
void UpdateElementState();
|
||||||
|
|
||||||
void OnClose();
|
void OnClose();
|
||||||
|
|
||||||
Application* app_ = nullptr;
|
Application* app_ = nullptr;
|
||||||
|
xe::debug::client::xdp::XdpClient* client_ = nullptr;
|
||||||
|
|
||||||
std::unique_ptr<xe::ui::Window> platform_window_;
|
std::unique_ptr<xe::ui::Window> platform_window_;
|
||||||
std::unique_ptr<el::Window> window_;
|
std::unique_ptr<el::Window> window_;
|
||||||
|
@ -47,6 +51,7 @@ class MainWindow {
|
||||||
el::LayoutBox* toolbar_box;
|
el::LayoutBox* toolbar_box;
|
||||||
el::TabContainer* tab_container;
|
el::TabContainer* tab_container;
|
||||||
} ui_ = {0};
|
} ui_ = {0};
|
||||||
|
std::unique_ptr<el::EventHandler> handler_;
|
||||||
views::cpu::CpuView cpu_view_;
|
views::cpu::CpuView cpu_view_;
|
||||||
views::gpu::GpuView gpu_view_;
|
views::gpu::GpuView gpu_view_;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
|
@ -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 <cstdint>
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace debug {
|
||||||
|
namespace ui {
|
||||||
|
namespace model {
|
||||||
|
|
||||||
|
class Function {
|
||||||
|
public:
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace model
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_DEBUG_UI_MODEL_FUNCTION_H_
|
|
@ -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
|
|
@ -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 <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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_
|
|
@ -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 <unordered_set>
|
||||||
|
|
||||||
|
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<std::recursive_mutex> lock(mutex_);
|
||||||
|
return client_->execution_state();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Module*> System::modules() {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
std::vector<Module*> result;
|
||||||
|
for (auto& module : modules_) {
|
||||||
|
result.push_back(module.get());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Thread*> System::threads() {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
std::vector<Thread*> result;
|
||||||
|
for (auto& thread : threads_) {
|
||||||
|
result.push_back(thread.get());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Module* System::GetModuleByHandle(uint32_t module_handle) {
|
||||||
|
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> lock(mutex_);
|
||||||
|
on_execution_state_changed();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::OnModulesUpdated(std::vector<const ModuleListEntry*> entries) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
std::unordered_set<uint32_t> extra_modules;
|
||||||
|
for (size_t i = 0; i < modules_.size(); ++i) {
|
||||||
|
extra_modules.emplace(modules_[i]->module_handle());
|
||||||
|
}
|
||||||
|
for (auto entry : entries) {
|
||||||
|
auto existing_module = modules_by_handle_.find(entry->module_handle);
|
||||||
|
if (existing_module == modules_by_handle_.end()) {
|
||||||
|
auto module = std::make_unique<Module>(this);
|
||||||
|
module->Update(entry);
|
||||||
|
modules_by_handle_.emplace(entry->module_handle, module.get());
|
||||||
|
modules_.emplace_back(std::move(module));
|
||||||
|
} else {
|
||||||
|
existing_module->second->Update(entry);
|
||||||
|
extra_modules.erase(existing_module->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto module_handle : extra_modules) {
|
||||||
|
modules_by_handle_[module_handle]->set_dead(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop_->Post([this]() {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
on_modules_updated();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void System::OnThreadsUpdated(std::vector<const ThreadListEntry*> entries) {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
std::unordered_set<uint32_t> extra_threads;
|
||||||
|
for (size_t i = 0; i < threads_.size(); ++i) {
|
||||||
|
extra_threads.emplace(threads_[i]->thread_handle());
|
||||||
|
}
|
||||||
|
for (auto entry : entries) {
|
||||||
|
auto existing_thread = threads_by_handle_.find(entry->thread_handle);
|
||||||
|
if (existing_thread == threads_by_handle_.end()) {
|
||||||
|
auto thread = std::make_unique<Thread>(this);
|
||||||
|
thread->Update(entry);
|
||||||
|
threads_by_handle_.emplace(entry->thread_handle, thread.get());
|
||||||
|
threads_.emplace_back(std::move(thread));
|
||||||
|
} else {
|
||||||
|
existing_thread->second->Update(entry);
|
||||||
|
extra_threads.erase(existing_thread->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto thread_handle : extra_threads) {
|
||||||
|
modules_by_handle_[thread_handle]->set_dead(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop_->Post([this]() {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||||
|
on_threads_updated();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace model
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
|
@ -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 <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<Module*> modules();
|
||||||
|
std::vector<Thread*> threads();
|
||||||
|
|
||||||
|
Module* GetModuleByHandle(uint32_t module_handle);
|
||||||
|
Thread* GetThreadByHandle(uint32_t thread_handle);
|
||||||
|
|
||||||
|
Delegate<void> on_execution_state_changed;
|
||||||
|
Delegate<void> on_modules_updated;
|
||||||
|
Delegate<void> on_threads_updated;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void OnExecutionStateChanged(ExecutionState execution_state) override;
|
||||||
|
void OnModulesUpdated(
|
||||||
|
std::vector<const proto::ModuleListEntry*> entries) override;
|
||||||
|
void OnThreadsUpdated(
|
||||||
|
std::vector<const proto::ThreadListEntry*> entries) override;
|
||||||
|
|
||||||
|
xe::ui::Loop* loop_ = nullptr;
|
||||||
|
client::xdp::XdpClient* client_ = nullptr;
|
||||||
|
|
||||||
|
std::recursive_mutex mutex_;
|
||||||
|
std::vector<std::unique_ptr<Module>> modules_;
|
||||||
|
std::unordered_map<uint32_t, Module*> modules_by_handle_;
|
||||||
|
std::vector<std::unique_ptr<Thread>> threads_;
|
||||||
|
std::unordered_map<uint32_t, Thread*> threads_by_handle_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace model
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_DEBUG_UI_MODEL_SYSTEM_H_
|
|
@ -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
|
|
@ -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 <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#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_
|
|
@ -14,6 +14,8 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "el/elements.h"
|
#include "el/elements.h"
|
||||||
|
#include "el/event_handler.h"
|
||||||
|
#include "xenia/debug/ui/application.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace debug {
|
namespace debug {
|
||||||
|
@ -25,14 +27,21 @@ class View {
|
||||||
|
|
||||||
std::string name() const { return name_; }
|
std::string name() const { return name_; }
|
||||||
el::LayoutBox* root_element() { return &root_element_; }
|
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 el::Element* BuildUI() = 0;
|
||||||
|
|
||||||
|
virtual void Setup(xe::debug::client::xdp::XdpClient* client) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
View(std::string name) : name_(name) {}
|
View(std::string name) : name_(name) {}
|
||||||
|
|
||||||
std::string name_;
|
std::string name_;
|
||||||
el::LayoutBox root_element_;
|
el::LayoutBox root_element_;
|
||||||
|
std::unique_ptr<el::EventHandler> handler_;
|
||||||
|
xe::debug::client::xdp::XdpClient* client_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -16,6 +16,8 @@ namespace ui {
|
||||||
namespace views {
|
namespace views {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
|
|
||||||
|
using namespace xe::debug::client::xdp;
|
||||||
|
|
||||||
CpuView::CpuView() : View("CPU") {}
|
CpuView::CpuView() : View("CPU") {}
|
||||||
|
|
||||||
CpuView::~CpuView() = default;
|
CpuView::~CpuView() = default;
|
||||||
|
@ -29,22 +31,13 @@ el::Element* CpuView::BuildUI() {
|
||||||
.gravity(Gravity::kAll)
|
.gravity(Gravity::kAll)
|
||||||
.distribution(LayoutDistribution::kAvailable)
|
.distribution(LayoutDistribution::kAvailable)
|
||||||
.axis(Axis::kY)
|
.axis(Axis::kY)
|
||||||
.child(
|
.child(LayoutBoxNode()
|
||||||
LayoutBoxNode()
|
|
||||||
.gravity(Gravity::kTop | Gravity::kLeftRight)
|
.gravity(Gravity::kTop | Gravity::kLeftRight)
|
||||||
.distribution(LayoutDistribution::kAvailable)
|
.distribution(LayoutDistribution::kAvailable)
|
||||||
.axis(Axis::kX)
|
.axis(Axis::kX)
|
||||||
.skin("button_group")
|
.skin("button_group")
|
||||||
.child(ButtonNode("?"))
|
.child(DropDownButtonNode().id("module_dropdown")))
|
||||||
.child(
|
.child(ListBoxNode().id("function_listbox").gravity(Gravity::kAll))
|
||||||
DropDownButtonNode().item("Module").item("Module").item(
|
|
||||||
"Module")))
|
|
||||||
.child(ListBoxNode()
|
|
||||||
.gravity(Gravity::kAll)
|
|
||||||
.item("fn")
|
|
||||||
.item("fn")
|
|
||||||
.item("fn")
|
|
||||||
.item("fn"))
|
|
||||||
.child(LayoutBoxNode()
|
.child(LayoutBoxNode()
|
||||||
.gravity(Gravity::kBottom | Gravity::kLeftRight)
|
.gravity(Gravity::kBottom | Gravity::kLeftRight)
|
||||||
.distribution(LayoutDistribution::kAvailable)
|
.distribution(LayoutDistribution::kAvailable)
|
||||||
|
@ -102,10 +95,9 @@ el::Element* CpuView::BuildUI() {
|
||||||
.distribution(LayoutDistribution::kGravity)
|
.distribution(LayoutDistribution::kGravity)
|
||||||
.distribution_position(LayoutDistributionPosition::kLeftTop)
|
.distribution_position(LayoutDistributionPosition::kLeftTop)
|
||||||
.axis(Axis::kX)
|
.axis(Axis::kX)
|
||||||
.child(ButtonNode("button"))
|
.child(DropDownButtonNode().id("thread_dropdown")))
|
||||||
.child(ButtonNode("button"))
|
|
||||||
.child(ButtonNode("button")))
|
|
||||||
.child(LayoutBoxNode()
|
.child(LayoutBoxNode()
|
||||||
|
.id("source_content")
|
||||||
.gravity(Gravity::kAll)
|
.gravity(Gravity::kAll)
|
||||||
.distribution(LayoutDistribution::kAvailable)
|
.distribution(LayoutDistribution::kAvailable)
|
||||||
.child(SplitContainerNode()
|
.child(SplitContainerNode()
|
||||||
|
@ -144,9 +136,91 @@ el::Element* CpuView::BuildUI() {
|
||||||
root_element_.GetElementsById({
|
root_element_.GetElementsById({
|
||||||
//
|
//
|
||||||
});
|
});
|
||||||
|
handler_ = std::make_unique<el::EventHandler>(&root_element_);
|
||||||
|
|
||||||
|
handler_->Listen(el::EventType::kChanged, TBIDC("module_dropdown"),
|
||||||
|
[this](const el::Event& ev) {
|
||||||
|
UpdateFunctionList();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
return &root_element_;
|
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<el::GenericStringItem>(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<el::GenericStringItem>(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 cpu
|
||||||
} // namespace views
|
} // namespace views
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -28,7 +28,13 @@ class CpuView : public View {
|
||||||
|
|
||||||
el::Element* BuildUI() override;
|
el::Element* BuildUI() override;
|
||||||
|
|
||||||
|
void Setup(xe::debug::client::xdp::XdpClient* client) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void UpdateElementState();
|
||||||
|
void UpdateModuleList();
|
||||||
|
void UpdateFunctionList();
|
||||||
|
void UpdateThreadList();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace cpu
|
} // namespace cpu
|
||||||
|
|
|
@ -32,9 +32,14 @@ el::Element* GpuView::BuildUI() {
|
||||||
root_element_.GetElementsById({
|
root_element_.GetElementsById({
|
||||||
//
|
//
|
||||||
});
|
});
|
||||||
|
handler_ = std::make_unique<el::EventHandler>(&root_element_);
|
||||||
return &root_element_;
|
return &root_element_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GpuView::Setup(xe::debug::client::xdp::XdpClient* client) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace gpu
|
} // namespace gpu
|
||||||
} // namespace views
|
} // namespace views
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -28,6 +28,8 @@ class GpuView : public View {
|
||||||
|
|
||||||
el::Element* BuildUI() override;
|
el::Element* BuildUI() override;
|
||||||
|
|
||||||
|
void Setup(xe::debug::client::xdp::XdpClient* client) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -353,7 +353,7 @@ void KernelState::TerminateTitle(bool from_guest_thread) {
|
||||||
|
|
||||||
// Second: Kill all guest threads.
|
// Second: Kill all guest threads.
|
||||||
for (auto it = threads_by_id_.begin(); it != threads_by_id_.end();) {
|
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;
|
auto thread = it->second;
|
||||||
|
|
||||||
if (from_guest_thread && XThread::IsInThread(thread)) {
|
if (from_guest_thread && XThread::IsInThread(thread)) {
|
||||||
|
@ -362,7 +362,7 @@ void KernelState::TerminateTitle(bool from_guest_thread) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (thread->running()) {
|
if (thread->is_running()) {
|
||||||
thread->Terminate(0);
|
thread->Terminate(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -457,6 +457,10 @@ void KernelState::OnThreadExit(XThread* thread) {
|
||||||
xe::countof(args));
|
xe::countof(args));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (emulator()->debugger()) {
|
||||||
|
emulator()->debugger()->OnThreadExit(thread);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object_ref<XThread> KernelState::GetThreadByID(uint32_t thread_id) {
|
object_ref<XThread> KernelState::GetThreadByID(uint32_t thread_id) {
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "xenia/base/mutex.h"
|
#include "xenia/base/mutex.h"
|
||||||
#include "xenia/base/threading.h"
|
#include "xenia/base/threading.h"
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/cpu/processor.h"
|
||||||
|
#include "xenia/emulator.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/kernel/native_list.h"
|
#include "xenia/kernel/native_list.h"
|
||||||
#include "xenia/kernel/objects/xevent.h"
|
#include "xenia/kernel/objects/xevent.h"
|
||||||
|
@ -70,6 +71,10 @@ XThread::~XThread() {
|
||||||
// Unregister first to prevent lookups while deleting.
|
// Unregister first to prevent lookups while deleting.
|
||||||
kernel_state_->UnregisterThread(this);
|
kernel_state_->UnregisterThread(this);
|
||||||
|
|
||||||
|
if (emulator()->debugger()) {
|
||||||
|
emulator()->debugger()->OnThreadDestroyed(this);
|
||||||
|
}
|
||||||
|
|
||||||
delete apc_list_;
|
delete apc_list_;
|
||||||
|
|
||||||
thread_.reset();
|
thread_.reset();
|
||||||
|
@ -329,6 +334,10 @@ X_STATUS XThread::Create() {
|
||||||
thread_->set_priority(creation_params_.creation_flags & 0x20 ? 1 : 0);
|
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) {
|
if ((creation_params_.creation_flags & X_CREATE_SUSPENDED) == 0) {
|
||||||
// Start the thread now that we're all setup.
|
// Start the thread now that we're all setup.
|
||||||
thread_->Resume();
|
thread_->Resume();
|
||||||
|
@ -344,17 +353,21 @@ X_STATUS XThread::Exit(int exit_code) {
|
||||||
// TODO(benvanik); dispatch events? waiters? etc?
|
// TODO(benvanik); dispatch events? waiters? etc?
|
||||||
RundownAPCs();
|
RundownAPCs();
|
||||||
|
|
||||||
|
// Set exit code.
|
||||||
|
X_KTHREAD* thread = guest_object<X_KTHREAD>();
|
||||||
|
thread->header.signal_state = 1;
|
||||||
|
thread->exit_status = exit_code;
|
||||||
|
|
||||||
kernel_state()->OnThreadExit(this);
|
kernel_state()->OnThreadExit(this);
|
||||||
|
|
||||||
|
if (emulator()->debugger()) {
|
||||||
|
emulator()->debugger()->OnThreadExit(this);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: unless PlatformExit fails, expect it to never return!
|
// NOTE: unless PlatformExit fails, expect it to never return!
|
||||||
current_thread_tls = nullptr;
|
current_thread_tls = nullptr;
|
||||||
xe::Profiler::ThreadExit();
|
xe::Profiler::ThreadExit();
|
||||||
|
|
||||||
// Set exit code
|
|
||||||
X_KTHREAD* thread = guest_object<X_KTHREAD>();
|
|
||||||
thread->header.signal_state = 1;
|
|
||||||
thread->exit_status = exit_code;
|
|
||||||
|
|
||||||
running_ = false;
|
running_ = false;
|
||||||
Release();
|
Release();
|
||||||
ReleaseHandle();
|
ReleaseHandle();
|
||||||
|
@ -367,11 +380,15 @@ X_STATUS XThread::Exit(int exit_code) {
|
||||||
X_STATUS XThread::Terminate(int exit_code) {
|
X_STATUS XThread::Terminate(int exit_code) {
|
||||||
// TODO: Inform the profiler that this thread is exiting.
|
// TODO: Inform the profiler that this thread is exiting.
|
||||||
|
|
||||||
// Set exit code
|
// Set exit code.
|
||||||
X_KTHREAD* thread = guest_object<X_KTHREAD>();
|
X_KTHREAD* thread = guest_object<X_KTHREAD>();
|
||||||
thread->header.signal_state = 1;
|
thread->header.signal_state = 1;
|
||||||
thread->exit_status = exit_code;
|
thread->exit_status = exit_code;
|
||||||
|
|
||||||
|
if (emulator()->debugger()) {
|
||||||
|
emulator()->debugger()->OnThreadExit(this);
|
||||||
|
}
|
||||||
|
|
||||||
running_ = false;
|
running_ = false;
|
||||||
Release();
|
Release();
|
||||||
ReleaseHandle();
|
ReleaseHandle();
|
||||||
|
|
|
@ -103,6 +103,14 @@ static_assert_size(X_KTHREAD, 0xAB0);
|
||||||
|
|
||||||
class XThread : public XObject {
|
class XThread : public XObject {
|
||||||
public:
|
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,
|
XThread(KernelState* kernel_state, uint32_t stack_size,
|
||||||
uint32_t xapi_thread_startup, uint32_t start_address,
|
uint32_t xapi_thread_startup, uint32_t start_address,
|
||||||
uint32_t start_context, uint32_t creation_flags, bool guest_thread);
|
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 GetCurrentThreadHandle();
|
||||||
static uint32_t GetCurrentThreadId();
|
static uint32_t GetCurrentThreadId();
|
||||||
|
|
||||||
|
const CreationParams* creation_params() const { return &creation_params_; }
|
||||||
uint32_t tls_ptr() const { return tls_address_; }
|
uint32_t tls_ptr() const { return tls_address_; }
|
||||||
uint32_t pcr_ptr() const { return pcr_address_; }
|
uint32_t pcr_ptr() const { return pcr_address_; }
|
||||||
bool guest_thread() const { return guest_thread_; }
|
bool is_guest_thread() const { return guest_thread_; }
|
||||||
bool running() const { return running_; }
|
bool is_running() const { return running_; }
|
||||||
|
|
||||||
cpu::ThreadState* thread_state() const { return thread_state_; }
|
cpu::ThreadState* thread_state() const { return thread_state_; }
|
||||||
uint32_t thread_id() const { return thread_id_; }
|
uint32_t thread_id() const { return thread_id_; }
|
||||||
|
@ -163,13 +172,7 @@ class XThread : public XObject {
|
||||||
void DeliverAPCs();
|
void DeliverAPCs();
|
||||||
void RundownAPCs();
|
void RundownAPCs();
|
||||||
|
|
||||||
struct {
|
CreationParams creation_params_ = {0};
|
||||||
uint32_t stack_size;
|
|
||||||
uint32_t xapi_thread_startup;
|
|
||||||
uint32_t start_address;
|
|
||||||
uint32_t start_context;
|
|
||||||
uint32_t creation_flags;
|
|
||||||
} creation_params_ = {0};
|
|
||||||
|
|
||||||
uint32_t thread_id_ = 0;
|
uint32_t thread_id_ = 0;
|
||||||
std::unique_ptr<xe::threading::Thread> thread_;
|
std::unique_ptr<xe::threading::Thread> thread_;
|
||||||
|
|
Loading…
Reference in New Issue