Reconcile debugger and save state stuff into a single implementation.
Fixes #497 and fixes #496. Still rough edges, but at least less duplication.
This commit is contained in:
@@ -1,136 +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/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->MapGuestAddressToMachineCode(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
|
||||
@@ -1,185 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#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 class Type {
|
||||
kStepping,
|
||||
kCode,
|
||||
kData,
|
||||
kExport,
|
||||
kGpu,
|
||||
};
|
||||
|
||||
Breakpoint(Debugger* debugger, Type type)
|
||||
: debugger_(debugger), type_(type) {}
|
||||
|
||||
virtual ~Breakpoint() { assert_false(installed_); }
|
||||
|
||||
Debugger* debugger() const { return debugger_; }
|
||||
Type type() const { return type_; }
|
||||
|
||||
// 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:
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_BREAKPOINT_H_
|
||||
@@ -1,63 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2016 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_DEBUG_LISTENER_H_
|
||||
#define XENIA_DEBUG_DEBUG_LISTENER_H_
|
||||
|
||||
#include "xenia/cpu/thread_state.h"
|
||||
#include "xenia/debug/breakpoint.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
class XThread;
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_DEBUG_LISTENER_H_
|
||||
@@ -1,951 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/debugger.h"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "third_party/capstone/include/capstone.h"
|
||||
#include "third_party/capstone/include/x86.h"
|
||||
#include "xenia/base/debugging.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/string.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/cpu/backend/code_cache.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
#include "xenia/cpu/ppc/ppc_decode_data.h"
|
||||
#include "xenia/cpu/ppc/ppc_opcode_info.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/cpu/stack_walker.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/kernel/kernel_module.h"
|
||||
#include "xenia/kernel/user_module.h"
|
||||
#include "xenia/kernel/xmodule.h"
|
||||
#include "xenia/kernel/xthread.h"
|
||||
|
||||
#if 0 && DEBUG
|
||||
#define DEFAULT_DEBUG_FLAG true
|
||||
#else
|
||||
#define DEFAULT_DEBUG_FLAG false
|
||||
#endif
|
||||
|
||||
DEFINE_bool(debug, DEFAULT_DEBUG_FLAG,
|
||||
"Allow debugging and retain debug information.");
|
||||
DEFINE_string(debug_session_path, "", "Debug output path.");
|
||||
DEFINE_bool(break_on_start, false, "Break into the debugger on startup.");
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
using xe::cpu::ThreadState;
|
||||
using xe::cpu::ppc::PPCOpcode;
|
||||
using xe::kernel::XObject;
|
||||
using xe::kernel::XThread;
|
||||
|
||||
ThreadExecutionInfo::ThreadExecutionInfo() = default;
|
||||
|
||||
ThreadExecutionInfo::~ThreadExecutionInfo() = default;
|
||||
|
||||
Debugger::Debugger(Emulator* emulator) : emulator_(emulator) {
|
||||
ExceptionHandler::Install(ExceptionCallbackThunk, this);
|
||||
|
||||
if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) != CS_ERR_OK) {
|
||||
assert_always("Failed to initialize capstone");
|
||||
}
|
||||
cs_option(capstone_handle_, CS_OPT_SYNTAX, CS_OPT_SYNTAX_INTEL);
|
||||
cs_option(capstone_handle_, CS_OPT_DETAIL, CS_OPT_ON);
|
||||
cs_option(capstone_handle_, CS_OPT_SKIPDATA, CS_OPT_OFF);
|
||||
}
|
||||
|
||||
Debugger::~Debugger() {
|
||||
ExceptionHandler::Uninstall(ExceptionCallbackThunk, this);
|
||||
|
||||
if (capstone_handle_) {
|
||||
cs_close(&capstone_handle_);
|
||||
}
|
||||
|
||||
set_debug_listener(nullptr);
|
||||
StopSession();
|
||||
}
|
||||
|
||||
void Debugger::set_debug_listener(DebugListener* debug_listener) {
|
||||
if (debug_listener == debug_listener_) {
|
||||
return;
|
||||
}
|
||||
if (debug_listener_) {
|
||||
// Detach old debug listener.
|
||||
debug_listener_->OnDetached();
|
||||
debug_listener_ = nullptr;
|
||||
}
|
||||
if (debug_listener) {
|
||||
debug_listener_ = debug_listener;
|
||||
} else {
|
||||
if (execution_state_ == ExecutionState::kPaused) {
|
||||
XELOGI("Debugger detaching while execution is paused; continuing...");
|
||||
Continue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::DemandDebugListener() {
|
||||
if (debug_listener_) {
|
||||
// Already present.
|
||||
debug_listener_->OnFocus();
|
||||
return;
|
||||
}
|
||||
if (!debug_listener_handler_) {
|
||||
XELOGE("Debugger demanded a listener but no handler was registered.");
|
||||
xe::debugging::Break();
|
||||
return;
|
||||
}
|
||||
set_debug_listener(debug_listener_handler_(this));
|
||||
}
|
||||
|
||||
bool Debugger::StartSession() {
|
||||
auto session_path = xe::to_wstring(FLAGS_debug_session_path);
|
||||
if (!session_path.empty()) {
|
||||
session_path = xe::to_absolute_path(session_path);
|
||||
xe::filesystem::CreateFolder(session_path);
|
||||
}
|
||||
|
||||
functions_path_ = xe::join_paths(session_path, L"functions");
|
||||
functions_file_ =
|
||||
ChunkedMappedMemoryWriter::Open(functions_path_, 32 * 1024 * 1024, false);
|
||||
|
||||
functions_trace_path_ = xe::join_paths(session_path, L"functions.trace");
|
||||
functions_trace_file_ = ChunkedMappedMemoryWriter::Open(
|
||||
functions_trace_path_, 32 * 1024 * 1024, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Debugger::PreLaunch() {
|
||||
if (FLAGS_break_on_start) {
|
||||
// Start paused.
|
||||
XELOGI("Breaking into debugger because of --break_on_start...");
|
||||
execution_state_ = ExecutionState::kRunning;
|
||||
Pause();
|
||||
} else {
|
||||
// Start running.
|
||||
execution_state_ = ExecutionState::kRunning;
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::StopSession() {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
|
||||
FlushSession();
|
||||
|
||||
functions_file_.reset();
|
||||
functions_trace_file_.reset();
|
||||
}
|
||||
|
||||
void Debugger::FlushSession() {
|
||||
if (functions_file_) {
|
||||
functions_file_->Flush();
|
||||
}
|
||||
if (functions_trace_file_) {
|
||||
functions_trace_file_->Flush();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* Debugger::AllocateFunctionData(size_t size) {
|
||||
if (!functions_file_) {
|
||||
return nullptr;
|
||||
}
|
||||
return functions_file_->Allocate(size);
|
||||
}
|
||||
|
||||
uint8_t* Debugger::AllocateFunctionTraceData(size_t size) {
|
||||
if (!functions_trace_file_) {
|
||||
return nullptr;
|
||||
}
|
||||
return functions_trace_file_->Allocate(size);
|
||||
}
|
||||
|
||||
std::vector<ThreadExecutionInfo*> Debugger::QueryThreadExecutionInfos() {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
std::vector<ThreadExecutionInfo*> result;
|
||||
for (auto& it : thread_execution_infos_) {
|
||||
result.push_back(it.second.get());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ThreadExecutionInfo* Debugger::QueryThreadExecutionInfo(uint32_t thread_id) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
const auto& it = thread_execution_infos_.find(thread_id);
|
||||
if (it == thread_execution_infos_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
void Debugger::AddBreakpoint(Breakpoint* breakpoint) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
|
||||
// Add to breakpoints map.
|
||||
breakpoints_.push_back(breakpoint);
|
||||
|
||||
if (execution_state_ == ExecutionState::kRunning) {
|
||||
breakpoint->Resume();
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::RemoveBreakpoint(Breakpoint* breakpoint) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
|
||||
// Uninstall (if needed).
|
||||
if (execution_state_ == ExecutionState::kRunning) {
|
||||
breakpoint->Suspend();
|
||||
}
|
||||
|
||||
// Remove from breakpoint map.
|
||||
auto it = std::find(breakpoints_.begin(), breakpoints_.end(), breakpoint);
|
||||
breakpoints_.erase(it);
|
||||
}
|
||||
|
||||
bool Debugger::SuspendAllThreads() {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
for (auto& it : thread_execution_infos_) {
|
||||
auto thread_info = it.second.get();
|
||||
if (thread_info->suspended) {
|
||||
// Already suspended - ignore.
|
||||
continue;
|
||||
} else if (thread_info->state == ThreadExecutionInfo::State::kZombie ||
|
||||
thread_info->state == ThreadExecutionInfo::State::kExited) {
|
||||
// Thread is dead and cannot be suspended - ignore.
|
||||
continue;
|
||||
} else if (!thread_info->thread->can_debugger_suspend()) {
|
||||
// Thread is a host thread, and we aren't suspending those (for now).
|
||||
continue;
|
||||
} else if (XThread::IsInThread() &&
|
||||
thread_info->thread_id == XThread::GetCurrentThreadId()) {
|
||||
// Can't suspend ourselves.
|
||||
continue;
|
||||
}
|
||||
bool did_suspend = XSUCCEEDED(thread_info->thread->Suspend(nullptr));
|
||||
assert_true(did_suspend);
|
||||
thread_info->suspended = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Debugger::ResumeThread(uint32_t thread_id) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
auto it = thread_execution_infos_.find(thread_id);
|
||||
if (it == thread_execution_infos_.end()) {
|
||||
return false;
|
||||
}
|
||||
auto thread_info = it->second.get();
|
||||
assert_true(thread_info->suspended);
|
||||
assert_false(thread_info->state == ThreadExecutionInfo::State::kExited ||
|
||||
thread_info->state == ThreadExecutionInfo::State::kZombie);
|
||||
thread_info->suspended = false;
|
||||
return XSUCCEEDED(thread_info->thread->Resume());
|
||||
}
|
||||
|
||||
bool Debugger::ResumeAllThreads() {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
for (auto& it : thread_execution_infos_) {
|
||||
auto thread_info = it.second.get();
|
||||
if (!thread_info->suspended) {
|
||||
// Not suspended by us - ignore.
|
||||
continue;
|
||||
} else if (thread_info->state == ThreadExecutionInfo::State::kZombie ||
|
||||
thread_info->state == ThreadExecutionInfo::State::kExited) {
|
||||
// Thread is dead and cannot be resumed - ignore.
|
||||
continue;
|
||||
} else if (!thread_info->thread->can_debugger_suspend()) {
|
||||
// Thread is a host thread, and we aren't suspending those (for now).
|
||||
continue;
|
||||
} else if (XThread::IsInThread() &&
|
||||
thread_info->thread_id == XThread::GetCurrentThreadId()) {
|
||||
// Can't resume ourselves.
|
||||
continue;
|
||||
}
|
||||
thread_info->suspended = false;
|
||||
bool did_resume = XSUCCEEDED(thread_info->thread->Resume());
|
||||
assert_true(did_resume);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Debugger::UpdateThreadExecutionStates(uint32_t override_thread_id,
|
||||
X64Context* override_context) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
auto stack_walker = emulator_->processor()->stack_walker();
|
||||
uint64_t frame_host_pcs[64];
|
||||
xe::cpu::StackFrame cpu_frames[64];
|
||||
for (auto& it : thread_execution_infos_) {
|
||||
auto thread_info = it.second.get();
|
||||
auto thread = thread_info->thread;
|
||||
if (!thread) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Grab PPC context.
|
||||
// Note that this is only up to date if --store_all_context_values is
|
||||
// enabled (or --debug).
|
||||
if (thread->can_debugger_suspend()) {
|
||||
std::memcpy(&thread_info->guest_context,
|
||||
thread->thread_state()->context(),
|
||||
sizeof(thread_info->guest_context));
|
||||
}
|
||||
|
||||
// Grab stack trace and X64 context then resolve all symbols.
|
||||
uint64_t hash;
|
||||
X64Context* in_host_context = nullptr;
|
||||
if (override_thread_id == thread_info->thread_id) {
|
||||
// If we were passed an override context we use that. Otherwise, ask the
|
||||
// stack walker for a new context.
|
||||
in_host_context = override_context;
|
||||
}
|
||||
size_t count = stack_walker->CaptureStackTrace(
|
||||
thread->thread()->native_handle(), frame_host_pcs, 0,
|
||||
xe::countof(frame_host_pcs), in_host_context,
|
||||
&thread_info->host_context, &hash);
|
||||
stack_walker->ResolveStack(frame_host_pcs, cpu_frames, count);
|
||||
thread_info->frames.resize(count);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
auto& cpu_frame = cpu_frames[i];
|
||||
auto& frame = thread_info->frames[i];
|
||||
frame.host_pc = cpu_frame.host_pc;
|
||||
frame.host_function_address = cpu_frame.host_symbol.address;
|
||||
frame.guest_pc = cpu_frame.guest_pc;
|
||||
frame.guest_function_address = 0;
|
||||
frame.guest_function = nullptr;
|
||||
auto function = cpu_frame.guest_symbol.function;
|
||||
if (cpu_frame.type == cpu::StackFrame::Type::kGuest && function) {
|
||||
frame.guest_function_address = function->address();
|
||||
frame.guest_function = function;
|
||||
} else {
|
||||
std::strncpy(frame.name, cpu_frame.host_symbol.name,
|
||||
xe::countof(frame.name));
|
||||
frame.name[xe::countof(frame.name) - 1] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::SuspendAllBreakpoints() {
|
||||
for (auto breakpoint : breakpoints_) {
|
||||
breakpoint->Suspend();
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::ResumeAllBreakpoints() {
|
||||
for (auto breakpoint : breakpoints_) {
|
||||
breakpoint->Resume();
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::Show() {
|
||||
if (debug_listener_) {
|
||||
debug_listener_->OnFocus();
|
||||
} else {
|
||||
DemandDebugListener();
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::Pause() {
|
||||
{
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
assert_true(execution_state_ == ExecutionState::kRunning);
|
||||
SuspendAllThreads();
|
||||
SuspendAllBreakpoints();
|
||||
UpdateThreadExecutionStates();
|
||||
execution_state_ = ExecutionState::kPaused;
|
||||
if (debug_listener_) {
|
||||
debug_listener_->OnExecutionPaused();
|
||||
}
|
||||
}
|
||||
DemandDebugListener();
|
||||
}
|
||||
|
||||
void Debugger::Continue() {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
if (execution_state_ == ExecutionState::kRunning) {
|
||||
return;
|
||||
} else if (execution_state_ == ExecutionState::kStepping) {
|
||||
assert_always("cancel stepping not done yet");
|
||||
}
|
||||
execution_state_ = ExecutionState::kRunning;
|
||||
ResumeAllBreakpoints();
|
||||
ResumeAllThreads();
|
||||
if (debug_listener_) {
|
||||
debug_listener_->OnExecutionContinued();
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::StepGuestInstruction(uint32_t thread_id) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
assert_true(execution_state_ == ExecutionState::kPaused);
|
||||
execution_state_ = ExecutionState::kStepping;
|
||||
|
||||
auto thread_info = thread_execution_infos_[thread_id].get();
|
||||
|
||||
uint32_t next_pc = CalculateNextGuestInstruction(
|
||||
thread_info, thread_info->frames[0].guest_pc);
|
||||
|
||||
assert_null(thread_info->step_breakpoint.get());
|
||||
thread_info->step_breakpoint = std::make_unique<StepBreakpoint>(
|
||||
this, StepBreakpoint::AddressType::kGuest, next_pc);
|
||||
AddBreakpoint(thread_info->step_breakpoint.get());
|
||||
thread_info->step_breakpoint->Resume();
|
||||
|
||||
// ResumeAllBreakpoints();
|
||||
ResumeThread(thread_id);
|
||||
}
|
||||
|
||||
void Debugger::StepHostInstruction(uint32_t thread_id) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
assert_true(execution_state_ == ExecutionState::kPaused);
|
||||
execution_state_ = ExecutionState::kStepping;
|
||||
|
||||
auto thread_info = thread_execution_infos_[thread_id].get();
|
||||
uint64_t new_host_pc =
|
||||
CalculateNextHostInstruction(thread_info, thread_info->frames[0].host_pc);
|
||||
|
||||
assert_null(thread_info->step_breakpoint.get());
|
||||
thread_info->step_breakpoint = std::make_unique<StepBreakpoint>(
|
||||
this, StepBreakpoint::AddressType::kHost, new_host_pc);
|
||||
AddBreakpoint(thread_info->step_breakpoint.get());
|
||||
thread_info->step_breakpoint->Resume();
|
||||
|
||||
// ResumeAllBreakpoints();
|
||||
ResumeThread(thread_id);
|
||||
}
|
||||
|
||||
void Debugger::OnThreadCreated(XThread* thread) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
auto thread_info = std::make_unique<ThreadExecutionInfo>();
|
||||
thread_info->thread_handle = thread->handle();
|
||||
thread_info->thread_id = thread->thread_id();
|
||||
thread_info->thread = thread;
|
||||
thread_info->state = ThreadExecutionInfo::State::kAlive;
|
||||
thread_info->suspended = false;
|
||||
thread_execution_infos_.emplace(thread_info->thread_id,
|
||||
std::move(thread_info));
|
||||
}
|
||||
|
||||
void Debugger::OnThreadExit(XThread* thread) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
auto it = thread_execution_infos_.find(thread->thread_id());
|
||||
assert_true(it != thread_execution_infos_.end());
|
||||
auto thread_info = it->second.get();
|
||||
thread_info->state = ThreadExecutionInfo::State::kExited;
|
||||
}
|
||||
|
||||
void Debugger::OnThreadDestroyed(XThread* thread) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
auto it = thread_execution_infos_.find(thread->thread_id());
|
||||
assert_true(it != thread_execution_infos_.end());
|
||||
auto thread_info = it->second.get();
|
||||
// TODO(benvanik): retain the thread?
|
||||
thread_info->thread = nullptr;
|
||||
thread_info->state = ThreadExecutionInfo::State::kZombie;
|
||||
}
|
||||
|
||||
void Debugger::OnThreadEnteringWait(XThread* thread) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
auto it = thread_execution_infos_.find(thread->thread_id());
|
||||
assert_true(it != thread_execution_infos_.end());
|
||||
auto thread_info = it->second.get();
|
||||
thread_info->state = ThreadExecutionInfo::State::kWaiting;
|
||||
}
|
||||
|
||||
void Debugger::OnThreadLeavingWait(XThread* thread) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
auto it = thread_execution_infos_.find(thread->thread_id());
|
||||
assert_true(it != thread_execution_infos_.end());
|
||||
auto thread_info = it->second.get();
|
||||
if (thread_info->state == ThreadExecutionInfo::State::kWaiting) {
|
||||
thread_info->state = ThreadExecutionInfo::State::kAlive;
|
||||
}
|
||||
}
|
||||
|
||||
void Debugger::OnFunctionDefined(cpu::Function* function) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
// TODO(benvanik): breakpoints?
|
||||
// Man, I'd love not to take this lock.
|
||||
// std::vector<Breakpoint*> breakpoints;
|
||||
//{
|
||||
// std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
// for (uint32_t address = function->address();
|
||||
// address <= function->end_address(); address += 4) {
|
||||
// auto range = breakpoints_.equal_range(address);
|
||||
// if (range.first == range.second) {
|
||||
// continue;
|
||||
// }
|
||||
// for (auto it = range.first; it != range.second; ++it) {
|
||||
// Breakpoint* breakpoint = it->second;
|
||||
// breakpoints.push_back(breakpoint);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
// if (breakpoints.size()) {
|
||||
// // Breakpoints to add!
|
||||
// for (auto breakpoint : breakpoints) {
|
||||
// function->AddBreakpoint(breakpoint);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
bool Debugger::ExceptionCallbackThunk(Exception* ex, void* data) {
|
||||
return reinterpret_cast<Debugger*>(data)->ExceptionCallback(ex);
|
||||
}
|
||||
|
||||
bool Debugger::ExceptionCallback(Exception* ex) {
|
||||
if (ex->code() != Exception::Code::kIllegalInstruction) {
|
||||
// We only care about illegal instructions. Other things will be handled by
|
||||
// other handlers (probably). If nothing else picks it up we'll be called
|
||||
// with OnUnhandledException to do real crash handling.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify an expected illegal instruction.
|
||||
auto instruction_bytes =
|
||||
xe::load_and_swap<uint16_t>(reinterpret_cast<void*>(ex->pc()));
|
||||
if (instruction_bytes != 0x0F0B) {
|
||||
// Not our ud2 - not us.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
|
||||
// Suspend all threads (but ourselves).
|
||||
SuspendAllThreads();
|
||||
|
||||
// Lookup thread info block.
|
||||
auto it = thread_execution_infos_.find(XThread::GetCurrentThreadId());
|
||||
if (it == thread_execution_infos_.end()) {
|
||||
// Not found - exception on a thread we don't know about?
|
||||
assert_always("UD2 on a thread we don't track");
|
||||
return false;
|
||||
}
|
||||
auto thread_info = it->second.get();
|
||||
|
||||
// Run through and uninstall all breakpoint UD2s to get us back to a clean
|
||||
// state.
|
||||
if (execution_state_ != ExecutionState::kStepping) {
|
||||
SuspendAllBreakpoints();
|
||||
}
|
||||
|
||||
// Update all thread states with their latest values, using the context we got
|
||||
// from the exception instead of a sampled value (as it would just show the
|
||||
// exception handler).
|
||||
UpdateThreadExecutionStates(thread_info->thread_id, ex->thread_context());
|
||||
|
||||
// Whether we should block waiting for a continue event.
|
||||
bool wait_for_continue = false;
|
||||
|
||||
// if (thread_info->restore_original_breakpoint &&
|
||||
// ex->pc() == thread_info->restore_step_breakpoint.host_address) {
|
||||
// assert_always("TODO");
|
||||
// // We were temporarily stepping to restore a breakpoint. Reinstall it.
|
||||
// PatchBreakpoint(thread_info->restore_original_breakpoint);
|
||||
// thread_info->restore_original_breakpoint = nullptr;
|
||||
// thread_info->is_stepping = false;
|
||||
// execution_state_ = ExecutionState::kRunning;
|
||||
// return true;
|
||||
//}
|
||||
|
||||
if (thread_info->step_breakpoint.get()) {
|
||||
// Thread is stepping. This is likely a stepping breakpoint.
|
||||
if (thread_info->step_breakpoint->ContainsHostAddress(ex->pc())) {
|
||||
// Our step request has completed. Remove the breakpoint and fire event.
|
||||
thread_info->step_breakpoint->Suspend();
|
||||
RemoveBreakpoint(thread_info->step_breakpoint.get());
|
||||
thread_info->step_breakpoint.reset();
|
||||
OnStepCompleted(thread_info);
|
||||
wait_for_continue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we weren't stepping check other breakpoints.
|
||||
if (!wait_for_continue && execution_state_ != ExecutionState::kStepping) {
|
||||
// TODO(benvanik): faster lookup.
|
||||
for (auto breakpoint : breakpoints_) {
|
||||
if (!breakpoint->is_enabled() ||
|
||||
breakpoint->type() != Breakpoint::Type::kCode) {
|
||||
continue;
|
||||
}
|
||||
auto code_breakpoint = static_cast<CodeBreakpoint*>(breakpoint);
|
||||
if (code_breakpoint->address_type() ==
|
||||
CodeBreakpoint::AddressType::kHost &&
|
||||
code_breakpoint->host_address() == ex->pc()) {
|
||||
// Hit host address breakpoint, which we can be confident is where we
|
||||
// want to be.
|
||||
OnBreakpointHit(thread_info, breakpoint);
|
||||
wait_for_continue = true;
|
||||
} else if (code_breakpoint->ContainsHostAddress(ex->pc())) {
|
||||
// Hit guest address breakpoint - maybe.
|
||||
OnBreakpointHit(thread_info, breakpoint);
|
||||
wait_for_continue = true;
|
||||
}
|
||||
}
|
||||
// if (breakpoint->is_stepping()) {
|
||||
// assert_always("TODO");
|
||||
// // Hit a stepping breakpoint for another thread.
|
||||
// // Skip it by uninstalling the previous step, adding a step for one
|
||||
// // after us, and resuming just our thread. The step handler will
|
||||
// // reinstall the restore_breakpoint after stepping and continue.
|
||||
// thread_info->restore_original_breakpoint = breakpoint;
|
||||
// // thread_info->restore_step_breakpoint(rip + N)
|
||||
// PatchBreakpoint(&thread_info->restore_step_breakpoint);
|
||||
// thread_info->is_stepping = true;
|
||||
// execution_state_ = ExecutionState::kStepping;
|
||||
// return true;
|
||||
//}
|
||||
}
|
||||
|
||||
// We are waiting on the debugger now. Either wait for it to continue, add a
|
||||
// new step, or direct us somewhere else.
|
||||
if (wait_for_continue) {
|
||||
// The debugger will ResumeAllThreads or just resume us (depending on what
|
||||
// it wants to do).
|
||||
execution_state_ = ExecutionState::kPaused;
|
||||
thread_info->suspended = true;
|
||||
|
||||
// Must unlock, or we will deadlock.
|
||||
global_lock.unlock();
|
||||
|
||||
thread_info->thread->Suspend(nullptr);
|
||||
}
|
||||
|
||||
// Apply thread context changes.
|
||||
// TODO(benvanik): apply to all threads?
|
||||
ex->set_resume_pc(thread_info->host_context.rip);
|
||||
|
||||
// Resume execution.
|
||||
return true;
|
||||
}
|
||||
|
||||
void Debugger::OnStepCompleted(ThreadExecutionInfo* thread_info) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
execution_state_ = ExecutionState::kPaused;
|
||||
if (debug_listener_) {
|
||||
debug_listener_->OnExecutionPaused();
|
||||
}
|
||||
|
||||
// Note that we stay suspended.
|
||||
}
|
||||
|
||||
void Debugger::OnBreakpointHit(ThreadExecutionInfo* thread_info,
|
||||
Breakpoint* breakpoint) {
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
execution_state_ = ExecutionState::kPaused;
|
||||
if (debug_listener_) {
|
||||
debug_listener_->OnExecutionPaused();
|
||||
}
|
||||
|
||||
// Note that we stay suspended.
|
||||
}
|
||||
|
||||
bool Debugger::OnUnhandledException(Exception* ex) {
|
||||
// If we have no listener return right away.
|
||||
// TODO(benvanik): DemandDebugListener()?
|
||||
if (!debug_listener_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If this isn't a managed thread, fail - let VS handle it for now.
|
||||
if (!XThread::IsInThread()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto global_lock = global_critical_region_.Acquire();
|
||||
|
||||
// Suspend all guest threads (but this one).
|
||||
SuspendAllThreads();
|
||||
|
||||
UpdateThreadExecutionStates(XThread::GetCurrentThreadId(),
|
||||
ex->thread_context());
|
||||
|
||||
// Stop and notify the listener.
|
||||
// This will take control.
|
||||
assert_true(execution_state_ == ExecutionState::kRunning);
|
||||
execution_state_ = ExecutionState::kPaused;
|
||||
|
||||
// Notify debugger that exceution stopped.
|
||||
// debug_listener_->OnException(info);
|
||||
debug_listener_->OnExecutionPaused();
|
||||
|
||||
// Suspend self.
|
||||
XThread::GetCurrentThread()->Suspend(nullptr);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TestPpcCondition(const xe::cpu::ppc::PPCContext* context, uint32_t bo,
|
||||
uint32_t bi, bool check_ctr, bool check_cond) {
|
||||
bool ctr_ok = true;
|
||||
if (check_ctr) {
|
||||
if (select_bits(bo, 2, 2)) {
|
||||
ctr_ok = true;
|
||||
} else {
|
||||
uint32_t new_ctr_value = static_cast<uint32_t>(context->ctr - 1);
|
||||
if (select_bits(bo, 1, 1)) {
|
||||
ctr_ok = new_ctr_value == 0;
|
||||
} else {
|
||||
ctr_ok = new_ctr_value != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool cond_ok = true;
|
||||
if (check_cond) {
|
||||
if (select_bits(bo, 4, 4)) {
|
||||
cond_ok = true;
|
||||
} else {
|
||||
uint8_t cr = *(reinterpret_cast<const uint8_t*>(&context->cr0) +
|
||||
(4 * (bi >> 2)) + (bi & 3));
|
||||
if (select_bits(bo, 3, 3)) {
|
||||
cond_ok = cr != 0;
|
||||
} else {
|
||||
cond_ok = cr == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctr_ok && cond_ok;
|
||||
}
|
||||
|
||||
uint32_t Debugger::CalculateNextGuestInstruction(
|
||||
ThreadExecutionInfo* thread_info, uint32_t current_pc) {
|
||||
xe::cpu::ppc::PPCDecodeData d;
|
||||
d.address = current_pc;
|
||||
d.code = xe::load_and_swap<uint32_t>(
|
||||
emulator_->memory()->TranslateVirtual(d.address));
|
||||
auto opcode = xe::cpu::ppc::LookupOpcode(d.code);
|
||||
if (d.code == 0x4E800020) {
|
||||
// blr -- unconditional branch to LR.
|
||||
uint32_t target_pc = static_cast<uint32_t>(thread_info->guest_context.lr);
|
||||
return target_pc;
|
||||
} else if (d.code == 0x4E800420) {
|
||||
// bctr -- unconditional branch to CTR.
|
||||
uint32_t target_pc = static_cast<uint32_t>(thread_info->guest_context.ctr);
|
||||
return target_pc;
|
||||
} else if (opcode == PPCOpcode::bx) {
|
||||
// b/ba/bl/bla
|
||||
uint32_t target_pc = d.I.ADDR();
|
||||
return target_pc;
|
||||
} else if (opcode == PPCOpcode::bcx) {
|
||||
// bc/bca/bcl/bcla
|
||||
uint32_t target_pc = d.B.ADDR();
|
||||
bool test_passed = TestPpcCondition(&thread_info->guest_context, d.B.BO(),
|
||||
d.B.BI(), true, true);
|
||||
return test_passed ? target_pc : current_pc + 4;
|
||||
} else if (opcode == PPCOpcode::bclrx) {
|
||||
// bclr/bclrl
|
||||
uint32_t target_pc = static_cast<uint32_t>(thread_info->guest_context.lr);
|
||||
bool test_passed = TestPpcCondition(&thread_info->guest_context, d.XL.BO(),
|
||||
d.XL.BI(), true, true);
|
||||
return test_passed ? target_pc : current_pc + 4;
|
||||
} else if (opcode == PPCOpcode::bcctrx) {
|
||||
// bcctr/bcctrl
|
||||
uint32_t target_pc = static_cast<uint32_t>(thread_info->guest_context.ctr);
|
||||
bool test_passed = TestPpcCondition(&thread_info->guest_context, d.XL.BO(),
|
||||
d.XL.BI(), false, true);
|
||||
return test_passed ? target_pc : current_pc + 4;
|
||||
} else {
|
||||
return current_pc + 4;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t ReadCapstoneReg(X64Context* context, x86_reg reg) {
|
||||
switch (reg) {
|
||||
case X86_REG_RAX:
|
||||
return context->rax;
|
||||
case X86_REG_RCX:
|
||||
return context->rcx;
|
||||
case X86_REG_RDX:
|
||||
return context->rdx;
|
||||
case X86_REG_RBX:
|
||||
return context->rbx;
|
||||
case X86_REG_RSP:
|
||||
return context->rsp;
|
||||
case X86_REG_RBP:
|
||||
return context->rbp;
|
||||
case X86_REG_RSI:
|
||||
return context->rsi;
|
||||
case X86_REG_RDI:
|
||||
return context->rdi;
|
||||
case X86_REG_R8:
|
||||
return context->r8;
|
||||
case X86_REG_R9:
|
||||
return context->r9;
|
||||
case X86_REG_R10:
|
||||
return context->r10;
|
||||
case X86_REG_R11:
|
||||
return context->r11;
|
||||
case X86_REG_R12:
|
||||
return context->r12;
|
||||
case X86_REG_R13:
|
||||
return context->r13;
|
||||
case X86_REG_R14:
|
||||
return context->r14;
|
||||
case X86_REG_R15:
|
||||
return context->r15;
|
||||
default:
|
||||
assert_unhandled_case(reg);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define X86_EFLAGS_CF 0x00000001 // Carry Flag
|
||||
#define X86_EFLAGS_PF 0x00000004 // Parity Flag
|
||||
#define X86_EFLAGS_ZF 0x00000040 // Zero Flag
|
||||
#define X86_EFLAGS_SF 0x00000080 // Sign Flag
|
||||
#define X86_EFLAGS_OF 0x00000800 // Overflow Flag
|
||||
bool TestCapstoneEflags(uint32_t eflags, uint32_t insn) {
|
||||
// http://www.felixcloutier.com/x86/Jcc.html
|
||||
switch (insn) {
|
||||
case X86_INS_JAE:
|
||||
// CF=0 && ZF=0
|
||||
return ((eflags & X86_EFLAGS_CF) == 0) && ((eflags & X86_EFLAGS_ZF) == 0);
|
||||
case X86_INS_JA:
|
||||
// CF=0
|
||||
return (eflags & X86_EFLAGS_CF) == 0;
|
||||
case X86_INS_JBE:
|
||||
// CF=1 || ZF=1
|
||||
return ((eflags & X86_EFLAGS_CF) == X86_EFLAGS_CF) ||
|
||||
((eflags & X86_EFLAGS_ZF) == X86_EFLAGS_ZF);
|
||||
case X86_INS_JB:
|
||||
// CF=1
|
||||
return (eflags & X86_EFLAGS_CF) == X86_EFLAGS_CF;
|
||||
case X86_INS_JE:
|
||||
// ZF=1
|
||||
return (eflags & X86_EFLAGS_ZF) == X86_EFLAGS_ZF;
|
||||
case X86_INS_JGE:
|
||||
// SF=OF
|
||||
return (eflags & X86_EFLAGS_SF) == (eflags & X86_EFLAGS_OF);
|
||||
case X86_INS_JG:
|
||||
// ZF=0 && SF=OF
|
||||
return ((eflags & X86_EFLAGS_ZF) == 0) &&
|
||||
((eflags & X86_EFLAGS_SF) == (eflags & X86_EFLAGS_OF));
|
||||
case X86_INS_JLE:
|
||||
// ZF=1 || SF!=OF
|
||||
return ((eflags & X86_EFLAGS_ZF) == X86_EFLAGS_ZF) ||
|
||||
((eflags & X86_EFLAGS_SF) != X86_EFLAGS_OF);
|
||||
case X86_INS_JL:
|
||||
// SF!=OF
|
||||
return (eflags & X86_EFLAGS_SF) != (eflags & X86_EFLAGS_OF);
|
||||
case X86_INS_JNE:
|
||||
// ZF=0
|
||||
return (eflags & X86_EFLAGS_ZF) == 0;
|
||||
case X86_INS_JNO:
|
||||
// OF=0
|
||||
return (eflags & X86_EFLAGS_OF) == 0;
|
||||
case X86_INS_JNP:
|
||||
// PF=0
|
||||
return (eflags & X86_EFLAGS_PF) == 0;
|
||||
case X86_INS_JNS:
|
||||
// SF=0
|
||||
return (eflags & X86_EFLAGS_SF) == 0;
|
||||
case X86_INS_JO:
|
||||
// OF=1
|
||||
return (eflags & X86_EFLAGS_OF) == X86_EFLAGS_OF;
|
||||
case X86_INS_JP:
|
||||
// PF=1
|
||||
return (eflags & X86_EFLAGS_PF) == X86_EFLAGS_PF;
|
||||
case X86_INS_JS:
|
||||
// SF=1
|
||||
return (eflags & X86_EFLAGS_SF) == X86_EFLAGS_SF;
|
||||
default:
|
||||
assert_unhandled_case(insn);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t Debugger::CalculateNextHostInstruction(
|
||||
ThreadExecutionInfo* thread_info, uint64_t current_pc) {
|
||||
auto machine_code_ptr = reinterpret_cast<const uint8_t*>(current_pc);
|
||||
size_t remaining_machine_code_size = 64;
|
||||
uint64_t host_address = current_pc;
|
||||
cs_insn insn = {0};
|
||||
cs_detail all_detail = {0};
|
||||
insn.detail = &all_detail;
|
||||
cs_disasm_iter(capstone_handle_, &machine_code_ptr,
|
||||
&remaining_machine_code_size, &host_address, &insn);
|
||||
auto& detail = all_detail.x86;
|
||||
switch (insn.id) {
|
||||
default:
|
||||
// Not a branching instruction - just move over it.
|
||||
return current_pc + insn.size;
|
||||
case X86_INS_CALL: {
|
||||
assert_true(detail.op_count == 1);
|
||||
assert_true(detail.operands[0].type == X86_OP_REG);
|
||||
uint64_t target_pc =
|
||||
ReadCapstoneReg(&thread_info->host_context, detail.operands[0].reg);
|
||||
return target_pc;
|
||||
} break;
|
||||
case X86_INS_RET: {
|
||||
assert_zero(detail.op_count);
|
||||
auto stack_ptr =
|
||||
reinterpret_cast<uint64_t*>(thread_info->host_context.rsp);
|
||||
uint64_t target_pc = stack_ptr[0];
|
||||
return target_pc;
|
||||
} break;
|
||||
case X86_INS_JMP: {
|
||||
assert_true(detail.op_count == 1);
|
||||
if (detail.operands[0].type == X86_OP_IMM) {
|
||||
uint64_t target_pc = static_cast<uint64_t>(detail.operands[0].imm);
|
||||
return target_pc;
|
||||
} else if (detail.operands[0].type == X86_OP_REG) {
|
||||
uint64_t target_pc =
|
||||
ReadCapstoneReg(&thread_info->host_context, detail.operands[0].reg);
|
||||
return target_pc;
|
||||
} else {
|
||||
// TODO(benvanik): find some more uses of this.
|
||||
assert_always("jmp branch emulation not yet implemented");
|
||||
return current_pc + insn.size;
|
||||
}
|
||||
} break;
|
||||
case X86_INS_JCXZ:
|
||||
case X86_INS_JECXZ:
|
||||
case X86_INS_JRCXZ:
|
||||
assert_always("j*cxz branch emulation not yet implemented");
|
||||
return current_pc + insn.size;
|
||||
case X86_INS_JAE:
|
||||
case X86_INS_JA:
|
||||
case X86_INS_JBE:
|
||||
case X86_INS_JB:
|
||||
case X86_INS_JE:
|
||||
case X86_INS_JGE:
|
||||
case X86_INS_JG:
|
||||
case X86_INS_JLE:
|
||||
case X86_INS_JL:
|
||||
case X86_INS_JNE:
|
||||
case X86_INS_JNO:
|
||||
case X86_INS_JNP:
|
||||
case X86_INS_JNS:
|
||||
case X86_INS_JO:
|
||||
case X86_INS_JP:
|
||||
case X86_INS_JS: {
|
||||
assert_true(detail.op_count == 1);
|
||||
assert_true(detail.operands[0].type == X86_OP_IMM);
|
||||
uint64_t target_pc = static_cast<uint64_t>(detail.operands[0].imm);
|
||||
bool test_passed =
|
||||
TestCapstoneEflags(thread_info->host_context.eflags, insn.id);
|
||||
if (test_passed) {
|
||||
return target_pc;
|
||||
} else {
|
||||
return current_pc + insn.size;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
@@ -1,211 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_DEBUGGER_H_
|
||||
#define XENIA_DEBUG_DEBUGGER_H_
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#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"
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/cpu/thread_state.h"
|
||||
#include "xenia/debug/breakpoint.h"
|
||||
#include "xenia/debug/debug_listener.h"
|
||||
#include "xenia/debug/thread_execution_info.h"
|
||||
|
||||
DECLARE_bool(debug);
|
||||
|
||||
namespace xe {
|
||||
class Emulator;
|
||||
namespace kernel {
|
||||
class XThread;
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
// 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,
|
||||
// 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,
|
||||
};
|
||||
|
||||
class Debugger {
|
||||
public:
|
||||
explicit Debugger(Emulator* emulator);
|
||||
~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);
|
||||
|
||||
// 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_id);
|
||||
|
||||
// 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).
|
||||
|
||||
// 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_id);
|
||||
// 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_id);
|
||||
|
||||
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(xe::cpu::Function* function);
|
||||
|
||||
bool OnUnhandledException(Exception* ex);
|
||||
|
||||
private:
|
||||
// Synchronously demands a debug listener.
|
||||
void DemandDebugListener();
|
||||
|
||||
// Suspends all known threads (except the caller).
|
||||
bool SuspendAllThreads();
|
||||
// Resumes the given thread.
|
||||
bool ResumeThread(uint32_t thread_id);
|
||||
// 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;
|
||||
|
||||
// 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_;
|
||||
|
||||
xe::global_critical_region global_critical_region_;
|
||||
|
||||
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
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_DEBUGGER_H_
|
||||
@@ -1,114 +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_FUNCTION_TRACE_DATA_H_
|
||||
#define XENIA_DEBUG_FUNCTION_TRACE_DATA_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#include "xenia/base/memory.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
class FunctionDisasmData {
|
||||
public:
|
||||
struct Header {
|
||||
// Format is used by tooling, changes must be made across all targets.
|
||||
// + 0 4b (data size)
|
||||
// + 4 4b start_address
|
||||
// + 8 4b end_address
|
||||
// +12 4b type (user, external, etc)
|
||||
// +16 4b offset of source disasm
|
||||
// +20 4b length of source disasm
|
||||
// +24 4b offset of raw hir disasm
|
||||
// +28 4b length of raw hir disasm
|
||||
// +32 4b offset of opt hir disasm
|
||||
// +36 4b length of opt hir disasm
|
||||
// +40 4b offset of machine code disasm
|
||||
// +44 4b length of machine code disasm
|
||||
};
|
||||
|
||||
static size_t SizeOfHeader() { return sizeof(Header); }
|
||||
};
|
||||
|
||||
class FunctionTraceData {
|
||||
public:
|
||||
static const int kFunctionCallerHistoryCount = 4;
|
||||
|
||||
struct Header {
|
||||
// Format is used by tooling, changes must be made across all targets.
|
||||
// + 0 4b (data size)
|
||||
// + 4 4b start_address
|
||||
// + 8 4b end_address
|
||||
// +12 4b type (user, external, etc)
|
||||
// +16 8b function_thread_use // bitmask of thread id
|
||||
// +24 8b function_call_count
|
||||
// +32 4b+ function_caller_history[4]
|
||||
// +48 8b+ instruction_execute_count[instruction count]
|
||||
uint32_t data_size;
|
||||
uint32_t start_address;
|
||||
uint32_t end_address;
|
||||
uint32_t type;
|
||||
uint64_t function_thread_use;
|
||||
uint64_t function_call_count;
|
||||
uint32_t function_caller_history[kFunctionCallerHistoryCount];
|
||||
// uint64_t instruction_execute_count[];
|
||||
};
|
||||
|
||||
FunctionTraceData() : header_(nullptr) {}
|
||||
|
||||
void Reset(uint8_t* trace_data, size_t trace_data_size,
|
||||
uint32_t start_address, uint32_t end_address) {
|
||||
header_ = reinterpret_cast<Header*>(trace_data);
|
||||
header_->data_size = uint32_t(trace_data_size);
|
||||
header_->start_address = start_address;
|
||||
header_->end_address = end_address;
|
||||
header_->type = 0;
|
||||
header_->function_thread_use = 0;
|
||||
header_->function_call_count = 0;
|
||||
for (int i = 0; i < kFunctionCallerHistoryCount; ++i) {
|
||||
header_->function_caller_history[i] = 0;
|
||||
}
|
||||
// Clear any remaining.
|
||||
std::memset(trace_data + sizeof(Header), 0,
|
||||
trace_data_size - sizeof(Header));
|
||||
}
|
||||
|
||||
bool is_valid() const { return header_ != nullptr; }
|
||||
|
||||
uint32_t start_address() const { return header_->start_address; }
|
||||
uint32_t end_address() const { return header_->end_address; }
|
||||
uint32_t instruction_count() const {
|
||||
return (header_->end_address - header_->start_address) / 4 + 1;
|
||||
}
|
||||
|
||||
Header* header() const { return header_; }
|
||||
|
||||
uint8_t* instruction_execute_counts() const {
|
||||
return reinterpret_cast<uint8_t*>(header_) + sizeof(Header);
|
||||
}
|
||||
|
||||
static size_t SizeOfHeader() { return sizeof(Header); }
|
||||
|
||||
static size_t SizeOfInstructionCounts(uint32_t start_address,
|
||||
uint32_t end_address) {
|
||||
uint32_t instruction_count = (end_address - start_address) / 4 + 1;
|
||||
return instruction_count * 8;
|
||||
}
|
||||
|
||||
private:
|
||||
Header* header_;
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_FUNCTION_TRACE_DATA_H_
|
||||
@@ -1,18 +0,0 @@
|
||||
project_root = "../../.."
|
||||
include(project_root.."/tools/build")
|
||||
|
||||
group("src")
|
||||
project("xenia-debug")
|
||||
uuid("1c33f25a-4bc0-4959-8092-4223ce749d9b")
|
||||
kind("StaticLib")
|
||||
language("C++")
|
||||
links({
|
||||
"xenia-base",
|
||||
"xenia-cpu",
|
||||
})
|
||||
defines({
|
||||
})
|
||||
includedirs({
|
||||
project_root.."/third_party/gflags/src",
|
||||
})
|
||||
local_platform_files()
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2016 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/debug/thread_execution_info.h"
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
ThreadExecutionInfo::ThreadExecutionInfo() = default;
|
||||
|
||||
ThreadExecutionInfo::~ThreadExecutionInfo() = default;
|
||||
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
@@ -1,108 +0,0 @@
|
||||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2016 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_DEBUG_THREAD_EXECUTION_INFO_H_
|
||||
#define XENIA_DEBUG_THREAD_EXECUTION_INFO_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/x64_context.h"
|
||||
#include "xenia/cpu/thread_state.h"
|
||||
#include "xenia/debug/breakpoint.h"
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
class XThread;
|
||||
} // namespace kernel
|
||||
} // namespace xe
|
||||
|
||||
namespace xe {
|
||||
namespace debug {
|
||||
|
||||
// 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::thread_id(), unique to the thread for the run of the emulator.
|
||||
uint32_t thread_id = 0;
|
||||
// XThread::handle() of the thread.
|
||||
// This will be invalidated when the thread dies.
|
||||
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::ppc::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;
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_DEBUG_THREAD_EXECUTION_INFO_H_
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/cpu/breakpoint.h"
|
||||
#include "xenia/cpu/ppc/ppc_opcode_info.h"
|
||||
#include "xenia/cpu/stack_walker.h"
|
||||
#include "xenia/gpu/graphics_system.h"
|
||||
@@ -40,6 +41,7 @@ namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
using xe::cpu::Breakpoint;
|
||||
using xe::kernel::XModule;
|
||||
using xe::kernel::XObject;
|
||||
using xe::kernel::XThread;
|
||||
@@ -52,7 +54,7 @@ const std::wstring kBaseTitle = L"Xenia Debugger";
|
||||
|
||||
DebugWindow::DebugWindow(Emulator* emulator, xe::ui::Loop* loop)
|
||||
: emulator_(emulator),
|
||||
debugger_(emulator->debugger()),
|
||||
processor_(emulator->processor()),
|
||||
loop_(loop),
|
||||
window_(xe::ui::Window::Create(loop_, kBaseTitle)) {
|
||||
if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) != CS_ERR_OK) {
|
||||
@@ -258,7 +260,7 @@ void DebugWindow::DrawToolbar() {
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered,
|
||||
ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
|
||||
if (ImGui::Button("Pause", ImVec2(80, 0))) {
|
||||
debugger_->Pause();
|
||||
processor_->Pause();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Interrupt the program and break into the debugger.");
|
||||
@@ -266,7 +268,7 @@ void DebugWindow::DrawToolbar() {
|
||||
ImGui::PopStyleColor(3);
|
||||
} else {
|
||||
if (ImGui::Button("Continue", ImVec2(80, 0))) {
|
||||
debugger_->Continue();
|
||||
processor_->Continue();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Resume the program from the current location.");
|
||||
@@ -281,20 +283,23 @@ void DebugWindow::DrawToolbar() {
|
||||
int current_thread_index = 0;
|
||||
StringBuffer thread_combo;
|
||||
int i = 0;
|
||||
for (auto thread_info : cache_.thread_execution_infos) {
|
||||
for (auto thread_info : cache_.thread_debug_infos) {
|
||||
if (thread_info == state_.thread_info) {
|
||||
current_thread_index = i;
|
||||
}
|
||||
thread_combo.Append(thread_info->thread ? thread_info->thread->name()
|
||||
: "(zombie)");
|
||||
thread_combo.Append(static_cast<char>(0));
|
||||
if (thread_info->state != cpu::ThreadDebugInfo::State::kZombie) {
|
||||
thread_combo.Append(thread_info->thread->name());
|
||||
} else {
|
||||
thread_combo.Append("(zombie)");
|
||||
}
|
||||
thread_combo.Append('\0');
|
||||
++i;
|
||||
}
|
||||
if (ImGui::Combo("##thread_combo", ¤t_thread_index,
|
||||
thread_combo.GetString(), 10)) {
|
||||
// Thread changed.
|
||||
SelectThreadStackFrame(cache_.thread_execution_infos[current_thread_index],
|
||||
0, true);
|
||||
SelectThreadStackFrame(cache_.thread_debug_infos[current_thread_index], 0,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,8 +356,8 @@ void DebugWindow::DrawSourcePane() {
|
||||
if (ImGui::ButtonEx("Step PPC", ImVec2(0, 0),
|
||||
can_step ? 0 : ImGuiButtonFlags_Disabled)) {
|
||||
// By enabling the button when stepping we allow repeat behavior.
|
||||
if (debugger_->execution_state() != ExecutionState::kStepping) {
|
||||
debugger_->StepGuestInstruction(state_.thread_info->thread_id);
|
||||
if (processor_->execution_state() != cpu::ExecutionState::kStepping) {
|
||||
processor_->StepGuestInstruction(state_.thread_info->thread_id);
|
||||
}
|
||||
}
|
||||
ImGui::PopButtonRepeat();
|
||||
@@ -369,8 +374,8 @@ void DebugWindow::DrawSourcePane() {
|
||||
if (ImGui::ButtonEx("Step x64", ImVec2(0, 0),
|
||||
can_step ? 0 : ImGuiButtonFlags_Disabled)) {
|
||||
// By enabling the button when stepping we allow repeat behavior.
|
||||
if (debugger_->execution_state() != ExecutionState::kStepping) {
|
||||
debugger_->StepHostInstruction(state_.thread_info->thread_id);
|
||||
if (processor_->execution_state() != cpu::ExecutionState::kStepping) {
|
||||
processor_->StepHostInstruction(state_.thread_info->thread_id);
|
||||
}
|
||||
}
|
||||
ImGui::PopButtonRepeat();
|
||||
@@ -494,10 +499,10 @@ void DebugWindow::DrawGuestFunctionSource() {
|
||||
}
|
||||
|
||||
bool has_guest_bp =
|
||||
LookupBreakpointAtAddress(CodeBreakpoint::AddressType::kGuest,
|
||||
address) != nullptr;
|
||||
DrawBreakpointGutterButton(has_guest_bp,
|
||||
CodeBreakpoint::AddressType::kGuest, address);
|
||||
LookupBreakpointAtAddress(Breakpoint::AddressType::kGuest, address) !=
|
||||
nullptr;
|
||||
DrawBreakpointGutterButton(has_guest_bp, Breakpoint::AddressType::kGuest,
|
||||
address);
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::Text(" %c ", is_current_instr ? '>' : ' ');
|
||||
@@ -563,10 +568,9 @@ void DebugWindow::DrawMachineCodeSource(const uint8_t* machine_code_ptr,
|
||||
ScrollToSourceIfPcChanged();
|
||||
}
|
||||
|
||||
bool has_host_bp =
|
||||
LookupBreakpointAtAddress(CodeBreakpoint::AddressType::kHost,
|
||||
insn.address) != nullptr;
|
||||
DrawBreakpointGutterButton(has_host_bp, CodeBreakpoint::AddressType::kHost,
|
||||
bool has_host_bp = LookupBreakpointAtAddress(Breakpoint::AddressType::kHost,
|
||||
insn.address) != nullptr;
|
||||
DrawBreakpointGutterButton(has_host_bp, Breakpoint::AddressType::kHost,
|
||||
insn.address);
|
||||
ImGui::SameLine();
|
||||
|
||||
@@ -585,7 +589,7 @@ void DebugWindow::DrawMachineCodeSource(const uint8_t* machine_code_ptr,
|
||||
}
|
||||
|
||||
void DebugWindow::DrawBreakpointGutterButton(
|
||||
bool has_breakpoint, CodeBreakpoint::AddressType address_type,
|
||||
bool has_breakpoint, Breakpoint::AddressType address_type,
|
||||
uint64_t address) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button,
|
||||
has_breakpoint
|
||||
@@ -991,10 +995,11 @@ void DebugWindow::DrawThreadsPane() {
|
||||
// expand all toggle
|
||||
ImGui::EndGroup();
|
||||
ImGui::BeginChild("##threads_listing");
|
||||
for (size_t i = 0; i < cache_.thread_execution_infos.size(); ++i) {
|
||||
auto thread_info = cache_.thread_execution_infos[i];
|
||||
for (size_t i = 0; i < cache_.thread_debug_infos.size(); ++i) {
|
||||
auto thread_info = cache_.thread_debug_infos[i];
|
||||
bool is_current_thread = thread_info == state_.thread_info;
|
||||
auto thread = thread_info->thread;
|
||||
auto thread =
|
||||
emulator_->kernel_state()->GetThreadByID(thread_info->thread_id);
|
||||
if (!thread) {
|
||||
// TODO(benvanik): better display of zombie thread states.
|
||||
continue;
|
||||
@@ -1146,7 +1151,7 @@ void DebugWindow::DrawBreakpointsPane() {
|
||||
if (ImGui::InputText("##guest_address", ppc_buffer, 9, input_flags)) {
|
||||
uint32_t address = string_util::from_string<uint32_t>(ppc_buffer, true);
|
||||
ppc_buffer[0] = 0;
|
||||
CreateCodeBreakpoint(CodeBreakpoint::AddressType::kGuest, address);
|
||||
CreateCodeBreakpoint(Breakpoint::AddressType::kGuest, address);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
@@ -1162,7 +1167,7 @@ void DebugWindow::DrawBreakpointsPane() {
|
||||
if (ImGui::InputText("##host_address", x64_buffer, 17, input_flags)) {
|
||||
uint64_t address = string_util::from_string<uint64_t>(x64_buffer, true);
|
||||
x64_buffer[0] = 0;
|
||||
CreateCodeBreakpoint(CodeBreakpoint::AddressType::kHost, address);
|
||||
CreateCodeBreakpoint(Breakpoint::AddressType::kHost, address);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
@@ -1299,7 +1304,7 @@ void DebugWindow::DrawBreakpointsPane() {
|
||||
if (ImGui::Button("Clear")) {
|
||||
// Unregister and delete all breakpoints.
|
||||
while (!state.all_breakpoints.empty()) {
|
||||
DeleteBreakpoint(state.all_breakpoints.front().get());
|
||||
DeleteCodeBreakpoint(state.all_breakpoints.front().get());
|
||||
}
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
@@ -1325,30 +1330,17 @@ void DebugWindow::DrawBreakpointsPane() {
|
||||
bool is_selected = false; // in function/stopped on line?
|
||||
if (ImGui::Selectable(breakpoint_str.c_str(), &is_selected,
|
||||
ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
switch (breakpoint->type()) {
|
||||
case Breakpoint::Type::kCode: {
|
||||
auto code_breakpoint =
|
||||
static_cast<CodeBreakpoint*>(breakpoint.get());
|
||||
auto function = code_breakpoint->guest_function();
|
||||
assert_not_null(function);
|
||||
if (code_breakpoint->address_type() ==
|
||||
CodeBreakpoint::AddressType::kGuest) {
|
||||
NavigateToFunction(code_breakpoint->guest_function(),
|
||||
code_breakpoint->guest_address(),
|
||||
function->MapGuestAddressToMachineCode(
|
||||
code_breakpoint->guest_address()));
|
||||
} else {
|
||||
NavigateToFunction(function,
|
||||
function->MapMachineCodeToGuestAddress(
|
||||
code_breakpoint->host_address()),
|
||||
code_breakpoint->host_address());
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Ignored.
|
||||
break;
|
||||
}
|
||||
auto function = breakpoint->guest_function();
|
||||
assert_not_null(function);
|
||||
if (breakpoint->address_type() == Breakpoint::AddressType::kGuest) {
|
||||
NavigateToFunction(breakpoint->guest_function(),
|
||||
breakpoint->guest_address(),
|
||||
function->MapGuestAddressToMachineCode(
|
||||
breakpoint->guest_address()));
|
||||
} else {
|
||||
NavigateToFunction(function, function->MapMachineCodeToGuestAddress(
|
||||
breakpoint->host_address()),
|
||||
breakpoint->host_address());
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem("##breakpoint_context_menu")) {
|
||||
@@ -1362,7 +1354,7 @@ void DebugWindow::DrawBreakpointsPane() {
|
||||
ImGui::ListBoxFooter();
|
||||
if (!to_delete.empty()) {
|
||||
for (auto breakpoint : to_delete) {
|
||||
DeleteBreakpoint(breakpoint);
|
||||
DeleteCodeBreakpoint(breakpoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1386,7 +1378,7 @@ void DebugWindow::DrawLogPane() {
|
||||
// if big, click to open dialog with contents
|
||||
}
|
||||
|
||||
void DebugWindow::SelectThreadStackFrame(ThreadExecutionInfo* thread_info,
|
||||
void DebugWindow::SelectThreadStackFrame(cpu::ThreadDebugInfo* thread_info,
|
||||
size_t stack_frame_index,
|
||||
bool always_navigate) {
|
||||
state_.has_changed_thread = false;
|
||||
@@ -1431,24 +1423,25 @@ void DebugWindow::UpdateCache() {
|
||||
|
||||
loop_->Post([this]() {
|
||||
std::wstring title = kBaseTitle;
|
||||
switch (debugger_->execution_state()) {
|
||||
case ExecutionState::kEnded:
|
||||
switch (processor_->execution_state()) {
|
||||
case cpu::ExecutionState::kEnded:
|
||||
title += L" (ended)";
|
||||
break;
|
||||
case ExecutionState::kPaused:
|
||||
case cpu::ExecutionState::kPaused:
|
||||
title += L" (paused)";
|
||||
break;
|
||||
case ExecutionState::kRunning:
|
||||
case cpu::ExecutionState::kRunning:
|
||||
title += L" (running)";
|
||||
break;
|
||||
case ExecutionState::kStepping:
|
||||
case cpu::ExecutionState::kStepping:
|
||||
title += L" (stepping)";
|
||||
break;
|
||||
}
|
||||
window_->set_title(title);
|
||||
});
|
||||
|
||||
cache_.is_running = debugger_->execution_state() == ExecutionState::kRunning;
|
||||
cache_.is_running =
|
||||
processor_->execution_state() == cpu::ExecutionState::kRunning;
|
||||
if (cache_.is_running) {
|
||||
// Early exit - the rest of the data is kept stale on purpose.
|
||||
return;
|
||||
@@ -1459,18 +1452,22 @@ void DebugWindow::UpdateCache() {
|
||||
cache_.modules =
|
||||
object_table->GetObjectsByType<XModule>(XObject::Type::kTypeModule);
|
||||
|
||||
cache_.thread_execution_infos = debugger_->QueryThreadExecutionInfos();
|
||||
cache_.thread_debug_infos = processor_->QueryThreadDebugInfos();
|
||||
|
||||
SelectThreadStackFrame(state_.thread_info, state_.thread_stack_frame_index,
|
||||
false);
|
||||
}
|
||||
|
||||
void DebugWindow::CreateCodeBreakpoint(CodeBreakpoint::AddressType address_type,
|
||||
void DebugWindow::CreateCodeBreakpoint(Breakpoint::AddressType address_type,
|
||||
uint64_t address) {
|
||||
auto& state = state_.breakpoints;
|
||||
auto breakpoint =
|
||||
std::make_unique<CodeBreakpoint>(debugger_, address_type, address);
|
||||
if (breakpoint->address_type() == CodeBreakpoint::AddressType::kGuest) {
|
||||
auto breakpoint = std::make_unique<Breakpoint>(
|
||||
processor_, address_type, address,
|
||||
[this](Breakpoint* breakpoint, cpu::ThreadDebugInfo* thread_info,
|
||||
uint64_t host_address) {
|
||||
OnBreakpointHit(breakpoint, thread_info);
|
||||
});
|
||||
if (breakpoint->address_type() == Breakpoint::AddressType::kGuest) {
|
||||
auto& map = state.code_breakpoints_by_guest_address;
|
||||
auto it = map.find(breakpoint->guest_address());
|
||||
if (it != map.end()) {
|
||||
@@ -1487,18 +1484,18 @@ void DebugWindow::CreateCodeBreakpoint(CodeBreakpoint::AddressType address_type,
|
||||
}
|
||||
map.emplace(breakpoint->host_address(), breakpoint.get());
|
||||
}
|
||||
debugger_->AddBreakpoint(breakpoint.get());
|
||||
processor_->AddBreakpoint(breakpoint.get());
|
||||
state.all_breakpoints.emplace_back(std::move(breakpoint));
|
||||
}
|
||||
|
||||
void DebugWindow::DeleteCodeBreakpoint(CodeBreakpoint* breakpoint) {
|
||||
void DebugWindow::DeleteCodeBreakpoint(Breakpoint* breakpoint) {
|
||||
auto& state = state_.breakpoints;
|
||||
for (size_t i = 0; i < state.all_breakpoints.size(); ++i) {
|
||||
if (state.all_breakpoints[i].get() != breakpoint) {
|
||||
continue;
|
||||
}
|
||||
debugger_->RemoveBreakpoint(breakpoint);
|
||||
if (breakpoint->address_type() == CodeBreakpoint::AddressType::kGuest) {
|
||||
processor_->RemoveBreakpoint(breakpoint);
|
||||
if (breakpoint->address_type() == Breakpoint::AddressType::kGuest) {
|
||||
auto& map = state.code_breakpoints_by_guest_address;
|
||||
auto it = map.find(breakpoint->guest_address());
|
||||
if (it != map.end()) {
|
||||
@@ -1516,25 +1513,10 @@ void DebugWindow::DeleteCodeBreakpoint(CodeBreakpoint* breakpoint) {
|
||||
}
|
||||
}
|
||||
|
||||
void DebugWindow::DeleteBreakpoint(Breakpoint* breakpoint) {
|
||||
Breakpoint* DebugWindow::LookupBreakpointAtAddress(
|
||||
Breakpoint::AddressType address_type, uint64_t address) {
|
||||
auto& state = state_.breakpoints;
|
||||
if (breakpoint->type() == Breakpoint::Type::kCode) {
|
||||
DeleteCodeBreakpoint(static_cast<CodeBreakpoint*>(breakpoint));
|
||||
} else {
|
||||
for (size_t i = 0; i < state.all_breakpoints.size(); ++i) {
|
||||
if (state.all_breakpoints[i].get() == breakpoint) {
|
||||
debugger_->RemoveBreakpoint(breakpoint);
|
||||
state.all_breakpoints.erase(state.all_breakpoints.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodeBreakpoint* DebugWindow::LookupBreakpointAtAddress(
|
||||
CodeBreakpoint::AddressType address_type, uint64_t address) {
|
||||
auto& state = state_.breakpoints;
|
||||
if (address_type == CodeBreakpoint::AddressType::kGuest) {
|
||||
if (address_type == Breakpoint::AddressType::kGuest) {
|
||||
auto& map = state.code_breakpoints_by_guest_address;
|
||||
auto it = map.find(static_cast<uint32_t>(address));
|
||||
return it == map.end() ? nullptr : it->second;
|
||||
@@ -1554,7 +1536,7 @@ void DebugWindow::OnDetached() {
|
||||
|
||||
// Remove all breakpoints.
|
||||
while (!state_.breakpoints.all_breakpoints.empty()) {
|
||||
DeleteBreakpoint(state_.breakpoints.all_breakpoints.front().get());
|
||||
DeleteCodeBreakpoint(state_.breakpoints.all_breakpoints.front().get());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1573,17 +1555,15 @@ void DebugWindow::OnExecutionEnded() {
|
||||
loop_->Post([this]() { window_->set_focus(true); });
|
||||
}
|
||||
|
||||
void DebugWindow::OnStepCompleted(xe::kernel::XThread* thread) {
|
||||
void DebugWindow::OnStepCompleted(cpu::ThreadDebugInfo* thread_info) {
|
||||
UpdateCache();
|
||||
auto thread_info = debugger_->QueryThreadExecutionInfo(thread->thread_id());
|
||||
SelectThreadStackFrame(thread_info, 0, true);
|
||||
loop_->Post([this]() { window_->set_focus(true); });
|
||||
}
|
||||
|
||||
void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint,
|
||||
xe::kernel::XThread* thread) {
|
||||
cpu::ThreadDebugInfo* thread_info) {
|
||||
UpdateCache();
|
||||
auto thread_info = debugger_->QueryThreadExecutionInfo(thread->thread_id());
|
||||
SelectThreadStackFrame(thread_info, 0, true);
|
||||
loop_->Post([this]() { window_->set_focus(true); });
|
||||
}
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/x64_context.h"
|
||||
#include "xenia/debug/debugger.h"
|
||||
#include "xenia/cpu/breakpoint.h"
|
||||
#include "xenia/cpu/debug_listener.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
#include "xenia/ui/menu_item.h"
|
||||
@@ -25,7 +27,7 @@ namespace xe {
|
||||
namespace debug {
|
||||
namespace ui {
|
||||
|
||||
class DebugWindow : public DebugListener {
|
||||
class DebugWindow : public cpu::DebugListener {
|
||||
public:
|
||||
~DebugWindow();
|
||||
|
||||
@@ -41,9 +43,9 @@ class DebugWindow : public DebugListener {
|
||||
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;
|
||||
void OnStepCompleted(cpu::ThreadDebugInfo* thread_info) override;
|
||||
void OnBreakpointHit(cpu::Breakpoint* breakpoint,
|
||||
cpu::ThreadDebugInfo* thread_info) override;
|
||||
|
||||
private:
|
||||
explicit DebugWindow(Emulator* emulator, xe::ui::Loop* loop);
|
||||
@@ -56,7 +58,7 @@ class DebugWindow : public DebugListener {
|
||||
void DrawGuestFunctionSource();
|
||||
void DrawMachineCodeSource(const uint8_t* ptr, size_t length);
|
||||
void DrawBreakpointGutterButton(bool has_breakpoint,
|
||||
CodeBreakpoint::AddressType address_type,
|
||||
cpu::Breakpoint::AddressType address_type,
|
||||
uint64_t address);
|
||||
void ScrollToSourceIfPcChanged();
|
||||
void DrawRegistersPane();
|
||||
@@ -69,7 +71,7 @@ class DebugWindow : public DebugListener {
|
||||
void DrawBreakpointsPane();
|
||||
void DrawLogPane();
|
||||
|
||||
void SelectThreadStackFrame(ThreadExecutionInfo* thread_info,
|
||||
void SelectThreadStackFrame(cpu::ThreadDebugInfo* thread_info,
|
||||
size_t stack_frame_index, bool always_navigate);
|
||||
void NavigateToFunction(cpu::Function* function, uint32_t guest_pc = 0,
|
||||
uint64_t host_pc = 0);
|
||||
@@ -78,15 +80,14 @@ class DebugWindow : public DebugListener {
|
||||
|
||||
void UpdateCache();
|
||||
|
||||
void CreateCodeBreakpoint(CodeBreakpoint::AddressType address_type,
|
||||
void CreateCodeBreakpoint(cpu::Breakpoint::AddressType address_type,
|
||||
uint64_t address);
|
||||
void DeleteCodeBreakpoint(CodeBreakpoint* breakpoint);
|
||||
void DeleteBreakpoint(Breakpoint* breakpoint);
|
||||
CodeBreakpoint* LookupBreakpointAtAddress(
|
||||
CodeBreakpoint::AddressType address_type, uint64_t address);
|
||||
void DeleteCodeBreakpoint(cpu::Breakpoint* breakpoint);
|
||||
cpu::Breakpoint* LookupBreakpointAtAddress(
|
||||
cpu::Breakpoint::AddressType address_type, uint64_t address);
|
||||
|
||||
Emulator* emulator_ = nullptr;
|
||||
Debugger* debugger_ = nullptr;
|
||||
cpu::Processor* processor_ = nullptr;
|
||||
xe::ui::Loop* loop_ = nullptr;
|
||||
std::unique_ptr<xe::ui::Window> window_;
|
||||
uint64_t last_draw_tick_count_ = 0;
|
||||
@@ -99,7 +100,7 @@ class DebugWindow : public DebugListener {
|
||||
struct ImDataCache {
|
||||
bool is_running = false;
|
||||
std::vector<kernel::object_ref<kernel::XModule>> modules;
|
||||
std::vector<ThreadExecutionInfo*> thread_execution_infos;
|
||||
std::vector<cpu::ThreadDebugInfo*> thread_debug_infos;
|
||||
} cache_;
|
||||
|
||||
enum class RegisterGroup {
|
||||
@@ -117,7 +118,7 @@ class DebugWindow : public DebugListener {
|
||||
static const int kRightPaneMemory = 1;
|
||||
int right_pane_tab = kRightPaneThreads;
|
||||
|
||||
ThreadExecutionInfo* thread_info = nullptr;
|
||||
cpu::ThreadDebugInfo* thread_info = nullptr;
|
||||
size_t thread_stack_frame_index = 0;
|
||||
bool has_changed_thread = false;
|
||||
|
||||
@@ -131,10 +132,10 @@ class DebugWindow : public DebugListener {
|
||||
|
||||
struct {
|
||||
char kernel_call_filter[64] = {0};
|
||||
std::vector<std::unique_ptr<Breakpoint>> all_breakpoints;
|
||||
std::unordered_map<uint32_t, CodeBreakpoint*>
|
||||
std::vector<std::unique_ptr<cpu::Breakpoint>> all_breakpoints;
|
||||
std::unordered_map<uint32_t, cpu::Breakpoint*>
|
||||
code_breakpoints_by_guest_address;
|
||||
std::unordered_map<uintptr_t, CodeBreakpoint*>
|
||||
std::unordered_map<uintptr_t, cpu::Breakpoint*>
|
||||
code_breakpoints_by_host_address;
|
||||
} breakpoints;
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ project("xenia-debug-ui")
|
||||
"imgui",
|
||||
"xenia-base",
|
||||
"xenia-cpu",
|
||||
"xenia-debug",
|
||||
"xenia-ui",
|
||||
"yaml-cpp",
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user