Files
Xenia-Canary/src/xenia/debug/debug_client.cc

272 lines
8.4 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2015 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/debug/debug_client.h"
#include "xenia/base/logging.h"
#include "xenia/ui/loop.h"
namespace xe {
namespace debug {
using namespace xe::debug::proto; // NOLINT(build/namespaces)
constexpr size_t kReceiveBufferSize = 1 * 1024 * 1024;
constexpr size_t kTransmitBufferSize = 1 * 1024 * 1024;
DebugClient::DebugClient()
: packet_reader_(kReceiveBufferSize), packet_writer_(kTransmitBufferSize) {
receive_buffer_.resize(kReceiveBufferSize);
}
DebugClient::~DebugClient() {
socket_->Close();
xe::threading::Wait(thread_.get(), true);
thread_.reset();
}
bool DebugClient::Connect(std::string hostname, uint16_t port) {
socket_ = Socket::Connect(std::move(hostname), port);
if (!socket_) {
XELOGE("Unable to connect to remote host");
return false;
}
// Create a thread to manage the connection.
thread_ = xe::threading::Thread::Create({}, [this]() {
// Main loop.
bool running = true;
while (running) {
auto wait_result = xe::threading::Wait(socket_->wait_handle(), true);
switch (wait_result) {
case xe::threading::WaitResult::kSuccess:
// Event (read or close).
running = HandleSocketEvent();
continue;
case xe::threading::WaitResult::kAbandoned:
case xe::threading::WaitResult::kFailed:
// Error - kill the thread.
running = false;
continue;
default:
// Eh. Continue.
continue;
}
}
// Kill socket (likely already disconnected).
socket_.reset();
});
return true;
}
bool DebugClient::HandleSocketEvent() {
if (!socket_->is_connected()) {
// Known-disconnected.
return false;
}
// Attempt to read into our buffer.
size_t bytes_read =
socket_->Receive(receive_buffer_.data(), receive_buffer_.capacity());
if (bytes_read == -1) {
// Disconnected.
return false;
} else if (bytes_read == 0) {
// No data available. Wait again.
return true;
}
// Pass off the command processor to do with it what it wants.
if (!ProcessBuffer(receive_buffer_.data(), bytes_read)) {
// Error.
XELOGE("Error processing incoming XDP command; forcing disconnect");
return false;
}
return true;
}
bool DebugClient::ProcessBuffer(const uint8_t* buffer, size_t buffer_length) {
// Grow and append the bytes to the receive buffer.
packet_reader_.AppendBuffer(buffer, buffer_length);
// Process all packets in the buffer.
while (true) {
auto packet = packet_reader_.Begin();
if (!packet) {
// Out of packets. Compact any unused space.
packet_reader_.Compact();
break;
}
// Emit packet. Possibly dispatch to a loop, if desired.
if (loop_) {
auto clone = packet_reader_.ClonePacket();
auto clone_ptr = clone.release();
loop_->Post([this, clone_ptr]() {
std::unique_ptr<PacketReader> clone(clone_ptr);
auto packet = clone->Begin();
if (!ProcessPacket(clone.get(), packet)) {
// Failed to process packet, but there's no good way to report that.
XELOGE("Failed to process incoming packet");
assert_always();
}
clone->End();
});
} else {
// Process inline.
if (!ProcessPacket(&packet_reader_, packet)) {
// Failed to process packet.
XELOGE("Failed to process incoming packet");
return false;
}
}
packet_reader_.End();
}
return true;
}
bool DebugClient::ProcessPacket(proto::PacketReader* packet_reader,
const proto::Packet* packet) {
// Hold lock during processing.
std::lock_guard<std::recursive_mutex> lock(mutex_);
switch (packet->packet_type) {
case PacketType::kGenericResponse: {
//
} break;
case PacketType::kExecutionNotification: {
auto body = packet_reader->Read<ExecutionNotification>();
switch (body->current_state) {
case ExecutionNotification::State::kStopped: {
// body->stop_reason
execution_state_ = ExecutionState::kStopped;
listener_->OnExecutionStateChanged(execution_state_);
BeginUpdateAllState();
} break;
case ExecutionNotification::State::kRunning: {
execution_state_ = ExecutionState::kRunning;
listener_->OnExecutionStateChanged(execution_state_);
} break;
case ExecutionNotification::State::kExited: {
execution_state_ = ExecutionState::kExited;
listener_->OnExecutionStateChanged(execution_state_);
BeginUpdateAllState();
} break;
}
} break;
case PacketType::kModuleListResponse: {
auto body = packet_reader->Read<ModuleListResponse>();
auto entries = packet_reader->ReadArray<ModuleListEntry>(body->count);
listener_->OnModulesUpdated(std::move(entries));
} break;
case PacketType::kThreadListResponse: {
auto body = packet_reader->Read<ThreadListResponse>();
auto entries = packet_reader->ReadArray<ThreadListEntry>(body->count);
listener_->OnThreadsUpdated(std::move(entries));
} break;
case PacketType::kThreadStatesResponse: {
auto body = packet_reader->Read<ThreadStatesResponse>();
for (size_t i = 0; i < body->count; ++i) {
auto entry = packet_reader->Read<ThreadStateEntry>();
auto frames =
packet_reader->ReadArray<ThreadCallStackFrame>(entry->frame_count);
listener_->OnThreadStateUpdated(entry->thread_handle, entry,
std::move(frames));
}
} break;
default: {
XELOGE("Unknown incoming packet type");
return false;
} break;
}
return true;
}
void DebugClient::Flush() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
if (!packet_writer_.buffer_offset()) {
return;
}
socket_->Send(packet_writer_.buffer(), packet_writer_.buffer_offset());
packet_writer_.Reset();
}
void DebugClient::Continue() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
packet_writer_.Begin(PacketType::kExecutionRequest);
auto body = packet_writer_.Append<ExecutionRequest>();
body->action = ExecutionRequest::Action::kContinue;
packet_writer_.End();
Flush();
}
void DebugClient::StepOne(uint32_t thread_id) {
std::lock_guard<std::recursive_mutex> lock(mutex_);
packet_writer_.Begin(PacketType::kExecutionRequest);
auto body = packet_writer_.Append<ExecutionRequest>();
body->action = ExecutionRequest::Action::kStep;
body->step_mode = ExecutionRequest::StepMode::kStepOne;
body->thread_id = thread_id;
packet_writer_.End();
Flush();
}
void DebugClient::StepTo(uint32_t thread_id, uint32_t target_pc) {
std::lock_guard<std::recursive_mutex> lock(mutex_);
packet_writer_.Begin(PacketType::kExecutionRequest);
auto body = packet_writer_.Append<ExecutionRequest>();
body->action = ExecutionRequest::Action::kStep;
body->step_mode = ExecutionRequest::StepMode::kStepTo;
body->thread_id = thread_id;
body->target_pc = target_pc;
packet_writer_.End();
Flush();
}
void DebugClient::Interrupt() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
packet_writer_.Begin(PacketType::kExecutionRequest);
auto body = packet_writer_.Append<ExecutionRequest>();
body->action = ExecutionRequest::Action::kInterrupt;
packet_writer_.End();
Flush();
}
void DebugClient::Exit() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
packet_writer_.Begin(PacketType::kExecutionRequest);
auto body = packet_writer_.Append<ExecutionRequest>();
body->action = ExecutionRequest::Action::kExit;
packet_writer_.End();
Flush();
}
void DebugClient::BeginUpdateAllState() {
std::lock_guard<std::recursive_mutex> lock(mutex_);
// Request all module infos.
packet_writer_.Begin(PacketType::kModuleListRequest);
packet_writer_.End();
// Request all thread infos.
packet_writer_.Begin(PacketType::kThreadListRequest);
packet_writer_.End();
// Request the full call stacks for all threads.
packet_writer_.Begin(PacketType::kThreadStatesRequest);
packet_writer_.End();
Flush();
}
} // namespace debug
} // namespace xe