/** ****************************************************************************** * 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 #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 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 fn) { xe::threading::Fence fence; { std::lock_guard lock(post_mutex_); post_queue_.push_back([&fence, fn]() { fn(); fence.Signal(); }); } post_event_->Set(); fence.Wait(); } void DebugServer::AcceptClient(std::unique_ptr client) { // If we have an existing client, kill it and join its thread. if (client_) { // TODO(benvanik): XDP say goodbye? client_->Close(); xe::threading::Wait(client_thread_.get(), true); client_thread_.reset(); } // Take ownership of the new one. client_ = std::move(client); // Create a thread to manage the connection. client_thread_ = xe::threading::Thread::Create({}, [this]() { 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 fn; { std::lock_guard 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(); switch (body->action) { case ExecutionRequest::Action::kContinue: { debugger_->Continue(); } break; case ExecutionRequest::Action::kStep: { switch (body->step_mode) { case ExecutionRequest::StepMode::kStepOne: { debugger_->StepOne(body->thread_id); } break; case ExecutionRequest::StepMode::kStepTo: { debugger_->StepTo(body->thread_id, body->target_pc); } break; default: assert_unhandled_case(body->step_mode); return false; } } break; case ExecutionRequest::Action::kInterrupt: { debugger_->Interrupt(); } break; case ExecutionRequest::Action::kExit: { XELOGI("Debug client requested exit"); exit(1); } break; } SendSuccess(packet->request_id); } break; case PacketType::kModuleListRequest: { packet_writer_.Begin(PacketType::kModuleListResponse); auto body = packet_writer_.Append(); auto modules = object_table->GetObjectsByType(XObject::Type::kTypeModule); body->count = uint32_t(modules.size()); for (auto& module : modules) { auto entry = packet_writer_.Append(); entry->module_handle = module->handle(); entry->module_ptr = module->hmodule_ptr(); entry->is_kernel_module = module->module_type() == XModule::ModuleType::kKernelModule; std::strncpy(entry->path, module->path().c_str(), xe::countof(entry->path)); std::strncpy(entry->name, module->name().c_str(), xe::countof(entry->name)); } packet_writer_.End(); } break; case PacketType::kThreadListRequest: { packet_writer_.Begin(PacketType::kThreadListResponse); auto body = packet_writer_.Append(); auto threads = object_table->GetObjectsByType(XObject::Type::kTypeThread); body->count = uint32_t(threads.size()); for (auto& thread : threads) { auto entry = packet_writer_.Append(); entry->thread_handle = thread->handle(); entry->thread_id = thread->thread_id(); entry->is_host_thread = !thread->is_guest_thread(); std::strncpy(entry->name, thread->name().c_str(), xe::countof(entry->name)); entry->exit_code; entry->priority = thread->priority(); entry->xapi_thread_startup = thread->creation_params()->xapi_thread_startup; entry->start_address = thread->creation_params()->start_address; entry->start_context = thread->creation_params()->start_context; entry->creation_flags = thread->creation_params()->creation_flags; entry->stack_address = thread->thread_state()->stack_address(); entry->stack_size = thread->thread_state()->stack_size(); entry->stack_base = thread->thread_state()->stack_base(); entry->stack_limit = thread->thread_state()->stack_limit(); entry->pcr_address = thread->pcr_ptr(); entry->tls_address = thread->tls_ptr(); } packet_writer_.End(); } break; case PacketType::kThreadCallStacksRequest: { packet_writer_.Begin(PacketType::kThreadCallStacksResponse); auto body = packet_writer_.Append(); auto stack_walker = emulator->processor()->stack_walker(); auto threads = emulator->kernel_state()->object_table()->GetObjectsByType( 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(); 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(); 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(); body->current_state = ExecutionNotification::State::kRunning; packet_writer_.End(); Flush(); } void DebugServer::OnExecutionInterrupted() { packet_writer_.Begin(PacketType::kExecutionNotification); auto body = packet_writer_.Append(); body->current_state = ExecutionNotification::State::kStopped; body->stop_reason = ExecutionNotification::Reason::kInterrupt; packet_writer_.End(); Flush(); } void 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(); 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(); 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(); body->code = GenericResponse::Code::kError; packet_writer_.AppendString(error_message); packet_writer_.End(); Flush(); } } // namespace debug } // namespace xe