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:
Ben Vanik 2016-01-18 11:48:21 -08:00
parent ca135eb0e7
commit 6777ce6668
51 changed files with 1687 additions and 2250 deletions

View File

@ -179,7 +179,6 @@ solution("xenia")
include("src/xenia/base")
include("src/xenia/cpu")
include("src/xenia/cpu/backend/x64")
include("src/xenia/debug")
include("src/xenia/debug/ui")
include("src/xenia/gpu")
include("src/xenia/gpu/gl4")

View File

@ -255,20 +255,20 @@ void EmulatorWindow::CpuTimeScalarSetDouble() {
}
void EmulatorWindow::CpuBreakIntoDebugger() {
auto debugger = emulator()->debugger();
if (!debugger) {
if (!FLAGS_debug) {
xe::ui::ImGuiDialog::ShowMessageBox(window_.get(), "Xenia Debugger",
"Xenia must be launched with the "
"--debug flag in order to enable "
"debugging.");
return;
}
if (debugger->execution_state() == debug::ExecutionState::kRunning) {
auto processor = emulator()->processor();
if (processor->execution_state() == cpu::ExecutionState::kRunning) {
// Currently running, so interrupt (and show the debugger).
debugger->Pause();
processor->Pause();
} else {
// Not running, so just bring the debugger into focus.
debugger->Show();
processor->ShowDebugger();
}
}

View File

@ -16,7 +16,6 @@ project("xenia-app")
"xenia-core",
"xenia-cpu",
"xenia-cpu-backend-x64",
"xenia-debug",
"xenia-debug-ui",
"xenia-gpu",
"xenia-gpu-gl4",

View File

@ -137,9 +137,9 @@ int xenia_main(const std::vector<std::wstring>& args) {
// Set a debug handler.
// This will respond to debugging requests so we can open the debug UI.
std::unique_ptr<xe::debug::ui::DebugWindow> debug_window;
if (emulator->debugger()) {
emulator->debugger()->set_debug_listener_request_handler([&](
xe::debug::Debugger* debugger) {
if (FLAGS_debug) {
emulator->processor()->set_debug_listener_request_handler([&](
xe::cpu::Processor* processor) {
if (debug_window) {
return debug_window.get();
}
@ -147,7 +147,7 @@ int xenia_main(const std::vector<std::wstring>& args) {
debug_window = xe::debug::ui::DebugWindow::Create(
emulator.get(), emulator_window->loop());
debug_window->window()->on_closed.AddListener([&](xe::ui::UIEvent* e) {
emulator->debugger()->set_debug_listener(nullptr);
emulator->processor()->set_debug_listener(nullptr);
emulator_window->loop()->Post([&]() { debug_window.reset(); });
});
});

View File

@ -58,6 +58,7 @@ std::string format_string(const char* format, va_list args) {
max_len *= 2;
} else {
// Everything fit for sure.
new_s.resize(ret);
return new_s;
}
}
@ -81,6 +82,7 @@ std::wstring format_string(const wchar_t* format, va_list args) {
max_len *= 2;
} else {
// Everything fit for sure.
new_s.resize(ret);
return new_s;
}
}

View File

@ -14,7 +14,7 @@
namespace xe {
namespace cpu {
class DebugInfo;
class FunctionDebugInfo;
class GuestFunction;
namespace hir {
class HIRBuilder;
@ -39,7 +39,7 @@ class Assembler {
virtual bool Assemble(GuestFunction* function, hir::HIRBuilder* builder,
uint32_t debug_info_flags,
std::unique_ptr<DebugInfo> debug_info) = 0;
std::unique_ptr<FunctionDebugInfo> debug_info) = 0;
protected:
Backend* backend_;

View File

@ -13,6 +13,7 @@
#include <memory>
#include "xenia/cpu/backend/machine_info.h"
#include "xenia/cpu/thread_debug_info.h"
namespace xe {
namespace cpu {
@ -53,11 +54,15 @@ class Backend {
virtual std::unique_ptr<GuestFunction> CreateGuestFunction(
Module* module, uint32_t address) = 0;
virtual bool InstallBreakpoint(Breakpoint* bp) { return false; }
virtual bool InstallBreakpoint(Breakpoint* bp, Function* func) {
return false;
}
virtual bool UninstallBreakpoint(Breakpoint* bp) { return false; }
// Calculates the next host instruction based on the current thread state and
// current PC. This will look for branches and other control flow
// instructions.
virtual uint64_t CalculateNextHostInstruction(ThreadDebugInfo* thread_info,
uint64_t current_pc) = 0;
virtual void InstallBreakpoint(Breakpoint* breakpoint) {}
virtual void InstallBreakpoint(Breakpoint* breakpoint, Function* fn) {}
virtual void UninstallBreakpoint(Breakpoint* breakpoint) {}
protected:
Processor* processor_;

View File

@ -16,6 +16,7 @@
#include "xenia/base/profiling.h"
#include "xenia/base/reset_scope.h"
#include "xenia/cpu/backend/x64/x64_backend.h"
#include "xenia/cpu/backend/x64/x64_code_cache.h"
#include "xenia/cpu/backend/x64/x64_emitter.h"
#include "xenia/cpu/backend/x64/x64_function.h"
#include "xenia/cpu/cpu_flags.h"
@ -67,7 +68,7 @@ void X64Assembler::Reset() {
bool X64Assembler::Assemble(GuestFunction* function, HIRBuilder* builder,
uint32_t debug_info_flags,
std::unique_ptr<DebugInfo> debug_info) {
std::unique_ptr<FunctionDebugInfo> debug_info) {
SCOPE_profile_cpu_f("cpu");
// Reset when we leave.
@ -89,18 +90,17 @@ bool X64Assembler::Assemble(GuestFunction* function, HIRBuilder* builder,
string_buffer_.Reset();
}
// Dump debug data.
if (FLAGS_disassemble_functions) {
if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmSource) {
// auto fn_data = backend_->processor()->debugger()->AllocateFunctionData(
// xe::debug::FunctionDisasmData::SizeOfHeader());
}
}
function->set_debug_info(std::move(debug_info));
static_cast<X64Function*>(function)->Setup(
reinterpret_cast<uint8_t*>(machine_code), code_size);
// Install into indirection table.
uint64_t host_address = reinterpret_cast<uint64_t>(machine_code);
assert_true((host_address >> 32) == 0);
reinterpret_cast<X64CodeCache*>(backend_->code_cache())
->AddIndirection(function->address(),
static_cast<uint32_t>(host_address));
return true;
}

View File

@ -37,7 +37,7 @@ class X64Assembler : public Assembler {
bool Assemble(GuestFunction* function, hir::HIRBuilder* builder,
uint32_t debug_info_flags,
std::unique_ptr<DebugInfo> debug_info) override;
std::unique_ptr<FunctionDebugInfo> debug_info) override;
private:
void DumpMachineCode(void* machine_code, size_t code_size,

View File

@ -9,6 +9,8 @@
#include "xenia/cpu/backend/x64/x64_backend.h"
#include "third_party/capstone/include/capstone.h"
#include "third_party/capstone/include/x86.h"
#include "xenia/base/exception_handler.h"
#include "xenia/cpu/backend/x64/x64_assembler.h"
#include "xenia/cpu/backend/x64/x64_code_cache.h"
@ -39,13 +41,23 @@ class X64ThunkEmitter : public X64Emitter {
};
X64Backend::X64Backend(Processor* processor)
: Backend(processor), code_cache_(nullptr), emitter_data_(0) {}
: Backend(processor), code_cache_(nullptr), emitter_data_(0) {
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);
}
X64Backend::~X64Backend() {
if (emitter_data_) {
processor()->memory()->SystemHeapFree(emitter_data_);
emitter_data_ = 0;
}
if (capstone_handle_) {
cs_close(&capstone_handle_);
}
ExceptionHandler::Uninstall(&ExceptionCallbackThunk, this);
}
@ -124,59 +136,227 @@ std::unique_ptr<GuestFunction> X64Backend::CreateGuestFunction(
return std::make_unique<X64Function>(module, address);
}
bool X64Backend::InstallBreakpoint(Breakpoint* bp) {
auto functions = processor()->FindFunctionsWithAddress(bp->address());
if (functions.empty()) {
// Go ahead and fail - let the caller handle this.
return false;
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;
}
for (auto function : functions) {
assert_true(function->is_guest());
auto guest_function = reinterpret_cast<cpu::GuestFunction*>(function);
auto code = guest_function->MapGuestAddressToMachineCode(bp->address());
if (!code) {
// This should not happen.
assert_always();
return false;
}
auto orig_bytes =
xe::load_and_swap<uint16_t>(reinterpret_cast<void*>(code + 0x0));
bp->backend_data().push_back({code, orig_bytes});
xe::store_and_swap<uint16_t>(reinterpret_cast<void*>(code + 0x0), 0x0F0C);
}
return true;
}
bool X64Backend::InstallBreakpoint(Breakpoint* bp, Function* func) {
assert_true(func->is_guest());
auto guest_function = reinterpret_cast<cpu::GuestFunction*>(func);
auto code = guest_function->MapGuestAddressToMachineCode(bp->address());
if (!code) {
#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 X64Backend::CalculateNextHostInstruction(ThreadDebugInfo* 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;
}
}
void X64Backend::InstallBreakpoint(Breakpoint* breakpoint) {
breakpoint->ForEachHostAddress([breakpoint](uint64_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);
breakpoint->backend_data().emplace_back(host_address, original_bytes);
});
}
void X64Backend::InstallBreakpoint(Breakpoint* breakpoint, Function* fn) {
assert_true(breakpoint->address_type() == Breakpoint::AddressType::kGuest);
assert_true(fn->is_guest());
auto guest_function = reinterpret_cast<cpu::GuestFunction*>(fn);
auto host_address =
guest_function->MapGuestAddressToMachineCode(breakpoint->guest_address());
if (!host_address) {
assert_always();
return false;
return;
}
// Assume we haven't already installed a breakpoint in this spot.
auto orig_bytes =
xe::load_and_swap<uint16_t>(reinterpret_cast<void*>(code + 0x0));
bp->backend_data().push_back({code, orig_bytes});
xe::store_and_swap<uint16_t>(reinterpret_cast<void*>(code + 0x0), 0x0F0C);
return true;
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);
breakpoint->backend_data().emplace_back(host_address, original_bytes);
}
bool X64Backend::UninstallBreakpoint(Breakpoint* bp) {
for (auto& pair : bp->backend_data()) {
xe::store_and_swap<uint16_t>(reinterpret_cast<void*>(pair.first),
uint16_t(pair.second));
void X64Backend::UninstallBreakpoint(Breakpoint* breakpoint) {
for (auto& pair : breakpoint->backend_data()) {
auto ptr = reinterpret_cast<uint8_t*>(pair.first);
auto instruction_bytes = xe::load_and_swap<uint16_t>(ptr);
assert_true(instruction_bytes == 0x0F0B);
xe::store_and_swap<uint16_t>(ptr, static_cast<uint16_t>(pair.second));
}
bp->backend_data().clear();
return true;
breakpoint->backend_data().clear();
}
bool X64Backend::ExceptionCallbackThunk(Exception* ex, void* data) {
@ -186,39 +366,22 @@ bool X64Backend::ExceptionCallbackThunk(Exception* ex, void* data) {
bool X64Backend::ExceptionCallback(Exception* ex) {
if (ex->code() != Exception::Code::kIllegalInstruction) {
// Has nothing to do with breakpoints. Not ours.
// 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 != 0x0F0C) {
// Not a BP instruction - not ours.
if (instruction_bytes != 0x0F0B) {
// Not our ud2 - not us.
return false;
}
uint64_t host_pcs[64];
cpu::StackFrame frames[64];
size_t count = processor()->stack_walker()->CaptureStackTrace(
host_pcs, 0, xe::countof(host_pcs));
processor()->stack_walker()->ResolveStack(host_pcs, frames, count);
if (count == 0) {
// Stack resolve failed.
return false;
}
for (int i = 0; i < count; i++) {
if (frames[i].type != cpu::StackFrame::Type::kGuest) {
continue;
}
if (processor()->BreakpointHit(frames[i].guest_pc, frames[i].host_pc)) {
return true;
}
}
// No breakpoints found at this address.
return false;
// Let the processor handle things.
return processor()->OnThreadBreakpointHit(ex);
}
X64ThunkEmitter::X64ThunkEmitter(X64Backend* backend, XbyakAllocator* allocator)

View File

@ -62,14 +62,19 @@ class X64Backend : public Backend {
std::unique_ptr<GuestFunction> CreateGuestFunction(Module* module,
uint32_t address) override;
bool InstallBreakpoint(Breakpoint* bp) override;
bool InstallBreakpoint(Breakpoint* bp, Function* func) override;
bool UninstallBreakpoint(Breakpoint* bp) override;
uint64_t CalculateNextHostInstruction(ThreadDebugInfo* thread_info,
uint64_t current_pc) override;
void InstallBreakpoint(Breakpoint* breakpoint) override;
void InstallBreakpoint(Breakpoint* breakpoint, Function* fn) override;
void UninstallBreakpoint(Breakpoint* breakpoint) override;
private:
static bool ExceptionCallbackThunk(Exception* ex, void* data);
bool ExceptionCallback(Exception* ex);
uintptr_t capstone_handle_ = 0;
std::unique_ptr<X64CodeCache> code_cache_;
uint32_t emitter_data_;

View File

@ -27,12 +27,11 @@
#include "xenia/cpu/backend/x64/x64_sequences.h"
#include "xenia/cpu/backend/x64/x64_stack_layout.h"
#include "xenia/cpu/cpu_flags.h"
#include "xenia/cpu/debug_info.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/function_debug_info.h"
#include "xenia/cpu/processor.h"
#include "xenia/cpu/symbol.h"
#include "xenia/cpu/thread_state.h"
#include "xenia/debug/debugger.h"
DEFINE_bool(enable_debugprint_log, false,
"Log debugprint traps to the active debugger");
@ -89,7 +88,7 @@ X64Emitter::X64Emitter(X64Backend* backend, XbyakAllocator* allocator)
X64Emitter::~X64Emitter() = default;
bool X64Emitter::Emit(GuestFunction* function, HIRBuilder* builder,
uint32_t debug_info_flags, DebugInfo* debug_info,
uint32_t debug_info_flags, FunctionDebugInfo* debug_info,
void** out_code_address, size_t* out_code_size,
std::vector<SourceMapEntry>* out_source_map) {
SCOPE_profile_cpu_f("cpu");
@ -187,7 +186,7 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t* out_stack_size) {
inc(qword[low_address(&trace_header->function_call_count)]);
// Get call history slot.
static_assert(debug::FunctionTraceData::kFunctionCallerHistoryCount == 4,
static_assert(FunctionTraceData::kFunctionCallerHistoryCount == 4,
"bitmask depends on count");
mov(rax, qword[low_address(&trace_header->function_call_count)]);
and_(rax, 0b00000011);

View File

@ -14,10 +14,10 @@
#include "xenia/base/arena.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/function_trace_data.h"
#include "xenia/cpu/hir/hir_builder.h"
#include "xenia/cpu/hir/instr.h"
#include "xenia/cpu/hir/value.h"
#include "xenia/debug/function_trace_data.h"
#include "xenia/memory.h"
// NOTE: must be included last as it expects windows.h to already be included.
@ -115,7 +115,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
X64Backend* backend() const { return backend_; }
bool Emit(GuestFunction* function, hir::HIRBuilder* builder,
uint32_t debug_info_flags, DebugInfo* debug_info,
uint32_t debug_info_flags, FunctionDebugInfo* debug_info,
void** out_code_address, size_t* out_code_size,
std::vector<SourceMapEntry>* out_source_map);
@ -190,7 +190,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
return (feature_flags_ & feature_flag) != 0;
}
DebugInfo* debug_info() const { return debug_info_; }
FunctionDebugInfo* debug_info() const { return debug_info_; }
size_t stack_size() const { return stack_size_; }
@ -212,9 +212,9 @@ class X64Emitter : public Xbyak::CodeGenerator {
hir::Instr* current_instr_ = nullptr;
DebugInfo* debug_info_ = nullptr;
FunctionDebugInfo* debug_info_ = nullptr;
uint32_t debug_info_flags_ = 0;
debug::FunctionTraceData* trace_data_ = nullptr;
FunctionTraceData* trace_data_ = nullptr;
Arena source_map_arena_;
size_t stack_size_ = 0;

View File

@ -9,31 +9,108 @@
#include "xenia/cpu/breakpoint.h"
#include "xenia/base/string_util.h"
#include "xenia/cpu/backend/backend.h"
#include "xenia/cpu/backend/code_cache.h"
namespace xe {
namespace cpu {
Breakpoint::Breakpoint(Processor* processor,
std::function<void(uint32_t, uint64_t)> hit_callback)
: processor_(processor), hit_callback_(hit_callback) {}
Breakpoint::Breakpoint(Processor* processor, uint32_t address,
std::function<void(uint32_t, uint64_t)> hit_callback)
: processor_(processor), address_(address), hit_callback_(hit_callback) {}
Breakpoint::Breakpoint(Processor* processor, AddressType address_type,
uint64_t address, HitCallback hit_callback)
: processor_(processor),
address_type_(address_type),
address_(address),
hit_callback_(hit_callback) {}
Breakpoint::~Breakpoint() { assert_false(installed_); }
bool Breakpoint::Install() {
void Breakpoint::Install() {
assert_false(installed_);
installed_ = processor_->InstallBreakpoint(this);
return installed_;
processor_->backend()->InstallBreakpoint(this);
installed_ = true;
}
bool Breakpoint::Uninstall() {
void Breakpoint::Uninstall() {
assert_true(installed_);
processor_->backend()->UninstallBreakpoint(this);
installed_ = false;
}
installed_ = !processor_->UninstallBreakpoint(this);
return !installed_;
std::string Breakpoint::to_string() const {
if (address_type_ == AddressType::kGuest) {
auto str =
std::string("PPC ") + xe::string_util::to_hex_string(guest_address());
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());
}
}
GuestFunction* Breakpoint::guest_function() const {
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 Breakpoint::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 Breakpoint::ForEachHostAddress(
std::function<void(uintptr_t)> callback) const {
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<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());
}
}
} // namespace cpu

View File

@ -17,22 +17,61 @@ namespace cpu {
class Breakpoint {
public:
Breakpoint(Processor* processor,
std::function<void(uint32_t, uint64_t)> hit_callback);
Breakpoint(Processor* processor, uint32_t address,
std::function<void(uint32_t, uint64_t)> hit_callback);
enum class AddressType {
kGuest,
kHost,
};
typedef std::function<void(Breakpoint*, ThreadDebugInfo*, uint64_t)>
HitCallback;
Breakpoint(Processor* processor, AddressType address_type, uint64_t address,
HitCallback hit_callback);
~Breakpoint();
uint32_t address() const { return address_; }
void set_address(uint32_t address) {
assert_false(installed_);
address_ = 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_);
}
bool installed() const { return installed_; }
bool Install();
bool Uninstall();
void Hit(uint64_t host_pc) { hit_callback_(address_, host_pc); }
// 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();
}
}
// Whether the breakpoint is currently installed and active.
bool is_installed() const { return installed_; }
std::string to_string() const;
// 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.
GuestFunction* guest_function() const;
// Returns true if this breakpoint, when installed, contains the given host
// address.
bool ContainsHostAddress(uintptr_t search_address) const;
// Enumerates all host addresses that correspond to this breakpoint.
// If this is a host type it will return the single host address, if it is a
// guest type it may return multiple host addresses.
void ForEachHostAddress(std::function<void(uintptr_t)> callback) const;
// CPU backend data. Implementation specific - DO NOT TOUCH THIS!
std::vector<std::pair<uint64_t, uint64_t>> backend_data() const {
@ -42,12 +81,50 @@ class Breakpoint {
return backend_data_;
}
private:
friend class Processor;
void OnHit(ThreadDebugInfo* thread_info, uint64_t host_pc) {
hit_callback_(this, thread_info, host_pc);
}
// 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();
}
}
private:
Processor* processor_ = nullptr;
void Install();
void Uninstall();
// True if patched into code.
bool installed_ = false;
uint32_t address_ = 0;
std::function<void(uint32_t, uint64_t)> hit_callback_;
// 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;
AddressType address_type_;
uint64_t address_ = 0;
HitCallback hit_callback_;
// Opaque backend data. Don't touch this.
std::vector<std::pair<uint64_t, uint64_t>> backend_data_;

View File

@ -7,20 +7,14 @@
******************************************************************************
*/
#ifndef XENIA_DEBUG_DEBUG_LISTENER_H_
#define XENIA_DEBUG_DEBUG_LISTENER_H_
#include "xenia/cpu/thread_state.h"
#include "xenia/debug/breakpoint.h"
#ifndef XENIA_CPU_DEBUG_LISTENER_H_
#define XENIA_CPU_DEBUG_LISTENER_H_
namespace xe {
namespace kernel {
class XThread;
} // namespace kernel
} // namespace xe
namespace cpu {
namespace xe {
namespace debug {
class Breakpoint;
struct ThreadDebugInfo;
// Debug event listener interface.
// Implementations will receive calls from arbitrary threads and must marshal
@ -49,15 +43,15 @@ class DebugListener {
// 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;
virtual void OnStepCompleted(ThreadDebugInfo* thread_info) = 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;
ThreadDebugInfo* thread_info) = 0;
};
} // namespace debug
} // namespace cpu
} // namespace xe
#endif // XENIA_DEBUG_DEBUG_LISTENER_H_
#endif // XENIA_CPU_DEBUG_LISTENER_H_

View File

@ -13,11 +13,11 @@
#include <memory>
#include <vector>
#include "xenia/cpu/debug_info.h"
#include "xenia/cpu/function_debug_info.h"
#include "xenia/cpu/function_trace_data.h"
#include "xenia/cpu/ppc/ppc_context.h"
#include "xenia/cpu/symbol.h"
#include "xenia/cpu/thread_state.h"
#include "xenia/debug/function_trace_data.h"
namespace xe {
namespace cpu {
@ -111,11 +111,11 @@ class GuestFunction : public Function {
virtual uint8_t* machine_code() const = 0;
virtual size_t machine_code_length() const = 0;
DebugInfo* debug_info() const { return debug_info_.get(); }
void set_debug_info(std::unique_ptr<DebugInfo> debug_info) {
FunctionDebugInfo* debug_info() const { return debug_info_.get(); }
void set_debug_info(std::unique_ptr<FunctionDebugInfo> debug_info) {
debug_info_ = std::move(debug_info);
}
debug::FunctionTraceData& trace_data() { return trace_data_; }
FunctionTraceData& trace_data() { return trace_data_; }
std::vector<SourceMapEntry>& source_map() { return source_map_; }
ExternHandler extern_handler() const { return extern_handler_; }
@ -136,8 +136,8 @@ class GuestFunction : public Function {
virtual bool CallImpl(ThreadState* thread_state, uint32_t return_address) = 0;
protected:
std::unique_ptr<DebugInfo> debug_info_;
debug::FunctionTraceData trace_data_;
std::unique_ptr<FunctionDebugInfo> debug_info_;
FunctionTraceData trace_data_;
std::vector<SourceMapEntry> source_map_;
ExternHandler extern_handler_ = nullptr;
Export* export_data_ = nullptr;

View File

@ -7,7 +7,7 @@
******************************************************************************
*/
#include "xenia/cpu/debug_info.h"
#include "xenia/cpu/function_debug_info.h"
#include <cstdio>
#include <cstdlib>
@ -17,20 +17,20 @@
namespace xe {
namespace cpu {
DebugInfo::DebugInfo()
FunctionDebugInfo::FunctionDebugInfo()
: source_disasm_(nullptr),
raw_hir_disasm_(nullptr),
hir_disasm_(nullptr),
machine_code_disasm_(nullptr) {}
DebugInfo::~DebugInfo() {
FunctionDebugInfo::~FunctionDebugInfo() {
free(source_disasm_);
free(raw_hir_disasm_);
free(hir_disasm_);
free(machine_code_disasm_);
}
void DebugInfo::Dump() {
void FunctionDebugInfo::Dump() {
if (source_disasm_) {
XELOGD("PPC:\n%s\n", source_disasm_);
}

View File

@ -7,8 +7,8 @@
******************************************************************************
*/
#ifndef XENIA_CPU_DEBUG_INFO_H_
#define XENIA_CPU_DEBUG_INFO_H_
#ifndef XENIA_CPU_FUNCTION_DEBUG_INFO_H_
#define XENIA_CPU_FUNCTION_DEBUG_INFO_H_
#include <cstddef>
#include <cstdint>
@ -38,10 +38,10 @@ enum DebugInfoFlags : uint32_t {
// DEPRECATED
// This will be getting removed or refactored to contain only on-demand
// disassembly data.
class DebugInfo {
class FunctionDebugInfo {
public:
DebugInfo();
~DebugInfo();
FunctionDebugInfo();
~FunctionDebugInfo();
uint32_t address_reference_count() const { return address_reference_count_; }
void set_address_reference_count(uint32_t value) {
@ -78,4 +78,4 @@ class DebugInfo {
} // namespace cpu
} // namespace xe
#endif // XENIA_CPU_DEBUG_INFO_H_
#endif // XENIA_CPU_FUNCTION_DEBUG_INFO_H_

View File

@ -7,8 +7,8 @@
******************************************************************************
*/
#ifndef XENIA_DEBUG_FUNCTION_TRACE_DATA_H_
#define XENIA_DEBUG_FUNCTION_TRACE_DATA_H_
#ifndef XENIA_CPU_FUNCTION_TRACE_DATA_H_
#define XENIA_CPU_FUNCTION_TRACE_DATA_H_
#include <cstdint>
#include <cstring>
@ -16,28 +16,7 @@
#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); }
};
namespace cpu {
class FunctionTraceData {
public:
@ -108,7 +87,7 @@ class FunctionTraceData {
Header* header_;
};
} // namespace debug
} // namespace cpu
} // namespace xe
#endif // XENIA_DEBUG_FUNCTION_TRACE_DATA_H_
#endif // XENIA_CPU_FUNCTION_TRACE_DATA_H_

View File

@ -41,7 +41,7 @@ bool PPCScanner::IsRestGprLr(uint32_t address) {
return function && function->behavior() == Function::Behavior::kEpilogReturn;
}
bool PPCScanner::Scan(GuestFunction* function, DebugInfo* debug_info) {
bool PPCScanner::Scan(GuestFunction* function, FunctionDebugInfo* debug_info) {
// This is a simple basic block analyizer. It walks the start address to the
// end address looking for branches. Each span of instructions between
// branches is considered a basic block. When the last blr (that has no

View File

@ -12,8 +12,8 @@
#include <vector>
#include "xenia/cpu/debug_info.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/function_debug_info.h"
namespace xe {
namespace cpu {
@ -31,7 +31,7 @@ class PPCScanner {
explicit PPCScanner(PPCFrontend* frontend);
~PPCScanner();
bool Scan(GuestFunction* function, DebugInfo* debug_info);
bool Scan(GuestFunction* function, FunctionDebugInfo* debug_info);
std::vector<BlockInfo> FindBlocks(GuestFunction* function);

View File

@ -23,7 +23,6 @@
#include "xenia/cpu/ppc/ppc_opcode_info.h"
#include "xenia/cpu/ppc/ppc_scanner.h"
#include "xenia/cpu/processor.h"
#include "xenia/debug/debugger.h"
namespace xe {
namespace cpu {
@ -118,9 +117,9 @@ bool PPCTranslator::Translate(GuestFunction* function,
if (FLAGS_trace_function_data) {
debug_info_flags |= DebugInfoFlags::kDebugInfoTraceFunctionData;
}
std::unique_ptr<DebugInfo> debug_info;
std::unique_ptr<FunctionDebugInfo> debug_info;
if (debug_info_flags) {
debug_info.reset(new DebugInfo());
debug_info.reset(new FunctionDebugInfo());
}
// Scan the function to find its extents and gather debug data.
@ -128,25 +127,24 @@ bool PPCTranslator::Translate(GuestFunction* function,
return false;
}
auto debugger = frontend_->processor()->debugger();
if (!debugger) {
debug_info_flags &= ~DebugInfoFlags::kDebugInfoAllTracing;
}
// Setup trace data, if needed.
if (debug_info_flags & DebugInfoFlags::kDebugInfoTraceFunctions) {
// Base trace data.
size_t trace_data_size = debug::FunctionTraceData::SizeOfHeader();
size_t trace_data_size = FunctionTraceData::SizeOfHeader();
if (debug_info_flags & DebugInfoFlags::kDebugInfoTraceFunctionCoverage) {
// Additional space for instruction coverage counts.
trace_data_size += debug::FunctionTraceData::SizeOfInstructionCounts(
trace_data_size += FunctionTraceData::SizeOfInstructionCounts(
function->address(), function->end_address());
}
uint8_t* trace_data = debugger->AllocateFunctionTraceData(trace_data_size);
uint8_t* trace_data =
frontend_->processor()->AllocateFunctionTraceData(trace_data_size);
if (trace_data) {
function->trace_data().Reset(trace_data, trace_data_size,
function->address(),
function->end_address());
} else {
debug_info_flags &= ~(DebugInfoFlags::kDebugInfoTraceFunctions |
DebugInfoFlags::kDebugInfoTraceFunctionCoverage);
}
}

View File

@ -189,7 +189,7 @@ class TestRunner {
memory->Reset();
// Setup a fresh processor.
processor.reset(new Processor(nullptr, memory.get(), nullptr, nullptr));
processor.reset(new Processor(memory.get(), nullptr));
processor->Setup();
processor->set_debug_info_flags(DebugInfoFlags::kDebugInfoAll);

View File

@ -14,7 +14,6 @@ project("xenia-cpu-ppc-tests")
"xenia-cpu-backend-x64",
-- TODO(benvanik): remove these dependencies.
"xenia-debug",
"xenia-kernel"
})
files({

File diff suppressed because it is too large Load Diff

View File

@ -10,34 +10,33 @@
#ifndef XENIA_CPU_PROCESSOR_H_
#define XENIA_CPU_PROCESSOR_H_
#include <gflags/gflags.h>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "xenia/base/mapped_memory.h"
#include "xenia/base/mutex.h"
#include "xenia/cpu/backend/backend.h"
#include "xenia/cpu/debug_listener.h"
#include "xenia/cpu/entry_table.h"
#include "xenia/cpu/export_resolver.h"
#include "xenia/cpu/function.h"
#include "xenia/cpu/module.h"
#include "xenia/cpu/ppc/ppc_frontend.h"
#include "xenia/cpu/thread_debug_info.h"
#include "xenia/cpu/thread_state.h"
#include "xenia/memory.h"
namespace xe {
class Emulator;
namespace debug {
class Debugger;
} // namespace debug
} // namespace xe
DECLARE_bool(debug);
namespace xe {
namespace cpu {
class Breakpoint;
class StackWalker;
class ThreadState;
class XexModule;
enum class Irql : uint32_t {
@ -47,15 +46,26 @@ enum class Irql : uint32_t {
DPC = 3,
};
// Describes the current state of the emulator 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 Processor {
public:
Processor(Emulator* emulator, Memory* memory, ExportResolver* export_resolver,
debug::Debugger* debugger);
Processor(Memory* memory, ExportResolver* export_resolver);
~Processor();
Emulator* emulator() const { return emulator_; }
Memory* memory() const { return memory_; }
debug::Debugger* debugger() const { return debugger_; }
StackWalker* stack_walker() const { return stack_walker_.get(); }
ppc::PPCFrontend* frontend() const { return frontend_.get(); }
backend::Backend* backend() const { return backend_.get(); }
@ -63,6 +73,28 @@ class Processor {
bool Setup();
// Runs any pre-launch logic once the module and thread have been setup.
void PreLaunch();
// The current execution state of the emulator.
ExecutionState execution_state() const { return execution_state_; }
// True if a debug listener is attached and the debugger is active.
bool is_debugger_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*(Processor*)> handler) {
debug_listener_handler_ = std::move(handler);
}
void set_debug_info_flags(uint32_t debug_info_flags) {
debug_info_flags_ = debug_info_flags;
}
@ -94,23 +126,128 @@ class Processor {
Irql RaiseIrql(Irql new_value);
void LowerIrql(Irql old_value);
bool InstallBreakpoint(Breakpoint* bp);
bool UninstallBreakpoint(Breakpoint* bp);
bool BreakpointHit(uint32_t address, uint64_t host_pc);
bool Save(ByteStream* stream);
bool Restore(ByteStream* stream);
// 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<ThreadDebugInfo*> QueryThreadDebugInfos();
// Returns the debugger info for the given thread.
ThreadDebugInfo* QueryThreadDebugInfo(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);
// Finds a breakpoint that may be registered at the given address.
Breakpoint* FindBreakpoint(uint32_t address);
std::vector<Breakpoint*> breakpoints() const { return breakpoints_; }
// Returns all currently registered breakpoints.
std::vector<Breakpoint*> breakpoints() const;
// Shows the debug listener, focusing it if it already exists.
void ShowDebugger();
// 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 x64 host instruction.
// If the step is over a branch the branch will be followed.
void StepHostInstruction(uint32_t thread_id);
// 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 until the guest address is hit.
// Returns false if the step could not be completed (invalid target address).
bool StepToGuestAddress(uint32_t thread_id, uint32_t pc);
// Steps the given thread to the target of the branch at the specified guest
// address. The address must specify a branch instruction.
// Returns the new PC guest address.
uint32_t StepIntoGuestBranchTarget(uint32_t thread_id, uint32_t pc);
// Steps the thread to a point where it's safe to terminate or read its
// context. Returns the PC after we've finished stepping.
// Pass true for ignore_host if you've stopped the thread yourself
// in host code you want to ignore.
// Returns the new PC guest address.
uint32_t StepToGuestSafePoint(uint32_t thread_id, bool ignore_host = false);
public:
// TODO(benvanik): hide.
void OnThreadCreated(uint32_t handle, ThreadState* thread_state,
kernel::XThread* thread);
void OnThreadExit(uint32_t thread_id);
void OnThreadDestroyed(uint32_t thread_id);
void OnThreadEnteringWait(uint32_t thread_id);
void OnThreadLeavingWait(uint32_t thread_id);
bool OnUnhandledException(Exception* ex);
bool OnThreadBreakpointHit(Exception* ex);
uint8_t* AllocateFunctionTraceData(size_t size);
private:
void BreakpointFunctionDefined(Function* function);
// 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();
void OnFunctionDefined(Function* function);
static bool ExceptionCallbackThunk(Exception* ex, void* data);
bool ExceptionCallback(Exception* ex);
void OnStepCompleted(ThreadDebugInfo* thread_info);
void OnBreakpointHit(ThreadDebugInfo* 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(ThreadDebugInfo* thread_info,
uint32_t current_pc);
bool DemandFunction(Function* function);
Emulator* emulator_ = nullptr;
Memory* memory_ = nullptr;
debug::Debugger* debugger_ = nullptr;
std::unique_ptr<StackWalker> stack_walker_;
std::function<DebugListener*(Processor*)> debug_listener_handler_;
DebugListener* debug_listener_ = nullptr;
// Which debug features are enabled in generated code.
uint32_t debug_info_flags_ = 0;
// If specified, the file trace data gets written to when running.
std::wstring functions_trace_path_;
std::unique_ptr<ChunkedMappedMemoryWriter> functions_trace_file_;
std::unique_ptr<ppc::PPCFrontend> frontend_;
std::unique_ptr<backend::Backend> backend_;
@ -118,11 +255,16 @@ class Processor {
EntryTable entry_table_;
xe::global_critical_region global_critical_region_;
ExecutionState execution_state_ = ExecutionState::kPaused;
std::vector<std::unique_ptr<Module>> modules_;
Module* builtin_module_ = nullptr;
uint32_t next_builtin_address_ = 0xFFFF0000u;
std::recursive_mutex breakpoint_lock_;
// Maps thread ID to state. Updated on thread create, and threads are never
// removed. Must be guarded with the global lock.
std::map<uint32_t, std::unique_ptr<ThreadDebugInfo>> thread_debug_infos_;
// TODO(benvanik): cleanup/change structures.
std::vector<Breakpoint*> breakpoints_;
Irql irql_;

View File

@ -241,8 +241,10 @@ class Win32StackWalker : public StackWalker {
// displacement in x64 from the JIT'ed code start to the PC.
if (function->is_guest()) {
auto guest_function = static_cast<GuestFunction*>(function);
// Adjust the host PC by -1 so that we will go back into whatever
// instruction was executing before the capture (like a call).
frame.guest_pc =
guest_function->MapMachineCodeToGuestAddress(frame.host_pc);
guest_function->MapMachineCodeToGuestAddress(frame.host_pc - 1);
}
} else {
frame.guest_symbol.function = nullptr;

View File

@ -12,7 +12,6 @@ test_suite("xenia-cpu-tests", project_root, ".", {
"xenia-cpu-backend-x64",
-- TODO(benvanik): cut these dependencies?
"xenia-debug",
"xenia-kernel",
},
})

View File

@ -39,8 +39,7 @@ class TestFunction {
#if XENIA_TEST_X64
{
auto processor =
std::make_unique<Processor>(nullptr, memory.get(), nullptr, nullptr);
auto processor = std::make_unique<Processor>(memory.get(), nullptr);
processor->Setup();
processors.emplace_back(std::move(processor));
}

View File

@ -7,14 +7,13 @@
******************************************************************************
*/
#ifndef XENIA_DEBUG_THREAD_EXECUTION_INFO_H_
#define XENIA_DEBUG_THREAD_EXECUTION_INFO_H_
#ifndef XENIA_CPU_THREAD_DEBUG_INFO_H_
#define XENIA_CPU_THREAD_DEBUG_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 {
@ -23,17 +22,20 @@ class XThread;
} // namespace xe
namespace xe {
namespace debug {
namespace cpu {
// Per-XThread structure holding debugger state and a cache of the sampled call
class Breakpoint;
class Function;
// Per-thread 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();
struct ThreadDebugInfo {
ThreadDebugInfo() = default;
~ThreadDebugInfo() = default;
enum class State {
// Thread is alive and running.
@ -51,8 +53,7 @@ struct ThreadExecutionInfo {
// 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 thread object. Only valid when the thread is alive.
kernel::XThread* thread = nullptr;
// Current state of the thread.
State state = State::kAlive;
@ -61,7 +62,7 @@ struct ThreadExecutionInfo {
// A breakpoint managed by the stepping system, installed as required to
// trigger a break at the next instruction.
std::unique_ptr<StepBreakpoint> step_breakpoint;
std::unique_ptr<Breakpoint> 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
@ -73,7 +74,7 @@ struct ThreadExecutionInfo {
// Last-sampled PPC context.
// This is updated whenever the debugger stops.
xe::cpu::ppc::PPCContext guest_context;
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.
@ -91,7 +92,7 @@ struct ThreadExecutionInfo {
// 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;
Function* guest_function = nullptr;
// Name of the function, if known.
// TODO(benvanik): string table?
char name[256] = {0};
@ -102,7 +103,7 @@ struct ThreadExecutionInfo {
std::vector<Frame> frames;
};
} // namespace debug
} // namespace cpu
} // namespace xe
#endif // XENIA_DEBUG_THREAD_EXECUTION_INFO_H_
#endif // XENIA_CPU_THREAD_DEBUG_INFO_H_

View File

@ -16,7 +16,6 @@
#include "xenia/base/logging.h"
#include "xenia/base/threading.h"
#include "xenia/cpu/processor.h"
#include "xenia/debug/debugger.h"
#include "xenia/xbox.h"
@ -73,7 +72,9 @@ void ThreadState::Bind(ThreadState* thread_state) {
ThreadState* ThreadState::Get() { return thread_state_; }
uint32_t ThreadState::GetThreadID() { return thread_state_->thread_id_; }
uint32_t ThreadState::GetThreadID() {
return thread_state_ ? thread_state_->thread_id_ : 0xFFFFFFFF;
}
} // namespace cpu
} // namespace xe

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -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()

View File

@ -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

View File

@ -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", &current_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); });
}

View File

@ -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;

View File

@ -10,7 +10,6 @@ project("xenia-debug-ui")
"imgui",
"xenia-base",
"xenia-cpu",
"xenia-debug",
"xenia-ui",
"yaml-cpp",
})

View File

@ -48,11 +48,6 @@ Emulator::Emulator(const std::wstring& command_line)
Emulator::~Emulator() {
// Note that we delete things in the reverse order they were initialized.
if (debugger_) {
// Kill the debugger first, so that we don't have it messing with things.
debugger_->StopSession();
}
// Give the systems time to shutdown before we delete them.
if (graphics_system_) {
graphics_system_->Shutdown();
@ -70,8 +65,6 @@ Emulator::~Emulator() {
processor_.reset();
debugger_.reset();
export_resolver_.reset();
ExceptionHandler::Uninstall(Emulator::ExceptionCallbackThunk, this);
@ -110,17 +103,9 @@ X_STATUS Emulator::Setup(
// Shared export resolver used to attach and query for HLE exports.
export_resolver_ = std::make_unique<xe::cpu::ExportResolver>();
if (FLAGS_debug) {
// Debugger first, as other parts hook into it.
debugger_.reset(new debug::Debugger(this));
// Create debugger first. Other types hook up to it.
debugger_->StartSession();
}
// Initialize the CPU.
processor_ = std::make_unique<xe::cpu::Processor>(
this, memory_.get(), export_resolver_.get(), debugger_.get());
processor_ = std::make_unique<xe::cpu::Processor>(memory_.get(),
export_resolver_.get());
if (!processor_->Setup()) {
return X_STATUS_UNSUCCESSFUL;
}
@ -361,6 +346,7 @@ bool Emulator::SaveToFile(const std::wstring& path) {
// It's important we don't hold the global lock here! XThreads need to step
// forward (possibly through guarded regions) without worry!
processor_->Save(&stream);
graphics_system_->Save(&stream);
audio_system_->Save(&stream);
kernel_state_->Save(&stream);
@ -390,6 +376,10 @@ bool Emulator::RestoreFromFile(const std::wstring& path) {
return false;
}
if (!processor_->Restore(&stream)) {
XELOGE("Could not restore processor!");
return false;
}
if (!graphics_system_->Restore(&stream)) {
XELOGE("Could not restore graphics system!");
return false;
@ -435,15 +425,14 @@ bool Emulator::ExceptionCallback(Exception* ex) {
auto code_base = code_cache->base_address();
auto code_end = code_base + code_cache->total_size();
if (!debugger() ||
(!debugger()->is_attached() && debugging::IsDebuggerAttached())) {
if (!processor()->is_debugger_attached() && debugging::IsDebuggerAttached()) {
// If Xenia's debugger isn't attached but another one is, pass it to that
// debugger.
return false;
} else if (debugger() && debugger()->is_attached()) {
} else if (processor()->is_debugger_attached()) {
// Let the debugger handle this exception. It may decide to continue past it
// (if it was a stepping breakpoint, etc).
return debugger()->OnUnhandledException(ex);
return processor()->OnUnhandledException(ex);
}
if (!(ex->pc() >= code_base && ex->pc() < code_end)) {

View File

@ -14,7 +14,6 @@
#include <string>
#include "xenia/base/exception_handler.h"
#include "xenia/debug/debugger.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/memory.h"
#include "xenia/vfs/virtual_file_system.h"
@ -63,9 +62,6 @@ class Emulator {
// system.
Memory* memory() const { return memory_.get(); }
// Active debugger, which may or may not be attached.
debug::Debugger* debugger() const { return debugger_.get(); }
// Virtualized processor that can execute PPC code.
cpu::Processor* processor() const { return processor_.get(); }
@ -146,8 +142,6 @@ class Emulator {
std::unique_ptr<Memory> memory_;
std::unique_ptr<debug::Debugger> debugger_;
std::unique_ptr<cpu::Processor> processor_;
std::unique_ptr<apu::AudioSystem> audio_system_;
std::unique_ptr<gpu::GraphicsSystem> graphics_system_;

View File

@ -40,7 +40,6 @@ project("xenia-gpu-gl4-trace-viewer")
"xenia-core",
"xenia-cpu",
"xenia-cpu-backend-x64",
"xenia-debug",
"xenia-gpu",
"xenia-gpu-gl4",
"xenia-hid-nop",
@ -94,7 +93,6 @@ project("xenia-gpu-gl4-trace-dump")
"xenia-core",
"xenia-cpu",
"xenia-cpu-backend-x64",
"xenia-debug",
"xenia-gpu",
"xenia-gpu-gl4",
"xenia-hid-nop",

View File

@ -11,6 +11,7 @@
#include "xenia/base/logging.h"
#include "xenia/base/mutex.h"
#include "xenia/cpu/processor.h"
#include "xenia/cpu/raw_module.h"
#include "xenia/emulator.h"
#include "xenia/kernel/xthread.h"

View File

@ -10,6 +10,8 @@
#ifndef XENIA_KERNEL_KERNEL_MODULE_H_
#define XENIA_KERNEL_KERNEL_MODULE_H_
#include <map>
#include "xenia/emulator.h"
#include "xenia/kernel/xmodule.h"

View File

@ -408,7 +408,7 @@ void KernelState::TerminateTitle() {
}
global_lock.unlock();
thread->StepToSafePoint();
processor_->StepToGuestSafePoint(thread->thread_id());
thread->Terminate(0);
global_lock.lock();
}
@ -420,8 +420,8 @@ void KernelState::TerminateTitle() {
}
}
// Third: Unload all user modules (including the executable)
for (int i = 0; i < user_modules_.size(); i++) {
// Third: Unload all user modules (including the executable).
for (size_t i = 0; i < user_modules_.size(); i++) {
X_STATUS status = user_modules_[i]->Unload();
assert_true(XSUCCEEDED(status));
@ -442,7 +442,7 @@ void KernelState::TerminateTitle() {
threads_by_id_.erase(XThread::GetCurrentThread()->thread_id());
// Now commit suicide (using Terminate, because we can't call into guest
// code anymore)
// code anymore).
global_lock.unlock();
XThread::GetCurrentThread()->Terminate(0);
}
@ -512,9 +512,7 @@ void KernelState::OnThreadExit(XThread* thread) {
}
}
if (emulator()->debugger()) {
emulator()->debugger()->OnThreadExit(thread);
}
emulator()->processor()->OnThreadExit(thread->thread_id());
}
object_ref<XThread> KernelState::GetThreadByID(uint32_t thread_id) {

View File

@ -315,9 +315,7 @@ object_ref<XThread> UserModule::Launch(uint32_t flags) {
}
// Waits for a debugger client, if desired.
if (emulator()->debugger()) {
emulator()->debugger()->PreLaunch();
}
emulator()->processor()->PreLaunch();
// Resume the thread now.
// If the debugger has requested a suspend this will just decrement the

View File

@ -22,7 +22,6 @@
#include "xenia/cpu/breakpoint.h"
#include "xenia/cpu/ppc/ppc_decode_data.h"
#include "xenia/cpu/processor.h"
#include "xenia/cpu/stack_walker.h"
#include "xenia/emulator.h"
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/user_module.h"
@ -80,9 +79,8 @@ XThread::~XThread() {
// Unregister first to prevent lookups while deleting.
kernel_state_->UnregisterThread(this);
if (emulator()->debugger()) {
emulator()->debugger()->OnThreadDestroyed(this);
}
// Notify processor of our impending destruction.
emulator()->processor()->OnThreadDestroyed(thread_id_);
thread_.reset();
@ -408,9 +406,8 @@ X_STATUS XThread::Create() {
thread_->set_priority(creation_params_.creation_flags & 0x20 ? 1 : 0);
}
if (emulator()->debugger()) {
emulator()->debugger()->OnThreadCreated(this);
}
// Notify processor of our creation.
emulator()->processor()->OnThreadCreated(handle(), thread_state_, this);
if ((creation_params_.creation_flags & X_CREATE_SUSPENDED) == 0) {
// Start the thread now that we're all setup.
@ -434,9 +431,8 @@ X_STATUS XThread::Exit(int exit_code) {
kernel_state()->OnThreadExit(this);
if (emulator()->debugger()) {
emulator()->debugger()->OnThreadExit(this);
}
// Notify processor of our exit.
emulator()->processor()->OnThreadExit(thread_id_);
// NOTE: unless PlatformExit fails, expect it to never return!
current_thread_tls_ = nullptr;
@ -458,9 +454,8 @@ X_STATUS XThread::Terminate(int exit_code) {
thread->header.signal_state = 1;
thread->exit_status = exit_code;
if (emulator()->debugger()) {
emulator()->debugger()->OnThreadExit(this);
}
// Notify processor of our exit.
emulator()->processor()->OnThreadExit(thread_id_);
running_ = false;
Release();
@ -790,245 +785,6 @@ X_STATUS XThread::Delay(uint32_t processor_mode, uint32_t alertable,
}
}
bool XThread::StepToAddress(uint32_t pc) {
auto functions = emulator()->processor()->FindFunctionsWithAddress(pc);
if (functions.empty()) {
// Function hasn't been generated yet. Generate it.
if (!emulator()->processor()->ResolveFunction(pc)) {
XELOGE("XThread::StepToAddress(%.8X) - Function could not be resolved",
pc);
return false;
}
}
// Instruct the thread to step forwards.
threading::Fence fence;
cpu::Breakpoint bp(kernel_state()->processor(), pc,
[&fence](uint32_t guest_address, uint64_t host_address) {
fence.Signal();
});
if (bp.Install()) {
// HACK
uint32_t suspend_count = 1;
while (suspend_count) {
thread_->Resume(&suspend_count);
}
fence.Wait();
bp.Uninstall();
} else {
assert_always();
XELOGE("XThread: Could not install breakpoint to step forward!");
return false;
}
return true;
}
uint32_t XThread::StepIntoBranch(uint32_t pc) {
xe::cpu::ppc::PPCDecodeData d;
d.address = pc;
d.code = xe::load_and_swap<uint32_t>(memory()->TranslateVirtual(d.address));
auto opcode = xe::cpu::ppc::LookupOpcode(d.code);
auto context = thread_state_->context();
if (d.code == 0x4E800020) {
// blr
uint32_t nia = uint32_t(context->lr);
StepToAddress(nia);
} else if (d.code == 0x4E800420) {
// bctr
uint32_t nia = uint32_t(context->ctr);
StepToAddress(nia);
} else if (opcode == PPCOpcode::bx) {
// bx
uint32_t nia = d.I.ADDR();
StepToAddress(nia);
} else if (opcode == PPCOpcode::bcx || opcode == PPCOpcode::bcctrx ||
opcode == PPCOpcode::bclrx) {
threading::Fence fence;
auto callback = [&fence, &pc](uint32_t guest_pc, uint64_t) {
pc = guest_pc;
fence.Signal();
};
cpu::Breakpoint bpt(kernel_state()->processor(), callback);
cpu::Breakpoint bpf(kernel_state()->processor(), pc + 4, callback);
if (!bpf.Install()) {
XELOGE("XThread: Could not install breakpoint to step forward!");
assert_always();
}
uint32_t nia = 0;
if (opcode == PPCOpcode::bcx) {
// bcx
nia = d.B.ADDR();
} else if (opcode == PPCOpcode::bcctrx) {
// bcctrx
nia = uint32_t(context->ctr);
} else if (opcode == PPCOpcode::bclrx) {
// bclrx
nia = uint32_t(context->lr);
}
bpt.set_address(nia);
if (!bpt.Install()) {
assert_always();
return 0;
}
// HACK
uint32_t suspend_count = 1;
while (suspend_count) {
thread_->Resume(&suspend_count);
}
fence.Wait();
bpt.Uninstall();
bpf.Uninstall();
}
return pc;
}
uint32_t XThread::StepToSafePoint(bool ignore_host) {
// This cannot be done if we're the calling thread!
if (IsInThread() && GetCurrentThread() == this) {
assert_always(
"XThread::StepToSafePoint(): target thread is the calling thread!");
return 0;
}
// Now the fun part begins: Registers are only guaranteed to be synchronized
// with the PPC context at a basic block boundary. Unfortunately, we most
// likely stopped the thread at some point other than a boundary. We need to
// step forward until we reach a boundary, and then perform the save.
auto stack_walker = kernel_state()->processor()->stack_walker();
uint64_t frame_host_pcs[64];
cpu::StackFrame cpu_frames[64];
size_t count = stack_walker->CaptureStackTrace(
thread_->native_handle(), frame_host_pcs, 0, xe::countof(frame_host_pcs),
nullptr, nullptr);
stack_walker->ResolveStack(frame_host_pcs, cpu_frames, count);
if (count == 0) {
return 0;
}
auto& first_frame = cpu_frames[0];
if (ignore_host) {
for (size_t i = 0; i < count; i++) {
if (cpu_frames[i].type == cpu::StackFrame::Type::kGuest &&
cpu_frames[i].guest_pc) {
first_frame = cpu_frames[i];
break;
}
}
}
// Check if we're in guest code or host code.
uint32_t pc = 0;
if (first_frame.type == cpu::StackFrame::Type::kGuest) {
auto& frame = first_frame;
if (!frame.guest_pc) {
// Lame. The guest->host thunk is a "guest" function.
frame = cpu_frames[1];
}
pc = frame.guest_pc;
// We're in guest code.
// First: Find a synchronizing instruction and go to it.
xe::cpu::ppc::PPCDecodeData d;
const xe::cpu::ppc::PPCOpcodeInfo* sync_info = nullptr;
d.address = cpu_frames[0].guest_pc - 4;
do {
d.address += 4;
d.code =
xe::load_and_swap<uint32_t>(memory()->TranslateVirtual(d.address));
auto& opcode_info = xe::cpu::ppc::LookupOpcodeInfo(d.code);
if (opcode_info.type == cpu::ppc::PPCOpcodeType::kSync) {
sync_info = &opcode_info;
break;
}
} while (true);
if (d.address != pc) {
StepToAddress(d.address);
pc = d.address;
}
// Okay. Now we're on a synchronizing instruction but we need to step
// past it in order to get a synchronized context.
// If we're on a branching instruction, it's guaranteed only going to have
// two possible targets. For non-branching instructions, we can just step
// over them.
if (sync_info->group == xe::cpu::ppc::PPCOpcodeGroup::kB) {
pc = StepIntoBranch(d.address);
}
} else {
// We're in host code. Search backwards til we can get an idea of where
// we are.
cpu::GuestFunction* thunk_func = nullptr;
cpu::Export* export_data = nullptr;
uint32_t first_pc = 0;
for (int i = 0; i < count; i++) {
auto& frame = cpu_frames[i];
if (frame.type == cpu::StackFrame::Type::kGuest && frame.guest_pc) {
auto func = frame.guest_symbol.function;
assert_true(func->is_guest());
if (!first_pc) {
first_pc = frame.guest_pc;
}
thunk_func = reinterpret_cast<cpu::GuestFunction*>(func);
export_data = thunk_func->export_data();
if (export_data) {
break;
}
}
}
// If the export is blocking, we wrap up and save inside the export thunk.
// When we're restored, we'll call the blocking export again.
// Otherwise, we return from the thunk and save.
if (export_data && export_data->tags & cpu::ExportTag::kBlocking) {
pc = thunk_func->address();
} else if (export_data) {
// Non-blocking. Run until we return from the thunk.
pc = uint32_t(thread_state_->context()->lr);
StepToAddress(pc);
} else if (first_pc) {
// We're in the MMIO handler/mfmsr/something calling out of the guest
// that doesn't use an export. If the current instruction is
// synchronizing, we can just save here. Otherwise, step forward
// (and call ourselves again so we run the correct logic).
uint32_t code =
xe::load_and_swap<uint32_t>(memory()->TranslateVirtual(first_pc));
auto& opcode_info = xe::cpu::ppc::LookupOpcodeInfo(code);
if (opcode_info.type == xe::cpu::ppc::PPCOpcodeType::kSync) {
// Good to go.
pc = first_pc;
} else {
// Step forward and run this logic again.
StepToAddress(first_pc + 4);
return StepToSafePoint(true);
}
} else {
// We've managed to catch a thread before it called into the guest.
// Set a breakpoint on its startup procedure and capture it there.
pc = creation_params_.xapi_thread_startup
? creation_params_.xapi_thread_startup
: creation_params_.start_address;
StepToAddress(pc);
}
}
return pc;
}
struct ThreadSavedState {
uint32_t thread_id;
bool is_main_thread; // Is this the main thread?
@ -1075,7 +831,7 @@ bool XThread::Save(ByteStream* stream) {
uint32_t pc = 0;
if (running_) {
pc = StepToSafePoint();
pc = emulator()->processor()->StepToGuestSafePoint(thread_id_);
if (!pc) {
XELOGE("XThread %.8X failed to save: could not step to a safe point!",
handle());
@ -1247,9 +1003,9 @@ object_ref<XThread> XThread::Restore(KernelState* kernel_state,
thread->Release();
});
if (thread->emulator()->debugger()) {
thread->emulator()->debugger()->OnThreadCreated(thread);
}
// Notify processor we were recreated.
thread->emulator()->processor()->OnThreadCreated(
thread->handle(), thread->thread_state(), thread);
}
return object_ref<XThread>(thread);

View File

@ -196,19 +196,11 @@ class XThread : public XObject {
pending_mutant_acquires_.push_back(mutant);
}
// Steps the thread to a point where it's safe to terminate or read its
// context. Returns the PC after we've finished stepping.
// Pass true for ignore_host if you've stopped the thread yourself
// in host code you want to ignore.
uint32_t StepToSafePoint(bool ignore_host = false);
protected:
bool AllocateStack(uint32_t size);
void FreeStack();
void InitializeGuestObject();
bool StepToAddress(uint32_t pc);
uint32_t StepIntoBranch(uint32_t pc);
void DeliverAPCs();
void RundownAPCs();

View File

@ -66,7 +66,7 @@ class MessageBoxDialog : public ImGuiDialog {
ImGuiWindowFlags_AlwaysAutoResize)) {
char* text = const_cast<char*>(body_.c_str());
ImGui::InputTextMultiline(
"##body", text, body_.size(), ImVec2(0, 0),
"##body", text, body_.size(), ImVec2(600, 0),
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_ReadOnly);
if (ImGui::Button("OK")) {
ImGui::CloseCurrentPopup();