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:
135
src/xenia/debug/breakpoint.cc
Normal file
135
src/xenia/debug/breakpoint.cc
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -16,4 +16,3 @@ project("xenia-debug")
|
||||
project_root.."/build_tools/third_party/gflags/src",
|
||||
})
|
||||
local_platform_files()
|
||||
recursive_platform_files("proto")
|
||||
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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_
|
||||
1683
src/xenia/debug/ui/debug_window.cc
Normal file
1683
src/xenia/debug/ui/debug_window.cc
Normal file
File diff suppressed because it is too large
Load Diff
153
src/xenia/debug/ui/debug_window.h
Normal file
153
src/xenia/debug/ui/debug_window.h
Normal 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_
|
||||
@@ -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);
|
||||
@@ -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"
|
||||
341
src/xenia/debug/ui/imgui_renderer.cc
Normal file
341
src/xenia/debug/ui/imgui_renderer.cc
Normal file
File diff suppressed because one or more lines are too long
55
src/xenia/debug/ui/imgui_renderer.h
Normal file
55
src/xenia/debug/ui/imgui_renderer.h
Normal 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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user