Debugger stuff. Lots of wasted work :/

This commit is contained in:
Ben Vanik 2015-07-26 22:47:03 -07:00
parent 42ef3f224a
commit 7ecc6362de
52 changed files with 3476 additions and 172 deletions

View File

@ -45,6 +45,33 @@ class Delegate {
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
#endif // XENIA_BASE_DELEGATE_H_

View File

@ -28,7 +28,8 @@ namespace xe {
// wait handle should be waited on until new data arrives.
class Socket {
public:
// TODO(benvanik): client socket static Connect method.
// Synchronously connects to the given hostname:port.
static std::unique_ptr<Socket> Connect(std::string hostname, uint16_t port);
virtual ~Socket() = default;
@ -84,6 +85,12 @@ class Socket {
auto buffer_list = std::make_pair(buffer.data(), buffer.size());
return Send(&buffer_list, 1);
}
// Asynchronously sends a string buffer.
// Returns false if the socket is disconnected or the data cannot be sent.
bool Send(const std::string& value) {
return Send(value.data(), value.size());
}
};
// Runs a socket server on the specified local port.

View File

@ -36,6 +36,51 @@ class Win32Socket : public Socket {
Win32Socket() = default;
~Win32Socket() override { Close(); }
bool Connect(std::string hostname, uint16_t port) {
addrinfo hints = {0};
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the server address and port.
// May return multiple results, so attempt to connect to an address until
// one succeeds.
addrinfo* result = nullptr;
auto port_string = std::to_string(port);
int ret =
getaddrinfo(hostname.c_str(), port_string.c_str(), &hints, &result);
if (ret != 0) {
XELOGE("getaddrinfo failed with error: %d", ret);
return false;
}
SOCKET try_socket = INVALID_SOCKET;
for (addrinfo* ptr = result; ptr; ptr = ptr->ai_next) {
// Create a SOCKET for connecting to server.
try_socket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (try_socket == INVALID_SOCKET) {
XELOGE("socket failed with error: %ld", WSAGetLastError());
freeaddrinfo(result);
return false;
}
// Connect to server.
ret = connect(try_socket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (ret == SOCKET_ERROR) {
closesocket(try_socket);
try_socket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (try_socket == INVALID_SOCKET) {
XELOGE("Unable to connect to server");
return false;
}
// Piggyback to setup the socket.
return Accept(try_socket);
}
bool Accept(SOCKET socket) {
std::lock_guard<std::recursive_mutex> lock(mutex_);
@ -142,6 +187,16 @@ class Win32Socket : public Socket {
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 {
public:
Win32SocketServer(

View File

@ -104,17 +104,9 @@ ThreadState::ThreadState(Processor* processor, uint32_t thread_id,
// Set initial registers.
context_->r[1] = stack_base_;
context_->r[13] = pcr_address_;
if (processor_->debugger()) {
processor_->debugger()->OnThreadCreated(this);
}
}
ThreadState::~ThreadState() {
if (processor_->debugger()) {
processor_->debugger()->OnThreadDestroyed(this);
}
if (backend_data_) {
processor_->backend()->FreeThreadData(backend_data_);
}

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -7,8 +7,8 @@
******************************************************************************
*/
#ifndef XENIA_DEBUG_TRANSPORT_H_
#define XENIA_DEBUG_TRANSPORT_H_
#ifndef XENIA_DEBUG_DEBUG_SERVER_H_
#define XENIA_DEBUG_DEBUG_SERVER_H_
#include "xenia/cpu/function.h"
#include "xenia/cpu/processor.h"
@ -19,25 +19,25 @@ namespace debug {
class Debugger;
class Transport {
class DebugServer {
public:
virtual ~Transport() = default;
virtual ~DebugServer() = default;
Debugger* debugger() const { return debugger_; }
virtual bool Initialize() = 0;
// break/resume state
// TODO(benvanik): better thread type (XThread?)
// virtual void OnThreadCreated(ThreadState* thread_state) = 0;
// virtual void OnThreadDestroyed(ThreadState* thread_state) = 0;
virtual void OnExecutionContinued() {}
virtual void OnExecutionInterrupted() {}
/*virtual void OnBreakpointHit(xe::cpu::ThreadState* thread_state,
Breakpoint* breakpoint) = 0;*/
protected:
Transport(Debugger* debugger) : debugger_(debugger) {}
DebugServer(Debugger* debugger) : debugger_(debugger) {}
Debugger* debugger_ = nullptr;
};
@ -45,4 +45,4 @@ class Transport {
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_TRANSPORT_H_
#endif // XENIA_DEBUG_DEBUG_SERVER_H_

View File

@ -20,7 +20,9 @@
#include "xenia/cpu/backend/code_cache.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/processor.h"
#include "xenia/debug/transport/gdb/gdb_transport.h"
#include "xenia/debug/server/gdb/gdb_server.h"
#include "xenia/debug/server/mi/mi_server.h"
#include "xenia/debug/server/xdp/xdp_server.h"
#include "xenia/emulator.h"
#include "xenia/kernel/objects/xkernel_module.h"
#include "xenia/kernel/objects/xmodule.h"
@ -40,6 +42,8 @@ DEFINE_bool(wait_for_debugger, false,
"Waits for a debugger to attach before starting the game.");
DEFINE_bool(exit_with_debugger, true, "Exit whe the debugger disconnects.");
DEFINE_string(debug_server, "xdp", "Debug server protocol [gdb, mi, xdp].");
namespace xe {
namespace debug {
@ -86,13 +90,24 @@ bool Debugger::StartSession() {
functions_trace_file_ = ChunkedMappedMemoryWriter::Open(
functions_trace_path_, 32 * 1024 * 1024, true);
// Add default transports.
auto gdb_transport =
std::make_unique<xe::debug::transport::gdb::GdbTransport>(this);
if (gdb_transport->Initialize()) {
transports_.emplace_back(std::move(gdb_transport));
if (FLAGS_debug_server == "gdb") {
server_ = std::make_unique<xe::debug::server::gdb::GdbServer>(this);
if (!server_->Initialize()) {
XELOGE("Unable to initialize GDB debug server");
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 {
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;
@ -103,17 +118,24 @@ void Debugger::PreLaunch() {
// Wait for the first client.
XELOGI("Waiting for debugger because of --wait_for_debugger...");
attach_fence_.Wait();
XELOGI("Debugger attached, continuing...");
}
XELOGI("Debugger attached, breaking and waiting for continue...");
// TODO(benvanik): notify transports?
// Start paused.
execution_state_ = ExecutionState::kRunning;
Interrupt();
} else {
// Start running.
execution_state_ = ExecutionState::kRunning;
}
}
void Debugger::StopSession() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
FlushSession();
// Kill all transports.
transports_.clear();
// Kill debug server.
server_.reset();
functions_file_.reset();
functions_trace_file_.reset();
@ -142,6 +164,78 @@ uint8_t* Debugger::AllocateFunctionTraceData(size_t size) {
return functions_trace_file_->Allocate(size);
}
int Debugger::AddBreakpoint(Breakpoint* breakpoint) {
// Add to breakpoints map.
{
std::lock_guard<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() {
auto threads =
emulator_->kernel_state()->object_table()->GetObjectsByType<XThread>(
@ -174,83 +268,43 @@ bool Debugger::ResumeAllThreads() {
return true;
}
int Debugger::AddBreakpoint(Breakpoint* breakpoint) {
// Add to breakpoints map.
{
std::lock_guard<std::mutex> guard(breakpoints_lock_);
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;
void Debugger::Interrupt() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
assert_true(execution_state_ == ExecutionState::kRunning);
SuspendAllThreads();
execution_state_ = ExecutionState::kStopped;
server_->OnExecutionInterrupted();
}
int Debugger::RemoveBreakpoint(Breakpoint* breakpoint) {
// Remove from breakpoint map.
{
std::lock_guard<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.
auto fns =
emulator_->processor()->FindFunctionsWithAddress(breakpoint->address());
// Remove.
for (auto fn : fns) {
fn->RemoveBreakpoint(breakpoint);
}
return 0;
void Debugger::Continue() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
assert_true(execution_state_ == ExecutionState::kStopped);
ResumeAllThreads();
execution_state_ = ExecutionState::kRunning;
server_->OnExecutionContinued();
}
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::StepOne(uint32_t thread_id) {
std::lock_guard<std::recursive_mutex> lock(mutex_);
assert_true(execution_state_ == ExecutionState::kStopped);
//
}
void Debugger::OnThreadCreated(ThreadState* thread_state) {
void Debugger::StepTo(uint32_t thread_id, uint32_t target_pc) {
std::lock_guard<std::recursive_mutex> lock(mutex_);
assert_true(execution_state_ == ExecutionState::kStopped);
//
}
void Debugger::OnThreadCreated(xe::kernel::XThread* thread) {
// TODO(benvanik): notify transports.
}
void Debugger::OnThreadDestroyed(ThreadState* thread_state) {
void Debugger::OnThreadExit(xe::kernel::XThread* thread) {
// TODO(benvanik): notify transports.
}
void Debugger::OnThreadDestroyed(xe::kernel::XThread* thread) {
// TODO(benvanik): notify transports.
}
@ -259,7 +313,7 @@ void Debugger::OnFunctionDefined(cpu::FunctionInfo* symbol_info,
// Man, I'd love not to take this lock.
std::vector<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();
address <= symbol_info->end_address(); address += 4) {
auto range = breakpoints_.equal_range(address);
@ -281,7 +335,7 @@ void Debugger::OnFunctionDefined(cpu::FunctionInfo* symbol_info,
}
}
void Debugger::OnBreakpointHit(ThreadState* thread_state,
void Debugger::OnBreakpointHit(xe::kernel::XThread* thread,
Breakpoint* breakpoint) {
// Suspend all threads immediately.
SuspendAllThreads();

View File

@ -29,12 +29,20 @@ DECLARE_bool(debug);
namespace xe {
class Emulator;
namespace kernel {
class XThread;
} // namespace kernel
} // namespace xe
namespace xe {
namespace debug {
class Transport;
class DebugServer;
enum class ExecutionState {
kRunning,
kStopped,
};
class Debugger {
public:
@ -54,9 +62,7 @@ class Debugger {
bool is_attached() const { return is_attached_; }
void set_attached(bool attached);
bool SuspendAllThreads();
bool ResumeThread(uint32_t thread_id);
bool ResumeAllThreads();
ExecutionState execution_state() const { return execution_state_; }
int AddBreakpoint(Breakpoint* breakpoint);
int RemoveBreakpoint(Breakpoint* breakpoint);
@ -66,19 +72,28 @@ class Debugger {
// TODO(benvanik): utility functions for modification (make function ignored,
// etc).
void OnThreadCreated(cpu::ThreadState* thread_state);
void OnThreadDestroyed(cpu::ThreadState* thread_state);
void Interrupt();
void Continue();
void StepOne(uint32_t thread_id);
void StepTo(uint32_t thread_id, uint32_t target_pc);
void OnThreadCreated(xe::kernel::XThread* thread);
void OnThreadExit(xe::kernel::XThread* thread);
void OnThreadDestroyed(xe::kernel::XThread* thread);
void OnFunctionDefined(cpu::FunctionInfo* symbol_info,
cpu::Function* function);
void OnBreakpointHit(cpu::ThreadState* thread_state, Breakpoint* breakpoint);
void OnBreakpointHit(xe::kernel::XThread* thread, Breakpoint* breakpoint);
private:
void OnMessage(std::vector<uint8_t> buffer);
bool SuspendAllThreads();
bool ResumeThread(uint32_t thread_id);
bool ResumeAllThreads();
Emulator* emulator_ = nullptr;
std::vector<std::unique_ptr<Transport>> transports_;
std::unique_ptr<DebugServer> server_;
bool is_attached_ = false;
xe::threading::Fence attach_fence_;
@ -87,7 +102,9 @@ class Debugger {
std::wstring functions_trace_path_;
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_;
};

View File

@ -16,4 +16,6 @@ project("xenia-debug")
project_root.."/third_party/flatbuffers/include"
})
local_platform_files()
recursive_platform_files("transport")
recursive_platform_files("client")
recursive_platform_files("proto")
recursive_platform_files("server")

View File

@ -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_

View File

@ -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_

View File

@ -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_

View File

@ -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_

View File

@ -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.

View File

@ -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

View File

@ -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_

View File

@ -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 "xenia/base/logging.h"
#include "xenia/debug/debugger.h"
#include "xenia/debug/server/gdb/gdb_command_processor.h"
DEFINE_int32(gdb_port, 9000, "Debugger gdbserver TCP port.");
DEFINE_int32(gdb_server_port, 9000, "Debugger gdbserver TCP port.");
namespace xe {
namespace debug {
namespace transport {
namespace server {
namespace gdb {
constexpr size_t kReceiveBufferSize = 2 * 1024 * 1024;
constexpr size_t kReceiveBufferSize = 32 * 1024;
GdbTransport::GdbTransport(Debugger* debugger) : Transport(debugger) {
GdbServer::GdbServer(Debugger* debugger) : DebugServer(debugger) {
receive_buffer_.resize(kReceiveBufferSize);
command_processor_ = std::make_unique<GdbCommandProcessor>(this, debugger);
}
GdbTransport::~GdbTransport() = default;
GdbServer::~GdbServer() = default;
bool GdbTransport::Initialize() {
socket_server_ = SocketServer::Create(uint16_t(FLAGS_gdb_port),
bool GdbServer::Initialize() {
socket_server_ = SocketServer::Create(uint16_t(FLAGS_gdb_server_port),
[this](std::unique_ptr<Socket> client) {
AcceptClient(std::move(client));
});
@ -42,7 +44,7 @@ bool GdbTransport::Initialize() {
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 (client_) {
// TODO(benvanik): GDB say goodbye?
@ -63,6 +65,9 @@ void GdbTransport::AcceptClient(std::unique_ptr<Socket> client) {
// Let the debugger know we are present.
debugger_->set_attached(true);
// Junk just to poke the remote client.
client_->Send("(gdb)\n");
// Main loop.
bool running = true;
while (running) {
@ -91,7 +96,7 @@ void GdbTransport::AcceptClient(std::unique_ptr<Socket> client) {
});
}
bool GdbTransport::HandleClientEvent() {
bool GdbServer::HandleClientEvent() {
if (!client_->is_connected()) {
// Known-disconnected.
return false;
@ -107,12 +112,17 @@ bool GdbTransport::HandleClientEvent() {
return true;
}
// TODO(benvanik): process incoming command.
// Pass off the command processor to do with it what it wants.
if (!command_processor_->ProcessBuffer(receive_buffer_.data(), bytes_read)) {
// Error.
XELOGE("Error processing incoming GDB command; forcing disconnect");
return false;
}
return true;
}
} // namespace gdb
} // namespace transport
} // namespace server
} // namespace debug
} // namespace xe

View File

@ -7,24 +7,28 @@
******************************************************************************
*/
#ifndef XENIA_DEBUG_TRANSPORT_GDB_GDB_TRANSPORT_H_
#define XENIA_DEBUG_TRANSPORT_GDB_GDB_TRANSPORT_H_
#ifndef XENIA_DEBUG_SERVER_GDB_GDB_SERVER_H_
#define XENIA_DEBUG_SERVER_GDB_GDB_SERVER_H_
#include <memory>
#include "xenia/base/socket.h"
#include "xenia/base/threading.h"
#include "xenia/debug/transport.h"
#include "xenia/debug/debug_server.h"
namespace xe {
namespace debug {
namespace transport {
namespace server {
namespace gdb {
class GdbTransport : public Transport {
class GdbCommandProcessor;
class GdbServer : public DebugServer {
public:
GdbTransport(Debugger* debugger);
~GdbTransport() override;
GdbServer(Debugger* debugger);
~GdbServer() override;
Socket* client() const { return client_.get(); }
bool Initialize() override;
@ -37,11 +41,13 @@ class GdbTransport : public Transport {
std::unique_ptr<Socket> client_;
std::unique_ptr<xe::threading::Thread> client_thread_;
std::vector<uint8_t> receive_buffer_;
std::unique_ptr<GdbCommandProcessor> command_processor_;
};
} // namespace gdb
} // namespace transport
} // namespace server
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_TRANSPORT_GDB_GDB_TRANSPORT_H_
#endif // XENIA_DEBUG_SERVER_GDB_GDB_SERVER_H_

View File

@ -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

View File

@ -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_

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -58,6 +58,15 @@ std::unique_ptr<Application> Application::Create() {
}
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);
if (!main_window_->Initialize()) {
XELOGE("Unable to initialize main window");

View File

@ -12,6 +12,8 @@
#include <memory>
#include "xenia/debug/client/xdp/xdp_client.h"
#include "xenia/debug/ui/model/system.h"
#include "xenia/ui/loop.h"
namespace xe {
@ -29,6 +31,8 @@ class Application {
xe::ui::Loop* loop() { return loop_.get(); }
MainWindow* main_window() const { return main_window_.get(); }
client::xdp::XdpClient* client() { return &client_; }
model::System* system() const { return system_.get(); }
void Quit();
@ -39,6 +43,9 @@ class Application {
std::unique_ptr<xe::ui::Loop> loop_;
std::unique_ptr<MainWindow> main_window_;
client::xdp::XdpClient client_;
std::unique_ptr<model::System> system_;
};
} // namespace ui

View File

@ -21,6 +21,8 @@ namespace xe {
namespace debug {
namespace ui {
using namespace xe::debug::client::xdp;
using xe::ui::MenuItem;
const std::wstring kBaseTitle = L"xenia debugger";
@ -30,6 +32,8 @@ MainWindow::MainWindow(Application* app) : app_(app) {}
MainWindow::~MainWindow() = default;
bool MainWindow::Initialize() {
client_ = app_->client();
platform_window_ = xe::ui::Window::Create(app()->loop(), kBaseTitle);
if (!platform_window_) {
return false;
@ -106,11 +110,14 @@ void MainWindow::BuildUI() {
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kY)
.child(LayoutBoxNode()
.id("toolbar_box")
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kAvailable)
.child(LabelNode("toolbar")))
.child(
LayoutBoxNode()
.id("toolbar_box")
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kGravity)
.distribution_position(LayoutDistributionPosition::kLeftTop)
.child(ButtonNode("Pause").id("pause_button"))
.child(ButtonNode("Continue").id("resume_button")))
.child(
SplitContainerNode()
.id("split_container")
@ -132,15 +139,50 @@ void MainWindow::BuildUI() {
{TBIDC("tab_container"), &ui_.tab_container},
});
handler_ = std::make_unique<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->content_root()->AddChild(cpu_view_.BuildUI());
cpu_view_.Setup(Application::current()->client());
ui_.tab_container->tab_bar()->LoadNodeTree(ButtonNode(gpu_view_.name()));
ui_.tab_container->content_root()->AddChild(gpu_view_.BuildUI());
gpu_view_.Setup(Application::current()->client());
UpdateElementState();
el::util::ShowDebugInfoSettingsWindow(root_element);
}
void MainWindow::UpdateElementState() {
bool is_running = client_->execution_state() == ExecutionState::kRunning;
el::TabContainer* tab_container;
el::Button* pause_button;
el::Button* resume_button;
window_->GetElementsById({
{TBIDC("tab_container"), &tab_container},
{TBIDC("pause_button"), &pause_button},
{TBIDC("resume_button"), &resume_button},
});
tab_container->set_enabled(!is_running);
pause_button->set_enabled(is_running);
resume_button->set_enabled(!is_running);
}
void MainWindow::OnClose() { app_->Quit(); }
} // namespace ui

View File

@ -28,6 +28,8 @@ class MainWindow {
~MainWindow();
Application* app() const { return app_; }
xe::ui::Loop* loop() const { return app_->loop(); }
model::System* system() const { return app_->system(); }
bool Initialize();
@ -35,10 +37,12 @@ class MainWindow {
private:
void BuildUI();
void UpdateElementState();
void OnClose();
Application* app_ = nullptr;
xe::debug::client::xdp::XdpClient* client_ = nullptr;
std::unique_ptr<xe::ui::Window> platform_window_;
std::unique_ptr<el::Window> window_;
@ -47,6 +51,7 @@ class MainWindow {
el::LayoutBox* toolbar_box;
el::TabContainer* tab_container;
} ui_ = {0};
std::unique_ptr<el::EventHandler> handler_;
views::cpu::CpuView cpu_view_;
views::gpu::GpuView gpu_view_;
};

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -14,6 +14,8 @@
#include <string>
#include "el/elements.h"
#include "el/event_handler.h"
#include "xenia/debug/ui/application.h"
namespace xe {
namespace debug {
@ -25,14 +27,21 @@ class View {
std::string name() const { return name_; }
el::LayoutBox* root_element() { return &root_element_; }
xe::ui::Loop* loop() const { return Application::current()->loop(); }
client::xdp::XdpClient* client() const { return client_; }
model::System* system() const { return Application::current()->system(); }
virtual el::Element* BuildUI() = 0;
virtual void Setup(xe::debug::client::xdp::XdpClient* client) = 0;
protected:
View(std::string name) : name_(name) {}
std::string name_;
el::LayoutBox root_element_;
std::unique_ptr<el::EventHandler> handler_;
xe::debug::client::xdp::XdpClient* client_ = nullptr;
};
} // namespace ui

View File

@ -16,6 +16,8 @@ namespace ui {
namespace views {
namespace cpu {
using namespace xe::debug::client::xdp;
CpuView::CpuView() : View("CPU") {}
CpuView::~CpuView() = default;
@ -29,22 +31,13 @@ el::Element* CpuView::BuildUI() {
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kY)
.child(
LayoutBoxNode()
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kX)
.skin("button_group")
.child(ButtonNode("?"))
.child(
DropDownButtonNode().item("Module").item("Module").item(
"Module")))
.child(ListBoxNode()
.gravity(Gravity::kAll)
.item("fn")
.item("fn")
.item("fn")
.item("fn"))
.child(LayoutBoxNode()
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kX)
.skin("button_group")
.child(DropDownButtonNode().id("module_dropdown")))
.child(ListBoxNode().id("function_listbox").gravity(Gravity::kAll))
.child(LayoutBoxNode()
.gravity(Gravity::kBottom | Gravity::kLeftRight)
.distribution(LayoutDistribution::kAvailable)
@ -102,10 +95,9 @@ el::Element* CpuView::BuildUI() {
.distribution(LayoutDistribution::kGravity)
.distribution_position(LayoutDistributionPosition::kLeftTop)
.axis(Axis::kX)
.child(ButtonNode("button"))
.child(ButtonNode("button"))
.child(ButtonNode("button")))
.child(DropDownButtonNode().id("thread_dropdown")))
.child(LayoutBoxNode()
.id("source_content")
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.child(SplitContainerNode()
@ -144,9 +136,91 @@ el::Element* CpuView::BuildUI() {
root_element_.GetElementsById({
//
});
handler_ = std::make_unique<el::EventHandler>(&root_element_);
handler_->Listen(el::EventType::kChanged, TBIDC("module_dropdown"),
[this](const el::Event& ev) {
UpdateFunctionList();
return true;
});
return &root_element_;
}
void CpuView::Setup(XdpClient* client) {
client_ = client;
system()->on_execution_state_changed.AddListener(
[this]() { UpdateElementState(); });
system()->on_modules_updated.AddListener([this]() { UpdateModuleList(); });
system()->on_threads_updated.AddListener([this]() { UpdateThreadList(); });
}
void CpuView::UpdateElementState() {
root_element_.GetElementsById({
//
});
}
void CpuView::UpdateModuleList() {
el::DropDownButton* module_dropdown;
root_element_.GetElementsById({
{TBIDC("module_dropdown"), &module_dropdown},
});
auto module_items = module_dropdown->default_source();
auto modules = system()->modules();
bool is_first = module_items->size() == 0;
for (size_t i = module_items->size(); i < modules.size(); ++i) {
auto module = modules[i];
auto item = std::make_unique<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 views
} // namespace ui

View File

@ -28,7 +28,13 @@ class CpuView : public View {
el::Element* BuildUI() override;
void Setup(xe::debug::client::xdp::XdpClient* client) override;
protected:
void UpdateElementState();
void UpdateModuleList();
void UpdateFunctionList();
void UpdateThreadList();
};
} // namespace cpu

View File

@ -32,9 +32,14 @@ el::Element* GpuView::BuildUI() {
root_element_.GetElementsById({
//
});
handler_ = std::make_unique<el::EventHandler>(&root_element_);
return &root_element_;
}
void GpuView::Setup(xe::debug::client::xdp::XdpClient* client) {
//
}
} // namespace gpu
} // namespace views
} // namespace ui

View File

@ -28,6 +28,8 @@ class GpuView : public View {
el::Element* BuildUI() override;
void Setup(xe::debug::client::xdp::XdpClient* client) override;
protected:
};

View File

@ -353,7 +353,7 @@ void KernelState::TerminateTitle(bool from_guest_thread) {
// Second: Kill all guest threads.
for (auto it = threads_by_id_.begin(); it != threads_by_id_.end();) {
if (it->second->guest_thread()) {
if (it->second->is_guest_thread()) {
auto thread = it->second;
if (from_guest_thread && XThread::IsInThread(thread)) {
@ -362,7 +362,7 @@ void KernelState::TerminateTitle(bool from_guest_thread) {
continue;
}
if (thread->running()) {
if (thread->is_running()) {
thread->Terminate(0);
}
@ -457,6 +457,10 @@ void KernelState::OnThreadExit(XThread* thread) {
xe::countof(args));
}
}
if (emulator()->debugger()) {
emulator()->debugger()->OnThreadExit(thread);
}
}
object_ref<XThread> KernelState::GetThreadByID(uint32_t thread_id) {

View File

@ -19,6 +19,7 @@
#include "xenia/base/mutex.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/processor.h"
#include "xenia/emulator.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/native_list.h"
#include "xenia/kernel/objects/xevent.h"
@ -70,6 +71,10 @@ XThread::~XThread() {
// Unregister first to prevent lookups while deleting.
kernel_state_->UnregisterThread(this);
if (emulator()->debugger()) {
emulator()->debugger()->OnThreadDestroyed(this);
}
delete apc_list_;
thread_.reset();
@ -329,6 +334,10 @@ X_STATUS XThread::Create() {
thread_->set_priority(creation_params_.creation_flags & 0x20 ? 1 : 0);
}
if (emulator()->debugger()) {
emulator()->debugger()->OnThreadCreated(this);
}
if ((creation_params_.creation_flags & X_CREATE_SUSPENDED) == 0) {
// Start the thread now that we're all setup.
thread_->Resume();
@ -344,17 +353,21 @@ X_STATUS XThread::Exit(int exit_code) {
// TODO(benvanik); dispatch events? waiters? etc?
RundownAPCs();
// Set exit code.
X_KTHREAD* thread = guest_object<X_KTHREAD>();
thread->header.signal_state = 1;
thread->exit_status = exit_code;
kernel_state()->OnThreadExit(this);
if (emulator()->debugger()) {
emulator()->debugger()->OnThreadExit(this);
}
// NOTE: unless PlatformExit fails, expect it to never return!
current_thread_tls = nullptr;
xe::Profiler::ThreadExit();
// Set exit code
X_KTHREAD* thread = guest_object<X_KTHREAD>();
thread->header.signal_state = 1;
thread->exit_status = exit_code;
running_ = false;
Release();
ReleaseHandle();
@ -367,11 +380,15 @@ X_STATUS XThread::Exit(int exit_code) {
X_STATUS XThread::Terminate(int exit_code) {
// TODO: Inform the profiler that this thread is exiting.
// Set exit code
// Set exit code.
X_KTHREAD* thread = guest_object<X_KTHREAD>();
thread->header.signal_state = 1;
thread->exit_status = exit_code;
if (emulator()->debugger()) {
emulator()->debugger()->OnThreadExit(this);
}
running_ = false;
Release();
ReleaseHandle();

View File

@ -103,6 +103,14 @@ static_assert_size(X_KTHREAD, 0xAB0);
class XThread : public XObject {
public:
struct CreationParams {
uint32_t stack_size;
uint32_t xapi_thread_startup;
uint32_t start_address;
uint32_t start_context;
uint32_t creation_flags;
};
XThread(KernelState* kernel_state, uint32_t stack_size,
uint32_t xapi_thread_startup, uint32_t start_address,
uint32_t start_context, uint32_t creation_flags, bool guest_thread);
@ -114,10 +122,11 @@ class XThread : public XObject {
static uint32_t GetCurrentThreadHandle();
static uint32_t GetCurrentThreadId();
const CreationParams* creation_params() const { return &creation_params_; }
uint32_t tls_ptr() const { return tls_address_; }
uint32_t pcr_ptr() const { return pcr_address_; }
bool guest_thread() const { return guest_thread_; }
bool running() const { return running_; }
bool is_guest_thread() const { return guest_thread_; }
bool is_running() const { return running_; }
cpu::ThreadState* thread_state() const { return thread_state_; }
uint32_t thread_id() const { return thread_id_; }
@ -163,13 +172,7 @@ class XThread : public XObject {
void DeliverAPCs();
void RundownAPCs();
struct {
uint32_t stack_size;
uint32_t xapi_thread_startup;
uint32_t start_address;
uint32_t start_context;
uint32_t creation_flags;
} creation_params_ = {0};
CreationParams creation_params_ = {0};
uint32_t thread_id_ = 0;
std::unique_ptr<xe::threading::Thread> thread_;