From 48d6e6becf628aa41d7b076e1bbd37afaf77c798 Mon Sep 17 00:00:00 2001 From: Ben Vanik Date: Wed, 5 Aug 2015 07:50:37 -0700 Subject: [PATCH] Cleaning up debugger threading and adding hacky callstacks to UI. --- src/xenia/cpu/stack_walker.h | 1 + src/xenia/cpu/stack_walker_win.cc | 5 +- src/xenia/debug/debug_client.cc | 59 ++++++++--- src/xenia/debug/debug_client.h | 23 +++- src/xenia/debug/debug_server.cc | 40 +++++++ src/xenia/debug/debugger.cc | 6 +- src/xenia/debug/proto/packet_reader.h | 10 ++ src/xenia/debug/proto/xdp_protocol.h | 31 ++++++ src/xenia/debug/ui/application.cc | 1 - src/xenia/debug/ui/model/module.cc | 8 +- src/xenia/debug/ui/model/module.h | 1 - src/xenia/debug/ui/model/system.cc | 128 +++++++++++------------ src/xenia/debug/ui/model/system.h | 9 +- src/xenia/debug/ui/model/thread.cc | 14 +-- src/xenia/debug/ui/model/thread.h | 7 +- src/xenia/debug/ui/views/cpu/cpu_view.cc | 46 ++++++++ src/xenia/debug/ui/views/cpu/cpu_view.h | 4 + 17 files changed, 283 insertions(+), 110 deletions(-) diff --git a/src/xenia/cpu/stack_walker.h b/src/xenia/cpu/stack_walker.h index eafdb8825..e8b00326d 100644 --- a/src/xenia/cpu/stack_walker.h +++ b/src/xenia/cpu/stack_walker.h @@ -44,6 +44,7 @@ struct StackFrame { // Contains symbol information for kHost frames. struct { // TODO(benvanik): better name, displacement, etc. + uint64_t address; char name[256]; } host_symbol; // Contains symbol information for kGuest frames. diff --git a/src/xenia/cpu/stack_walker_win.cc b/src/xenia/cpu/stack_walker_win.cc index 06f2096b6..a9550c910 100644 --- a/src/xenia/cpu/stack_walker_win.cc +++ b/src/xenia/cpu/stack_walker_win.cc @@ -198,10 +198,8 @@ class Win32StackWalker : public StackWalker { for (size_t i = 0; i < frame_count; ++i) { auto& frame = frames[i]; + std::memset(&frame, 0, sizeof(frame)); frame.host_pc = frame_host_pcs[i]; - frame.host_symbol.name[0] = 0; - frame.guest_pc = 0; - frame.guest_symbol.function_info = nullptr; // If in the generated range, we know it's ours. if (frame.host_pc >= code_cache_min_ && frame.host_pc < code_cache_max_) { @@ -238,6 +236,7 @@ class Win32StackWalker : public StackWalker { &displacement, &symbol.info)) { // Resolved successfully. // TODO(benvanik): stash: module, base, displacement, name? + frame.host_symbol.address = symbol.info.Address; std::strncpy(frame.host_symbol.name, symbol.info.Name, 256); } } diff --git a/src/xenia/debug/debug_client.cc b/src/xenia/debug/debug_client.cc index 22f3e12b3..db2de2eca 100644 --- a/src/xenia/debug/debug_client.cc +++ b/src/xenia/debug/debug_client.cc @@ -10,6 +10,7 @@ #include "xenia/debug/debug_client.h" #include "xenia/base/logging.h" +#include "xenia/ui/loop.h" namespace xe { namespace debug { @@ -105,11 +106,27 @@ bool DebugClient::ProcessBuffer(const uint8_t* buffer, size_t buffer_length) { break; } - // Emit packet. - if (!ProcessPacket(packet)) { - // Failed to process packet. - XELOGE("Failed to process incoming packet"); - return false; + // 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 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(); @@ -118,7 +135,8 @@ bool DebugClient::ProcessBuffer(const uint8_t* buffer, size_t buffer_length) { return true; } -bool DebugClient::ProcessPacket(const proto::Packet* packet) { +bool DebugClient::ProcessPacket(proto::PacketReader* packet_reader, + const proto::Packet* packet) { // Hold lock during processing. std::lock_guard lock(mutex_); @@ -127,7 +145,7 @@ bool DebugClient::ProcessPacket(const proto::Packet* packet) { // } break; case PacketType::kExecutionNotification: { - auto body = packet_reader_.Read(); + auto body = packet_reader->Read(); switch (body->current_state) { case ExecutionNotification::State::kStopped: { // body->stop_reason @@ -147,14 +165,24 @@ bool DebugClient::ProcessPacket(const proto::Packet* packet) { } } break; case PacketType::kModuleListResponse: { - auto body = packet_reader_.Read(); - auto entries = packet_reader_.ReadArray(body->count); - listener_->OnModulesUpdated(entries); + auto body = packet_reader->Read(); + auto entries = packet_reader->ReadArray(body->count); + listener_->OnModulesUpdated(std::move(entries)); } break; case PacketType::kThreadListResponse: { - auto body = packet_reader_.Read(); - auto entries = packet_reader_.ReadArray(body->count); - listener_->OnThreadsUpdated(entries); + auto body = packet_reader->Read(); + auto entries = packet_reader->ReadArray(body->count); + listener_->OnThreadsUpdated(std::move(entries)); + } break; + case PacketType::kThreadCallStacksResponse: { + auto body = packet_reader->Read(); + for (size_t i = 0; i < body->count; ++i) { + auto entry = packet_reader->Read(); + auto frames = + packet_reader->ReadArray(entry->frame_count); + listener_->OnThreadCallStackUpdated(entry->thread_handle, + std::move(frames)); + } } break; default: { XELOGE("Unknown incoming packet type"); @@ -226,10 +254,15 @@ void DebugClient::Exit() { void DebugClient::BeginUpdateAllState() { std::lock_guard 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::kThreadCallStacksRequest); + packet_writer_.End(); Flush(); } diff --git a/src/xenia/debug/debug_client.h b/src/xenia/debug/debug_client.h index 88d8cd62a..ae704f2e9 100644 --- a/src/xenia/debug/debug_client.h +++ b/src/xenia/debug/debug_client.h @@ -19,10 +19,17 @@ #include "xenia/debug/proto/packet_writer.h" #include "xenia/debug/proto/xdp_protocol.h" +namespace xe { +namespace ui { +class Loop; +} // namespace ui +} // namespace xe + namespace xe { namespace debug { using proto::ModuleListEntry; +using proto::ThreadCallStackFrame; using proto::ThreadListEntry; enum class ExecutionState { @@ -34,13 +41,18 @@ enum class ExecutionState { // 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 { +class DebugClientListener { public: + virtual ~DebugClientListener() = default; + virtual void OnExecutionStateChanged(ExecutionState execution_state) = 0; virtual void OnModulesUpdated( std::vector entries) = 0; virtual void OnThreadsUpdated( std::vector entries) = 0; + virtual void OnThreadCallStackUpdated( + uint32_t thread_handle, + std::vector frames) = 0; }; class DebugClient { @@ -49,7 +61,8 @@ class DebugClient { ~DebugClient(); Socket* socket() const { return socket_.get(); } - void set_listener(ClientListener* listener) { listener_ = listener; } + void set_listener(DebugClientListener* listener) { listener_ = listener; } + void set_loop(xe::ui::Loop* loop) { loop_ = loop; } ExecutionState execution_state() const { return execution_state_; } @@ -64,7 +77,8 @@ class DebugClient { private: bool HandleSocketEvent(); bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length); - bool ProcessPacket(const proto::Packet* packet); + bool ProcessPacket(proto::PacketReader* packet_reader, + const proto::Packet* packet); void Flush(); void BeginUpdateAllState(); @@ -77,7 +91,8 @@ class DebugClient { proto::PacketReader packet_reader_; proto::PacketWriter packet_writer_; - ClientListener* listener_ = nullptr; + DebugClientListener* listener_ = nullptr; + xe::ui::Loop* loop_ = nullptr; ExecutionState execution_state_ = ExecutionState::kStopped; }; diff --git a/src/xenia/debug/debug_server.cc b/src/xenia/debug/debug_server.cc index 607c06eaf..8f001435b 100644 --- a/src/xenia/debug/debug_server.cc +++ b/src/xenia/debug/debug_server.cc @@ -12,6 +12,7 @@ #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" @@ -281,6 +282,45 @@ bool DebugServer::ProcessPacket(const proto::Packet* packet) { } 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_info = frame.guest_symbol.function_info; + if (frame.type == cpu::StackFrame::Type::kGuest && function_info) { + frame_body->guest_function_address = function_info->address(); + std::strncpy(frame_body->name, function_info->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"); diff --git a/src/xenia/debug/debugger.cc b/src/xenia/debug/debugger.cc index 4a5dcc29f..f9c7a4f02 100644 --- a/src/xenia/debug/debugger.cc +++ b/src/xenia/debug/debugger.cc @@ -148,6 +148,8 @@ uint8_t* Debugger::AllocateFunctionTraceData(size_t size) { } void Debugger::DumpThreadStacks() { + // NOTE: if any other thread is suspended in a logging line, this will + // hang. So, this probably shouldn't be used. auto stack_walker = emulator()->processor()->stack_walker(); auto threads = emulator_->kernel_state()->object_table()->GetObjectsByType( @@ -286,10 +288,6 @@ void Debugger::Interrupt() { SuspendAllThreads(); execution_state_ = ExecutionState::kStopped; server_->OnExecutionInterrupted(); - - // TEST CODE. - // TODO(benvanik): remove when UI shows threads. - DumpThreadStacks(); } void Debugger::Continue() { diff --git a/src/xenia/debug/proto/packet_reader.h b/src/xenia/debug/proto/packet_reader.h index 97b95703b..9a51e378c 100644 --- a/src/xenia/debug/proto/packet_reader.h +++ b/src/xenia/debug/proto/packet_reader.h @@ -82,6 +82,16 @@ class PacketReader { packet_offset_ = 0; } + std::unique_ptr ClonePacket() { + assert_not_null(current_packet_); + size_t length = sizeof(Packet) + current_packet_->body_length; + auto clone = std::make_unique(length); + std::memcpy(clone->buffer_.data(), buffer_.data() + buffer_offset_ - length, + length); + clone->buffer_size_ = length; + return clone; + } + const uint8_t* Read(size_t length) { // Can't read into next packet/off of end. assert_not_null(current_packet_); diff --git a/src/xenia/debug/proto/xdp_protocol.h b/src/xenia/debug/proto/xdp_protocol.h index 1a5409794..a5dc15a98 100644 --- a/src/xenia/debug/proto/xdp_protocol.h +++ b/src/xenia/debug/proto/xdp_protocol.h @@ -28,6 +28,8 @@ enum class PacketType : uint16_t { kThreadListRequest = 30, kThreadListResponse = 31, + kThreadCallStacksRequest = 32, + kThreadCallStacksResponse = 33, }; using request_id_t = uint16_t; @@ -166,6 +168,35 @@ struct ThreadListEntry { uint32_t tls_address; }; +struct ThreadCallStacksRequest { + static const PacketType type = PacketType::kThreadCallStacksRequest; +}; +struct ThreadCallStacksResponse { + static const PacketType type = PacketType::kThreadCallStacksResponse; + + uint32_t count; + // ThreadCallStackEntry[count] +}; +struct ThreadCallStackEntry { + uint32_t thread_handle; + uint32_t frame_count; + // ThreadCallStackFrame[frame_count] +}; +struct ThreadCallStackFrame { + // PC of the current instruction in host code. + uint64_t host_pc; + // Base of the function the current host_pc is located within. + uint64_t host_function_address; + // PC of the current instruction in guest code. + // 0 if not a guest address or not known. + uint32_t guest_pc; + // Base of the function the current guest_pc is located within. + uint32_t guest_function_address; + // Name of the function, if known. + // TODO(benvanik): string table? + char name[256]; +}; + } // namespace proto } // namespace debug } // namespace xe diff --git a/src/xenia/debug/ui/application.cc b/src/xenia/debug/ui/application.cc index f0b0d1477..a5aeaeea1 100644 --- a/src/xenia/debug/ui/application.cc +++ b/src/xenia/debug/ui/application.cc @@ -60,7 +60,6 @@ std::unique_ptr Application::Create() { bool Application::Initialize() { // Bind the object model to the client so it'll maintain state. system_ = std::make_unique(loop(), &client_); - client_.set_listener(system_.get()); // TODO(benvanik): flags and such. if (!client_.Connect("localhost", 9002)) { diff --git a/src/xenia/debug/ui/model/module.cc b/src/xenia/debug/ui/model/module.cc index 675df0647..70c435d54 100644 --- a/src/xenia/debug/ui/model/module.cc +++ b/src/xenia/debug/ui/model/module.cc @@ -17,13 +17,7 @@ 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_)); }); - } + std::memcpy(&entry_, entry, sizeof(entry_)); } } // namespace model diff --git a/src/xenia/debug/ui/model/module.h b/src/xenia/debug/ui/model/module.h index 3ff3dd3a2..ec66ec040 100644 --- a/src/xenia/debug/ui/model/module.h +++ b/src/xenia/debug/ui/model/module.h @@ -40,7 +40,6 @@ class Module { System* system_ = nullptr; bool is_dead_ = false; proto::ModuleListEntry entry_ = {0}; - proto::ModuleListEntry temp_entry_ = {0}; }; } // namespace model diff --git a/src/xenia/debug/ui/model/system.cc b/src/xenia/debug/ui/model/system.cc index 80a9da99e..64a60f6be 100644 --- a/src/xenia/debug/ui/model/system.cc +++ b/src/xenia/debug/ui/model/system.cc @@ -19,15 +19,16 @@ namespace model { using namespace xe::debug::proto; System::System(xe::ui::Loop* loop, DebugClient* client) - : loop_(loop), client_(client) {} - -ExecutionState System::execution_state() { - std::lock_guard lock(mutex_); - return client_->execution_state(); + : loop_(loop), client_(client) { + client_->set_listener(this); + client_->set_loop(loop); } +System::~System() { client_->set_listener(nullptr); } + +ExecutionState System::execution_state() { return client_->execution_state(); } + std::vector System::modules() { - std::lock_guard lock(mutex_); std::vector result; for (auto& module : modules_) { result.push_back(module.get()); @@ -36,7 +37,6 @@ std::vector System::modules() { } std::vector System::threads() { - std::lock_guard lock(mutex_); std::vector result; for (auto& thread : threads_) { result.push_back(thread.get()); @@ -45,86 +45,80 @@ std::vector System::threads() { } Module* System::GetModuleByHandle(uint32_t module_handle) { - std::lock_guard 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 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 lock(mutex_); - on_execution_state_changed(); - }); + on_execution_state_changed(); } void System::OnModulesUpdated(std::vector entries) { - { - std::lock_guard lock(mutex_); - std::unordered_set 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(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) { - auto module = modules_by_handle_.find(module_handle); - if (module != modules_by_handle_.end()) { - module->second->set_dead(true); - } + std::unordered_set 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(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); } } - loop_->Post([this]() { - std::lock_guard lock(mutex_); - on_modules_updated(); - }); + for (auto module_handle : extra_modules) { + auto module = modules_by_handle_.find(module_handle); + if (module != modules_by_handle_.end()) { + module->second->set_dead(true); + } + } + + on_modules_updated(); } void System::OnThreadsUpdated(std::vector entries) { - { - std::lock_guard lock(mutex_); - std::unordered_set 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(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) { - auto thread = threads_by_handle_.find(thread_handle); - if (thread != threads_by_handle_.end()) { - thread->second->set_dead(true); - } + std::unordered_set 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(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); } } - loop_->Post([this]() { - std::lock_guard lock(mutex_); - on_threads_updated(); - }); + for (auto thread_handle : extra_threads) { + auto thread = threads_by_handle_.find(thread_handle); + if (thread != threads_by_handle_.end()) { + thread->second->set_dead(true); + } + } + + on_threads_updated(); +} + +void System::OnThreadCallStackUpdated( + uint32_t thread_handle, std::vector frames) { + auto thread = threads_by_handle_[thread_handle]; + if (thread != nullptr) { + thread->UpdateCallStack(std::move(frames)); + on_thread_call_stack_updated(thread); + } } } // namespace model diff --git a/src/xenia/debug/ui/model/system.h b/src/xenia/debug/ui/model/system.h index 88f814d9d..2aa718378 100644 --- a/src/xenia/debug/ui/model/system.h +++ b/src/xenia/debug/ui/model/system.h @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -28,9 +27,10 @@ namespace debug { namespace ui { namespace model { -class System : public ClientListener { +class System : public DebugClientListener { public: System(xe::ui::Loop* loop, DebugClient* client); + ~System() override; xe::ui::Loop* loop() const { return loop_; } DebugClient* client() const { return client_; } @@ -46,6 +46,7 @@ class System : public ClientListener { Delegate on_execution_state_changed; Delegate on_modules_updated; Delegate on_threads_updated; + Delegate on_thread_call_stack_updated; private: void OnExecutionStateChanged(ExecutionState execution_state) override; @@ -53,11 +54,13 @@ class System : public ClientListener { std::vector entries) override; void OnThreadsUpdated( std::vector entries) override; + void OnThreadCallStackUpdated( + uint32_t thread_handle, + std::vector frames) override; xe::ui::Loop* loop_ = nullptr; DebugClient* client_ = nullptr; - std::recursive_mutex mutex_; std::vector> modules_; std::unordered_map modules_by_handle_; std::vector> threads_; diff --git a/src/xenia/debug/ui/model/thread.cc b/src/xenia/debug/ui/model/thread.cc index 0635acd0b..7bd24d2d9 100644 --- a/src/xenia/debug/ui/model/thread.cc +++ b/src/xenia/debug/ui/model/thread.cc @@ -25,12 +25,14 @@ std::string Thread::to_string() { } 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_)); }); + std::memcpy(&entry_, entry, sizeof(entry_)); +} + +void Thread::UpdateCallStack( + std::vector frames) { + call_stack_.resize(frames.size()); + for (size_t i = 0; i < frames.size(); ++i) { + std::memcpy(call_stack_.data() + i, frames[i], sizeof(Frame)); } } diff --git a/src/xenia/debug/ui/model/thread.h b/src/xenia/debug/ui/model/thread.h index f60ddd5d6..af1a24c85 100644 --- a/src/xenia/debug/ui/model/thread.h +++ b/src/xenia/debug/ui/model/thread.h @@ -12,6 +12,7 @@ #include #include +#include #include "xenia/debug/proto/xdp_protocol.h" @@ -24,6 +25,8 @@ class System; class Thread { public: + using Frame = proto::ThreadCallStackFrame; + Thread(System* system) : system_(system) {} bool is_dead() const { return is_dead_; } @@ -34,16 +37,18 @@ class Thread { bool is_host_thread() const { return entry_.is_host_thread; } std::string name() const { return entry_.name; } const proto::ThreadListEntry* entry() const { return &entry_; } + const std::vector& call_stack() const { return call_stack_; } std::string to_string(); void Update(const proto::ThreadListEntry* entry); + void UpdateCallStack(std::vector frames); private: System* system_ = nullptr; bool is_dead_ = false; proto::ThreadListEntry entry_ = {0}; - proto::ThreadListEntry temp_entry_ = {0}; + std::vector call_stack_; }; } // namespace model diff --git a/src/xenia/debug/ui/views/cpu/cpu_view.cc b/src/xenia/debug/ui/views/cpu/cpu_view.cc index ec200f1dd..850bea932 100644 --- a/src/xenia/debug/ui/views/cpu/cpu_view.cc +++ b/src/xenia/debug/ui/views/cpu/cpu_view.cc @@ -8,6 +8,7 @@ */ #include "el/animation_manager.h" +#include "xenia/base/string_buffer.h" #include "xenia/debug/ui/views/cpu/cpu_view.h" namespace xe { @@ -57,6 +58,7 @@ el::Element* CpuView::BuildUI() { .axis(Axis::kX) .child(ButtonNode("A"))) .child(TextBoxNode("source!") + .id("source_textbox") .gravity(Gravity::kAll) .is_multiline(true) .is_read_only(true)); @@ -141,6 +143,20 @@ el::Element* CpuView::BuildUI() { UpdateFunctionList(); return true; }); + handler_->Listen( + el::EventType::kChanged, TBIDC("thread_dropdown"), + [this](const el::Event& ev) { + auto thread_dropdown = root_element_.GetElementById( + TBIDC("thread_dropdown")); + auto thread_handle = uint32_t(thread_dropdown->selected_item_id()); + if (thread_handle) { + current_thread_ = system()->GetThreadByHandle(thread_handle); + } else { + current_thread_ = nullptr; + } + UpdateThreadCallStack(current_thread_); + return true; + }); return &root_element_; } @@ -152,6 +168,12 @@ void CpuView::Setup(DebugClient* client) { [this]() { UpdateElementState(); }); system()->on_modules_updated.AddListener([this]() { UpdateModuleList(); }); system()->on_threads_updated.AddListener([this]() { UpdateThreadList(); }); + system()->on_thread_call_stack_updated.AddListener( + [this](model::Thread* thread) { + if (thread == current_thread_) { + UpdateThreadCallStack(thread); + } + }); } void CpuView::UpdateElementState() { @@ -219,6 +241,30 @@ void CpuView::UpdateThreadList() { } } +void CpuView::UpdateThreadCallStack(model::Thread* thread) { + auto textbox = + root_element_.GetElementById(TBIDC("source_textbox")); + if (!thread) { + textbox->set_text("no thread"); + return; + } + auto& call_stack = thread->call_stack(); + StringBuffer str; + for (size_t i = 0; i < call_stack.size(); ++i) { + auto& frame = call_stack[i]; + size_t ordinal = call_stack.size() - i - 1; + if (frame.guest_pc) { + str.AppendFormat(" %.2lld %.16llX %.8X %s", ordinal, frame.host_pc, + frame.guest_pc, frame.name); + } else { + str.AppendFormat(" %.2lld %.16llX %s", ordinal, frame.host_pc, + frame.name); + } + str.Append('\n'); + } + textbox->set_text(str.to_string()); +} + } // namespace cpu } // namespace views } // namespace ui diff --git a/src/xenia/debug/ui/views/cpu/cpu_view.h b/src/xenia/debug/ui/views/cpu/cpu_view.h index 1beb40611..4f7b490cb 100644 --- a/src/xenia/debug/ui/views/cpu/cpu_view.h +++ b/src/xenia/debug/ui/views/cpu/cpu_view.h @@ -35,6 +35,10 @@ class CpuView : public View { void UpdateModuleList(); void UpdateFunctionList(); void UpdateThreadList(); + void UpdateThreadCallStack(model::Thread* thread); + + // TODO(benvanik): better state machine. + model::Thread* current_thread_ = nullptr; }; } // namespace cpu