A new debugger.

Lots of bugs/rough edges/etc - issues will be filed.
Old-style debugging still works (just use --emit_source_annotations to get
the helpful movs back and --break_on_instruction will still fire).
This commit is contained in:
Ben Vanik
2015-09-20 21:31:05 -07:00
parent 8046c19898
commit 5d033f9cb3
90 changed files with 4183 additions and 4651 deletions

View File

@@ -0,0 +1,135 @@
/**
******************************************************************************
* 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/breakpoint.h"
#include "xenia/base/memory.h"
#include "xenia/base/string_util.h"
#include "xenia/cpu/backend/code_cache.h"
#include "xenia/debug/debugger.h"
#include "xenia/emulator.h"
namespace xe {
namespace debug {
void CodeBreakpoint::ForEachHostAddress(
std::function<void(uintptr_t)> callback) const {
auto processor = debugger_->emulator()->processor();
if (address_type_ == AddressType::kGuest) {
auto guest_address = this->guest_address();
// Lookup all functions that contain this guest address and patch them.
auto functions = processor->FindFunctionsWithAddress(guest_address);
if (functions.empty()) {
// If function does not exist demand it, as we need someplace to put our
// breakpoint. Note that this follows the same resolution rules as the
// JIT, so what's returned is the function the JIT would have jumped to.
auto fn = processor->ResolveFunction(guest_address);
if (!fn) {
// TODO(benvanik): error out better with 'invalid breakpoint'?
assert_not_null(fn);
return;
}
functions.push_back(fn);
}
assert_false(functions.empty());
for (auto function : functions) {
// TODO(benvanik): other function types.
assert_true(function->is_guest());
auto guest_function = reinterpret_cast<cpu::GuestFunction*>(function);
uintptr_t host_address = guest_function->MapSourceToCode(guest_address);
assert_not_zero(host_address);
callback(host_address);
}
} else {
// Direct host address patching.
callback(host_address());
}
}
xe::cpu::GuestFunction* CodeBreakpoint::guest_function() const {
auto processor = debugger_->emulator()->processor();
if (address_type_ == AddressType::kGuest) {
auto functions = processor->FindFunctionsWithAddress(guest_address());
if (functions.empty()) {
return nullptr;
} else if (functions[0]->is_guest()) {
return static_cast<xe::cpu::GuestFunction*>(functions[0]);
}
return nullptr;
} else {
return processor->backend()->code_cache()->LookupFunction(host_address());
}
}
bool CodeBreakpoint::ContainsHostAddress(uintptr_t search_address) const {
bool contains = false;
ForEachHostAddress([&contains, search_address](uintptr_t host_address) {
if (host_address == search_address) {
contains = true;
}
});
return contains;
}
void CodeBreakpoint::Install() {
Breakpoint::Install();
assert_true(patches_.empty());
ForEachHostAddress([this](uintptr_t host_address) {
patches_.emplace_back(host_address, PatchAddress(host_address));
});
}
void CodeBreakpoint::Uninstall() {
// Simply unpatch all locations we patched when installing.
for (auto& location : patches_) {
UnpatchAddress(location.first, location.second);
}
patches_.clear();
Breakpoint::Uninstall();
}
uint16_t CodeBreakpoint::PatchAddress(uintptr_t host_address) {
auto ptr = reinterpret_cast<void*>(host_address);
auto original_bytes = xe::load_and_swap<uint16_t>(ptr);
assert_true(original_bytes != 0x0F0B);
xe::store_and_swap<uint16_t>(ptr, 0x0F0B);
return original_bytes;
}
void CodeBreakpoint::UnpatchAddress(uintptr_t host_address,
uint16_t original_bytes) {
auto ptr = reinterpret_cast<uint8_t*>(host_address);
auto instruction_bytes = xe::load_and_swap<uint16_t>(ptr);
assert_true(instruction_bytes == 0x0F0B);
xe::store_and_swap<uint16_t>(ptr, original_bytes);
}
std::string CodeBreakpoint::to_string() const {
if (address_type_ == AddressType::kGuest) {
auto str =
std::string("PPC ") + xe::string_util::to_hex_string(guest_address());
auto processor = debugger_->emulator()->processor();
auto functions = processor->FindFunctionsWithAddress(guest_address());
if (functions.empty()) {
return str;
}
str += " " + functions[0]->name();
return str;
} else {
return std::string("x64 ") + xe::string_util::to_hex_string(host_address());
}
}
} // namespace debug
} // namespace xe

View File

@@ -10,33 +10,173 @@
#ifndef XENIA_DEBUG_BREAKPOINT_H_
#define XENIA_DEBUG_BREAKPOINT_H_
#include <functional>
#include <string>
#include <utility>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/cpu/function.h"
namespace xe {
namespace debug {
class Debugger;
class Breakpoint {
public:
enum Type {
TEMP_TYPE,
CODE_TYPE,
enum class Type {
kStepping,
kCode,
kData,
kExport,
kGpu,
};
public:
Breakpoint(Type type, uint32_t address);
~Breakpoint();
Breakpoint(Debugger* debugger, Type type)
: debugger_(debugger), type_(type) {}
virtual ~Breakpoint() { assert_false(installed_); }
Debugger* debugger() const { return debugger_; }
Type type() const { return type_; }
uint32_t address() const { return address_; }
const char* id() const { return id_.c_str(); }
void set_id(const char* id) { id_ = std::string(id); }
// Whether the breakpoint has been enabled by the user.
bool is_enabled() const { return enabled_; }
// Toggles the breakpoint state.
// Assumes the caller holds the global lock.
void set_enabled(bool is_enabled) {
enabled_ = is_enabled;
if (!enabled_ && installed_) {
Uninstall();
} else if (enabled_ && !installed_ && !suspend_count_) {
Install();
}
}
virtual std::string to_string() const { return "Breakpoint"; }
private:
Type type_;
uint32_t address_;
friend class Debugger;
// Suspends the breakpoint until it is resumed with Resume.
// This preserves the user enabled state.
// Assumes the caller holds the global lock.
void Suspend() {
++suspend_count_;
if (installed_) {
Uninstall();
}
}
std::string id_;
// Resumes a previously-suspended breakpoint, reinstalling it if required.
// Assumes the caller holds the global lock.
void Resume() {
--suspend_count_;
if (!suspend_count_ && enabled_) {
Install();
}
}
protected:
virtual void Install() { installed_ = true; }
virtual void Uninstall() { installed_ = false; }
Debugger* debugger_ = nullptr;
Type type_;
// True if patched into code.
bool installed_ = false;
// True if user enabled.
bool enabled_ = true;
// Depth of suspends (must be 0 to be installed). Defaults to suspended so
// that it's never installed unless the debugger knows about it.
int suspend_count_ = 1;
};
class CodeBreakpoint : public Breakpoint {
public:
enum class AddressType {
kGuest,
kHost,
};
CodeBreakpoint(Debugger* debugger, AddressType address_type, uint64_t address)
: CodeBreakpoint(debugger, Type::kCode, address_type, address) {}
AddressType address_type() const { return address_type_; }
uint64_t address() const { return address_; }
uint32_t guest_address() const {
assert_true(address_type_ == AddressType::kGuest);
return static_cast<uint32_t>(address_);
}
uintptr_t host_address() const {
assert_true(address_type_ == AddressType::kHost);
return static_cast<uintptr_t>(address_);
}
// TODO(benvanik): additional support:
// - conditions (expressions? etc?)
// - thread restrictions (set of thread ids)
// - hit counters
// Returns a guest function that contains the guest address, if any.
// If there are multiple functions that contain the address a random one will
// be returned. If this is a host-address code breakpoint this will attempt to
// find a function with that address and otherwise return nullptr.
xe::cpu::GuestFunction* guest_function() const;
// Returns true if this breakpoint, when installed, contains the given host
// address.
bool ContainsHostAddress(uintptr_t search_address) const;
std::string to_string() const override;
protected:
CodeBreakpoint(Debugger* debugger, Type type, AddressType address_type,
uint64_t address)
: Breakpoint(debugger, type),
address_type_(address_type),
address_(address) {}
void ForEachHostAddress(std::function<void(uintptr_t)> callback) const;
void Install() override;
void Uninstall() override;
uint16_t PatchAddress(uintptr_t host_address);
void UnpatchAddress(uintptr_t host_address, uint16_t original_bytes);
AddressType address_type_;
uint64_t address_ = 0;
// Pairs of x64 address : original byte values when installed.
// These locations have been patched and must be restored when the breakpoint
// is uninstalled.
std::vector<std::pair<uintptr_t, uint16_t>> patches_;
};
class StepBreakpoint : public CodeBreakpoint {
public:
StepBreakpoint(Debugger* debugger, AddressType address_type, uint64_t address)
: CodeBreakpoint(debugger, Type::kStepping, address_type, address) {}
};
class DataBreakpoint : public Breakpoint {
public:
explicit DataBreakpoint(Debugger* debugger)
: Breakpoint(debugger, Type::kData) {}
};
class ExportBreakpoint : public Breakpoint {
public:
explicit ExportBreakpoint(Debugger* debugger)
: Breakpoint(debugger, Type::kExport) {}
};
class GpuBreakpoint : public Breakpoint {
public:
explicit GpuBreakpoint(Debugger* debugger)
: Breakpoint(debugger, Type::kGpu) {}
};
} // namespace debug

View File

@@ -1,271 +0,0 @@
/**
******************************************************************************
* 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

View File

@@ -1,106 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_DEBUG_CLIENT_H_
#define XENIA_DEBUG_DEBUG_CLIENT_H_
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "xenia/base/socket.h"
#include "xenia/base/threading.h"
#include "xenia/debug/proto/packet_reader.h"
#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;
using proto::ThreadStateEntry;
enum class ExecutionState {
kRunning,
kStopped,
kExited,
};
// 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 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 OnThreadStateUpdated(
uint32_t thread_handle, const ThreadStateEntry* entry,
std::vector<const ThreadCallStackFrame*> frames) = 0;
};
class DebugClient {
public:
DebugClient();
~DebugClient();
Socket* socket() const { return socket_.get(); }
void set_listener(DebugClientListener* listener) { listener_ = listener; }
void set_loop(xe::ui::Loop* loop) { loop_ = loop; }
ExecutionState execution_state() const { return execution_state_; }
bool Connect(std::string hostname, uint16_t port);
void Continue();
void StepOne(uint32_t thread_id);
void StepTo(uint32_t thread_id, uint32_t target_pc);
void Interrupt();
void Exit();
private:
bool HandleSocketEvent();
bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length);
bool ProcessPacket(proto::PacketReader* packet_reader,
const proto::Packet* packet);
void Flush();
void BeginUpdateAllState();
std::recursive_mutex mutex_;
std::unique_ptr<Socket> socket_;
std::unique_ptr<xe::threading::Thread> thread_;
std::vector<uint8_t> receive_buffer_;
proto::PacketReader packet_reader_;
proto::PacketWriter packet_writer_;
DebugClientListener* listener_ = nullptr;
xe::ui::Loop* loop_ = nullptr;
ExecutionState execution_state_ = ExecutionState::kStopped;
};
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_DEBUG_CLIENT_H_

View File

@@ -1,406 +0,0 @@
/**
******************************************************************************
* 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/xmodule.h"
#include "xenia/kernel/xthread.h"
DEFINE_int32(debug_server_port, 9002, "Debugger XDP server TCP port.");
namespace xe {
namespace debug {
using namespace xe::debug::proto; // NOLINT(build/namespaces)
using xe::kernel::XModule;
using xe::kernel::XObject;
using xe::kernel::XThread;
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::kThreadStatesRequest: {
packet_writer_.Begin(PacketType::kThreadStatesResponse);
auto body = packet_writer_.Append<ThreadStatesResponse>();
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) {
auto thread_entry_body = packet_writer_.Append<ThreadStateEntry>();
thread_entry_body->thread_handle = thread->handle();
// Grab PPC context.
// Note that this is only up to date if --store_all_context_values is
// enabled (or --debug).
if (thread->is_guest_thread()) {
std::memcpy(&thread_entry_body->guest_context,
thread->thread_state()->context(),
sizeof(thread_entry_body->guest_context));
} else {
std::memset(&thread_entry_body->guest_context, 0,
sizeof(thread_entry_body->guest_context));
}
// Grab stack trace and X64 context then resolve all symbols.
uint64_t hash;
size_t count = stack_walker->CaptureStackTrace(
thread->GetWaitHandle()->native_handle(), frame_host_pcs, 0, 64,
&thread_entry_body->host_context, &hash);
stack_walker->ResolveStack(frame_host_pcs, frames, count);
// Populate stack frames with additional information.
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

View File

@@ -1,88 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_DEBUG_SERVER_H_
#define XENIA_DEBUG_DEBUG_SERVER_H_
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "xenia/base/socket.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/processor.h"
#include "xenia/debug/breakpoint.h"
#include "xenia/debug/proto/packet_reader.h"
#include "xenia/debug/proto/packet_writer.h"
#include "xenia/debug/proto/xdp_protocol.h"
namespace xe {
namespace debug {
class Debugger;
class DebugServer {
public:
explicit DebugServer(Debugger* debugger);
~DebugServer();
Debugger* debugger() const { return debugger_; }
Socket* client() const { return client_.get(); }
bool Initialize();
void PostSynchronous(std::function<void()> fn);
// TODO(benvanik): better thread type (XThread?)
// void OnThreadCreated(ThreadState* thread_state);
// void OnThreadDestroyed(ThreadState* thread_state);
void OnExecutionContinued();
void OnExecutionInterrupted();
/*void OnBreakpointHit(xe::cpu::ThreadState* thread_state,
Breakpoint* breakpoint);*/
private:
void AcceptClient(std::unique_ptr<Socket> client);
bool HandleClientEvent();
bool ProcessBuffer(const uint8_t* buffer, size_t buffer_length);
bool ProcessPacket(const proto::Packet* packet);
void Flush();
void SendSuccess(proto::request_id_t request_id);
void SendError(proto::request_id_t request_id,
const char* error_message = nullptr);
void SendError(proto::request_id_t request_id,
const std::string& error_message);
Debugger* debugger_ = nullptr;
std::unique_ptr<SocketServer> socket_server_;
std::unique_ptr<Socket> client_;
std::unique_ptr<xe::threading::Thread> client_thread_;
std::mutex post_mutex_;
std::unique_ptr<xe::threading::Event> post_event_;
std::list<std::function<void()>> post_queue_;
std::vector<uint8_t> receive_buffer_;
proto::PacketReader packet_reader_;
proto::PacketWriter packet_writer_;
};
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_DEBUG_SERVER_H_

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,7 @@
#include <unordered_map>
#include <vector>
#include "xenia/base/exception_handler.h"
#include "xenia/base/mapped_memory.h"
#include "xenia/base/mutex.h"
#include "xenia/base/threading.h"
@@ -38,11 +39,127 @@ class XThread;
namespace xe {
namespace debug {
class DebugServer;
// Describes the current state of the debug target as known to the debugger.
// This determines which state the debugger is in and what operations are
// allowed.
enum class ExecutionState {
// Target is running; the debugger is not waiting for any events.
kRunning,
kStopped,
// Target is running in stepping mode with the debugger waiting for feedback.
kStepping,
// Target is paused for debugging.
kPaused,
// Target has been stopped and cannot be restarted (crash, etc).
kEnded,
};
// Per-XThread structure holding debugger state and a cache of the sampled call
// stack.
//
// In most cases debug consumers should rely only on data in this structure as
// it is never removed (even when a thread is destroyed) and always available
// even when running.
struct ThreadExecutionInfo {
ThreadExecutionInfo();
~ThreadExecutionInfo();
enum class State {
// Thread is alive and running.
kAlive,
// Thread is in a wait state.
kWaiting,
// Thread has exited but not yet been killed.
kExited,
// Thread has been killed.
kZombie,
};
// XThread::handle() of the thread.
uint32_t thread_handle = 0;
// Target XThread, if it has not been destroyed.
// TODO(benvanik): hold a ref here to keep zombie threads around?
kernel::XThread* thread = nullptr;
// Current state of the thread.
State state = State::kAlive;
// Whether the debugger has forcefully suspended this thread.
bool suspended = false;
// A breakpoint managed by the stepping system, installed as required to
// trigger a break at the next instruction.
std::unique_ptr<StepBreakpoint> step_breakpoint;
// A breakpoint managed by the stepping system, installed as required to
// trigger after a step over a disabled breakpoint.
// When this breakpoint is hit the breakpoint referenced in
// restore_original_breakpoint will be reinstalled.
// Breakpoint restore_step_breakpoint;
// If the thread is stepping over a disabled breakpoint this will point to
// that breakpoint so it can be restored.
// Breakpoint* restore_original_breakpoint = nullptr;
// Last-sampled PPC context.
// This is updated whenever the debugger stops.
xe::cpu::frontend::PPCContext guest_context;
// Last-sampled host x64 context.
// This is updated whenever the debugger stops and must be used instead of any
// value taken from the StackWalker as it properly respects exception stacks.
X64Context host_context;
// A single frame in a call stack.
struct Frame {
// PC of the current instruction in host code.
uint64_t host_pc = 0;
// Base of the function the current host_pc is located within.
uint64_t host_function_address = 0;
// PC of the current instruction in guest code.
// 0 if not a guest address or not known.
uint32_t guest_pc = 0;
// Base of the function the current guest_pc is located within.
uint32_t guest_function_address = 0;
// Function the current guest_pc is located within.
cpu::Function* guest_function = nullptr;
// Name of the function, if known.
// TODO(benvanik): string table?
char name[256] = {0};
};
// Last-sampled call stack.
// This is updated whenever the debugger stops.
std::vector<Frame> frames;
};
// Debug event listener interface.
// Implementations will receive calls from arbitrary threads and must marshal
// them as required.
class DebugListener {
public:
// Handles request for debugger focus (such as when the user requests the
// debugger be brought to the front).
virtual void OnFocus() = 0;
// Handles the debugger detaching from the target.
// This will be called on shutdown or when the user requests the debug session
// end.
virtual void OnDetached() = 0;
// Handles execution being interrupted and transitioning to
// ExceutionState::kPaused.
virtual void OnExecutionPaused() = 0;
// Handles execution continuing when transitioning to
// ExecutionState::kRunning or ExecutionState::kStepping.
virtual void OnExecutionContinued() = 0;
// Handles execution ending when transitioning to ExecutionState::kEnded.
virtual void OnExecutionEnded() = 0;
// Handles step completion events when a requested step operation completes.
// The thread is the one that hit its step target. Note that because multiple
// threads could be stepping simultaneously (such as a run-to-cursor) use the
// thread passed instead of keeping any other state.
virtual void OnStepCompleted(xe::kernel::XThread* thread) = 0;
// Handles breakpoint events as they are hit per-thread.
// Breakpoints may be hit during stepping.
virtual void OnBreakpointHit(Breakpoint* breakpoint,
xe::kernel::XThread* thread) = 0;
};
class Debugger {
@@ -52,63 +169,147 @@ class Debugger {
Emulator* emulator() const { return emulator_; }
// True if a debug listener is attached and the debugger is active.
bool is_attached() const { return !!debug_listener_; }
// Gets the active debug listener, if any.
DebugListener* debug_listener() const { return debug_listener_; }
// Sets the active debug listener, if any.
// This can be used to detach the listener.
void set_debug_listener(DebugListener* debug_listener);
// Sets a handler that will be called from a random thread when a debugger
// listener is required (such as on a breakpoint hit/etc).
// Will only be called if the debug listener has not already been specified
// with set_debug_listener.
void set_debug_listener_request_handler(
std::function<DebugListener*(Debugger*)> handler) {
debug_listener_handler_ = std::move(handler);
}
// The current execution state of the target.
ExecutionState execution_state() const { return execution_state_; }
// TODO(benvanik): remove/cleanup.
bool StartSession();
void PreLaunch();
void StopSession();
void FlushSession();
// TODO(benvanik): hide.
uint8_t* AllocateFunctionData(size_t size);
uint8_t* AllocateFunctionTraceData(size_t size);
bool is_attached() const { return is_attached_; }
void set_attached(bool attached);
// Returns a list of debugger info for all threads that have ever existed.
// This is the preferred way to sample thread state vs. attempting to ask
// the kernel.
std::vector<ThreadExecutionInfo*> QueryThreadExecutionInfos();
// Returns the debugger info for the given thread.
ThreadExecutionInfo* QueryThreadExecutionInfo(uint32_t thread_handle);
ExecutionState execution_state() const { return execution_state_; }
void DumpThreadStacks();
int AddBreakpoint(Breakpoint* breakpoint);
int RemoveBreakpoint(Breakpoint* breakpoint);
void FindBreakpoints(uint32_t address,
std::vector<Breakpoint*>* out_breakpoints);
// Adds a breakpoint to the debugger and activates it (if enabled).
// The given breakpoint will not be owned by the debugger and must remain
// allocated so long as it is added.
void AddBreakpoint(Breakpoint* breakpoint);
// Removes a breakpoint from the debugger and deactivates it.
void RemoveBreakpoint(Breakpoint* breakpoint);
// Returns all currently registered breakpoints.
const std::vector<Breakpoint*>& breakpoints() const { return breakpoints_; }
// TODO(benvanik): utility functions for modification (make function ignored,
// etc).
void Interrupt();
void Continue();
void StepOne(uint32_t thread_id);
void StepTo(uint32_t thread_id, uint32_t target_pc);
// Shows the debug listener, focusing it if it already exists.
void Show();
// Pauses target execution by suspending all threads.
// The debug listener will be requested if it has not been attached.
void Pause();
// Continues target execution from wherever it is.
// This will cancel any active step operations and resume all threads.
void Continue();
// Steps the given thread a single PPC guest instruction.
// If the step is over a branch the branch will be followed.
void StepGuestInstruction(uint32_t thread_handle);
// Steps the given thread a single x64 host instruction.
// If the step is over a branch the branch will be followed.
void StepHostInstruction(uint32_t thread_handle);
public:
// TODO(benvanik): hide.
void OnThreadCreated(xe::kernel::XThread* thread);
void OnThreadExit(xe::kernel::XThread* thread);
void OnThreadDestroyed(xe::kernel::XThread* thread);
void OnThreadEnteringWait(xe::kernel::XThread* thread);
void OnThreadLeavingWait(xe::kernel::XThread* thread);
void OnFunctionDefined(cpu::Function* function);
void OnFunctionDefined(xe::cpu::Function* function);
void OnBreakpointHit(xe::kernel::XThread* thread, Breakpoint* breakpoint);
bool OnUnhandledException(Exception* ex);
private:
// Synchronously demands a debug listener.
void DemandDebugListener();
// Suspends all known threads (except the caller).
bool SuspendAllThreads();
bool ResumeThread(uint32_t thread_id);
// Resumes the given thread.
bool ResumeThread(uint32_t thread_handle);
// Resumes all known threads (except the caller).
bool ResumeAllThreads();
// Updates all cached thread execution info (state, call stacks, etc).
// The given override thread handle and context will be used in place of
// sampled values for that thread.
void UpdateThreadExecutionStates(uint32_t override_handle = 0,
X64Context* override_context = nullptr);
// Suspends all breakpoints, uninstalling them as required.
// No breakpoints will be triggered until they are resumed.
void SuspendAllBreakpoints();
// Resumes all breakpoints, re-installing them if required.
void ResumeAllBreakpoints();
static bool ExceptionCallbackThunk(Exception* ex, void* data);
bool ExceptionCallback(Exception* ex);
void OnStepCompleted(ThreadExecutionInfo* thread_info);
void OnBreakpointHit(ThreadExecutionInfo* thread_info,
Breakpoint* breakpoint);
// Calculates the next guest instruction based on the current thread state and
// current PC. This will look for branches and other control flow
// instructions.
uint32_t CalculateNextGuestInstruction(ThreadExecutionInfo* thread_info,
uint32_t current_pc);
// Calculates the next host instruction based on the current thread state and
// current PC. This will look for branches and other control flow
// instructions.
uint64_t CalculateNextHostInstruction(ThreadExecutionInfo* thread_info,
uint64_t current_pc);
Emulator* emulator_ = nullptr;
std::unique_ptr<DebugServer> server_;
bool is_attached_ = false;
xe::threading::Fence attach_fence_;
// TODO(benvanik): move this to the CPU backend and remove x64-specific stuff?
uintptr_t capstone_handle_ = 0;
std::function<DebugListener*(Debugger*)> debug_listener_handler_;
DebugListener* debug_listener_ = nullptr;
// TODO(benvanik): cleanup - maybe remove now that in-process?
std::wstring functions_path_;
std::unique_ptr<ChunkedMappedMemoryWriter> functions_file_;
std::wstring functions_trace_path_;
std::unique_ptr<ChunkedMappedMemoryWriter> functions_trace_file_;
std::recursive_mutex mutex_;
ExecutionState execution_state_ = ExecutionState::kStopped;
xe::global_critical_region global_critical_region_;
std::multimap<uint32_t, Breakpoint*> breakpoints_;
ExecutionState execution_state_ = ExecutionState::kPaused;
// Maps thread ID to state. Updated on thread create, and threads are never
// removed.
std::map<uint32_t, std::unique_ptr<ThreadExecutionInfo>>
thread_execution_infos_;
// TODO(benvanik): cleanup/change structures.
std::vector<Breakpoint*> breakpoints_;
};
} // namespace debug

View File

@@ -16,4 +16,3 @@ project("xenia-debug")
project_root.."/build_tools/third_party/gflags/src",
})
local_platform_files()
recursive_platform_files("proto")

