388 lines
13 KiB
C++
388 lines
13 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_server.h"
|
|
|
|
#include <gflags/gflags.h>
|
|
|
|
#include "xenia/base/logging.h"
|
|
#include "xenia/cpu/stack_walker.h"
|
|
#include "xenia/debug/debugger.h"
|
|
#include "xenia/emulator.h"
|
|
#include "xenia/kernel/kernel_state.h"
|
|
#include "xenia/kernel/objects/xmodule.h"
|
|
#include "xenia/kernel/objects/xthread.h"
|
|
|
|
DEFINE_int32(debug_server_port, 9002, "Debugger XDP server TCP port.");
|
|
|
|
namespace xe {
|
|
namespace debug {
|
|
|
|
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;
|
|
|
|
DebugServer::DebugServer(Debugger* debugger)
|
|
: debugger_(debugger),
|
|
packet_reader_(kReadBufferSize),
|
|
packet_writer_(kWriteBufferSize) {
|
|
receive_buffer_.resize(kReceiveBufferSize);
|
|
}
|
|
|
|
DebugServer::~DebugServer() = default;
|
|
|
|
bool DebugServer::Initialize() {
|
|
post_event_ = xe::threading::Event::CreateAutoResetEvent(false);
|
|
|
|
socket_server_ = SocketServer::Create(uint16_t(FLAGS_debug_server_port),
|
|
[this](std::unique_ptr<Socket> client) {
|
|
AcceptClient(std::move(client));
|
|
});
|
|
if (!socket_server_) {
|
|
XELOGE("Unable to create XDP socket server - port in use?");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void DebugServer::PostSynchronous(std::function<void()> fn) {
|
|
xe::threading::Fence fence;
|
|
{
|
|
std::lock_guard<std::mutex> lock(post_mutex_);
|
|
post_queue_.push_back([&fence, fn]() {
|
|
fn();
|
|
fence.Signal();
|
|
});
|
|
}
|
|
post_event_->Set();
|
|
fence.Wait();
|
|
}
|
|
|
|
void DebugServer::AcceptClient(std::unique_ptr<Socket> client) {
|
|
// If we have an existing client, kill it and join its thread.
|
|
if (client_) {
|
|
// TODO(benvanik): XDP say goodbye?
|
|
|
|
client_->Close();
|
|
xe::threading::Wait(client_thread_.get(), true);
|
|
client_thread_.reset();
|
|
}
|
|
|
|
// Take ownership of the new one.
|
|
client_ = std::move(client);
|
|
|
|
// Create a thread to manage the connection.
|
|
client_thread_ = xe::threading::Thread::Create({}, [this]() {
|
|
xe::threading::set_name("XDP Debug Server");
|
|
|
|
// Let the debugger know we are present.
|
|
debugger_->set_attached(true);
|
|
|
|
// Notify the client of the current state.
|
|
if (debugger_->execution_state() == ExecutionState::kRunning) {
|
|
OnExecutionContinued();
|
|
} else {
|
|
OnExecutionInterrupted();
|
|
}
|
|
|
|
// Main loop.
|
|
bool running = true;
|
|
while (running) {
|
|
xe::threading::WaitHandle* wait_handles[] = {
|
|
client_->wait_handle(), post_event_.get(),
|
|
};
|
|
auto wait_result = xe::threading::WaitMultiple(
|
|
wait_handles, xe::countof(wait_handles), false, true);
|
|
switch (wait_result.first) {
|
|
case xe::threading::WaitResult::kSuccess:
|
|
// Event (read or close).
|
|
switch (wait_result.second) {
|
|
case 0: {
|
|
running = HandleClientEvent();
|
|
} break;
|
|
case 1: {
|
|
bool has_remaining = true;
|
|
while (has_remaining) {
|
|
std::function<void()> fn;
|
|
{
|
|
std::lock_guard<std::mutex> lock(post_mutex_);
|
|
fn = std::move(post_queue_.front());
|
|
post_queue_.pop_front();
|
|
has_remaining = !post_queue_.empty();
|
|
if (!has_remaining) {
|
|
post_event_->Reset();
|
|
}
|
|
}
|
|
fn();
|
|
}
|
|
} break;
|
|
}
|
|
continue;
|
|
case xe::threading::WaitResult::kAbandoned:
|
|
case xe::threading::WaitResult::kFailed:
|
|
// Error - kill the thread.
|
|
running = false;
|
|
continue;
|
|
default:
|
|
// Eh. Continue.
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Kill client (likely already disconnected).
|
|
client_.reset();
|
|
|
|
// Notify debugger we are no longer attached.
|
|
debugger_->set_attached(false);
|
|
});
|
|
}
|
|
|
|
bool DebugServer::HandleClientEvent() {
|
|
if (!client_->is_connected()) {
|
|
// Known-disconnected.
|
|
return false;
|
|
}
|
|
// Attempt to read into our buffer.
|
|
size_t bytes_read =
|
|
client_->Receive(receive_buffer_.data(), receive_buffer_.capacity());
|
|
if (bytes_read == -1) {
|
|
// Disconnected.
|
|
return false;
|
|
} else if (bytes_read == 0) {
|
|
// No data available. Wait again.
|
|
return true;
|
|
}
|
|
|
|
// Pass off the command processor to do with it what it wants.
|
|
if (!ProcessBuffer(receive_buffer_.data(), bytes_read)) {
|
|
// Error.
|
|
XELOGE("Error processing incoming XDP command; forcing disconnect");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool DebugServer::ProcessBuffer(const uint8_t* buffer, size_t buffer_length) {
|
|
// Grow and append the bytes to the receive buffer.
|
|
packet_reader_.AppendBuffer(buffer, buffer_length);
|
|
|
|
// Process all packets in the buffer.
|
|
while (true) {
|
|
auto packet = packet_reader_.Begin();
|
|
if (!packet) {
|
|
// Out of packets. Compact any unused space.
|
|
packet_reader_.Compact();
|
|
break;
|
|
}
|
|
|
|
// Emit packet.
|
|
if (!ProcessPacket(packet)) {
|
|
// Failed to process packet.
|
|
XELOGE("Failed to process incoming packet");
|
|
return false;
|
|
}
|
|
|
|
packet_reader_.End();
|
|
}
|
|
|
|
Flush();
|
|
return true;
|
|
}
|
|
|
|
bool DebugServer::ProcessPacket(const proto::Packet* packet) {
|
|
auto emulator = debugger()->emulator();
|
|
auto kernel_state = emulator->kernel_state();
|
|
auto object_table = kernel_state->object_table();
|
|
|
|
switch (packet->packet_type) {
|
|
case PacketType::kExecutionRequest: {
|
|
auto body = packet_reader_.Read<ExecutionRequest>();
|
|
switch (body->action) {
|
|
case ExecutionRequest::Action::kContinue: {
|
|
debugger_->Continue();
|
|
} break;
|
|
case ExecutionRequest::Action::kStep: {
|
|
switch (body->step_mode) {
|
|
case ExecutionRequest::StepMode::kStepOne: {
|
|
debugger_->StepOne(body->thread_id);
|
|
} break;
|
|
case ExecutionRequest::StepMode::kStepTo: {
|
|
debugger_->StepTo(body->thread_id, body->target_pc);
|
|
} break;
|
|
default:
|
|
assert_unhandled_case(body->step_mode);
|
|
return false;
|
|
}
|
|
} break;
|
|
case ExecutionRequest::Action::kInterrupt: {
|
|
debugger_->Interrupt();
|
|
} break;
|
|
case ExecutionRequest::Action::kExit: {
|
|
XELOGI("Debug client requested exit");
|
|
exit(1);
|
|
} break;
|
|
}
|
|
SendSuccess(packet->request_id);
|
|
} break;
|
|
case PacketType::kModuleListRequest: {
|
|
packet_writer_.Begin(PacketType::kModuleListResponse);
|
|
auto body = packet_writer_.Append<ModuleListResponse>();
|
|
auto modules =
|
|
object_table->GetObjectsByType<XModule>(XObject::Type::kTypeModule);
|
|
body->count = uint32_t(modules.size());
|
|
for (auto& module : modules) {
|
|
auto entry = packet_writer_.Append<ModuleListEntry>();
|
|
entry->module_handle = module->handle();
|
|
entry->module_ptr = module->hmodule_ptr();
|
|
entry->is_kernel_module =
|
|
module->module_type() == XModule::ModuleType::kKernelModule;
|
|
std::strncpy(entry->path, module->path().c_str(),
|
|
xe::countof(entry->path));
|
|
std::strncpy(entry->name, module->name().c_str(),
|
|
xe::countof(entry->name));
|
|
}
|
|
packet_writer_.End();
|
|
} break;
|
|
case PacketType::kThreadListRequest: {
|
|
packet_writer_.Begin(PacketType::kThreadListResponse);
|
|
auto body = packet_writer_.Append<ThreadListResponse>();
|
|
auto threads =
|
|
object_table->GetObjectsByType<XThread>(XObject::Type::kTypeThread);
|
|
body->count = uint32_t(threads.size());
|
|
for (auto& thread : threads) {
|
|
auto entry = packet_writer_.Append<ThreadListEntry>();
|
|
entry->thread_handle = thread->handle();
|
|
entry->thread_id = thread->thread_id();
|
|
entry->is_host_thread = !thread->is_guest_thread();
|
|
std::strncpy(entry->name, thread->name().c_str(),
|
|
xe::countof(entry->name));
|
|
entry->exit_code;
|
|
entry->priority = thread->priority();
|
|
entry->xapi_thread_startup =
|
|
thread->creation_params()->xapi_thread_startup;
|
|
entry->start_address = thread->creation_params()->start_address;
|
|
entry->start_context = thread->creation_params()->start_context;
|
|
entry->creation_flags = thread->creation_params()->creation_flags;
|
|
entry->stack_address = thread->thread_state()->stack_address();
|
|
entry->stack_size = thread->thread_state()->stack_size();
|
|
entry->stack_base = thread->thread_state()->stack_base();
|
|
entry->stack_limit = thread->thread_state()->stack_limit();
|
|
entry->pcr_address = thread->pcr_ptr();
|
|
entry->tls_address = thread->tls_ptr();
|
|
}
|
|
packet_writer_.End();
|
|
} break;
|
|
case PacketType::kThreadCallStacksRequest: {
|
|
packet_writer_.Begin(PacketType::kThreadCallStacksResponse);
|
|
auto body = packet_writer_.Append<ThreadCallStacksResponse>();
|
|
auto stack_walker = emulator->processor()->stack_walker();
|
|
auto threads =
|
|
emulator->kernel_state()->object_table()->GetObjectsByType<XThread>(
|
|
XObject::kTypeThread);
|
|
body->count = uint32_t(threads.size());
|
|
uint64_t frame_host_pcs[64];
|
|
cpu::StackFrame frames[64];
|
|
for (auto& thread : threads) {
|
|
uint64_t hash;
|
|
size_t count = stack_walker->CaptureStackTrace(
|
|
thread->GetWaitHandle()->native_handle(), frame_host_pcs, 0, 64,
|
|
&hash);
|
|
stack_walker->ResolveStack(frame_host_pcs, frames, count);
|
|
auto thread_entry_body = packet_writer_.Append<ThreadCallStackEntry>();
|
|
thread_entry_body->thread_handle = thread->handle();
|
|
thread_entry_body->frame_count = uint32_t(count);
|
|
for (size_t i = 0; i < count; ++i) {
|
|
auto& frame = frames[i];
|
|
auto frame_body = packet_writer_.Append<ThreadCallStackFrame>();
|
|
frame_body->host_pc = frame.host_pc;
|
|
frame_body->host_function_address = frame.host_symbol.address;
|
|
frame_body->guest_pc = frame.guest_pc;
|
|
frame_body->guest_function_address = 0;
|
|
auto function = frame.guest_symbol.function;
|
|
if (frame.type == cpu::StackFrame::Type::kGuest && function) {
|
|
frame_body->guest_function_address = function->address();
|
|
std::strncpy(frame_body->name, function->name().c_str(),
|
|
xe::countof(frame_body->name));
|
|
} else {
|
|
std::strncpy(frame_body->name, frame.host_symbol.name,
|
|
xe::countof(frame_body->name));
|
|
}
|
|
}
|
|
}
|
|
packet_writer_.End();
|
|
} break;
|
|
default: {
|
|
XELOGE("Unknown packet type");
|
|
SendError(packet->request_id, "Unknown packet type");
|
|
return false;
|
|
} break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void DebugServer::OnExecutionContinued() {
|
|
packet_writer_.Begin(PacketType::kExecutionNotification);
|
|
auto body = packet_writer_.Append<ExecutionNotification>();
|
|
body->current_state = ExecutionNotification::State::kRunning;
|
|
packet_writer_.End();
|
|
Flush();
|
|
}
|
|
|
|
void DebugServer::OnExecutionInterrupted() {
|
|
packet_writer_.Begin(PacketType::kExecutionNotification);
|
|
auto body = packet_writer_.Append<ExecutionNotification>();
|
|
body->current_state = ExecutionNotification::State::kStopped;
|
|
body->stop_reason = ExecutionNotification::Reason::kInterrupt;
|
|
packet_writer_.End();
|
|
Flush();
|
|
}
|
|
|
|
void DebugServer::Flush() {
|
|
if (!packet_writer_.buffer_offset()) {
|
|
return;
|
|
}
|
|
client_->Send(packet_writer_.buffer(), packet_writer_.buffer_offset());
|
|
packet_writer_.Reset();
|
|
}
|
|
|
|
void DebugServer::SendSuccess(proto::request_id_t request_id) {
|
|
packet_writer_.Begin(PacketType::kGenericResponse, request_id);
|
|
auto body = packet_writer_.Append<GenericResponse>();
|
|
body->code = GenericResponse::Code::kSuccess;
|
|
packet_writer_.End();
|
|
Flush();
|
|
}
|
|
|
|
void DebugServer::SendError(proto::request_id_t request_id,
|
|
const char* error_message) {
|
|
packet_writer_.Begin(PacketType::kGenericResponse, request_id);
|
|
auto body = packet_writer_.Append<GenericResponse>();
|
|
body->code = GenericResponse::Code::kError;
|
|
packet_writer_.AppendString(error_message ? error_message : "");
|
|
packet_writer_.End();
|
|
Flush();
|
|
}
|
|
|
|
void DebugServer::SendError(proto::request_id_t request_id,
|
|
const std::string& error_message) {
|
|
packet_writer_.Begin(PacketType::kGenericResponse, request_id);
|
|
auto body = packet_writer_.Append<GenericResponse>();
|
|
body->code = GenericResponse::Code::kError;
|
|
packet_writer_.AppendString(error_message);
|
|
packet_writer_.End();
|
|
Flush();
|
|
}
|
|
|
|
} // namespace debug
|
|
} // namespace xe
|