diff --git a/premake5.lua b/premake5.lua index 3431b60da..54277ad27 100644 --- a/premake5.lua +++ b/premake5.lua @@ -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") diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index abd010696..e4030ca9e 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -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(); } } diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index d72059e22..3cfef9a82 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -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", diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index ab4934666..0c95bc562 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -137,9 +137,9 @@ int xenia_main(const std::vector& args) { // Set a debug handler. // This will respond to debugging requests so we can open the debug UI. std::unique_ptr 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& 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(); }); }); }); diff --git a/src/xenia/base/string.cc b/src/xenia/base/string.cc index 760527477..feff3a21b 100644 --- a/src/xenia/base/string.cc +++ b/src/xenia/base/string.cc @@ -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; } } diff --git a/src/xenia/cpu/backend/assembler.h b/src/xenia/cpu/backend/assembler.h index 594a83d7d..8cdf18fe7 100644 --- a/src/xenia/cpu/backend/assembler.h +++ b/src/xenia/cpu/backend/assembler.h @@ -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 debug_info) = 0; + std::unique_ptr debug_info) = 0; protected: Backend* backend_; diff --git a/src/xenia/cpu/backend/backend.h b/src/xenia/cpu/backend/backend.h index 81a0d33d6..4d6f68346 100644 --- a/src/xenia/cpu/backend/backend.h +++ b/src/xenia/cpu/backend/backend.h @@ -13,6 +13,7 @@ #include #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 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_; diff --git a/src/xenia/cpu/backend/x64/x64_assembler.cc b/src/xenia/cpu/backend/x64/x64_assembler.cc index 2b1cd7bfe..a72a2e711 100644 --- a/src/xenia/cpu/backend/x64/x64_assembler.cc +++ b/src/xenia/cpu/backend/x64/x64_assembler.cc @@ -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 debug_info) { + std::unique_ptr 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(function)->Setup( reinterpret_cast(machine_code), code_size); + // Install into indirection table. + uint64_t host_address = reinterpret_cast(machine_code); + assert_true((host_address >> 32) == 0); + reinterpret_cast(backend_->code_cache()) + ->AddIndirection(function->address(), + static_cast(host_address)); + return true; } diff --git a/src/xenia/cpu/backend/x64/x64_assembler.h b/src/xenia/cpu/backend/x64/x64_assembler.h index 6a5621ce4..2a9cb02b3 100644 --- a/src/xenia/cpu/backend/x64/x64_assembler.h +++ b/src/xenia/cpu/backend/x64/x64_assembler.h @@ -37,7 +37,7 @@ class X64Assembler : public Assembler { bool Assemble(GuestFunction* function, hir::HIRBuilder* builder, uint32_t debug_info_flags, - std::unique_ptr debug_info) override; + std::unique_ptr debug_info) override; private: void DumpMachineCode(void* machine_code, size_t code_size, diff --git a/src/xenia/cpu/backend/x64/x64_backend.cc b/src/xenia/cpu/backend/x64/x64_backend.cc index f5bf94034..3bfae53ce 100644 --- a/src/xenia/cpu/backend/x64/x64_backend.cc +++ b/src/xenia/cpu/backend/x64/x64_backend.cc @@ -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 X64Backend::CreateGuestFunction( return std::make_unique(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(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(reinterpret_cast(code + 0x0)); - bp->backend_data().push_back({code, orig_bytes}); - - xe::store_and_swap(reinterpret_cast(code + 0x0), 0x0F0C); - } - - return true; } -bool X64Backend::InstallBreakpoint(Breakpoint* bp, Function* func) { - assert_true(func->is_guest()); - auto guest_function = reinterpret_cast(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(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(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(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(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(host_address); + auto original_bytes = xe::load_and_swap(ptr); + assert_true(original_bytes != 0x0F0B); + xe::store_and_swap(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(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(reinterpret_cast(code + 0x0)); - bp->backend_data().push_back({code, orig_bytes}); - - xe::store_and_swap(reinterpret_cast(code + 0x0), 0x0F0C); - return true; + auto ptr = reinterpret_cast(host_address); + auto original_bytes = xe::load_and_swap(ptr); + assert_true(original_bytes != 0x0F0B); + xe::store_and_swap(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(reinterpret_cast(pair.first), - uint16_t(pair.second)); +void X64Backend::UninstallBreakpoint(Breakpoint* breakpoint) { + for (auto& pair : breakpoint->backend_data()) { + auto ptr = reinterpret_cast(pair.first); + auto instruction_bytes = xe::load_and_swap(ptr); + assert_true(instruction_bytes == 0x0F0B); + xe::store_and_swap(ptr, static_cast(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(reinterpret_cast(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) diff --git a/src/xenia/cpu/backend/x64/x64_backend.h b/src/xenia/cpu/backend/x64/x64_backend.h index 02748d71f..5c060ae86 100644 --- a/src/xenia/cpu/backend/x64/x64_backend.h +++ b/src/xenia/cpu/backend/x64/x64_backend.h @@ -62,14 +62,19 @@ class X64Backend : public Backend { std::unique_ptr 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 code_cache_; uint32_t emitter_data_; diff --git a/src/xenia/cpu/backend/x64/x64_emitter.cc b/src/xenia/cpu/backend/x64/x64_emitter.cc index 51d68e72a..0feb06471 100644 --- a/src/xenia/cpu/backend/x64/x64_emitter.cc +++ b/src/xenia/cpu/backend/x64/x64_emitter.cc @@ -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* 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); diff --git a/src/xenia/cpu/backend/x64/x64_emitter.h b/src/xenia/cpu/backend/x64/x64_emitter.h index e851d2441..110ca4315 100644 --- a/src/xenia/cpu/backend/x64/x64_emitter.h +++ b/src/xenia/cpu/backend/x64/x64_emitter.h @@ -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* 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; diff --git a/src/xenia/cpu/breakpoint.cc b/src/xenia/cpu/breakpoint.cc index c18bfc99d..9572d4760 100644 --- a/src/xenia/cpu/breakpoint.cc +++ b/src/xenia/cpu/breakpoint.cc @@ -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 hit_callback) - : processor_(processor), hit_callback_(hit_callback) {} -Breakpoint::Breakpoint(Processor* processor, uint32_t address, - std::function 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(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 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(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 diff --git a/src/xenia/cpu/breakpoint.h b/src/xenia/cpu/breakpoint.h index d3399a5e1..b0ad8f611 100644 --- a/src/xenia/cpu/breakpoint.h +++ b/src/xenia/cpu/breakpoint.h @@ -17,22 +17,61 @@ namespace cpu { class Breakpoint { public: - Breakpoint(Processor* processor, - std::function hit_callback); - Breakpoint(Processor* processor, uint32_t address, - std::function hit_callback); + enum class AddressType { + kGuest, + kHost, + }; + typedef std::function + 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(address_); + } + uintptr_t host_address() const { + assert_true(address_type_ == AddressType::kHost); + return static_cast(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 callback) const; // CPU backend data. Implementation specific - DO NOT TOUCH THIS! std::vector> 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 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> backend_data_; diff --git a/src/xenia/debug/debug_listener.h b/src/xenia/cpu/debug_listener.h similarity index 80% rename from src/xenia/debug/debug_listener.h rename to src/xenia/cpu/debug_listener.h index e437fde43..3e10321db 100644 --- a/src/xenia/debug/debug_listener.h +++ b/src/xenia/cpu/debug_listener.h @@ -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_ diff --git a/src/xenia/cpu/function.h b/src/xenia/cpu/function.h index 8c0e5cc97..57969cfda 100644 --- a/src/xenia/cpu/function.h +++ b/src/xenia/cpu/function.h @@ -13,11 +13,11 @@ #include #include -#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 debug_info) { + FunctionDebugInfo* debug_info() const { return debug_info_.get(); } + void set_debug_info(std::unique_ptr debug_info) { debug_info_ = std::move(debug_info); } - debug::FunctionTraceData& trace_data() { return trace_data_; } + FunctionTraceData& trace_data() { return trace_data_; } std::vector& 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 debug_info_; - debug::FunctionTraceData trace_data_; + std::unique_ptr debug_info_; + FunctionTraceData trace_data_; std::vector source_map_; ExternHandler extern_handler_ = nullptr; Export* export_data_ = nullptr; diff --git a/src/xenia/cpu/debug_info.cc b/src/xenia/cpu/function_debug_info.cc similarity index 88% rename from src/xenia/cpu/debug_info.cc rename to src/xenia/cpu/function_debug_info.cc index 43e041fe6..1cb7b484e 100644 --- a/src/xenia/cpu/debug_info.cc +++ b/src/xenia/cpu/function_debug_info.cc @@ -7,7 +7,7 @@ ****************************************************************************** */ -#include "xenia/cpu/debug_info.h" +#include "xenia/cpu/function_debug_info.h" #include #include @@ -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_); } diff --git a/src/xenia/cpu/debug_info.h b/src/xenia/cpu/function_debug_info.h similarity index 92% rename from src/xenia/cpu/debug_info.h rename to src/xenia/cpu/function_debug_info.h index b31a69a25..4725fb7f3 100644 --- a/src/xenia/cpu/debug_info.h +++ b/src/xenia/cpu/function_debug_info.h @@ -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 #include @@ -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_ diff --git a/src/xenia/debug/function_trace_data.h b/src/xenia/cpu/function_trace_data.h similarity index 77% rename from src/xenia/debug/function_trace_data.h rename to src/xenia/cpu/function_trace_data.h index eb9ba38bc..f7b89682c 100644 --- a/src/xenia/debug/function_trace_data.h +++ b/src/xenia/cpu/function_trace_data.h @@ -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 #include @@ -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_ diff --git a/src/xenia/cpu/ppc/ppc_scanner.cc b/src/xenia/cpu/ppc/ppc_scanner.cc index 569de3494..edacf31b5 100644 --- a/src/xenia/cpu/ppc/ppc_scanner.cc +++ b/src/xenia/cpu/ppc/ppc_scanner.cc @@ -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 diff --git a/src/xenia/cpu/ppc/ppc_scanner.h b/src/xenia/cpu/ppc/ppc_scanner.h index bb2aa1149..abb76501f 100644 --- a/src/xenia/cpu/ppc/ppc_scanner.h +++ b/src/xenia/cpu/ppc/ppc_scanner.h @@ -12,8 +12,8 @@ #include -#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 FindBlocks(GuestFunction* function); diff --git a/src/xenia/cpu/ppc/ppc_translator.cc b/src/xenia/cpu/ppc/ppc_translator.cc index 36e567ff4..84aa47f99 100644 --- a/src/xenia/cpu/ppc/ppc_translator.cc +++ b/src/xenia/cpu/ppc/ppc_translator.cc @@ -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 debug_info; + std::unique_ptr 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); } } diff --git a/src/xenia/cpu/ppc/testing/ppc_testing_main.cc b/src/xenia/cpu/ppc/testing/ppc_testing_main.cc index 3618e0994..08e015614 100644 --- a/src/xenia/cpu/ppc/testing/ppc_testing_main.cc +++ b/src/xenia/cpu/ppc/testing/ppc_testing_main.cc @@ -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); diff --git a/src/xenia/cpu/ppc/testing/premake5.lua b/src/xenia/cpu/ppc/testing/premake5.lua index 8797793b9..556806715 100644 --- a/src/xenia/cpu/ppc/testing/premake5.lua +++ b/src/xenia/cpu/ppc/testing/premake5.lua @@ -14,7 +14,6 @@ project("xenia-cpu-ppc-tests") "xenia-cpu-backend-x64", -- TODO(benvanik): remove these dependencies. - "xenia-debug", "xenia-kernel" }) files({ diff --git a/src/xenia/cpu/processor.cc b/src/xenia/cpu/processor.cc index f9304bc83..eaa9165c8 100644 --- a/src/xenia/cpu/processor.cc +++ b/src/xenia/cpu/processor.cc @@ -14,6 +14,9 @@ #include "xenia/base/assert.h" #include "xenia/base/atomic.h" #include "xenia/base/byte_order.h" +#include "xenia/base/byte_stream.h" +#include "xenia/base/debugging.h" +#include "xenia/base/exception_handler.h" #include "xenia/base/logging.h" #include "xenia/base/memory.h" #include "xenia/base/profiling.h" @@ -22,18 +25,35 @@ #include "xenia/cpu/cpu_flags.h" #include "xenia/cpu/export_resolver.h" #include "xenia/cpu/module.h" +#include "xenia/cpu/ppc/ppc_decode_data.h" #include "xenia/cpu/ppc/ppc_frontend.h" #include "xenia/cpu/stack_walker.h" #include "xenia/cpu/thread_state.h" #include "xenia/cpu/xex_module.h" -#include "xenia/debug/debugger.h" + +// HACK(benvanik): XThread has too much stuff :/ +#include "xenia/kernel/xthread.h" // TODO(benvanik): based on compiler support #include "xenia/cpu/backend/x64/x64_backend.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(trace_function_data_path, "", "File to write trace data to."); +DEFINE_bool(break_on_start, false, "Break into the debugger on startup."); + namespace xe { namespace cpu { +using xe::cpu::ppc::PPCOpcode; +using xe::kernel::XThread; + class BuiltinModule : public Module { public: explicit BuiltinModule(Processor* processor) @@ -54,12 +74,8 @@ class BuiltinModule : public Module { std::string name_; }; -Processor::Processor(xe::Emulator* emulator, xe::Memory* memory, - ExportResolver* export_resolver, debug::Debugger* debugger) - : emulator_(emulator), - memory_(memory), - debugger_(debugger), - export_resolver_(export_resolver) {} +Processor::Processor(xe::Memory* memory, ExportResolver* export_resolver) + : memory_(memory), export_resolver_(export_resolver) {} Processor::~Processor() { { @@ -69,6 +85,11 @@ Processor::~Processor() { frontend_.reset(); backend_.reset(); + + if (functions_trace_file_) { + functions_trace_file_->Flush(); + functions_trace_file_.reset(); + } } bool Processor::Setup() { @@ -125,9 +146,28 @@ bool Processor::Setup() { return false; } + // Open the trace data path, if requested. + functions_trace_path_ = xe::to_wstring(FLAGS_trace_function_data_path); + if (!functions_trace_path_.empty()) { + functions_trace_file_ = ChunkedMappedMemoryWriter::Open( + functions_trace_path_, 32 * 1024 * 1024, true); + } + return true; } +void Processor::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; + } +} + bool Processor::AddModule(std::unique_ptr module) { auto global_lock = global_critical_region_.Acquire(); modules_.push_back(std::move(module)); @@ -269,11 +309,7 @@ bool Processor::DemandFunction(Function* function) { } // Before we give the symbol back to the rest, let the debugger know. - if (debugger_) { - debugger_->OnFunctionDefined(function); - } - - BreakpointFunctionDefined(function); + OnFunctionDefined(function); function->set_status(Symbol::Status::kDefined); symbol_status = function->status(); @@ -403,82 +439,856 @@ void Processor::LowerIrql(Irql old_value) { reinterpret_cast(&irql_)); } -void Processor::BreakpointFunctionDefined(Function* function) { - std::lock_guard lock(breakpoint_lock_); - - for (auto bp : breakpoints_) { - if (function->ContainsAddress(bp->address())) { - backend_->InstallBreakpoint(bp, function); - } - } +bool Processor::Save(ByteStream* stream) { + stream->Write('PROC'); + return true; } -bool Processor::InstallBreakpoint(Breakpoint* bp) { - std::lock_guard lock(breakpoint_lock_); - - if (FindBreakpoint(bp->address())) { +bool Processor::Restore(ByteStream* stream) { + if (stream->Read() != 'PROC') { + XELOGE("Processor::Restore - Invalid magic value!"); return false; } - auto functions = FindFunctionsWithAddress(bp->address()); - if (functions.empty()) { - // Go ahead and generate a function. - if (!ResolveFunction(bp->address())) { - // Could not generate a function - fail! - return false; + // Clear cached thread data for zombie threads. + std::vector to_delete; + for (auto& it : thread_debug_infos_) { + if (it.second->state == ThreadDebugInfo::State::kZombie) { + to_delete.push_back(it.first); } } - - // We need to register the breakpoint before installing it with the backend - // in-case a thread hits it while we're here. - breakpoints_.push_back(bp); - if (!backend_->InstallBreakpoint(bp)) { - breakpoints_.pop_back(); - return false; + for (uint32_t thread_id : to_delete) { + thread_debug_infos_.erase(thread_id); } return true; } -bool Processor::UninstallBreakpoint(Breakpoint* bp) { - std::lock_guard lock(breakpoint_lock_); - - if (!backend_->UninstallBreakpoint(bp)) { - return false; +uint8_t* Processor::AllocateFunctionTraceData(size_t size) { + if (!functions_trace_file_) { + return nullptr; } - - for (auto it = breakpoints_.begin(); it != breakpoints_.end(); it++) { - if ((*it)->address() == bp->address()) { - breakpoints_.erase(it); - return true; - } - } - - return false; + return functions_trace_file_->Allocate(size); } -bool Processor::BreakpointHit(uint32_t address, uint64_t host_pc) { - auto bp = FindBreakpoint(address); - if (bp) { - bp->Hit(host_pc); +void Processor::OnFunctionDefined(Function* function) { + auto global_lock = global_critical_region_.Acquire(); + for (auto breakpoint : breakpoints_) { + if (breakpoint->address_type() == Breakpoint::AddressType::kGuest) { + if (function->ContainsAddress(breakpoint->guest_address())) { + if (breakpoint->is_installed()) { + backend_->InstallBreakpoint(breakpoint, function); + } + } + } + } +} - xe::threading::Thread::GetCurrentThread()->Suspend(); - return true; +void Processor::OnThreadCreated(uint32_t thread_handle, + ThreadState* thread_state, + kernel::XThread* thread) { + auto global_lock = global_critical_region_.Acquire(); + auto thread_info = std::make_unique(); + thread_info->thread_handle = thread_handle; + thread_info->thread_id = thread_state->thread_id(); + thread_info->thread = thread; + thread_info->state = ThreadDebugInfo::State::kAlive; + thread_info->suspended = false; + thread_debug_infos_.emplace(thread_info->thread_id, std::move(thread_info)); +} + +void Processor::OnThreadExit(uint32_t thread_id) { + auto global_lock = global_critical_region_.Acquire(); + auto it = thread_debug_infos_.find(thread_id); + assert_true(it != thread_debug_infos_.end()); + auto thread_info = it->second.get(); + thread_info->state = ThreadDebugInfo::State::kExited; +} + +void Processor::OnThreadDestroyed(uint32_t thread_id) { + auto global_lock = global_critical_region_.Acquire(); + auto it = thread_debug_infos_.find(thread_id); + assert_true(it != thread_debug_infos_.end()); + auto thread_info = it->second.get(); + thread_info->state = ThreadDebugInfo::State::kZombie; + thread_info->thread = nullptr; +} + +void Processor::OnThreadEnteringWait(uint32_t thread_id) { + auto global_lock = global_critical_region_.Acquire(); + auto it = thread_debug_infos_.find(thread_id); + assert_true(it != thread_debug_infos_.end()); + auto thread_info = it->second.get(); + thread_info->state = ThreadDebugInfo::State::kWaiting; +} + +void Processor::OnThreadLeavingWait(uint32_t thread_id) { + auto global_lock = global_critical_region_.Acquire(); + auto it = thread_debug_infos_.find(thread_id); + assert_true(it != thread_debug_infos_.end()); + auto thread_info = it->second.get(); + if (thread_info->state == ThreadDebugInfo::State::kWaiting) { + thread_info->state = ThreadDebugInfo::State::kAlive; + } +} + +std::vector Processor::QueryThreadDebugInfos() { + auto global_lock = global_critical_region_.Acquire(); + std::vector result; + for (auto& it : thread_debug_infos_) { + result.push_back(it.second.get()); + } + return result; +} + +ThreadDebugInfo* Processor::QueryThreadDebugInfo(uint32_t thread_id) { + auto global_lock = global_critical_region_.Acquire(); + const auto& it = thread_debug_infos_.find(thread_id); + if (it == thread_debug_infos_.end()) { + return nullptr; + } + return it->second.get(); +} + +void Processor::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 Processor::RemoveBreakpoint(Breakpoint* breakpoint) { + auto global_lock = global_critical_region_.Acquire(); + + // Uninstall (if needed). + if (execution_state_ == ExecutionState::kRunning) { + breakpoint->Suspend(); } - return false; + // Remove from breakpoint map. + auto it = std::find(breakpoints_.begin(), breakpoints_.end(), breakpoint); + breakpoints_.erase(it); } Breakpoint* Processor::FindBreakpoint(uint32_t address) { - std::lock_guard lock(breakpoint_lock_); + auto global_lock = global_critical_region_.Acquire(); + for (auto breakpoint : breakpoints_) { + if (breakpoint->address() == address) { + return breakpoint; + } + } + return nullptr; +} - for (auto bp : breakpoints_) { - if (bp->address() == address) { - return bp; +void Processor::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 Processor::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 Processor::OnThreadBreakpointHit(Exception* ex) { + auto global_lock = global_critical_region_.Acquire(); + + // Suspend all threads (but ourselves). + SuspendAllThreads(); + + // Lookup thread info block. + auto it = thread_debug_infos_.find(ThreadState::GetThreadID()); + if (it == thread_debug_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()); + + // Walk the captured thread stack and look for breakpoints at any address in + // the stack. We just look for the first one. + Breakpoint* breakpoint = nullptr; + for (size_t i = 0; i < thread_info->frames.size(); ++i) { + auto& frame = thread_info->frames[i]; + for (auto scan_breakpoint : breakpoints_) { + if ((scan_breakpoint->address_type() == Breakpoint::AddressType::kGuest && + scan_breakpoint->guest_address() == frame.guest_pc) || + (scan_breakpoint->address_type() == Breakpoint::AddressType::kHost && + scan_breakpoint->host_address() == frame.host_pc)) { + breakpoint = scan_breakpoint; + break; + } + } + if (breakpoint) { + breakpoint->OnHit(thread_info, frame.host_pc); + break; } } - return nullptr; + // We are waiting on the debugger now. Either wait for it to continue, add a + // new step, or direct us somewhere else. + // 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(); + + if (debug_listener_) { + debug_listener_->OnExecutionPaused(); + } + + 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 Processor::OnStepCompleted(ThreadDebugInfo* thread_info) { + auto global_lock = global_critical_region_.Acquire(); + execution_state_ = ExecutionState::kPaused; + if (debug_listener_) { + debug_listener_->OnExecutionPaused(); + } + + // Note that we stay suspended. +} + +bool Processor::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; +} + +void Processor::ShowDebugger() { + if (debug_listener_) { + debug_listener_->OnFocus(); + } else { + DemandDebugListener(); + } +} + +bool Processor::SuspendAllThreads() { + auto global_lock = global_critical_region_.Acquire(); + for (auto& it : thread_debug_infos_) { + auto thread_info = it.second.get(); + if (thread_info->suspended) { + // Already suspended - ignore. + continue; + } else if (thread_info->state == ThreadDebugInfo::State::kZombie || + thread_info->state == ThreadDebugInfo::State::kExited) { + // Thread is dead and cannot be suspended - ignore. + continue; + } else if (XThread::IsInThread() && + thread_info->thread_id == XThread::GetCurrentThreadId()) { + // Can't suspend ourselves. + continue; + } + auto thread = thread_info->thread; + if (!thread->can_debugger_suspend()) { + // Thread is a host thread, and we aren't suspending those (for now). + continue; + } + bool did_suspend = XSUCCEEDED(thread->Suspend(nullptr)); + assert_true(did_suspend); + thread_info->suspended = true; + } + return true; +} + +bool Processor::ResumeThread(uint32_t thread_id) { + auto global_lock = global_critical_region_.Acquire(); + auto it = thread_debug_infos_.find(thread_id); + if (it == thread_debug_infos_.end()) { + return false; + } + auto thread_info = it->second.get(); + assert_true(thread_info->suspended); + assert_false(thread_info->state == ThreadDebugInfo::State::kExited || + thread_info->state == ThreadDebugInfo::State::kZombie); + thread_info->suspended = false; + auto thread = thread_info->thread; + return XSUCCEEDED(thread->Resume()); +} + +bool Processor::ResumeAllThreads() { + auto global_lock = global_critical_region_.Acquire(); + for (auto& it : thread_debug_infos_) { + auto thread_info = it.second.get(); + if (!thread_info->suspended) { + // Not suspended by us - ignore. + continue; + } else if (thread_info->state == ThreadDebugInfo::State::kZombie || + thread_info->state == ThreadDebugInfo::State::kExited) { + // Thread is dead and cannot be resumed - ignore. + continue; + } else if (XThread::IsInThread() && + thread_info->thread_id == XThread::GetCurrentThreadId()) { + // Can't resume ourselves. + continue; + } + auto thread = thread_info->thread; + if (!thread->can_debugger_suspend()) { + // Thread is a host thread, and we aren't suspending those (for now). + continue; + } + thread_info->suspended = false; + bool did_resume = XSUCCEEDED(thread->Resume()); + assert_true(did_resume); + } + return true; +} + +void Processor::UpdateThreadExecutionStates(uint32_t override_thread_id, + X64Context* override_context) { + auto global_lock = global_critical_region_.Acquire(); + uint64_t frame_host_pcs[64]; + xe::cpu::StackFrame cpu_frames[64]; + for (auto& it : thread_debug_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 Processor::SuspendAllBreakpoints() { + auto global_lock = global_critical_region_.Acquire(); + for (auto breakpoint : breakpoints_) { + breakpoint->Suspend(); + } +} + +void Processor::ResumeAllBreakpoints() { + auto global_lock = global_critical_region_.Acquire(); + for (auto breakpoint : breakpoints_) { + breakpoint->Resume(); + } +} + +void Processor::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 Processor::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 Processor::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 = QueryThreadDebugInfo(thread_id); + uint64_t new_host_pc = backend_->CalculateNextHostInstruction( + thread_info, thread_info->frames[0].host_pc); + + assert_null(thread_info->step_breakpoint.get()); + thread_info->step_breakpoint.reset( + new Breakpoint(this, Breakpoint::AddressType::kHost, new_host_pc, + [this, thread_info](Breakpoint* breakpoint, + ThreadDebugInfo* breaking_thread_info, + uint64_t host_address) { + if (thread_info != breaking_thread_info) { + assert_always("Step in another thread?"); + } + // Our step request has completed. Remove the breakpoint + // and fire event. + breakpoint->Suspend(); + RemoveBreakpoint(breakpoint); + thread_info->step_breakpoint.reset(); + OnStepCompleted(thread_info); + })); + AddBreakpoint(thread_info->step_breakpoint.get()); + thread_info->step_breakpoint->Resume(); + + // ResumeAllBreakpoints(); + ResumeThread(thread_id); +} + +void Processor::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 = QueryThreadDebugInfo(thread_id); + + uint32_t next_pc = CalculateNextGuestInstruction( + thread_info, thread_info->frames[0].guest_pc); + + assert_null(thread_info->step_breakpoint.get()); + thread_info->step_breakpoint.reset( + new Breakpoint(this, Breakpoint::AddressType::kGuest, next_pc, + [this, thread_info](Breakpoint* breakpoint, + ThreadDebugInfo* breaking_thread_info, + uint64_t host_address) { + if (thread_info != breaking_thread_info) { + assert_always("Step in another thread?"); + } + // Our step request has completed. Remove the breakpoint + // and fire event. + breakpoint->Suspend(); + RemoveBreakpoint(breakpoint); + thread_info->step_breakpoint.reset(); + OnStepCompleted(thread_info); + })); + AddBreakpoint(thread_info->step_breakpoint.get()); + thread_info->step_breakpoint->Resume(); + + // ResumeAllBreakpoints(); + ResumeThread(thread_id); +} + +bool Processor::StepToGuestAddress(uint32_t thread_id, uint32_t pc) { + auto functions = FindFunctionsWithAddress(pc); + if (functions.empty()) { + // Function hasn't been generated yet. Generate it. + if (!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( + this, Breakpoint::AddressType::kGuest, pc, + [&fence](Breakpoint* breakpoint, ThreadDebugInfo* thread_info, + uint64_t host_address) { fence.Signal(); }); + bp.Resume(); + + // HACK + auto thread_info = QueryThreadDebugInfo(thread_id); + uint32_t suspend_count = 1; + while (suspend_count) { + thread_info->thread->Resume(&suspend_count); + } + + fence.Wait(); + bp.Suspend(); + + return true; +} + +uint32_t Processor::StepIntoGuestBranchTarget(uint32_t thread_id, uint32_t pc) { + xe::cpu::ppc::PPCDecodeData d; + d.address = pc; + d.code = xe::load_and_swap(memory()->TranslateVirtual(d.address)); + auto opcode = xe::cpu::ppc::LookupOpcode(d.code); + + // Must be on a branch. + assert_true(xe::cpu::ppc::GetOpcodeInfo(opcode).group == + xe::cpu::ppc::PPCOpcodeGroup::kB); + + auto thread_info = QueryThreadDebugInfo(thread_id); + auto thread = thread_info->thread; + auto context = thread->thread_state()->context(); + + if (d.code == 0x4E800020) { + // blr + uint32_t nia = uint32_t(context->lr); + StepToGuestAddress(thread_id, nia); + pc = nia; + } else if (d.code == 0x4E800420) { + // bctr + uint32_t nia = uint32_t(context->ctr); + StepToGuestAddress(thread_id, nia); + pc = nia; + } else if (opcode == PPCOpcode::bx) { + // bx + uint32_t nia = d.I.ADDR(); + StepToGuestAddress(thread_id, nia); + pc = nia; + } else if (opcode == PPCOpcode::bcx || opcode == PPCOpcode::bcctrx || + opcode == PPCOpcode::bclrx) { + threading::Fence fence; + auto callback = [&fence, &pc](Breakpoint* breakpoint, + ThreadDebugInfo* thread_info, + uint64_t host_address) { + pc = breakpoint->guest_address(); + fence.Signal(); + }; + + cpu::Breakpoint bpf(this, Breakpoint::AddressType::kGuest, pc + 4, + callback); + bpf.Resume(); + + 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); + } + + cpu::Breakpoint bpt(this, Breakpoint::AddressType::kGuest, nia, callback); + bpt.Resume(); + + // HACK + uint32_t suspend_count = 1; + while (suspend_count) { + thread->Resume(&suspend_count); + } + + fence.Wait(); + bpt.Suspend(); + bpf.Suspend(); + } + + return pc; +} + +uint32_t Processor::StepToGuestSafePoint(uint32_t thread_id, bool ignore_host) { + // This cannot be done if we're the calling thread! + if (thread_id == ThreadState::GetThreadID()) { + assert_always( + "XThread::StepToSafePoint(): target thread is the calling thread!"); + return 0; + } + auto thread_info = QueryThreadDebugInfo(thread_id); + auto thread = thread_info->thread; + + // 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. + uint64_t frame_host_pcs[64]; + cpu::StackFrame cpu_frames[64]; + size_t count = stack_walker_->CaptureStackTrace( + thread->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(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) { + StepToGuestAddress(thread_id, 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 = StepIntoGuestBranchTarget(thread_id, 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(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 = static_cast(thread->thread_state()->context()->lr); + StepToGuestAddress(thread_id, 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(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. + StepToGuestAddress(thread_id, first_pc + 4); + return StepToGuestSafePoint(thread_id, 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. + auto creation_params = thread->creation_params(); + pc = creation_params->xapi_thread_startup + ? creation_params->xapi_thread_startup + : creation_params->start_address; + StepToGuestAddress(thread_id, pc); + } + } + + return pc; +} + +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(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(&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 Processor::CalculateNextGuestInstruction(ThreadDebugInfo* thread_info, + uint32_t current_pc) { + xe::cpu::ppc::PPCDecodeData d; + d.address = current_pc; + d.code = xe::load_and_swap(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(thread_info->guest_context.lr); + return target_pc; + } else if (d.code == 0x4E800420) { + // bctr -- unconditional branch to CTR. + uint32_t target_pc = static_cast(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(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(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; + } } } // namespace cpu diff --git a/src/xenia/cpu/processor.h b/src/xenia/cpu/processor.h index ca5296e99..7121d9ea0 100644 --- a/src/xenia/cpu/processor.h +++ b/src/xenia/cpu/processor.h @@ -10,34 +10,33 @@ #ifndef XENIA_CPU_PROCESSOR_H_ #define XENIA_CPU_PROCESSOR_H_ +#include + +#include #include #include #include +#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 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 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 breakpoints() const { return breakpoints_; } + + // Returns all currently registered breakpoints. + std::vector 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 stack_walker_; + std::function 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 functions_trace_file_; std::unique_ptr frontend_; std::unique_ptr backend_; @@ -118,11 +255,16 @@ class Processor { EntryTable entry_table_; xe::global_critical_region global_critical_region_; + ExecutionState execution_state_ = ExecutionState::kPaused; std::vector> 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> thread_debug_infos_; + + // TODO(benvanik): cleanup/change structures. std::vector breakpoints_; Irql irql_; diff --git a/src/xenia/cpu/stack_walker_win.cc b/src/xenia/cpu/stack_walker_win.cc index 97c273ccd..50ec5707f 100644 --- a/src/xenia/cpu/stack_walker_win.cc +++ b/src/xenia/cpu/stack_walker_win.cc @@ -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(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; diff --git a/src/xenia/cpu/testing/premake5.lua b/src/xenia/cpu/testing/premake5.lua index 258a502fe..17ea9f44d 100644 --- a/src/xenia/cpu/testing/premake5.lua +++ b/src/xenia/cpu/testing/premake5.lua @@ -12,7 +12,6 @@ test_suite("xenia-cpu-tests", project_root, ".", { "xenia-cpu-backend-x64", -- TODO(benvanik): cut these dependencies? - "xenia-debug", "xenia-kernel", }, }) diff --git a/src/xenia/cpu/testing/util.h b/src/xenia/cpu/testing/util.h index 8557b1c0f..c04f0a756 100644 --- a/src/xenia/cpu/testing/util.h +++ b/src/xenia/cpu/testing/util.h @@ -39,8 +39,7 @@ class TestFunction { #if XENIA_TEST_X64 { - auto processor = - std::make_unique(nullptr, memory.get(), nullptr, nullptr); + auto processor = std::make_unique(memory.get(), nullptr); processor->Setup(); processors.emplace_back(std::move(processor)); } diff --git a/src/xenia/debug/thread_execution_info.h b/src/xenia/cpu/thread_debug_info.h similarity index 83% rename from src/xenia/debug/thread_execution_info.h rename to src/xenia/cpu/thread_debug_info.h index 317561e63..c83212ae2 100644 --- a/src/xenia/debug/thread_execution_info.h +++ b/src/xenia/cpu/thread_debug_info.h @@ -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 #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 step_breakpoint; + std::unique_ptr 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 frames; }; -} // namespace debug +} // namespace cpu } // namespace xe -#endif // XENIA_DEBUG_THREAD_EXECUTION_INFO_H_ +#endif // XENIA_CPU_THREAD_DEBUG_INFO_H_ diff --git a/src/xenia/cpu/thread_state.cc b/src/xenia/cpu/thread_state.cc index fabd176e6..3816446fc 100644 --- a/src/xenia/cpu/thread_state.cc +++ b/src/xenia/cpu/thread_state.cc @@ -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 diff --git a/src/xenia/debug/breakpoint.cc b/src/xenia/debug/breakpoint.cc deleted file mode 100644 index 08b740257..000000000 --- a/src/xenia/debug/breakpoint.cc +++ /dev/null @@ -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 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(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(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(host_address); - auto original_bytes = xe::load_and_swap(ptr); - assert_true(original_bytes != 0x0F0B); - xe::store_and_swap(ptr, 0x0F0B); - return original_bytes; -} - -void CodeBreakpoint::UnpatchAddress(uintptr_t host_address, - uint16_t original_bytes) { - auto ptr = reinterpret_cast(host_address); - auto instruction_bytes = xe::load_and_swap(ptr); - assert_true(instruction_bytes == 0x0F0B); - xe::store_and_swap(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 diff --git a/src/xenia/debug/breakpoint.h b/src/xenia/debug/breakpoint.h deleted file mode 100644 index f4ec9619a..000000000 --- a/src/xenia/debug/breakpoint.h +++ /dev/null @@ -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 -#include -#include -#include - -#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(address_); - } - uintptr_t host_address() const { - assert_true(address_type_ == AddressType::kHost); - return static_cast(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 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> 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_ diff --git a/src/xenia/debug/debugger.cc b/src/xenia/debug/debugger.cc deleted file mode 100644 index 0393649bc..000000000 --- a/src/xenia/debug/debugger.cc +++ /dev/null @@ -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 - -#include -#include - -#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 Debugger::QueryThreadExecutionInfos() { - auto global_lock = global_critical_region_.Acquire(); - std::vector 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( - 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( - 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(); - 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 breakpoints; - //{ - // std::lock_guard 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(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(reinterpret_cast(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(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(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(&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( - 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(thread_info->guest_context.lr); - return target_pc; - } else if (d.code == 0x4E800420) { - // bctr -- unconditional branch to CTR. - uint32_t target_pc = static_cast(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(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(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(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(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(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(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 diff --git a/src/xenia/debug/debugger.h b/src/xenia/debug/debugger.h deleted file mode 100644 index 9d660a3f2..000000000 --- a/src/xenia/debug/debugger.h +++ /dev/null @@ -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 - -#include -#include -#include -#include -#include -#include - -#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 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 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& 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 debug_listener_handler_; - DebugListener* debug_listener_ = nullptr; - - // TODO(benvanik): cleanup - maybe remove now that in-process? - std::wstring functions_path_; - std::unique_ptr functions_file_; - std::wstring functions_trace_path_; - std::unique_ptr 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> - thread_execution_infos_; - // TODO(benvanik): cleanup/change structures. - std::vector breakpoints_; -}; - -} // namespace debug -} // namespace xe - -#endif // XENIA_DEBUG_DEBUGGER_H_ diff --git a/src/xenia/debug/premake5.lua b/src/xenia/debug/premake5.lua deleted file mode 100644 index 98d5145d2..000000000 --- a/src/xenia/debug/premake5.lua +++ /dev/null @@ -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() diff --git a/src/xenia/debug/thread_execution_info.cc b/src/xenia/debug/thread_execution_info.cc deleted file mode 100644 index 4f837907f..000000000 --- a/src/xenia/debug/thread_execution_info.cc +++ /dev/null @@ -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 diff --git a/src/xenia/debug/ui/debug_window.cc b/src/xenia/debug/ui/debug_window.cc index 77cdd45e1..0d3eb36c2 100644 --- a/src/xenia/debug/ui/debug_window.cc +++ b/src/xenia/debug/ui/debug_window.cc @@ -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(0)); + if (thread_info->state != cpu::ThreadDebugInfo::State::kZombie) { + thread_combo.Append(thread_info->thread->name()); + } else { + thread_combo.Append("(zombie)"); + } + thread_combo.Append('\0'); ++i; } if (ImGui::Combo("##thread_combo", ¤t_thread_index, thread_combo.GetString(), 10)) { // Thread changed. - SelectThreadStackFrame(cache_.thread_execution_infos[current_thread_index], - 0, true); + SelectThreadStackFrame(cache_.thread_debug_infos[current_thread_index], 0, + true); } } @@ -351,8 +356,8 @@ void DebugWindow::DrawSourcePane() { if (ImGui::ButtonEx("Step PPC", ImVec2(0, 0), can_step ? 0 : ImGuiButtonFlags_Disabled)) { // By enabling the button when stepping we allow repeat behavior. - if (debugger_->execution_state() != ExecutionState::kStepping) { - debugger_->StepGuestInstruction(state_.thread_info->thread_id); + if (processor_->execution_state() != cpu::ExecutionState::kStepping) { + processor_->StepGuestInstruction(state_.thread_info->thread_id); } } ImGui::PopButtonRepeat(); @@ -369,8 +374,8 @@ void DebugWindow::DrawSourcePane() { if (ImGui::ButtonEx("Step x64", ImVec2(0, 0), can_step ? 0 : ImGuiButtonFlags_Disabled)) { // By enabling the button when stepping we allow repeat behavior. - if (debugger_->execution_state() != ExecutionState::kStepping) { - debugger_->StepHostInstruction(state_.thread_info->thread_id); + if (processor_->execution_state() != cpu::ExecutionState::kStepping) { + processor_->StepHostInstruction(state_.thread_info->thread_id); } } ImGui::PopButtonRepeat(); @@ -494,10 +499,10 @@ void DebugWindow::DrawGuestFunctionSource() { } bool has_guest_bp = - LookupBreakpointAtAddress(CodeBreakpoint::AddressType::kGuest, - address) != nullptr; - DrawBreakpointGutterButton(has_guest_bp, - CodeBreakpoint::AddressType::kGuest, address); + LookupBreakpointAtAddress(Breakpoint::AddressType::kGuest, address) != + nullptr; + DrawBreakpointGutterButton(has_guest_bp, Breakpoint::AddressType::kGuest, + address); ImGui::SameLine(); ImGui::Text(" %c ", is_current_instr ? '>' : ' '); @@ -563,10 +568,9 @@ void DebugWindow::DrawMachineCodeSource(const uint8_t* machine_code_ptr, ScrollToSourceIfPcChanged(); } - bool has_host_bp = - LookupBreakpointAtAddress(CodeBreakpoint::AddressType::kHost, - insn.address) != nullptr; - DrawBreakpointGutterButton(has_host_bp, CodeBreakpoint::AddressType::kHost, + bool has_host_bp = LookupBreakpointAtAddress(Breakpoint::AddressType::kHost, + insn.address) != nullptr; + DrawBreakpointGutterButton(has_host_bp, Breakpoint::AddressType::kHost, insn.address); ImGui::SameLine(); @@ -585,7 +589,7 @@ void DebugWindow::DrawMachineCodeSource(const uint8_t* machine_code_ptr, } void DebugWindow::DrawBreakpointGutterButton( - bool has_breakpoint, CodeBreakpoint::AddressType address_type, + bool has_breakpoint, Breakpoint::AddressType address_type, uint64_t address) { ImGui::PushStyleColor(ImGuiCol_Button, has_breakpoint @@ -991,10 +995,11 @@ void DebugWindow::DrawThreadsPane() { // expand all toggle ImGui::EndGroup(); ImGui::BeginChild("##threads_listing"); - for (size_t i = 0; i < cache_.thread_execution_infos.size(); ++i) { - auto thread_info = cache_.thread_execution_infos[i]; + for (size_t i = 0; i < cache_.thread_debug_infos.size(); ++i) { + auto thread_info = cache_.thread_debug_infos[i]; bool is_current_thread = thread_info == state_.thread_info; - auto thread = thread_info->thread; + auto thread = + emulator_->kernel_state()->GetThreadByID(thread_info->thread_id); if (!thread) { // TODO(benvanik): better display of zombie thread states. continue; @@ -1146,7 +1151,7 @@ void DebugWindow::DrawBreakpointsPane() { if (ImGui::InputText("##guest_address", ppc_buffer, 9, input_flags)) { uint32_t address = string_util::from_string(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(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(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(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(debugger_, address_type, address); - if (breakpoint->address_type() == CodeBreakpoint::AddressType::kGuest) { + auto breakpoint = std::make_unique( + 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(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(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); }); } diff --git a/src/xenia/debug/ui/debug_window.h b/src/xenia/debug/ui/debug_window.h index c67503831..5a0a5844b 100644 --- a/src/xenia/debug/ui/debug_window.h +++ b/src/xenia/debug/ui/debug_window.h @@ -14,7 +14,9 @@ #include #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 window_; uint64_t last_draw_tick_count_ = 0; @@ -99,7 +100,7 @@ class DebugWindow : public DebugListener { struct ImDataCache { bool is_running = false; std::vector> modules; - std::vector thread_execution_infos; + std::vector 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> all_breakpoints; - std::unordered_map + std::vector> all_breakpoints; + std::unordered_map code_breakpoints_by_guest_address; - std::unordered_map + std::unordered_map code_breakpoints_by_host_address; } breakpoints; diff --git a/src/xenia/debug/ui/premake5.lua b/src/xenia/debug/ui/premake5.lua index 912b1ec0e..e631f7f5b 100644 --- a/src/xenia/debug/ui/premake5.lua +++ b/src/xenia/debug/ui/premake5.lua @@ -10,7 +10,6 @@ project("xenia-debug-ui") "imgui", "xenia-base", "xenia-cpu", - "xenia-debug", "xenia-ui", "yaml-cpp", }) diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index d0e3a2c2d..a12569dcc 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -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(); - 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( - this, memory_.get(), export_resolver_.get(), debugger_.get()); + processor_ = std::make_unique(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)) { diff --git a/src/xenia/emulator.h b/src/xenia/emulator.h index 692e57f8c..05780d690 100644 --- a/src/xenia/emulator.h +++ b/src/xenia/emulator.h @@ -14,7 +14,6 @@ #include #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_; - std::unique_ptr debugger_; - std::unique_ptr processor_; std::unique_ptr audio_system_; std::unique_ptr graphics_system_; diff --git a/src/xenia/gpu/gl4/premake5.lua b/src/xenia/gpu/gl4/premake5.lua index b0d980b1a..2786de17b 100644 --- a/src/xenia/gpu/gl4/premake5.lua +++ b/src/xenia/gpu/gl4/premake5.lua @@ -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", diff --git a/src/xenia/kernel/kernel_module.cc b/src/xenia/kernel/kernel_module.cc index fdad0256f..7cdaf9a3f 100644 --- a/src/xenia/kernel/kernel_module.cc +++ b/src/xenia/kernel/kernel_module.cc @@ -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" diff --git a/src/xenia/kernel/kernel_module.h b/src/xenia/kernel/kernel_module.h index 0aaba2fb4..f9642aca6 100644 --- a/src/xenia/kernel/kernel_module.h +++ b/src/xenia/kernel/kernel_module.h @@ -10,6 +10,8 @@ #ifndef XENIA_KERNEL_KERNEL_MODULE_H_ #define XENIA_KERNEL_KERNEL_MODULE_H_ +#include + #include "xenia/emulator.h" #include "xenia/kernel/xmodule.h" diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index c67794358..18b822fda 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -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 KernelState::GetThreadByID(uint32_t thread_id) { diff --git a/src/xenia/kernel/user_module.cc b/src/xenia/kernel/user_module.cc index 31947b38e..35aa956fe 100644 --- a/src/xenia/kernel/user_module.cc +++ b/src/xenia/kernel/user_module.cc @@ -315,9 +315,7 @@ object_ref 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 diff --git a/src/xenia/kernel/xthread.cc b/src/xenia/kernel/xthread.cc index e90703870..da009c69d 100644 --- a/src/xenia/kernel/xthread.cc +++ b/src/xenia/kernel/xthread.cc @@ -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(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(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(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(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::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(thread); diff --git a/src/xenia/kernel/xthread.h b/src/xenia/kernel/xthread.h index f4ef2264e..2ca565eb7 100644 --- a/src/xenia/kernel/xthread.h +++ b/src/xenia/kernel/xthread.h @@ -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(); diff --git a/src/xenia/ui/imgui_dialog.cc b/src/xenia/ui/imgui_dialog.cc index e4fda5d8b..2e2370bd9 100644 --- a/src/xenia/ui/imgui_dialog.cc +++ b/src/xenia/ui/imgui_dialog.cc @@ -66,7 +66,7 @@ class MessageBoxDialog : public ImGuiDialog { ImGuiWindowFlags_AlwaysAutoResize)) { char* text = const_cast(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();