View File

@@ -1,161 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_PROTO_PACKET_READER_H_
#define XENIA_DEBUG_PROTO_PACKET_READER_H_
#include <algorithm>
#include <memory>
#include <string>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/debug/proto/varint.h"
#include "xenia/debug/proto/xdp_protocol.h"
namespace xe {
namespace debug {
namespace proto {
class PacketReader {
public:
explicit PacketReader(size_t buffer_capacity) : buffer_(buffer_capacity) {}
const uint8_t* buffer() { return buffer_.data(); }
size_t buffer_capacity() const { return buffer_.size(); }
size_t buffer_offset() const { return buffer_offset_; }
void Reset() {
buffer_offset_ = 0;
buffer_size_ = 0;
}
void AppendBuffer(const uint8_t* buffer, size_t buffer_length) {
if (buffer_size_ + buffer_length > buffer_.size()) {
size_t new_size =
std::max(buffer_.size() * 2,
xe::round_up(buffer_size_ + buffer_length, 16 * 1024));
buffer_.resize(new_size);
}
std::memcpy(buffer_.data() + buffer_size_, buffer, buffer_length);
buffer_size_ += buffer_length;
}
void Compact() {
assert_null(current_packet_);
if (!buffer_offset_) {
return;
}
std::memmove(buffer_.data(), buffer_.data() + buffer_offset_,
buffer_size_ - buffer_offset_);
buffer_size_ -= buffer_offset_;
buffer_offset_ = 0;
}
const Packet* Begin() {
assert_null(current_packet_);
size_t bytes_remaining = buffer_size_ - buffer_offset_;
if (bytes_remaining < sizeof(Packet)) {
return nullptr;
}
auto packet =
reinterpret_cast<const Packet*>(buffer_.data() + buffer_offset_);
if (bytes_remaining < sizeof(Packet) + packet->body_length) {
return nullptr;
}
packet_offset_ = buffer_offset_ + sizeof(Packet);
buffer_offset_ += sizeof(Packet) + packet->body_length;
current_packet_ = packet;
return packet;
}
void End() {
assert_not_null(current_packet_);
current_packet_ = nullptr;
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_);
assert_true(packet_offset_ + length <= buffer_offset_);
auto ptr = buffer_.data() + packet_offset_;
packet_offset_ += length;
return ptr;
}
template <typename T>
const T* Read() {
return reinterpret_cast<const T*>(Read(sizeof(T)));
}
template <typename T>
std::vector<const T*> ReadArray(size_t count) {
std::vector<const T*> entries;
for (size_t i = 0; i < count; ++i) {
entries.push_back(Read<T>());
}
return entries;
}
void Read(void* buffer, size_t buffer_length) {
assert_not_null(current_packet_);
auto src = Read(buffer_length);
std::memcpy(buffer, src, buffer_length);
}
void ReadString(char* buffer, size_t buffer_length) {
assert_not_null(current_packet_);
varint_t length;
auto src = length.Read(buffer_.data() + packet_offset_);
assert_not_null(src);
assert_true(length < buffer_length);
assert_true(length.size() + length < (buffer_offset_ - packet_offset_));
std::memcpy(buffer, src, length);
packet_offset_ += length;
}
std::string ReadString() {
assert_not_null(current_packet_);
varint_t length;
auto src = length.Read(buffer_.data() + packet_offset_);
assert_not_null(src);
assert_true(length.size() + length < (buffer_offset_ - packet_offset_));
std::string value;
value.resize(length.value());
std::memcpy(const_cast<char*>(value.data()), src, length);
packet_offset_ += length;
return value;
}
private:
std::vector<uint8_t> buffer_;
size_t buffer_offset_ = 0;
size_t buffer_size_ = 0;
const Packet* current_packet_ = nullptr;
size_t packet_offset_ = 0;
};
} // namespace proto
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_PROTO_PACKET_READER_H_

View File

@@ -1,98 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_PROTO_PACKET_WRITER_H_
#define XENIA_DEBUG_PROTO_PACKET_WRITER_H_
#include <algorithm>
#include <string>
#include <vector>
#include "xenia/base/assert.h"
#include "xenia/base/math.h"
#include "xenia/debug/proto/varint.h"
#include "xenia/debug/proto/xdp_protocol.h"
namespace xe {
namespace debug {
namespace proto {
class PacketWriter {
public:
explicit PacketWriter(size_t buffer_capacity) : buffer_(buffer_capacity) {}
uint8_t* buffer() { return buffer_.data(); }
size_t buffer_capacity() const { return buffer_.size(); }
size_t buffer_offset() const { return buffer_offset_; }
void Reset() { buffer_offset_ = 0; }
void Begin(PacketType packet_type, request_id_t request_id = 0) {
assert_null(current_packet_);
current_packet_ = Append<Packet>();
current_packet_->packet_type = packet_type;
current_packet_->request_id = request_id;
current_packet_->body_length = 0;
}
void End() {
assert_not_null(current_packet_);
current_packet_->body_length =
uint32_t((buffer_.data() + buffer_offset_) -
reinterpret_cast<uint8_t*>(current_packet_) - sizeof(Packet));
current_packet_ = nullptr;
}
uint8_t* Append(size_t length) {
if (buffer_offset_ + length > buffer_.size()) {
size_t new_size = std::max(
xe::round_up(buffer_offset_ + length, 16 * 1024), buffer_.size() * 2);
buffer_.resize(new_size);
}
auto ptr = buffer_.data() + buffer_offset_;
buffer_offset_ += length;
return ptr;
}
template <typename T>
T* Append() {
return reinterpret_cast<T*>(Append(sizeof(T)));
}
void Append(const void* buffer, size_t buffer_length) {
auto dest = Append(buffer_length);
std::memcpy(dest, buffer, buffer_length);
}
void AppendString(const char* value) {
size_t value_length = std::strlen(value);
varint_t length(value_length);
auto dest = Append(length.size() + value_length);
dest = length.Write(dest);
std::memcpy(dest, value, value_length);
}
void AppendString(const std::string& value) {
varint_t length(value.size());
auto dest = Append(length.size() + value.size());
dest = length.Write(dest);
std::memcpy(dest, value.data(), value.size());
}
private:
std::vector<uint8_t> buffer_;
size_t buffer_offset_ = 0;
Packet* current_packet_ = nullptr;
};
} // namespace proto
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_PROTO_PACKET_WRITER_H_

View File

@@ -1,74 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_PROTO_VARINT_H_
#define XENIA_DEBUG_PROTO_VARINT_H_
#include <cstdint>
namespace xe {
namespace debug {
namespace proto {
struct varint_t {
varint_t() = default;
varint_t(uint64_t value) : value_(value) {} // NOLINT(runtime/explicit)
varint_t(const varint_t& other) : value_(other.value_) {}
operator uint64_t() const { return value_; }
uint64_t value() const { return value_; }
size_t size() const {
uint64_t value = value_;
size_t i = 0;
do {
value >>= 7;
++i;
} while (value >= 0x80);
return i + 1;
}
const uint8_t* Read(const uint8_t* src) {
if (!(src[0] & 0x80)) {
value_ = src[0];
return src + 1;
}
uint64_t r = src[0] & 0x7F;
size_t i;
for (i = 1; i < 10; ++i) {
r |= (src[i] & 0x7F) << (7 * i);
if (!(src[i] & 0x80)) {
break;
}
}
value_ = r;
return src + i + 1;
}
uint8_t* Write(uint8_t* dest) {
uint64_t value = value_;
size_t i = 0;
do {
dest[i] = uint8_t(value | 0x80);
value >>= 7;
++i;
} while (value >= 0x80);
dest[i] = uint8_t(value);
return dest + i + 1;
}
private:
uint64_t value_ = 0;
};
} // namespace proto
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_PROTO_VARINT_H_

View File

@@ -1,210 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_PROTO_XDP_PROTOCOL_H_
#define XENIA_DEBUG_PROTO_XDP_PROTOCOL_H_
#include <cstdint>
#include "xenia/base/vec128.h"
#include "xenia/cpu/frontend/ppc_context.h"
#include "xenia/cpu/stack_walker.h"
namespace xe {
namespace debug {
namespace proto {
enum class PacketType : uint16_t {
kUnknown = 0,
kGenericResponse = 1,
kExecutionRequest = 10,
kExecutionNotification = 11,
kModuleListRequest = 20,
kModuleListResponse = 21,
kThreadListRequest = 30,
kThreadListResponse = 31,
kThreadStatesRequest = 32,
kThreadStatesResponse = 33,
};
using request_id_t = uint16_t;
struct Packet {
PacketType packet_type;
request_id_t request_id;
uint32_t body_length;
uint8_t* body() { return reinterpret_cast<uint8_t*>(this) + sizeof(this); }
};
static_assert(sizeof(Packet) == 8, "Fixed packet size");
// Generic response to requests (S->C).
// Used for any request type that doesn't define its own response.
struct GenericResponse {
static const PacketType type = PacketType::kGenericResponse;
enum class Code : uint32_t {
// Command was received and successfully acted upon.
// If the request is handled asynchronously this only indicates that the
// command was recognized.
kSuccess = 0,
// Command was invalid or could not be executed.
// A string will follow this response containing an error message.
kError = 1,
};
Code code;
bool is_success() const { return code == Code::kSuccess; }
};
// Requests execution state changes (C->S).
// Response is a GenericResponse, and as execution changes one or more
// ExecutionNotification packets will be sent S->C.
struct ExecutionRequest {
static const PacketType type = PacketType::kExecutionRequest;
enum class Action : uint32_t {
// Continues execution until the next breakpoint.
kContinue,
// Steps execution by the given step_mode.
kStep,
// Interrupts execution and breaks.
kInterrupt,
// Stops execution and causes the server to exit.
kExit,
};
enum class StepMode : uint32_t {
// Steps a single statement, following branches.
kStepOne,
// Runs until the given target_pc.
kStepTo,
};
Action action;
StepMode step_mode;
uint32_t thread_id;
uint32_t target_pc;
};
// Notifies clients of changes in execution state (S->C).
// This is fired asynchronously from any request and possibly due to events
// external to the client requested ones.
// After stopping clients should query execution state.
struct ExecutionNotification {
static const PacketType type = PacketType::kExecutionNotification;
enum class State : uint32_t {
// Execution has stopped for stop_reason.
kStopped,
// Execution has resumed and the target is now running.
kRunning,
// The target has exited.
kExited,
};
enum class Reason : uint32_t {
// Stopped because the client requested it with Action::kInterrupt.
kInterrupt,
// Stopped due to a completed step.
kStep,
// Stopped at a breakpoint.
kBreakpoint,
// TODO(benvanik): others? catches? library loads? etc?
};
State current_state;
Reason stop_reason;
};
struct ModuleListRequest {
static const PacketType type = PacketType::kModuleListRequest;
};
struct ModuleListResponse {
static const PacketType type = PacketType::kModuleListResponse;
uint32_t count;
// ModuleListEntry[count]
};
struct ModuleListEntry {
uint32_t module_handle;
uint32_t module_ptr;
bool is_kernel_module;
char path[256];
char name[256];
};
struct ThreadListRequest {
static const PacketType type = PacketType::kThreadListRequest;
};
struct ThreadListResponse {
static const PacketType type = PacketType::kThreadListResponse;
uint32_t count;
// ThreadListEntry[count]
};
struct ThreadListEntry {
uint32_t thread_handle;
uint32_t thread_id;
bool is_host_thread;
char name[64];
uint32_t exit_code;
int32_t priority;
uint32_t affinity;
uint32_t xapi_thread_startup;
uint32_t start_address;
uint32_t start_context;
uint32_t creation_flags;
uint32_t stack_address;
uint32_t stack_size;
uint32_t stack_base;
uint32_t stack_limit;
uint32_t pcr_address;
uint32_t tls_address;
};
struct ThreadStatesRequest {
static const PacketType type = PacketType::kThreadStatesRequest;
};
struct ThreadStatesResponse {
static const PacketType type = PacketType::kThreadStatesResponse;
uint32_t count;
// ThreadStateEntry[count]
};
struct ThreadStateEntry {
uint32_t thread_handle;
cpu::frontend::PPCContext guest_context;
cpu::X64Context host_context;
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
#endif // XENIA_DEBUG_PROTO_XDP_PROTOCOL_H_

View File

@@ -1,44 +0,0 @@
XDP: Xenia Debug Protocol
--------------------------
## Overview
### General
* Packets flow in both directions (client and server).
* Each packet is length-prefixed (exclusive) and tagged with a type.
* The entire protocol is fully asynchronous, and supports both request/response
style RPCs as well as notifications.
* Request/response RPCs are matched on ID and multiple are allowed to be in
flight at a time.
* Notifications may come in any order.
### Server
The debug server is single threaded and processes commands as a FIFO. This
means that all requests will be responded to in the order they are received.
### Client
TODO
## Packet Structure
Each packet consists of a length, type, and request ID. The length denotes
the total number of bytes within the packet body, excluding the packet header.
The request ID is an opaque value sent by the client when making a request and
the server returns whatever the ID is when it responds to that request. The ID
is unused for notifications.
```
struct Packet {
uint16_t packet_type;
uint16_t request_id;
uint32_t body_length;
uint8_t body[body_length];
};
```
## Packets
TODO(benvanik): document the various types, etc.

View File

@@ -1,88 +0,0 @@
/**
******************************************************************************
* 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/ui/application.h"
#include "xenia/base/assert.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/base/threading.h"
#include "xenia/debug/ui/main_window.h"
#include "xenia/profiling.h"
namespace xe {
namespace debug {
namespace ui {
Application* current_application_ = nullptr;
Application* Application::current() {
assert_not_null(current_application_);
return current_application_;
}
Application::Application() {
current_application_ = this;
loop_ = xe::ui::Loop::Create();
}
Application::~Application() {
assert_true(current_application_ == this);
current_application_ = nullptr;
}
std::unique_ptr<Application> Application::Create() {
std::unique_ptr<Application> app(new Application());
xe::threading::Fence fence;
app->loop()->Post([&app, &fence]() {
xe::threading::set_name("Win32 Loop");
xe::Profiler::ThreadEnter("Win32 Loop");
if (!app->Initialize()) {
xe::FatalError("Failed to initialize application");
return;
}
fence.Signal();
});
fence.Wait();
return app;
}
bool Application::Initialize() {
// Bind the object model to the client so it'll maintain state.
system_ = std::make_unique<model::System>(loop(), &client_);
// TODO(benvanik): flags and such.
if (!client_.Connect("localhost", 9002)) {
return false;
}
main_window_ = std::make_unique<MainWindow>(this);
if (!main_window_->Initialize()) {
XELOGE("Unable to initialize main window");
return false;
}
return true;
}
void Application::Quit() {
loop_->Quit();
// TODO(benvanik): proper exit.
XELOGI("User-initiated death!");
exit(1);
}
} // namespace ui
} // namespace debug
} // namespace xe

View File

@@ -1,55 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_APPLICATION_H_
#define XENIA_DEBUG_UI_APPLICATION_H_
#include <memory>
#include "xenia/debug/debug_client.h"
#include "xenia/debug/ui/model/system.h"
#include "xenia/ui/loop.h"
namespace xe {
namespace debug {
namespace ui {
class MainWindow;
class Application {
public:
~Application();
static std::unique_ptr<Application> Create();
static Application* current();
xe::ui::Loop* loop() { return loop_.get(); }
MainWindow* main_window() const { return main_window_.get(); }
DebugClient* client() { return &client_; }
model::System* system() const { return system_.get(); }
void Quit();
private:
Application();
bool Initialize();
std::unique_ptr<xe::ui::Loop> loop_;
std::unique_ptr<MainWindow> main_window_;
DebugClient client_;
std::unique_ptr<model::System> system_;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_APPLICATION_H_

View File

@@ -1,49 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_CONTROL_H_
#define XENIA_DEBUG_UI_CONTROL_H_
#include <memory>
#include <string>
#include "el/elements.h"
#include "el/event_handler.h"
#include "xenia/debug/ui/application.h"
namespace xe {
namespace debug {
namespace ui {
class Control {
public:
virtual ~Control() = default;
el::LayoutBox* root_element() { return &root_element_; }
xe::ui::Loop* loop() const { return Application::current()->loop(); }
DebugClient* client() const { return client_; }
model::System* system() const { return Application::current()->system(); }
virtual el::Element* BuildUI() = 0;
virtual void Setup(xe::debug::DebugClient* client) = 0;
protected:
Control() = default;
el::LayoutBox root_element_;
std::unique_ptr<el::EventHandler> handler_;
xe::debug::DebugClient* client_ = nullptr;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_CONTROL_H_

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,153 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_DEBUG_WINDOW_H_
#define XENIA_DEBUG_UI_DEBUG_WINDOW_H_
#include <memory>
#include <vector>
#include "xenia/base/x64_context.h"
#include "xenia/debug/debugger.h"
#include "xenia/emulator.h"
#include "xenia/ui/loop.h"
#include "xenia/ui/menu_item.h"
#include "xenia/ui/window.h"
#include "xenia/xbox.h"
namespace xe {
namespace debug {
namespace ui {
class ImGuiRenderer;
class DebugWindow : public DebugListener {
public:
~DebugWindow();
static std::unique_ptr<DebugWindow> Create(Emulator* emulator,
xe::ui::Loop* loop);
Emulator* emulator() const { return emulator_; }
xe::ui::Loop* loop() const { return loop_; }
xe::ui::Window* window() const { return window_.get(); }
void OnFocus() override;
void OnDetached() override;
void OnExecutionPaused() override;
void OnExecutionContinued() override;
void OnExecutionEnded() override;
void OnStepCompleted(xe::kernel::XThread* thread) override;
void OnBreakpointHit(Breakpoint* breakpoint,
xe::kernel::XThread* thread) override;
private:
explicit DebugWindow(Emulator* emulator, xe::ui::Loop* loop);
bool Initialize();
void DrawFrame();
void DrawToolbar();
void DrawFunctionsPane();
void DrawSourcePane();
void DrawGuestFunctionSource();
void DrawMachineCodeSource(const uint8_t* ptr, size_t length);
void DrawBreakpointGutterButton(bool has_breakpoint,
CodeBreakpoint::AddressType address_type,
uint64_t address);
void ScrollToSourceIfPcChanged();
void DrawRegistersPane();
bool DrawRegisterTextBox(int id, uint32_t* value);
bool DrawRegisterTextBox(int id, uint64_t* value);
bool DrawRegisterTextBox(int id, double* value);
bool DrawRegisterTextBoxes(int id, float* value);
void DrawThreadsPane();
void DrawMemoryPane();
void DrawBreakpointsPane();
void DrawLogPane();
void SelectThreadStackFrame(kernel::XThread* thread, size_t stack_frame_index,
bool always_navigate);
void NavigateToFunction(cpu::Function* function, uint32_t guest_pc = 0,
uint64_t host_pc = 0);
// void NavigateToMemory(uint64_t address, uint64_t length = 0);
// void ToggleLogThreadFocus(thread | nullptr);
void UpdateCache();
void CreateCodeBreakpoint(CodeBreakpoint::AddressType address_type,
uint64_t address);
void DeleteCodeBreakpoint(CodeBreakpoint* breakpoint);
void DeleteBreakpoint(Breakpoint* breakpoint);
CodeBreakpoint* LookupBreakpointAtAddress(
CodeBreakpoint::AddressType address_type, uint64_t address);
Emulator* emulator_ = nullptr;
Debugger* debugger_ = nullptr;
xe::ui::Loop* loop_ = nullptr;
std::unique_ptr<xe::ui::Window> window_;
std::unique_ptr<ImGuiRenderer> imgui_renderer_;
uint64_t last_draw_tick_count_ = 0;
uintptr_t capstone_handle_ = 0;
// Cached debugger data, updated on every break before a frame is drawn.
// Prefer putting stuff here that will be queried either each frame or
// multiple times per frame to avoid expensive redundant work.
struct ImDataCache {
bool is_running = false;
std::vector<kernel::object_ref<kernel::XModule>> modules;
std::vector<ThreadExecutionInfo*> thread_execution_infos;
} cache_;
enum class RegisterGroup {
kGuestGeneral,
kGuestFloat,
kGuestVector,
kHostGeneral,
kHostVector,
};
// The current state of the UI. Use this to synchronize multiple parts of the
// UI.
struct ImState {
static const int kRightPaneThreads = 0;
static const int kRightPaneMemory = 1;
int right_pane_tab = kRightPaneThreads;
xe::kernel::XThread* thread = nullptr;
ThreadExecutionInfo* thread_info = nullptr;
size_t thread_stack_frame_index = 0;
bool has_changed_thread = false;
xe::cpu::Function* function = nullptr;
uint64_t last_host_pc = 0;
bool has_changed_pc = false;
int source_display_mode = 3;
RegisterGroup register_group = RegisterGroup::kGuestGeneral;
bool register_input_hex = true;
struct {
char kernel_call_filter[64] = {0};
std::vector<std::unique_ptr<Breakpoint>> all_breakpoints;
std::unordered_map<uint32_t, CodeBreakpoint*>
code_breakpoints_by_guest_address;
std::unordered_map<uintptr_t, CodeBreakpoint*>
code_breakpoints_by_host_address;
} breakpoints;
xe::kernel::XThread* isolated_log_thread = nullptr;
} state_;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_DEBUG_WINDOW_H_

View File

@@ -1,32 +0,0 @@
/**
******************************************************************************
* 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 <gflags/gflags.h>
#include <cstring>
#include "xenia/base/main.h"
#include "xenia/debug/ui/application.h"
namespace xe {
namespace debug {
namespace ui {
int main(const std::vector<std::wstring>& args) {
auto app = Application::Create();
app->loop()->AwaitQuit();
return 0;
}
} // namespace ui
} // namespace debug
} // namespace xe
DEFINE_ENTRY_POINT(L"xenia-debug-ui", L"xenia-debug-ui ??",
xe::debug::ui::main);

View File

@@ -1,6 +0,0 @@
//{{NO_DEPENDENCIES}}
#include "third_party\\elemental-forms\\resources.rc"
//IDR_xe_debug_ui_resources_skin_bg_tile_png RCDATA ".\\resources\\skin\\bg_tile.png"
IDR_xe_debug_ui_resources_smaller_skin_skin_tb_txt RCDATA ".\\resources\\smaller_skin\\skin.tb.txt"

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,55 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_IMGUI_RENDERER_H_
#define XENIA_DEBUG_UI_IMGUI_RENDERER_H_
#include <memory>
#include "third_party/imgui/imgui.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/ui/gl/circular_buffer.h"
#include "xenia/ui/gl/gl_context.h"
#include "xenia/ui/window.h"
namespace xe {
namespace debug {
namespace ui {
class ImGuiRenderer {
public:
ImGuiRenderer(xe::ui::Window* window, xe::ui::GraphicsContext* context);
~ImGuiRenderer();
private:
static ImGuiRenderer* global_renderer_;
void Initialize();
void InitializeShaders();
void InitializeFontTextures();
void Shutdown();
void RenderDrawLists(ImDrawData* data);
xe::ui::Window* window_ = nullptr;
xe::ui::GraphicsContext* context_ = nullptr;
GLuint program_ = 0;
GLuint vao_ = 0;
xe::ui::gl::CircularBuffer vertex_buffer_;
xe::ui::gl::CircularBuffer index_buffer_;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_IMGUI_RENDERER_H_

View File

@@ -1,197 +0,0 @@
/**
******************************************************************************
* 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/ui/main_window.h"
#include "el/animation_manager.h"
#include "el/io/file_manager.h"
#include "el/util/debug.h"
#include "xenia/base/clock.h"
#include "xenia/base/logging.h"
#include "xenia/base/platform.h"
#include "xenia/base/threading.h"
#include "xenia/ui/gl/gl_context.h"
#if XE_PLATFORM_WIN32
#include "el/io/win32_res_file_system_win.h"
#endif // XE_PLATFORM_WIN32
namespace xe {
namespace debug {
namespace ui {
using xe::ui::MenuItem;
const std::wstring kBaseTitle = L"xenia debugger";
MainWindow::MainWindow(Application* app) : app_(app) {}
MainWindow::~MainWindow() = default;
bool MainWindow::Initialize() {
client_ = app_->client();
window_ = xe::ui::Window::Create(app()->loop(), kBaseTitle);
if (!window_) {
return false;
}
window_->Initialize();
window_->set_context(xe::ui::gl::GLContext::Create(window_.get()));
#if XE_PLATFORM_WIN32
el::io::FileManager::RegisterFileSystem(
std::make_unique<el::io::Win32ResFileSystem>(
"IDR_xe_debug_ui_resources_"));
#endif // XE_PLATFORM_WIN32
window_->LoadSkin("smaller_skin/skin.tb.txt");
window_->on_closed.AddListener(std::bind(&MainWindow::OnClose, this));
window_->on_key_down.AddListener([this](xe::ui::KeyEvent* e) {
bool handled = true;
switch (e->key_code()) {
case 0x1B: { // VK_ESCAPE
// Allow users to escape fullscreen (but not enter it).
if (window_->is_fullscreen()) {
window_->ToggleFullscreen(false);
}
} break;
case 0x70: { // VK_F1
LaunchBrowser("http://xenia.jp");
} break;
default: { handled = false; } break;
}
e->set_handled(handled);
});
// Main menu.
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&File");
{
file_menu->AddChild(MenuItem::Create(MenuItem::Type::kString, L"E&xit",
L"Alt+F4",
[this]() { window_->Close(); }));
}
main_menu->AddChild(std::move(file_menu));
// Help menu.
auto help_menu = MenuItem::Create(MenuItem::Type::kPopup, L"&Help");
{
help_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, L"&Website...", L"F1",
[]() { LaunchBrowser("http://xenia.jp"); }));
help_menu->AddChild(
MenuItem::Create(MenuItem::Type::kString, L"&About...",
[]() { LaunchBrowser("http://xenia.jp/about/"); }));
}
main_menu->AddChild(std::move(help_menu));
window_->set_main_menu(std::move(main_menu));
window_->Resize(1440, 1200);
BuildUI();
return true;
}
void MainWindow::BuildUI() {
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto root_element = window_->root_element();
form_ = std::make_unique<el::Form>();
form_->set_settings(el::FormSettings::kFullScreen);
root_element->AddChild(form_.get());
auto root_node =
LayoutBoxNode()
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kY)
.child(
LayoutBoxNode()
.id("toolbar_box")
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kGravity)
.distribution_position(LayoutDistributionPosition::kLeftTop)
.child(ButtonNode("Pause").id("pause_button"))
.child(ButtonNode("Continue").id("resume_button")))
.child(
SplitContainerNode()
.id("split_container")
.gravity(Gravity::kAll)
.axis(Axis::kX)
.fixed_pane(FixedPane::kSecond)
.min(128)
.value(250)
.pane(TabContainerNode()
.id("tab_container")
.gravity(Gravity::kAll)
.align(Align::kTop))
.pane(LayoutBoxNode().id("log_box").gravity(Gravity::kAll)));
form_->LoadNodeTree(root_node);
form_->GetElementsById({
{TBIDC("split_container"), &ui_.split_container},
{TBIDC("toolbar_box"), &ui_.toolbar_box},
{TBIDC("tab_container"), &ui_.tab_container},
});
handler_ = std::make_unique<el::EventHandler>(form_.get());
handler_->Listen(el::EventType::kClick, TBIDC("pause_button"),
[this](const el::Event& ev) {
client_->Interrupt();
return true;
});
handler_->Listen(el::EventType::kClick, TBIDC("resume_button"),
[this](const el::Event& ev) {
client_->Continue();
return true;
});
system()->on_execution_state_changed.AddListener(
[this]() { UpdateElementState(); });
ui_.tab_container->tab_bar()->LoadNodeTree(ButtonNode(cpu_view_.name()));
ui_.tab_container->content_root()->AddChild(cpu_view_.BuildUI());
cpu_view_.Setup(Application::current()->client());
ui_.tab_container->tab_bar()->LoadNodeTree(ButtonNode(gpu_view_.name()));
ui_.tab_container->content_root()->AddChild(gpu_view_.BuildUI());
gpu_view_.Setup(Application::current()->client());
UpdateElementState();
el::util::ShowDebugInfoSettingsForm(root_element);
}
void MainWindow::UpdateElementState() {
bool is_running = client_->execution_state() == ExecutionState::kRunning;
el::TabContainer* tab_container;
el::Button* pause_button;
el::Button* resume_button;
form_->GetElementsById({
{TBIDC("tab_container"), &tab_container},
{TBIDC("pause_button"), &pause_button},
{TBIDC("resume_button"), &resume_button},
});
tab_container->set_enabled(!is_running);
pause_button->set_enabled(is_running);
resume_button->set_enabled(!is_running);
}
void MainWindow::OnClose() { app_->Quit(); }
} // namespace ui
} // namespace debug
} // namespace xe

View File

@@ -1,63 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_MAIN_WINDOW_H_
#define XENIA_DEBUG_UI_MAIN_WINDOW_H_
#include <memory>
#include "el/elements.h"
#include "xenia/debug/ui/application.h"
#include "xenia/debug/ui/views/cpu/cpu_view.h"
#include "xenia/debug/ui/views/gpu/gpu_view.h"
#include "xenia/ui/window.h"
namespace xe {
namespace debug {
namespace ui {
class MainWindow {
public:
explicit MainWindow(Application* app);
~MainWindow();
Application* app() const { return app_; }
xe::ui::Loop* loop() const { return app_->loop(); }
model::System* system() const { return app_->system(); }
bool Initialize();
// void NavigateToCpuView(uint32_t address);
private:
void BuildUI();
void UpdateElementState();
void OnClose();
Application* app_ = nullptr;
xe::debug::DebugClient* client_ = nullptr;
std::unique_ptr<xe::ui::Window> window_;
std::unique_ptr<el::Form> form_;
struct {
el::SplitContainer* split_container;
el::LayoutBox* toolbar_box;
el::TabContainer* tab_container;
} ui_ = {0};
std::unique_ptr<el::EventHandler> handler_;
views::cpu::CpuView cpu_view_;
views::gpu::GpuView gpu_view_;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_MAIN_WINDOW_H_

View File

@@ -1,22 +0,0 @@
/**
******************************************************************************
* 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/ui/model/function.h"
namespace xe {
namespace debug {
namespace ui {
namespace model {
//
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe

View File

@@ -1,29 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_MODEL_FUNCTION_H_
#define XENIA_DEBUG_UI_MODEL_FUNCTION_H_
#include <cstdint>
namespace xe {
namespace debug {
namespace ui {
namespace model {
class Function {
public:
};
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_MODEL_FUNCTION_H_

View File

@@ -1,26 +0,0 @@
/**
******************************************************************************
* 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/ui/model/module.h"
#include "xenia/debug/ui/model/system.h"
namespace xe {
namespace debug {
namespace ui {
namespace model {
void Module::Update(const proto::ModuleListEntry* entry) {
std::memcpy(&entry_, entry, sizeof(entry_));
}
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe

View File

@@ -1,50 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_MODEL_MODULE_H_
#define XENIA_DEBUG_UI_MODEL_MODULE_H_
#include <cstdint>
#include <string>
#include "xenia/debug/proto/xdp_protocol.h"
namespace xe {
namespace debug {
namespace ui {
namespace model {
class System;
class Module {
public:
explicit Module(System* system) : system_(system) {}
bool is_dead() const { return is_dead_; }
void set_dead(bool is_dead) { is_dead_ = is_dead; }
uint32_t module_handle() const { return entry_.module_handle; }
bool is_kernel_module() const { return entry_.is_kernel_module; }
std::string name() const { return entry_.name; }
const proto::ModuleListEntry* entry() const { return &entry_; }
void Update(const proto::ModuleListEntry* entry);
private:
System* system_ = nullptr;
bool is_dead_ = false;
proto::ModuleListEntry entry_ = {0};
};
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_MODEL_MODULE_H_

View File

@@ -1,128 +0,0 @@
/**
******************************************************************************
* 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/ui/model/system.h"
#include <unordered_set>
namespace xe {
namespace debug {
namespace ui {
namespace model {
using namespace xe::debug::proto; // NOLINT(build/namespaces)
System::System(xe::ui::Loop* loop, DebugClient* client)
: 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::vector<Module*> result;
for (auto& module : modules_) {
result.push_back(module.get());
}
return result;
}
std::vector<Thread*> System::threads() {
std::vector<Thread*> result;
for (auto& thread : threads_) {
result.push_back(thread.get());
}
return result;
}
Module* System::GetModuleByHandle(uint32_t module_handle) {
auto it = modules_by_handle_.find(module_handle);
return it != modules_by_handle_.end() ? it->second : nullptr;
}
Thread* System::GetThreadByHandle(uint32_t thread_handle) {
auto it = threads_by_handle_.find(thread_handle);
return it != threads_by_handle_.end() ? it->second : nullptr;
}
void System::OnExecutionStateChanged(ExecutionState execution_state) {
on_execution_state_changed();
}
void System::OnModulesUpdated(std::vector<const ModuleListEntry*> entries) {
std::unordered_set<uint32_t> 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<Module>(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);
}
}
on_modules_updated();
}
void System::OnThreadsUpdated(std::vector<const ThreadListEntry*> entries) {
std::unordered_set<uint32_t> 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<Thread>(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);
}
}
on_threads_updated();
}
void System::OnThreadStateUpdated(
uint32_t thread_handle, const ThreadStateEntry* entry,
std::vector<const ThreadCallStackFrame*> frames) {
auto thread = threads_by_handle_[thread_handle];
if (thread != nullptr) {
thread->UpdateState(entry, std::move(frames));
on_thread_state_updated(thread);
}
}
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe

View File

@@ -1,75 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_MODEL_SYSTEM_H_
#define XENIA_DEBUG_UI_MODEL_SYSTEM_H_
#include <cstdint>
#include <memory>
#include <unordered_map>
#include <vector>
#include "xenia/base/delegate.h"
#include "xenia/debug/debug_client.h"
#include "xenia/debug/ui/model/function.h"
#include "xenia/debug/ui/model/module.h"
#include "xenia/debug/ui/model/thread.h"
#include "xenia/ui/loop.h"
namespace xe {
namespace debug {
namespace ui {
namespace model {
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_; }
ExecutionState execution_state();
std::vector<Module*> modules();
std::vector<Thread*> threads();
Module* GetModuleByHandle(uint32_t module_handle);
Thread* GetThreadByHandle(uint32_t thread_handle);
Delegate<void> on_execution_state_changed;
Delegate<void> on_modules_updated;
Delegate<void> on_threads_updated;
Delegate<Thread*> on_thread_state_updated;
private:
void OnExecutionStateChanged(ExecutionState execution_state) override;
void OnModulesUpdated(
std::vector<const proto::ModuleListEntry*> entries) override;
void OnThreadsUpdated(
std::vector<const proto::ThreadListEntry*> entries) override;
void OnThreadStateUpdated(
uint32_t thread_handle, const ThreadStateEntry* entry,
std::vector<const ThreadCallStackFrame*> frames) override;
xe::ui::Loop* loop_ = nullptr;
DebugClient* client_ = nullptr;
std::vector<std::unique_ptr<Module>> modules_;
std::unordered_map<uint32_t, Module*> modules_by_handle_;
std::vector<std::unique_ptr<Thread>> threads_;
std::unordered_map<uint32_t, Thread*> threads_by_handle_;
};
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_MODEL_SYSTEM_H_

View File

@@ -1,50 +0,0 @@
/**
******************************************************************************
* 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/ui/model/thread.h"
#include "xenia/debug/ui/model/system.h"
namespace xe {
namespace debug {
namespace ui {
namespace model {
Thread::Thread(System* system) : system_(system) {
state_ = memory::AlignedAlloc<proto::ThreadStateEntry>(64);
}
Thread::~Thread() { memory::AlignedFree(state_); }
std::string Thread::to_string() {
std::string value = entry_.name;
if (is_host_thread()) {
value += " (host)";
}
return value;
}
void Thread::Update(const proto::ThreadListEntry* entry) {
std::memcpy(&entry_, entry, sizeof(entry_));
}
void Thread::UpdateState(
const proto::ThreadStateEntry* entry,
std::vector<const proto::ThreadCallStackFrame*> frames) {
std::memcpy(state_, entry, sizeof(*state_));
call_stack_.resize(frames.size());
for (size_t i = 0; i < frames.size(); ++i) {
std::memcpy(call_stack_.data() + i, frames[i], sizeof(Frame));
}
}
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe

View File

@@ -1,69 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_MODEL_THREAD_H_
#define XENIA_DEBUG_UI_MODEL_THREAD_H_
#include <cstdint>
#include <string>
#include <vector>
#include "xenia/debug/proto/xdp_protocol.h"
namespace xe {
namespace debug {
namespace ui {
namespace model {
class System;
class Thread {
public:
using Frame = proto::ThreadCallStackFrame;
explicit Thread(System* system);
~Thread();
bool is_dead() const { return is_dead_; }
void set_dead(bool is_dead) { is_dead_ = is_dead; }
const proto::ThreadListEntry* entry() const { return &entry_; }
const proto::ThreadStateEntry* state() const { return state_; }
uint32_t thread_handle() const { return entry_.thread_handle; }
uint32_t thread_id() const { return entry_.thread_id; }
bool is_host_thread() const { return entry_.is_host_thread; }
std::string name() const { return entry_.name; }
const cpu::frontend::PPCContext* guest_context() const {
return &state_->guest_context;
}
const cpu::X64Context* host_context() const { return &state_->host_context; }
const std::vector<Frame>& call_stack() const { return call_stack_; }
std::string to_string();
void Update(const proto::ThreadListEntry* entry);
void UpdateState(const proto::ThreadStateEntry* entry,
std::vector<const proto::ThreadCallStackFrame*> frames);
private:
System* system_ = nullptr;
bool is_dead_ = false;
proto::ThreadListEntry entry_ = {0};
proto::ThreadStateEntry* state_ = nullptr;
std::vector<Frame> call_stack_;
};
} // namespace model
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_MODEL_THREAD_H_

View File

@@ -3,56 +3,25 @@ include(project_root.."/build_tools")
group("src")
project("xenia-debug-ui")
uuid("ed128630-8b4e-4dd7-afc9-ef00052fe3a7")
kind("WindowedApp")
uuid("9193a274-f4c2-4746-bd85-93fcfc5c3e38")
kind("StaticLib")
language("C++")
links({
"elemental-forms",
"gflags",
"xenia-apu",
"xenia-apu-nop",
"xenia-apu-xaudio2",
"glew",
"imgui",
"xenia-base",
"xenia-core",
"xenia-cpu",
"xenia-cpu-backend-x64",
"xenia-debug",
"xenia-gpu",
"xenia-gpu-gl4",
"xenia-hid-nop",
"xenia-hid-winkey",
"xenia-hid-xinput",
"xenia-kernel",
"xenia-ui",
"xenia-ui-gl",
"xenia-vfs",
})
flags({
"WinMain", -- Use WinMain instead of main.
})
defines({
"GLEW_STATIC=1",
"GLEW_MX=1",
})
includedirs({
project_root.."/third_party/elemental-forms/src",
project_root.."/build_tools/third_party/gflags/src",
project_root.."/third_party/elemental-forms/src",
})
recursive_platform_files()
files({
"debugger_main.cc",
"../../base/main_"..platform_suffix..".cc",
})
files({
"debugger_resources.rc",
})
resincludedirs({
project_root,
project_root.."/third_party/elemental-forms",
})
filter("platforms:Windows")
debugdir(project_root)
debugargs({
"--flagfile=scratch/flags.txt",
"2>&1",
"1>scratch/stdout-debugger.txt",
})
local_platform_files()

View File

@@ -1,11 +0,0 @@
elements
Button
padding 3 4
Button.flat
padding 3 4
ButtonInGroup
padding 3 4
Box
padding 5
ListItem
padding 1 4

View File

@@ -1,51 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_VIEW_H_
#define XENIA_DEBUG_UI_VIEW_H_
#include <memory>
#include <string>
#include "el/elements.h"
#include "el/event_handler.h"
#include "xenia/debug/ui/application.h"
namespace xe {
namespace debug {
namespace ui {
class View {
public:
virtual ~View() = default;
std::string name() const { return name_; }
el::LayoutBox* root_element() { return &root_element_; }
xe::ui::Loop* loop() const { return Application::current()->loop(); }
DebugClient* client() const { return client_; }
model::System* system() const { return Application::current()->system(); }
virtual el::Element* BuildUI() = 0;
virtual void Setup(xe::debug::DebugClient* client) = 0;
protected:
explicit View(std::string name) : name_(name) {}
std::string name_;
el::LayoutBox root_element_;
std::unique_ptr<el::EventHandler> handler_;
xe::debug::DebugClient* client_ = nullptr;
};
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_VIEW_H_

View File

@@ -1,152 +0,0 @@
/**
******************************************************************************
* 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/ui/views/cpu/call_stack_control.h"
#include "el/animation_manager.h"
#include "xenia/base/string_buffer.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
class CallStackItem : public el::GenericStringItem {
public:
CallStackItem(size_t ordinal, const ThreadCallStackFrame* frame)
: GenericStringItem(""), ordinal_(ordinal), frame_(frame) {
StringBuffer sb;
if (frame_->guest_pc) {
sb.AppendFormat(" %.2lld %.16llX %.8X %s", ordinal_, frame_->host_pc,
frame_->guest_pc, frame_->name);
} else {
sb.AppendFormat(" %.2lld %.16llX %s", ordinal_, frame_->host_pc,
frame_->name);
}
str = sb.to_string();
}
private:
size_t ordinal_ = 0;
const ThreadCallStackFrame* frame_ = nullptr;
};
class CallStackItemElement : public el::LayoutBox {
public:
CallStackItemElement(CallStackItem* item, CallStackItemSource* source,
el::ListItemObserver* source_viewer, size_t index)
: item_(item),
source_(source),
source_viewer_(source_viewer),
index_(index) {
set_background_skin(TBIDC("ListItem"));
set_axis(el::Axis::kY);
set_gravity(el::Gravity::kAll);
set_layout_position(el::LayoutPosition::kLeftTop);
set_layout_distribution(el::LayoutDistribution::kAvailable);
set_layout_distribution_position(el::LayoutDistributionPosition::kLeftTop);
set_layout_size(el::LayoutSize::kAvailable);
set_paint_overflow_fadeout(false);
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto node = LabelNode(item_->str.c_str())
.gravity(Gravity::kLeft)
.ignore_input(true)
.text_align(el::TextAlign::kLeft);
content_root()->LoadNodeTree(node);
}
bool OnEvent(const el::Event& ev) override {
return el::LayoutBox::OnEvent(ev);
}
private:
CallStackItem* item_ = nullptr;
CallStackItemSource* source_ = nullptr;
el::ListItemObserver* source_viewer_ = nullptr;
size_t index_ = 0;
};
class CallStackItemSource : public el::ListItemSourceList<CallStackItem> {
public:
el::Element* CreateItemElement(size_t index,
el::ListItemObserver* viewer) override {
return new CallStackItemElement(at(index), this, viewer, index);
}
};
CallStackControl::CallStackControl()
: item_source_(new CallStackItemSource()) {}
CallStackControl::~CallStackControl() = default;
el::Element* CallStackControl::BuildUI() {
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto node =
LayoutBoxNode()
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.child(ListBoxNode().id("stack_listbox").gravity(Gravity::kAll));
root_element_.set_gravity(Gravity::kAll);
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
root_element_.LoadNodeTree(node);
auto stack_listbox =
root_element_.GetElementById<el::ListBox>(TBIDC("stack_listbox"));
stack_listbox->set_source(item_source_.get());
stack_listbox->scroll_container()->set_scroll_mode(
el::ScrollMode::kAutoXAutoY);
handler_ = std::make_unique<el::EventHandler>(&root_element_);
return &root_element_;
}
void CallStackControl::Setup(DebugClient* client) {
client_ = client;
system()->on_thread_state_updated.AddListener([this](model::Thread* thread) {
if (thread == thread_) {
InvalidateCallStack();
}
});
}
void CallStackControl::set_thread(model::Thread* thread) {
thread_ = thread;
InvalidateCallStack();
}
void CallStackControl::InvalidateCallStack() {
item_source_->clear();
if (!thread_) {
return;
}
auto& call_stack = thread_->call_stack();
for (size_t i = 0; i < call_stack.size(); ++i) {
auto& frame = call_stack[i];
size_t ordinal = call_stack.size() - i - 1;
auto item = std::make_unique<CallStackItem>(ordinal, &frame);
item->tag.set_integer(static_cast<int>(i));
item_source_->push_back(std::move(item));
}
item_source_->InvokeAllItemsRemoved();
}
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe

View File

@@ -1,51 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_
#define XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_
#include <memory>
#include <string>
#include "xenia/debug/ui/control.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
class CallStackItemSource;
class CallStackControl : public Control {
public:
CallStackControl();
~CallStackControl() override;
el::Element* BuildUI() override;
void Setup(xe::debug::DebugClient* client) override;
model::Thread* thread() const { return thread_; }
void set_thread(model::Thread* thread);
private:
void InvalidateCallStack();
model::Thread* thread_ = nullptr;
std::unique_ptr<CallStackItemSource> item_source_;
};
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_VIEWS_CPU_CALL_STACK_CONTROL_H_

View File

@@ -1,278 +0,0 @@
/**
******************************************************************************
* 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/ui/views/cpu/cpu_view.h"
#include "el/animation_manager.h"
#include "xenia/base/string_buffer.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
CpuView::CpuView()
: View("CPU"),
gr_registers_control_(RegisterSet::kGeneral),
fr_registers_control_(RegisterSet::kFloat),
vr_registers_control_(RegisterSet::kVector),
host_registers_control_(RegisterSet::kHost) {}
CpuView::~CpuView() = default;
el::Element* CpuView::BuildUI() {
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto functions_node =
LayoutBoxNode()
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kY)
.child(LayoutBoxNode()
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kX)
.skin("button_group")
.child(DropDownButtonNode().id("module_dropdown")))
.child(ListBoxNode().id("function_listbox").gravity(Gravity::kAll))
.child(LayoutBoxNode()
.gravity(Gravity::kBottom | Gravity::kLeftRight)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kX)
.child(TextBoxNode()
.type(EditType::kSearch)
.placeholder("Filter")));
auto register_list_node = ListBoxNode()
.gravity(Gravity::kAll)
.item("A")
.item("A")
.item("A")
.item("A");
auto source_registers_node =
TabContainerNode()
.gravity(Gravity::kAll)
.tab(ButtonNode("GR"), LabelNode("<register list control>")
.id("gr_registers_placeholder"))
.tab(ButtonNode("FR"), LabelNode("<register list control>")
.id("fr_registers_placeholder"))
.tab(ButtonNode("VR"), LabelNode("<register list control>")
.id("vr_registers_placeholder"))
.tab(ButtonNode("X64"), LabelNode("<register list control>")
.id("host_registers_placeholder"));
auto source_tools_node =
TabContainerNode()
.gravity(Gravity::kAll)
.align(Align::kLeft)
.tab(ButtonNode("Stack"),
LabelNode("<callstack control>").id("call_stack_placeholder"))
.tab(ButtonNode("BPs"), LabelNode("BREAKPOINTS"));
auto source_pane_node =
LayoutBoxNode()
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kY)
.child(
LayoutBoxNode()
.id("source_toolbar")
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kGravity)
.distribution_position(LayoutDistributionPosition::kLeftTop)
.axis(Axis::kX)
.child(DropDownButtonNode().id("thread_dropdown")))
.child(LayoutBoxNode()
.id("source_content")
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.child(SplitContainerNode()
.gravity(Gravity::kAll)
.axis(Axis::kX)
.fixed_pane(FixedPane::kSecond)
.value(250)
.pane(SplitContainerNode()
.gravity(Gravity::kAll)
.axis(Axis::kY)
.fixed_pane(FixedPane::kSecond)
.value(240)
.pane(LabelNode("<source control>")
.id("source_placeholder"))
.pane(source_registers_node))
.pane(source_tools_node)));
auto memory_pane_node = LabelNode("MEMORY");
auto node = SplitContainerNode()
.gravity(Gravity::kAll)
.axis(Axis::kY)
.min(128)
.max(512)
.value(128)
.pane(functions_node)
.pane(SplitContainerNode()
.gravity(Gravity::kAll)
.axis(Axis::kY)
.value(800)
.pane(source_pane_node)
.pane(memory_pane_node));
root_element_.set_gravity(Gravity::kAll);
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
root_element_.LoadNodeTree(node);
el::Label* source_placeholder;
el::Label* gr_registers_placeholder;
el::Label* fr_registers_placeholder;
el::Label* vr_registers_placeholder;
el::Label* host_registers_placeholder;
el::Label* call_stack_placeholder;
root_element_.GetElementsById({
{TBIDC("source_placeholder"), &source_placeholder},
{TBIDC("gr_registers_placeholder"), &gr_registers_placeholder},
{TBIDC("fr_registers_placeholder"), &fr_registers_placeholder},
{TBIDC("vr_registers_placeholder"), &vr_registers_placeholder},
{TBIDC("host_registers_placeholder"), &host_registers_placeholder},
{TBIDC("call_stack_placeholder"), &call_stack_placeholder},
});
source_placeholder->parent()->ReplaceChild(source_placeholder,
source_control_.BuildUI());
source_control_.Setup(client_);
gr_registers_placeholder->parent()->ReplaceChild(
gr_registers_placeholder, gr_registers_control_.BuildUI());
gr_registers_control_.Setup(client_);
fr_registers_placeholder->parent()->ReplaceChild(
fr_registers_placeholder, fr_registers_control_.BuildUI());
fr_registers_control_.Setup(client_);
vr_registers_placeholder->parent()->ReplaceChild(
vr_registers_placeholder, vr_registers_control_.BuildUI());
vr_registers_control_.Setup(client_);
host_registers_placeholder->parent()->ReplaceChild(
host_registers_placeholder, host_registers_control_.BuildUI());
host_registers_control_.Setup(client_);
call_stack_placeholder->parent()->ReplaceChild(call_stack_placeholder,
call_stack_control_.BuildUI());
call_stack_control_.Setup(client_);
handler_ = std::make_unique<el::EventHandler>(&root_element_);
handler_->Listen(el::EventType::kChanged, TBIDC("module_dropdown"),
[this](const el::Event& ev) {
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;
}
SwitchCurrentThread(current_thread_);
return true;
});
return &root_element_;
}
void CpuView::Setup(DebugClient* client) {
client_ = client;
system()->on_execution_state_changed.AddListener(
[this]() { UpdateElementState(); });
system()->on_modules_updated.AddListener([this]() { UpdateModuleList(); });
system()->on_threads_updated.AddListener([this]() { UpdateThreadList(); });
}
void CpuView::UpdateElementState() {
root_element_.GetElementsById({
//
});
}
void CpuView::UpdateModuleList() {
el::DropDownButton* module_dropdown;
root_element_.GetElementsById({
{TBIDC("module_dropdown"), &module_dropdown},
});
auto module_items = module_dropdown->default_source();
auto modules = system()->modules();
bool is_first = module_items->size() == 0;
for (size_t i = module_items->size(); i < modules.size(); ++i) {
auto module = modules[i];
auto item = std::make_unique<el::GenericStringItem>(module->name());
item->id = module->module_handle();
module_items->push_back(std::move(item));
}
if (is_first) {
module_dropdown->set_value(static_cast<int>(module_items->size() - 1));
}
}
void CpuView::UpdateFunctionList() {
el::DropDownButton* module_dropdown;
el::ListBox* function_listbox;
root_element_.GetElementsById({
{TBIDC("module_dropdown"), &module_dropdown},
{TBIDC("function_listbox"), &function_listbox},
});
auto module_handle = module_dropdown->selected_item_id();
auto module = system()->GetModuleByHandle(module_handle);
if (!module) {
function_listbox->default_source()->clear();
return;
}
// TODO(benvanik): fetch list.
}
void CpuView::UpdateThreadList() {
el::DropDownButton* thread_dropdown;
root_element_.GetElementsById({
{TBIDC("thread_dropdown"), &thread_dropdown},
});
auto thread_items = thread_dropdown->default_source();
auto threads = system()->threads();
bool is_first = thread_items->size() == 0;
for (size_t i = 0; i < thread_items->size(); ++i) {
auto thread = threads[i];
auto item = thread_items->at(i);
item->str = thread->to_string();
}
for (size_t i = thread_items->size(); i < threads.size(); ++i) {
auto thread = threads[i];
auto item = std::make_unique<el::GenericStringItem>(thread->to_string());
item->id = thread->thread_handle();
thread_items->push_back(std::move(item));
}
if (is_first) {
thread_dropdown->set_value(static_cast<int>(thread_items->size() - 1));
}
}
void CpuView::SwitchCurrentThread(model::Thread* thread) {
current_thread_ = thread;
source_control_.set_thread(thread);
gr_registers_control_.set_thread(thread);
fr_registers_control_.set_thread(thread);
vr_registers_control_.set_thread(thread);
host_registers_control_.set_thread(thread);
call_stack_control_.set_thread(thread);
}
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe

View File

@@ -1,61 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_VIEWS_CPU_CPU_VIEW_H_
#define XENIA_DEBUG_UI_VIEWS_CPU_CPU_VIEW_H_
#include <memory>
#include <string>
#include "xenia/debug/ui/view.h"
#include "xenia/debug/ui/views/cpu/call_stack_control.h"
#include "xenia/debug/ui/views/cpu/register_list_control.h"
#include "xenia/debug/ui/views/cpu/source_control.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
class CpuView : public View {
public:
CpuView();
~CpuView() override;
el::Element* BuildUI() override;
void Setup(xe::debug::DebugClient* client) override;
protected:
void UpdateElementState();
void UpdateModuleList();
void UpdateFunctionList();
void UpdateThreadList();
void SwitchCurrentThread(model::Thread* thread);
// TODO(benvanik): better state machine.
model::Thread* current_thread_ = nullptr;
SourceControl source_control_;
RegisterListControl gr_registers_control_;
RegisterListControl fr_registers_control_;
RegisterListControl vr_registers_control_;
RegisterListControl host_registers_control_;
CallStackControl call_stack_control_;
};
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_VIEWS_CPU_CPU_VIEW_H_

View File

@@ -1,325 +0,0 @@
/**
******************************************************************************
* 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/ui/views/cpu/register_list_control.h"
#include <cinttypes>
#include <cstdlib>
#include "el/animation_manager.h"
#include "xenia/base/string_buffer.h"
#include "xenia/base/string_util.h"
#include "xenia/cpu/x64_context.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
using xe::cpu::frontend::PPCRegister;
using xe::cpu::X64Register;
enum class RegisterType {
kControl,
kInteger,
kFloat,
kVector,
};
class RegisterItem : public el::GenericStringItem {
public:
const char* name() { return name_.c_str(); }
virtual void set_thread(model::Thread* thread) { thread_ = thread; }
protected:
RegisterItem(RegisterSet set, RegisterType type, int ordinal)
: GenericStringItem(""), set_(set), type_(type), ordinal_(ordinal) {
tag.set_integer(ordinal);
}
RegisterSet set_;
RegisterType type_;
int ordinal_;
model::Thread* thread_ = nullptr;
std::string name_;
};
class GuestRegisterItem : public RegisterItem {
public:
GuestRegisterItem(RegisterSet set, RegisterType type, PPCRegister reg)
: RegisterItem(set, type, static_cast<int>(reg)), reg_(reg) {
name_ = xe::cpu::frontend::PPCContext::GetRegisterName(reg_);
}
void set_thread(model::Thread* thread) override {
RegisterItem::set_thread(thread);
if (thread_) {
str = thread_->guest_context()->GetStringFromValue(reg_);
} else {
str = "unknown";
}
}
private:
PPCRegister reg_;
};
class HostRegisterItem : public RegisterItem {
public:
HostRegisterItem(RegisterSet set, RegisterType type, X64Register reg)
: RegisterItem(set, type, static_cast<int>(reg)), reg_(reg) {
name_ = xe::cpu::X64Context::GetRegisterName(reg_);
}
void set_thread(model::Thread* thread) override {
RegisterItem::set_thread(thread);
if (thread_) {
str = thread_->host_context()->GetStringFromValue(reg_);
} else {
str = "unknown";
}
}
private:
X64Register reg_;
};
class RegisterItemElement : public el::LayoutBox {
public:
RegisterItemElement(RegisterItem* item, RegisterItemSource* source,
el::ListItemObserver* source_viewer, size_t index)
: item_(item),
source_(source),
source_viewer_(source_viewer),
index_(index) {
set_background_skin(TBIDC("ListItem"));
set_axis(el::Axis::kY);
set_gravity(el::Gravity::kAll);
set_layout_position(el::LayoutPosition::kLeftTop);
set_layout_distribution(el::LayoutDistribution::kAvailable);
set_layout_distribution_position(el::LayoutDistributionPosition::kLeftTop);
set_layout_size(el::LayoutSize::kAvailable);
set_paint_overflow_fadeout(false);
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto node = LayoutBoxNode()
.axis(Axis::kX)
.gravity(Gravity::kLeft)
.distribution(LayoutDistribution::kAvailable)
.child(LabelNode(item_->name())
.ignore_input(true)
.width(45_px)
.gravity(Gravity::kLeft))
.child(TextBoxNode(item_->str.c_str())
.gravity(Gravity::kLeftRight));
content_root()->LoadNodeTree(node);
}
bool OnEvent(const el::Event& ev) override {
return el::LayoutBox::OnEvent(ev);
}
private:
RegisterItem* item_ = nullptr;
RegisterItemSource* source_ = nullptr;
el::ListItemObserver* source_viewer_ = nullptr;
size_t index_ = 0;
};
class RegisterItemSource : public el::ListItemSourceList<RegisterItem> {
public:
el::Element* CreateItemElement(size_t index,
el::ListItemObserver* viewer) override {
return new RegisterItemElement(at(index), this, viewer, index);
}
};
void DefineRegisterItem(RegisterItemSource* item_source, RegisterSet set,
RegisterType type, PPCRegister reg) {
auto item = std::make_unique<GuestRegisterItem>(set, type, reg);
item->tag.set_integer(static_cast<int>(reg));
item_source->push_back(std::move(item));
}
void DefineRegisterItem(RegisterItemSource* item_source, RegisterSet set,
RegisterType type, X64Register reg) {
auto item = std::make_unique<HostRegisterItem>(set, type, reg);
item->tag.set_integer(static_cast<int>(reg));
item_source->push_back(std::move(item));
}
RegisterListControl::RegisterListControl(RegisterSet set)
: set_(set), item_source_(new RegisterItemSource()) {
switch (set_) {
case RegisterSet::kGeneral: {
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
PPCRegister::kLR);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
PPCRegister::kCTR);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl,
PPCRegister::kXER);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl,
PPCRegister::kCR);
for (size_t i = 0; i < 32; ++i) {
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
static_cast<PPCRegister>(
static_cast<size_t>(PPCRegister::kR0) + i));
}
} break;
case RegisterSet::kFloat: {
DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl,
PPCRegister::kFPSCR);
for (size_t i = 0; i < 32; ++i) {
DefineRegisterItem(item_source_.get(), set_, RegisterType::kFloat,
static_cast<PPCRegister>(
static_cast<size_t>(PPCRegister::kFR0) + i));
}
} break;
case RegisterSet::kVector: {
DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl,
PPCRegister::kVSCR);
for (size_t i = 0; i < 128; ++i) {
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
static_cast<PPCRegister>(
static_cast<size_t>(PPCRegister::kVR0) + i));
}
} break;
case RegisterSet::kHost: {
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kRip);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kControl,
X64Register::kEflags);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kRax);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kRcx);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kRdx);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kRbx);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kRsp);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kRbp);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kRsi);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kRdi);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kR8);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kR9);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kR10);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kR11);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kR12);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kR13);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kR14);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kInteger,
X64Register::kR15);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm0);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm1);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm2);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm3);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm4);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm5);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm6);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm7);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm8);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm9);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm10);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm11);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm12);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm13);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm14);
DefineRegisterItem(item_source_.get(), set_, RegisterType::kVector,
X64Register::kXmm15);
} break;
}
}
RegisterListControl::~RegisterListControl() = default;
el::Element* RegisterListControl::BuildUI() {
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto node =
LayoutBoxNode()
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.child(ListBoxNode().id("register_listbox").gravity(Gravity::kAll));
root_element_.set_gravity(Gravity::kAll);
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
root_element_.LoadNodeTree(node);
auto register_listbox =
root_element_.GetElementById<el::ListBox>(TBIDC("register_listbox"));
register_listbox->set_source(item_source_.get());
register_listbox->scroll_container()->set_scroll_mode(
el::ScrollMode::kAutoXAutoY);
handler_ = std::make_unique<el::EventHandler>(&root_element_);
return &root_element_;
}
void RegisterListControl::Setup(DebugClient* client) {
client_ = client;
system()->on_thread_state_updated.AddListener([this](model::Thread* thread) {
if (thread == thread_) {
InvalidateRegisters();
}
});
}
void RegisterListControl::set_thread(model::Thread* thread) {
thread_ = thread;
InvalidateRegisters();
}
void RegisterListControl::InvalidateRegisters() {
for (size_t i = 0; i < item_source_->size(); ++i) {
item_source_->at(i)->set_thread(thread_);
}
auto register_listbox =
root_element_.GetElementById<el::ListBox>(TBIDC("register_listbox"));
register_listbox->InvalidateList();
}
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe

View File

@@ -1,59 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_VIEWS_CPU_REGISTER_LIST_CONTROL_H_
#define XENIA_DEBUG_UI_VIEWS_CPU_REGISTER_LIST_CONTROL_H_
#include <memory>
#include <string>
#include "xenia/debug/ui/control.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
class RegisterItemSource;
enum class RegisterSet {
kGeneral,
kFloat,
kVector,
kHost,
};
class RegisterListControl : public Control {
public:
explicit RegisterListControl(RegisterSet set);
~RegisterListControl() override;
el::Element* BuildUI() override;
void Setup(xe::debug::DebugClient* client) override;
model::Thread* thread() const { return thread_; }
void set_thread(model::Thread* thread);
private:
void InvalidateRegisters();
RegisterSet set_;
model::Thread* thread_ = nullptr;
std::unique_ptr<RegisterItemSource> item_source_;
};
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_VIEWS_CPU_REGISTER_LIST_CONTROL_H_

View File

@@ -1,75 +0,0 @@
/**
******************************************************************************
* 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/ui/views/cpu/source_control.h"
#include "el/animation_manager.h"
#include "xenia/base/string_buffer.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
SourceControl::SourceControl() = default;
SourceControl::~SourceControl() = default;
el::Element* SourceControl::BuildUI() {
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto node = LayoutBoxNode()
.gravity(Gravity::kAll)
.distribution(LayoutDistribution::kAvailable)
.axis(Axis::kY)
.child(LayoutBoxNode()
.gravity(Gravity::kTop | Gravity::kLeftRight)
.distribution(LayoutDistribution::kGravity)
.distribution_position(
LayoutDistributionPosition::kLeftTop)
.axis(Axis::kX)
.child(ButtonNode("A")))
.child(TextBoxNode("source!")
.id("source_textbox")
.gravity(Gravity::kAll)
.is_multiline(true)
.is_read_only(true));
root_element_.set_gravity(Gravity::kAll);
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
root_element_.LoadNodeTree(node);
handler_ = std::make_unique<el::EventHandler>(&root_element_);
return &root_element_;
}
void SourceControl::Setup(DebugClient* client) {
client_ = client;
// system()->on_thread_state_updated.AddListener([this](model::Thread* thread)
// {
// if (thread == thread_) {
// InvalidateCallStack();
// }
//});
}
void SourceControl::set_thread(model::Thread* thread) {
thread_ = thread;
// InvalidateCallStack();
}
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe

View File

@@ -1,46 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_VIEWS_CPU_SOURCE_CONTROL_H_
#define XENIA_DEBUG_UI_VIEWS_CPU_SOURCE_CONTROL_H_
#include <memory>
#include <string>
#include "xenia/debug/ui/control.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace cpu {
class SourceControl : public Control {
public:
SourceControl();
~SourceControl() override;
el::Element* BuildUI() override;
void Setup(xe::debug::DebugClient* client) override;
model::Thread* thread() const { return thread_; }
void set_thread(model::Thread* thread);
private:
model::Thread* thread_ = nullptr;
};
} // namespace cpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_VIEWS_CPU_SOURCE_CONTROL_H_

View File

@@ -1,47 +0,0 @@
/**
******************************************************************************
* 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 "el/animation_manager.h"
#include "xenia/debug/ui/views/gpu/gpu_view.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace gpu {
GpuView::GpuView() : View("GPU") {}
GpuView::~GpuView() = default;
el::Element* GpuView::BuildUI() {
using namespace el::dsl; // NOLINT(build/namespaces)
el::AnimationBlocker animation_blocker;
auto node = LabelNode("TODO");
root_element_.set_gravity(Gravity::kAll);
root_element_.set_layout_distribution(LayoutDistribution::kAvailable);
root_element_.LoadNodeTree(node);
root_element_.GetElementsById({
//
});
handler_ = std::make_unique<el::EventHandler>(&root_element_);
return &root_element_;
}
void GpuView::Setup(xe::debug::DebugClient* client) {
//
}
} // namespace gpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe

View File

@@ -1,42 +0,0 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
#ifndef XENIA_DEBUG_UI_VIEWS_GPU_GPU_VIEW_H_
#define XENIA_DEBUG_UI_VIEWS_GPU_GPU_VIEW_H_
#include <memory>
#include <string>
#include "xenia/debug/ui/view.h"
namespace xe {
namespace debug {
namespace ui {
namespace views {
namespace gpu {
class GpuView : public View {
public:
GpuView();
~GpuView() override;
el::Element* BuildUI() override;
void Setup(xe::debug::DebugClient* client) override;
protected:
};
} // namespace gpu
} // namespace views
} // namespace ui
} // namespace debug
} // namespace xe
#endif // XENIA_DEBUG_UI_VIEWS_GPU_GPU_VIEW_H_

View File

@@ -1,101 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{C5BA52F0-C86B-4817-921C-CCA257FC04BE}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>xedebugui</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v140</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).Common.props" />
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).$(Configuration).props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).Common.props" />
<Import Project="..\..\..\..\build\Xenia.Cpp.$(Platform).$(Configuration).props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>libgflags.lib;libglew.lib;libxenia.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>libgflags.lib;libglew.lib;libxenia.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\..\base\main.h" />
<ClInclude Include="application.h" />
<ClInclude Include="main_window.h" />
<ClInclude Include="view.h" />
<ClInclude Include="views\cpu\cpu_view.h" />
<ClInclude Include="views\gpu\gpu_view.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\base\main_win.cc" />
<ClCompile Include="application.cc" />
<ClCompile Include="main_window.cc" />
<ClCompile Include="views\cpu\cpu_view.cc" />
<ClCompile Include="views\gpu\gpu_view.cc" />
<ClCompile Include="xe-debug-ui.cc" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc">
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)third_party/elemental-forms;$(SolutionDir);.</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(SolutionDir)third_party/elemental-forms;$(SolutionDir);.</AdditionalIncludeDirectories>
</ResourceCompile>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="src">
<UniqueIdentifier>{52d1c314-7464-4640-bee4-59b30d2b2df4}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia">
<UniqueIdentifier>{f8d1867c-424d-4942-ba97-a7b29fa8c87c}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug">
<UniqueIdentifier>{1b70e26e-0024-410f-b27f-ceaba190d5a9}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug\ui">
<UniqueIdentifier>{9b5a4bce-210a-4488-863a-9175f0543d00}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\base">
<UniqueIdentifier>{9a5724c2-5473-4d53-93b4-26531201f38d}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug\ui\elements">
<UniqueIdentifier>{f0ac4999-4700-4b41-b73d-c6dc8b23c5e6}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug\ui\views">
<UniqueIdentifier>{5601b0a2-a720-4d89-9fca-df1637fce0b1}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug\ui\views\cpu">
<UniqueIdentifier>{5b186e9a-1fef-42f5-b06b-2ac0b10a8a5b}</UniqueIdentifier>
</Filter>
<Filter Include="src\xenia\debug\ui\views\gpu">
<UniqueIdentifier>{c66e9a6b-0947-4610-9b2b-29db99c08e91}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\base\main.h">
<Filter>src\xenia\base</Filter>
</ClInclude>
<ClInclude Include="main_window.h">
<Filter>src\xenia\debug\ui</Filter>
</ClInclude>
<ClInclude Include="application.h">
<Filter>src\xenia\debug\ui</Filter>
</ClInclude>
<ClInclude Include="view.h">
<Filter>src\xenia\debug\ui</Filter>
</ClInclude>
<ClInclude Include="views\gpu\gpu_view.h">
<Filter>src\xenia\debug\ui\views\gpu</Filter>
</ClInclude>
<ClInclude Include="views\cpu\cpu_view.h">
<Filter>src\xenia\debug\ui\views\cpu</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\base\main_win.cc">
<Filter>src\xenia\base</Filter>
</ClCompile>
<ClCompile Include="xe-debug-ui.cc">
<Filter>src\xenia\debug\ui</Filter>
</ClCompile>
<ClCompile Include="main_window.cc">
<Filter>src\xenia\debug\ui</Filter>
</ClCompile>
<ClCompile Include="application.cc">
<Filter>src\xenia\debug\ui</Filter>
</ClCompile>
<ClCompile Include="views\gpu\gpu_view.cc">
<Filter>src\xenia\debug\ui\views\gpu</Filter>
</ClCompile>
<ClCompile Include="views\cpu\cpu_view.cc">
<Filter>src\xenia\debug\ui\views\cpu</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="resources.rc">
<Filter>src\xenia\debug\ui</Filter>
</ResourceCompile>
</ItemGroup>
</Project>