Cleaning up debugger threading and adding hacky callstacks to UI.
This commit is contained in:
parent
0a8d6eec91
commit
48d6e6becf
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,12 +106,28 @@ bool DebugClient::ProcessBuffer(const uint8_t* buffer, size_t buffer_length) {
|
|||
break;
|
||||
}
|
||||
|
||||
// Emit packet.
|
||||
if (!ProcessPacket(packet)) {
|
||||
// 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();
|
||||
}
|
||||
|
@ -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<std::recursive_mutex> lock(mutex_);
|
||||
|
||||
|
@ -127,7 +145,7 @@ bool DebugClient::ProcessPacket(const proto::Packet* packet) {
|
|||
//
|
||||
} break;
|
||||
case PacketType::kExecutionNotification: {
|
||||
auto body = packet_reader_.Read<ExecutionNotification>();
|
||||
auto body = packet_reader->Read<ExecutionNotification>();
|
||||
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<ModuleListResponse>();
|
||||
auto entries = packet_reader_.ReadArray<ModuleListEntry>(body->count);
|
||||
listener_->OnModulesUpdated(entries);
|
||||
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(entries);
|
||||
auto body = packet_reader->Read<ThreadListResponse>();
|
||||
auto entries = packet_reader->ReadArray<ThreadListEntry>(body->count);
|
||||
listener_->OnThreadsUpdated(std::move(entries));
|
||||
} break;
|
||||
case PacketType::kThreadCallStacksResponse: {
|
||||
auto body = packet_reader->Read<ThreadCallStacksResponse>();
|
||||
for (size_t i = 0; i < body->count; ++i) {
|
||||
auto entry = packet_reader->Read<ThreadCallStackEntry>();
|
||||
auto frames =
|
||||
packet_reader->ReadArray<ThreadCallStackFrame>(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<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::kThreadCallStacksRequest);
|
||||
packet_writer_.End();
|
||||
|
||||
Flush();
|
||||
}
|
||||
|
|
|
@ -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<const ModuleListEntry*> entries) = 0;
|
||||
virtual void OnThreadsUpdated(
|
||||
std::vector<const ThreadListEntry*> entries) = 0;
|
||||
virtual void OnThreadCallStackUpdated(
|
||||
uint32_t thread_handle,
|
||||
std::vector<const ThreadCallStackFrame*> 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;
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#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"
|
||||
|
@ -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<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_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");
|
||||
|
|
|
@ -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<XThread>(
|
||||
|
@ -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() {
|
||||
|
|
|
@ -82,6 +82,16 @@ class PacketReader {
|
|||
packet_offset_ = 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<PacketReader> ClonePacket() {
|
||||
assert_not_null(current_packet_);
|
||||
size_t length = sizeof(Packet) + current_packet_->body_length;
|
||||
auto clone = std::make_unique<PacketReader>(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_);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -60,7 +60,6 @@ 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)) {
|
||||
|
|
|
@ -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_)); });
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace model
|
||||
|
|
|
@ -40,7 +40,6 @@ class Module {
|
|||
System* system_ = nullptr;
|
||||
bool is_dead_ = false;
|
||||
proto::ModuleListEntry entry_ = {0};
|
||||
proto::ModuleListEntry temp_entry_ = {0};
|
||||
};
|
||||
|
||||
} // namespace model
|
||||
|
|
|
@ -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<std::recursive_mutex> 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<Module*> System::modules() {
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
std::vector<Module*> result;
|
||||
for (auto& module : modules_) {
|
||||
result.push_back(module.get());
|
||||
|
@ -36,7 +37,6 @@ std::vector<Module*> System::modules() {
|
|||
}
|
||||
|
||||
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());
|
||||
|
@ -45,27 +45,20 @@ std::vector<Thread*> System::threads() {
|
|||
}
|
||||
|
||||
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());
|
||||
|
@ -88,16 +81,11 @@ void System::OnModulesUpdated(std::vector<const ModuleListEntry*> entries) {
|
|||
module->second->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());
|
||||
|
@ -120,11 +108,17 @@ void System::OnThreadsUpdated(std::vector<const ThreadListEntry*> entries) {
|
|||
thread->second->set_dead(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
loop_->Post([this]() {
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
|
||||
on_threads_updated();
|
||||
});
|
||||
}
|
||||
|
||||
void System::OnThreadCallStackUpdated(
|
||||
uint32_t thread_handle, std::vector<const ThreadCallStackFrame*> frames) {
|
||||
auto thread = threads_by_handle_[thread_handle];
|
||||
if (thread != nullptr) {
|
||||
thread->UpdateCallStack(std::move(frames));
|
||||
on_thread_call_stack_updated(thread);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace model
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
|
@ -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<void> on_execution_state_changed;
|
||||
Delegate<void> on_modules_updated;
|
||||
Delegate<void> on_threads_updated;
|
||||
Delegate<Thread*> on_thread_call_stack_updated;
|
||||
|
||||
private:
|
||||
void OnExecutionStateChanged(ExecutionState execution_state) override;
|
||||
|
@ -53,11 +54,13 @@ class System : public ClientListener {
|
|||
std::vector<const proto::ModuleListEntry*> entries) override;
|
||||
void OnThreadsUpdated(
|
||||
std::vector<const proto::ThreadListEntry*> entries) override;
|
||||
void OnThreadCallStackUpdated(
|
||||
uint32_t thread_handle,
|
||||
std::vector<const ThreadCallStackFrame*> frames) override;
|
||||
|
||||
xe::ui::Loop* loop_ = nullptr;
|
||||
DebugClient* 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_;
|
||||
|
|
|
@ -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_)); });
|
||||
}
|
||||
|
||||
void Thread::UpdateCallStack(
|
||||
std::vector<const proto::ThreadCallStackFrame*> 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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<Frame>& call_stack() const { return call_stack_; }
|
||||
|
||||
std::string to_string();
|
||||
|
||||
void Update(const proto::ThreadListEntry* entry);
|
||||
void UpdateCallStack(std::vector<const proto::ThreadCallStackFrame*> frames);
|
||||
|
||||
private:
|
||||
System* system_ = nullptr;
|
||||
bool is_dead_ = false;
|
||||
proto::ThreadListEntry entry_ = {0};
|
||||
proto::ThreadListEntry temp_entry_ = {0};
|
||||
std::vector<Frame> call_stack_;
|
||||
};
|
||||
|
||||
} // namespace model
|
||||
|
|
|
@ -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<el::DropDownButton>(
|
||||
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<el::TextBox>(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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue