diff --git a/src/xenia/base/mapped_memory.h b/src/xenia/base/mapped_memory.h index 90e8ee8b5..95eeb0430 100644 --- a/src/xenia/base/mapped_memory.h +++ b/src/xenia/base/mapped_memory.h @@ -48,18 +48,23 @@ class ChunkedMappedMemoryWriter { virtual ~ChunkedMappedMemoryWriter() = default; static std::unique_ptr Open( - const std::wstring& path, size_t chunk_size); + const std::wstring& path, size_t chunk_size, + bool low_address_space = false); virtual uint8_t* Allocate(size_t length) = 0; virtual void Flush() = 0; virtual void FlushNew() = 0; protected: - ChunkedMappedMemoryWriter(const std::wstring& path, size_t chunk_size) - : path_(path), chunk_size_(chunk_size) {} + ChunkedMappedMemoryWriter(const std::wstring& path, size_t chunk_size, + bool low_address_space) + : path_(path), + chunk_size_(chunk_size), + low_address_space_(low_address_space) {} std::wstring path_; size_t chunk_size_; + bool low_address_space_; }; } // namespace xe diff --git a/src/xenia/base/mapped_memory_win.cc b/src/xenia/base/mapped_memory_win.cc index dbe1d1ebb..6889436ae 100644 --- a/src/xenia/base/mapped_memory_win.cc +++ b/src/xenia/base/mapped_memory_win.cc @@ -15,6 +15,7 @@ #include #include +#include "xenia/base/logging.h" #include "xenia/base/math.h" namespace xe { @@ -38,9 +39,7 @@ class Win32MappedMemory : public MappedMemory { } } - void Flush() override { - FlushViewOfFile(data(), size()); - } + void Flush() override { FlushViewOfFile(data(), size()); } HANDLE file_handle; HANDLE mapping_handle; @@ -114,8 +113,9 @@ std::unique_ptr MappedMemory::Open(const std::wstring& path, class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter { public: - Win32ChunkedMappedMemoryWriter(const std::wstring& path, size_t chunk_size) - : ChunkedMappedMemoryWriter(path, chunk_size) {} + Win32ChunkedMappedMemoryWriter(const std::wstring& path, size_t chunk_size, + bool low_address_space) + : ChunkedMappedMemoryWriter(path, chunk_size, low_address_space) {} ~Win32ChunkedMappedMemoryWriter() override { std::lock_guard lock(mutex_); @@ -132,7 +132,7 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter { } auto chunk = std::make_unique(chunk_size_); auto chunk_path = path_ + L"." + std::to_wstring(chunks_.size()); - if (!chunk->Open(chunk_path)) { + if (!chunk->Open(chunk_path, low_address_space_)) { return nullptr; } uint8_t* result = chunk->Allocate(length); @@ -177,10 +177,10 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter { } } - bool Open(const std::wstring& path) { + bool Open(const std::wstring& path, bool low_address_space) { DWORD file_access = GENERIC_READ | GENERIC_WRITE; DWORD file_share = 0; - DWORD create_mode = OPEN_EXISTING; + DWORD create_mode = CREATE_ALWAYS; DWORD mapping_protect = PAGE_READWRITE; DWORD view_access = FILE_MAP_READ | FILE_MAP_WRITE; @@ -197,8 +197,28 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter { return false; } - data_ = reinterpret_cast( - MapViewOfFile(mapping_handle_, view_access, 0, 0, capacity_)); + // If specified, ensure the allocation occurs in the lower 32-bit address + // space. + if (low_address_space) { + bool successful = false; + data_ = reinterpret_cast(0x10000000); + for (int i = 0; i < 1000; ++i) { + if (MapViewOfFileEx(mapping_handle_, view_access, 0, 0, capacity_, + data_)) { + successful = true; + break; + } + data_ += capacity_; + if (!successful) { + XELOGE("Unable to find space for mapping"); + data_ = nullptr; + return false; + } + } + } else { + data_ = reinterpret_cast( + MapViewOfFile(mapping_handle_, view_access, 0, 0, capacity_)); + } if (!data_) { return false; } @@ -215,9 +235,7 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter { return result; } - void Flush() { - FlushViewOfFile(data_, offset_); - } + void Flush() { FlushViewOfFile(data_, offset_); } void FlushNew() { FlushViewOfFile(data_ + last_flush_offset_, offset_ - last_flush_offset_); @@ -238,13 +256,13 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter { }; std::unique_ptr ChunkedMappedMemoryWriter::Open( - const std::wstring& path, size_t chunk_size) { + const std::wstring& path, size_t chunk_size, bool low_address_space) { SYSTEM_INFO system_info; GetSystemInfo(&system_info); size_t aligned_chunk_size = xe::round_up(chunk_size, system_info.dwAllocationGranularity); - return std::make_unique(path, - aligned_chunk_size); + return std::make_unique( + path, aligned_chunk_size, low_address_space); } } // namespace xe diff --git a/src/xenia/base/memory.h b/src/xenia/base/memory.h index 0e6ec9d9d..c1209aa87 100644 --- a/src/xenia/base/memory.h +++ b/src/xenia/base/memory.h @@ -29,6 +29,10 @@ size_t hash_combine(size_t seed, const T& v, const Ts&... vs) { size_t page_size(); +constexpr void* low_address(void* address) { + return (void*)(uint64_t(address) & 0xFFFFFFFF); +} + void copy_and_swap_16_aligned(uint16_t* dest, const uint16_t* src, size_t count); void copy_and_swap_16_unaligned(uint16_t* dest, const uint16_t* src, diff --git a/src/xenia/cpu/backend/assembler.h b/src/xenia/cpu/backend/assembler.h index a804f4adb..e9312c0d7 100644 --- a/src/xenia/cpu/backend/assembler.h +++ b/src/xenia/cpu/backend/assembler.h @@ -41,7 +41,7 @@ class Assembler { virtual bool Assemble(FunctionInfo* symbol_info, hir::HIRBuilder* builder, uint32_t debug_info_flags, std::unique_ptr debug_info, - uint32_t trace_flags, Function** out_function) = 0; + Function** out_function) = 0; protected: Backend* backend_; diff --git a/src/xenia/cpu/backend/x64/x64_assembler.cc b/src/xenia/cpu/backend/x64/x64_assembler.cc index 15fd0ef49..18e5dc088 100644 --- a/src/xenia/cpu/backend/x64/x64_assembler.cc +++ b/src/xenia/cpu/backend/x64/x64_assembler.cc @@ -60,22 +60,22 @@ void X64Assembler::Reset() { bool X64Assembler::Assemble(FunctionInfo* symbol_info, HIRBuilder* builder, uint32_t debug_info_flags, std::unique_ptr debug_info, - uint32_t trace_flags, Function** out_function) { + Function** out_function) { SCOPE_profile_cpu_f("cpu"); // Reset when we leave. xe::make_reset_scope(this); // Lower HIR -> x64. - void* machine_code = 0; + void* machine_code = nullptr; size_t code_size = 0; - if (!emitter_->Emit(builder, debug_info_flags, debug_info.get(), trace_flags, - machine_code, code_size)) { + if (!emitter_->Emit(builder, debug_info_flags, debug_info.get(), machine_code, + code_size)) { return false; } // Stash generated machine code. - if (debug_info_flags & DebugInfoFlags::DEBUG_INFO_MACHINE_CODE_DISASM) { + if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmMachineCode) { DumpMachineCode(debug_info.get(), machine_code, code_size, &string_buffer_); debug_info->set_machine_code_disasm(string_buffer_.ToString()); string_buffer_.Reset(); diff --git a/src/xenia/cpu/backend/x64/x64_assembler.h b/src/xenia/cpu/backend/x64/x64_assembler.h index 98e766bbe..c7d3ff8e1 100644 --- a/src/xenia/cpu/backend/x64/x64_assembler.h +++ b/src/xenia/cpu/backend/x64/x64_assembler.h @@ -35,7 +35,7 @@ class X64Assembler : public Assembler { bool Assemble(FunctionInfo* symbol_info, hir::HIRBuilder* builder, uint32_t debug_info_flags, - std::unique_ptr debug_info, uint32_t trace_flags, + std::unique_ptr debug_info, Function** out_function) override; private: diff --git a/src/xenia/cpu/backend/x64/x64_emitter.cc b/src/xenia/cpu/backend/x64/x64_emitter.cc index 3ce7c0cc6..d2a1df0a7 100644 --- a/src/xenia/cpu/backend/x64/x64_emitter.cc +++ b/src/xenia/cpu/backend/x64/x64_emitter.cc @@ -13,6 +13,7 @@ #include "xenia/base/atomic.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" +#include "xenia/base/memory.h" #include "xenia/base/vec128.h" #include "xenia/cpu/backend/x64/x64_backend.h" #include "xenia/cpu/backend/x64/x64_code_cache.h" @@ -65,24 +66,25 @@ X64Emitter::X64Emitter(X64Backend* backend, XbyakAllocator* allocator) code_cache_(backend->code_cache()), allocator_(allocator), current_instr_(0), + debug_info_(nullptr), + debug_info_flags_(0), source_map_count_(0), - stack_size_(0), - trace_flags_(0), - cpu_() {} + stack_size_(0) {} X64Emitter::~X64Emitter() = default; bool X64Emitter::Emit(HIRBuilder* builder, uint32_t debug_info_flags, - DebugInfo* debug_info, uint32_t trace_flags, - void*& out_code_address, size_t& out_code_size) { + DebugInfo* debug_info, void*& out_code_address, + size_t& out_code_size) { SCOPE_profile_cpu_f("cpu"); // Reset. - if (debug_info_flags & DEBUG_INFO_SOURCE_MAP) { + debug_info_ = debug_info; + debug_info_flags_ = debug_info_flags; + if (debug_info_flags_ & DebugInfoFlags::kDebugInfoSourceMap) { source_map_count_ = 0; source_map_arena_.Reset(); } - trace_flags_ = trace_flags; // Fill the generator with code. size_t stack_size = 0; @@ -95,7 +97,7 @@ bool X64Emitter::Emit(HIRBuilder* builder, uint32_t debug_info_flags, out_code_address = Emplace(stack_size); // Stash source map. - if (debug_info_flags & DEBUG_INFO_SOURCE_MAP) { + if (debug_info_flags_ & DebugInfoFlags::kDebugInfoSourceMap) { debug_info->InitializeSourceMap( source_map_count_, (SourceMapEntry*)source_map_arena_.CloneContents()); } @@ -145,19 +147,46 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t& out_stack_size) { // IMPORTANT: any changes to the prolog must be kept in sync with // X64CodeCache, which dynamically generates exception information. // Adding or changing anything here must be matched! - const bool emit_prolog = true; const size_t stack_size = StackLayout::GUEST_STACK_SIZE + stack_offset; assert_true((stack_size + 8) % 16 == 0); out_stack_size = stack_size; stack_size_ = stack_size; - if (emit_prolog) { - sub(rsp, (uint32_t)stack_size); - mov(qword[rsp + StackLayout::GUEST_RCX_HOME], rcx); - mov(qword[rsp + StackLayout::GUEST_RET_ADDR], rdx); - mov(qword[rsp + StackLayout::GUEST_CALL_RET_ADDR], 0); - mov(rdx, qword[rcx + 8]); // membase + sub(rsp, (uint32_t)stack_size); + mov(qword[rsp + StackLayout::GUEST_RCX_HOME], rcx); + mov(qword[rsp + StackLayout::GUEST_RET_ADDR], rdx); + mov(qword[rsp + StackLayout::GUEST_CALL_RET_ADDR], 0); + + // Safe now to do some tracing. + if (debug_info_flags_ & DebugInfoFlags::kDebugInfoTraceFunctions) { + // We require 32-bit addresses. + assert_true(uint64_t(debug_info_->trace_data().header()) < UINT_MAX); + auto trace_header = debug_info_->trace_data().header(); + + // Call count. + lock(); + inc(qword[low_address(&trace_header->function_call_count)]); + + // Get call history slot. + static_assert(debug::FunctionTraceData::kFunctionCallerHistoryCount == 4, + "bitmask depends on count"); + mov(rax, qword[low_address(&trace_header->function_call_count)]); + and(rax, B00000011); + + // Record call history value into slot (guest addr in RDX). + mov(dword[RegExp(uint32_t(uint64_t( + low_address(&trace_header->function_caller_history)))) + + rax * 4], + edx); + + // Calling thread. Load ax with thread ID. + EmitGetCurrentThreadId(); + lock(); + bts(qword[low_address(&trace_header->function_thread_use)], rax); } + // Load membase. + mov(rdx, qword[rcx + 8]); + // Body. auto block = builder->first_block(); while (block) { @@ -187,10 +216,8 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t& out_stack_size) { // Function epilog. L("epilog"); EmitTraceUserCallReturn(); - if (emit_prolog) { - mov(rcx, qword[rsp + StackLayout::GUEST_RCX_HOME]); - add(rsp, (uint32_t)stack_size); - } + mov(rcx, qword[rsp + StackLayout::GUEST_RCX_HOME]); + add(rsp, (uint32_t)stack_size); ret(); #if XE_DEBUG @@ -210,6 +237,23 @@ void X64Emitter::MarkSourceOffset(const Instr* i) { entry->hir_offset = uint32_t(i->block->ordinal << 16) | i->ordinal; entry->code_offset = static_cast(getSize()); source_map_count_++; + +#if XE_DEBUG + nop(); + nop(); + mov(eax, entry->source_offset); + nop(); + nop(); +#endif // XE_DEBUG + + if (debug_info_flags_ & DebugInfoFlags::kDebugInfoTraceFunctionCoverage) { + auto trace_data = debug_info_->trace_data(); + uint32_t instruction_index = + (entry->source_offset - trace_data.start_address()) / 4; + lock(); + inc(qword[low_address(trace_data.instruction_execute_counts() + + instruction_index * 8)]); + } } void X64Emitter::EmitGetCurrentThreadId() { @@ -234,6 +278,7 @@ uint64_t TrapDebugPrint(void* raw_context, uint64_t address) { XELOGD("(DebugPrint) %s", str); return 0; } + void X64Emitter::Trap(uint16_t trap_type) { switch (trap_type) { case 20: diff --git a/src/xenia/cpu/backend/x64/x64_emitter.h b/src/xenia/cpu/backend/x64/x64_emitter.h index 1808d2809..3cc956232 100644 --- a/src/xenia/cpu/backend/x64/x64_emitter.h +++ b/src/xenia/cpu/backend/x64/x64_emitter.h @@ -15,6 +15,7 @@ #include "xenia/base/arena.h" #include "xenia/cpu/hir/value.h" +#include "xenia/debug/trace_data.h" namespace xe { namespace cpu { @@ -106,8 +107,8 @@ class X64Emitter : public Xbyak::CodeGenerator { const Xbyak::util::Cpu* cpu() const { return &cpu_; } bool Emit(hir::HIRBuilder* builder, uint32_t debug_info_flags, - DebugInfo* debug_info, uint32_t trace_flags, - void*& out_code_address, size_t& out_code_size); + DebugInfo* debug_info, void*& out_code_address, + size_t& out_code_size); public: // Reserved: rsp @@ -176,6 +177,8 @@ class X64Emitter : public Xbyak::CodeGenerator { void LoadConstantXmm(Xbyak::Xmm dest, const vec128_t& v); Xbyak::Address StashXmm(int index, const Xbyak::Xmm& r); + DebugInfo* debug_info() const { return debug_info_; } + size_t stack_size() const { return stack_size_; } protected: @@ -193,13 +196,13 @@ class X64Emitter : public Xbyak::CodeGenerator { hir::Instr* current_instr_; + DebugInfo* debug_info_; + uint32_t debug_info_flags_; size_t source_map_count_; Arena source_map_arena_; size_t stack_size_; - uint32_t trace_flags_; - static const uint32_t gpr_reg_map_[GPR_COUNT]; static const uint32_t xmm_reg_map_[XMM_COUNT]; }; diff --git a/src/xenia/cpu/backend/x64/x64_sequences.cc b/src/xenia/cpu/backend/x64/x64_sequences.cc index 4c8f24cf9..432690dda 100644 --- a/src/xenia/cpu/backend/x64/x64_sequences.cc +++ b/src/xenia/cpu/backend/x64/x64_sequences.cc @@ -95,13 +95,6 @@ EMITTER_OPCODE_TABLE( // ============================================================================ EMITTER(SOURCE_OFFSET, MATCH(I)) { static void Emit(X64Emitter& e, const EmitArgType& i) { -#if XE_DEBUG - e.nop(); - e.nop(); - e.mov(e.eax, (uint32_t)i.src1.value); - e.nop(); - e.nop(); -#endif // XE_DEBUG e.MarkSourceOffset(i.instr); } }; diff --git a/src/xenia/cpu/cpu-private.h b/src/xenia/cpu/cpu-private.h index e1449386a..28520843a 100644 --- a/src/xenia/cpu/cpu-private.h +++ b/src/xenia/cpu/cpu-private.h @@ -22,6 +22,11 @@ DECLARE_bool(dump_module_map); DECLARE_bool(debug); DECLARE_bool(always_disasm); +DECLARE_bool(trace_functions); +DECLARE_bool(trace_function_coverage); +DECLARE_bool(trace_function_references); +DECLARE_bool(trace_function_data); + DECLARE_bool(validate_hir); DECLARE_uint64(break_on_instruction); diff --git a/src/xenia/cpu/cpu.cc b/src/xenia/cpu/cpu.cc index 88cde586c..7dd87992d 100644 --- a/src/xenia/cpu/cpu.cc +++ b/src/xenia/cpu/cpu.cc @@ -36,6 +36,15 @@ DEFINE_bool( always_disasm, false, "Always add debug info to functions, even when no debugger is attached."); +DEFINE_bool(trace_functions, false, + "Generate tracing for function statistics."); +DEFINE_bool(trace_function_coverage, false, + "Generate tracing for function instruction coverage statistics."); +DEFINE_bool(trace_function_references, false, + "Generate tracing for function address references."); +DEFINE_bool(trace_function_data, false, + "Generate tracing for function result data."); + DEFINE_bool(validate_hir, false, "Perform validation checks on the HIR during compilation."); diff --git a/src/xenia/cpu/debug_info.h b/src/xenia/cpu/debug_info.h index d6b129e6c..1ca37ce44 100644 --- a/src/xenia/cpu/debug_info.h +++ b/src/xenia/cpu/debug_info.h @@ -13,18 +13,25 @@ #include #include +#include "xenia/debug/trace_data.h" + namespace xe { namespace cpu { enum DebugInfoFlags { - DEBUG_INFO_NONE = 0, - DEBUG_INFO_SOURCE_DISASM = (1 << 1), - DEBUG_INFO_RAW_HIR_DISASM = (1 << 2), - DEBUG_INFO_HIR_DISASM = (1 << 3), - DEBUG_INFO_MACHINE_CODE_DISASM = (1 << 4), - DEBUG_INFO_SOURCE_MAP = (1 << 5), - DEBUG_INFO_DEFAULT = DEBUG_INFO_SOURCE_MAP, - DEBUG_INFO_ALL_DISASM = 0xFFFF, + kDebugInfoNone = 0, + kDebugInfoDisasmSource = (1 << 1), + kDebugInfoDisasmRawHir = (1 << 2), + kDebugInfoDisasmHir = (1 << 3), + kDebugInfoDisasmMachineCode = (1 << 4), + kDebugInfoAllDisasm = kDebugInfoDisasmSource | kDebugInfoDisasmRawHir | + kDebugInfoDisasmHir | kDebugInfoDisasmMachineCode, + kDebugInfoSourceMap = (1 << 5), + kDebugInfoTraceFunctions = (1 << 6), + kDebugInfoTraceFunctionCoverage = (1 << 7) | kDebugInfoTraceFunctions, + kDebugInfoTraceFunctionReferences = (1 << 8) | kDebugInfoTraceFunctions, + kDebugInfoTraceFunctionData = (1 << 9) | kDebugInfoTraceFunctions, + kDebugInfoAll = 0xFFFFFFFF, }; typedef struct SourceMapEntry_s { @@ -38,6 +45,19 @@ class DebugInfo { DebugInfo(); ~DebugInfo(); + uint32_t address_reference_count() const { return address_reference_count_; } + void set_address_reference_count(uint32_t value) { + address_reference_count_ = value; + } + uint32_t instruction_result_count() const { + return instruction_result_count_; + } + void set_instruction_result_count(uint32_t value) { + instruction_result_count_ = value; + } + + debug::FunctionTraceData& trace_data() { return trace_data_; } + const char* source_disasm() const { return source_disasm_; } void set_source_disasm(char* value) { source_disasm_ = value; } const char* raw_hir_disasm() const { return raw_hir_disasm_; } @@ -53,6 +73,11 @@ class DebugInfo { SourceMapEntry* LookupCodeOffset(uint32_t offset); private: + uint32_t address_reference_count_; + uint32_t instruction_result_count_; + + debug::FunctionTraceData trace_data_; + char* source_disasm_; char* raw_hir_disasm_; char* hir_disasm_; diff --git a/src/xenia/cpu/frontend/ppc_frontend.cc b/src/xenia/cpu/frontend/ppc_frontend.cc index a9024ed3d..fe7f01205 100644 --- a/src/xenia/cpu/frontend/ppc_frontend.cc +++ b/src/xenia/cpu/frontend/ppc_frontend.cc @@ -97,11 +97,10 @@ bool PPCFrontend::DeclareFunction(FunctionInfo* symbol_info) { bool PPCFrontend::DefineFunction(FunctionInfo* symbol_info, uint32_t debug_info_flags, - uint32_t trace_flags, Function** out_function) { PPCTranslator* translator = translator_pool_.Allocate(this); - bool result = translator->Translate(symbol_info, debug_info_flags, - trace_flags, out_function); + bool result = + translator->Translate(symbol_info, debug_info_flags, out_function); translator_pool_.Release(translator); return result; } diff --git a/src/xenia/cpu/frontend/ppc_frontend.h b/src/xenia/cpu/frontend/ppc_frontend.h index 6a60ff8fd..1905517f4 100644 --- a/src/xenia/cpu/frontend/ppc_frontend.h +++ b/src/xenia/cpu/frontend/ppc_frontend.h @@ -52,7 +52,7 @@ class PPCFrontend { bool DeclareFunction(FunctionInfo* symbol_info); bool DefineFunction(FunctionInfo* symbol_info, uint32_t debug_info_flags, - uint32_t trace_flags, Function** out_function); + Function** out_function); private: Processor* processor_; diff --git a/src/xenia/cpu/frontend/ppc_scanner.cc b/src/xenia/cpu/frontend/ppc_scanner.cc index 1f50ad238..5220a3181 100644 --- a/src/xenia/cpu/frontend/ppc_scanner.cc +++ b/src/xenia/cpu/frontend/ppc_scanner.cc @@ -53,6 +53,10 @@ bool PPCScanner::Scan(FunctionInfo* symbol_info, DebugInfo* debug_info) { LOGPPC("Analyzing function %.8X...", symbol_info->address()); + // For debug info, only if needed. + uint32_t address_reference_count = 0; + uint32_t instruction_result_count = 0; + uint32_t start_address = static_cast(symbol_info->address()); uint32_t end_address = static_cast(symbol_info->end_address()); uint32_t address = start_address; @@ -78,6 +82,10 @@ bool PPCScanner::Scan(FunctionInfo* symbol_info, DebugInfo* debug_info) { // This lookup is *expensive* and should be avoided when scanning. i.type = GetInstrType(i.code); + // TODO(benvanik): switch on instruction metadata. + ++address_reference_count; + ++instruction_result_count; + // Check if the function starts with a mfspr lr, as that's a good indication // of whether or not this is a normal function with a prolog/epilog. // Some valid leaf functions won't have this, but most will. @@ -274,6 +282,11 @@ bool PPCScanner::Scan(FunctionInfo* symbol_info, DebugInfo* debug_info) { // - if present, flag function as needing a stack // - record prolog/epilog lengths/stack size/etc + if (debug_info) { + debug_info->set_address_reference_count(address_reference_count); + debug_info->set_instruction_result_count(instruction_result_count); + } + LOGPPC("Finished analyzing %.8X", start_address); return true; } diff --git a/src/xenia/cpu/frontend/ppc_translator.cc b/src/xenia/cpu/frontend/ppc_translator.cc index 82ea79a06..d293ab680 100644 --- a/src/xenia/cpu/frontend/ppc_translator.cc +++ b/src/xenia/cpu/frontend/ppc_translator.cc @@ -21,6 +21,7 @@ #include "xenia/cpu/frontend/ppc_instr.h" #include "xenia/cpu/frontend/ppc_scanner.h" #include "xenia/cpu/processor.h" +#include "xenia/debug/debugger.h" #include "xenia/profiling.h" namespace xe { @@ -87,7 +88,7 @@ PPCTranslator::PPCTranslator(PPCFrontend* frontend) : frontend_(frontend) { PPCTranslator::~PPCTranslator() = default; bool PPCTranslator::Translate(FunctionInfo* symbol_info, - uint32_t debug_info_flags, uint32_t trace_flags, + uint32_t debug_info_flags, Function** out_function) { SCOPE_profile_cpu_f("cpu"); @@ -99,7 +100,19 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info, // NOTE: we only want to do this when required, as it's expensive to build. if (FLAGS_always_disasm) { - debug_info_flags |= DEBUG_INFO_ALL_DISASM; + debug_info_flags |= DebugInfoFlags::kDebugInfoAllDisasm; + } + if (FLAGS_trace_functions) { + debug_info_flags |= DebugInfoFlags::kDebugInfoTraceFunctions; + } + if (FLAGS_trace_function_coverage) { + debug_info_flags |= DebugInfoFlags::kDebugInfoTraceFunctionCoverage; + } + if (FLAGS_trace_function_references) { + debug_info_flags |= DebugInfoFlags::kDebugInfoTraceFunctionReferences; + } + if (FLAGS_trace_function_data) { + debug_info_flags |= DebugInfoFlags::kDebugInfoTraceFunctionData; } std::unique_ptr debug_info; if (debug_info_flags) { @@ -111,8 +124,25 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info, return false; } + // Setup trace data, if needed. + if (debug_info_flags & DebugInfoFlags::kDebugInfoTraceFunctions) { + // Base trace data. + size_t trace_data_size = debug::FunctionTraceData::SizeOfHeader(); + if (debug_info_flags & DebugInfoFlags::kDebugInfoTraceFunctionCoverage) { + // Additional space for instruction coverage counts. + trace_data_size += debug::FunctionTraceData::SizeOfInstructionCounts( + symbol_info->address(), symbol_info->end_address()); + } + uint8_t* trace_data = + frontend_->processor()->debugger()->AllocateTraceFunctionData( + trace_data_size); + debug_info->trace_data().Reset(trace_data, trace_data_size, + symbol_info->address(), + symbol_info->end_address()); + } + // Stash source. - if (debug_info_flags & DEBUG_INFO_SOURCE_DISASM) { + if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmSource) { DumpSource(symbol_info, &string_buffer_); debug_info->set_source_disasm(string_buffer_.ToString()); string_buffer_.Reset(); @@ -132,7 +162,7 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info, } // Stash raw HIR. - if (debug_info_flags & DEBUG_INFO_RAW_HIR_DISASM) { + if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmRawHir) { builder_->Dump(&string_buffer_); debug_info->set_raw_hir_disasm(string_buffer_.ToString()); string_buffer_.Reset(); @@ -144,7 +174,7 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info, } // Stash optimized HIR. - if (debug_info_flags & DEBUG_INFO_HIR_DISASM) { + if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmHir) { builder_->Dump(&string_buffer_); debug_info->set_hir_disasm(string_buffer_.ToString()); string_buffer_.Reset(); @@ -152,7 +182,7 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info, // Assemble to backend machine code. if (!assembler_->Assemble(symbol_info, builder_.get(), debug_info_flags, - std::move(debug_info), trace_flags, out_function)) { + std::move(debug_info), out_function)) { return false; } diff --git a/src/xenia/cpu/frontend/ppc_translator.h b/src/xenia/cpu/frontend/ppc_translator.h index e851e0b43..f0263c859 100644 --- a/src/xenia/cpu/frontend/ppc_translator.h +++ b/src/xenia/cpu/frontend/ppc_translator.h @@ -31,7 +31,7 @@ class PPCTranslator { ~PPCTranslator(); bool Translate(FunctionInfo* symbol_info, uint32_t debug_info_flags, - uint32_t trace_flags, Function** out_function); + Function** out_function); private: void DumpSource(FunctionInfo* symbol_info, StringBuffer* string_buffer); diff --git a/src/xenia/cpu/processor.cc b/src/xenia/cpu/processor.cc index 5ba95f875..ffb884a7f 100644 --- a/src/xenia/cpu/processor.cc +++ b/src/xenia/cpu/processor.cc @@ -73,7 +73,6 @@ class BuiltinModule : public Module { Processor::Processor(xe::Memory* memory, ExportResolver* export_resolver) : memory_(memory), debug_info_flags_(0), - trace_flags_(0), builtin_module_(nullptr), next_builtin_address_(0xFFFF0000ul), export_resolver_(export_resolver), @@ -99,8 +98,8 @@ Processor::~Processor() { } bool Processor::Setup() { - debug_info_flags_ = DEBUG_INFO_DEFAULT; - trace_flags_ = 0; + // TODO(benvanik): query mode from debugger? + debug_info_flags_ = DebugInfoFlags::kDebugInfoSourceMap; auto frontend = std::make_unique(this); // TODO(benvanik): set options/etc. @@ -110,6 +109,7 @@ bool Processor::Setup() { // Create debugger first. Other types hook up to it. debugger_.reset(new xe::debug::Debugger(this)); + debugger_->StartSession(); std::unique_ptr builtin_module(new BuiltinModule(this)); builtin_module_ = builtin_module.get(); @@ -289,8 +289,7 @@ bool Processor::DemandFunction(FunctionInfo* symbol_info, if (symbol_status == SymbolInfo::STATUS_NEW) { // Symbol is undefined, so define now. Function* function = nullptr; - if (!frontend_->DefineFunction(symbol_info, debug_info_flags_, trace_flags_, - &function)) { + if (!frontend_->DefineFunction(symbol_info, debug_info_flags_, &function)) { symbol_info->set_status(SymbolInfo::STATUS_FAILED); return false; } diff --git a/src/xenia/cpu/processor.h b/src/xenia/cpu/processor.h index f70b0083c..ebb03481f 100644 --- a/src/xenia/cpu/processor.h +++ b/src/xenia/cpu/processor.h @@ -87,7 +87,6 @@ class Processor { Memory* memory_; uint32_t debug_info_flags_; - uint32_t trace_flags_; std::unique_ptr debugger_; diff --git a/src/xenia/cpu/test_module.cc b/src/xenia/cpu/test_module.cc index 4ff8429f3..c1920164f 100644 --- a/src/xenia/cpu/test_module.cc +++ b/src/xenia/cpu/test_module.cc @@ -89,7 +89,7 @@ SymbolInfo::Status TestModule::DeclareFunction(uint32_t address, compiler_->Compile(builder_.get()); Function* fn = nullptr; - assembler_->Assemble(symbol_info, builder_.get(), 0, nullptr, 0, &fn); + assembler_->Assemble(symbol_info, builder_.get(), 0, nullptr, &fn); symbol_info->set_function(fn); status = SymbolInfo::STATUS_DEFINED; diff --git a/src/xenia/debug/debugger.cc b/src/xenia/debug/debugger.cc index 1ee153d94..7752a31b0 100644 --- a/src/xenia/debug/debugger.cc +++ b/src/xenia/debug/debugger.cc @@ -9,11 +9,16 @@ #include "xenia/debug/debugger.h" +#include + #include +#include "xenia/base/string.h" #include "xenia/cpu/function.h" #include "xenia/cpu/processor.h" +DEFINE_string(debug_session_path, "", "Debug output path."); + namespace xe { namespace debug { @@ -28,6 +33,34 @@ Debugger::Debugger(cpu::Processor* processor) : processor_(processor) {} Debugger::~Debugger() = default; +bool Debugger::StartSession() { + std::wstring session_path = xe::to_wstring(FLAGS_debug_session_path); + + std::wstring trace_functions_path = + xe::join_paths(session_path, L"trace.functions"); + trace_functions_ = ChunkedMappedMemoryWriter::Open(trace_functions_path, + 32 * 1024 * 1024, true); + return true; +} + +void Debugger::StopSession() { + FlushSession(); + trace_functions_.reset(); +} + +void Debugger::FlushSession() { + if (trace_functions_) { + trace_functions_->Flush(); + } +} + +uint8_t* Debugger::AllocateTraceFunctionData(size_t size) { + if (!trace_functions_) { + return nullptr; + } + return trace_functions_->Allocate(size); +} + int Debugger::SuspendAllThreads(uint32_t timeout_ms) { std::lock_guard guard(threads_lock_); diff --git a/src/xenia/debug/debugger.h b/src/xenia/debug/debugger.h index 69eddff55..9e82950eb 100644 --- a/src/xenia/debug/debugger.h +++ b/src/xenia/debug/debugger.h @@ -11,11 +11,13 @@ #define XENIA_DEBUG_DEBUGGER_H_ #include +#include #include #include #include #include "xenia/base/delegate.h" +#include "xenia/base/mapped_memory.h" #include "xenia/cpu/thread_state.h" #include "xenia/debug/breakpoint.h" @@ -65,6 +67,12 @@ class Debugger { cpu::Processor* processor() const { return processor_; } + bool StartSession(); + void StopSession(); + void FlushSession(); + + uint8_t* AllocateTraceFunctionData(size_t size); + int SuspendAllThreads(uint32_t timeout_ms = UINT_MAX); int ResumeThread(uint32_t thread_id); int ResumeAllThreads(bool force = false); @@ -92,6 +100,8 @@ class Debugger { private: cpu::Processor* processor_; + std::unique_ptr trace_functions_; + std::mutex threads_lock_; std::unordered_map threads_; diff --git a/src/xenia/debug/sources.gypi b/src/xenia/debug/sources.gypi index 3055770c1..42751bd16 100644 --- a/src/xenia/debug/sources.gypi +++ b/src/xenia/debug/sources.gypi @@ -5,5 +5,6 @@ 'debug_server.h', 'debugger.cc', 'debugger.h', + 'trace_data.h', ], } diff --git a/src/xenia/debug/trace_data.h b/src/xenia/debug/trace_data.h new file mode 100644 index 000000000..60d621d44 --- /dev/null +++ b/src/xenia/debug/trace_data.h @@ -0,0 +1,92 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2015 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_DEBUG_TRACE_DATA_H_ +#define XENIA_DEBUG_TRACE_DATA_H_ + +#include + +#include "xenia/base/memory.h" + +namespace xe { +namespace debug { + +class FunctionTraceData { + public: + static const int kFunctionCallerHistoryCount = 4; + + struct Header { + // Format is used by tooling, changes must be made across all targets. + // + 0 4b (data size) + // + 4 4b start_address + // + 8 4b end_address + // +12 4b type (user, external, etc) + // +16 8b function_thread_use // bitmask of thread id + // +24 8b function_call_count + // +32 4b+ function_caller_history[4] + // +48 8b+ instruction_execute_count[instruction count] + uint32_t data_size; + uint32_t start_address; + uint32_t end_address; + uint32_t type; + uint64_t function_thread_use; + uint64_t function_call_count; + uint32_t function_caller_history[kFunctionCallerHistoryCount]; + // uint64_t instruction_execute_count[]; + }; + + FunctionTraceData() : header_(nullptr) {} + + void Reset(uint8_t* trace_data, size_t trace_data_size, + uint32_t start_address, uint32_t end_address) { + header_ = reinterpret_cast(trace_data); + header_->data_size = uint32_t(trace_data_size); + header_->start_address = start_address; + header_->end_address = end_address; + header_->type = 0; + header_->function_thread_use = 0; + header_->function_call_count = 0; + for (int i = 0; i < kFunctionCallerHistoryCount; ++i) { + header_->function_caller_history[i] = 0; + } + // Clear any remaining. + std::memset(trace_data + sizeof(Header), 0, + trace_data_size - sizeof(Header)); + } + + bool is_valid() const { return header_ != nullptr; } + + uint32_t start_address() const { return header_->start_address; } + uint32_t end_address() const { return header_->end_address; } + uint32_t instruction_count() const { + return (header_->end_address - header_->start_address) / 4 + 1; + } + + Header* header() const { return header_; } + + uint8_t* instruction_execute_counts() const { + return reinterpret_cast(header_) + sizeof(Header); + } + + static size_t SizeOfHeader() { return sizeof(Header); } + + static size_t SizeOfInstructionCounts(uint32_t start_address, + uint32_t end_address) { + uint32_t instruction_count = (end_address - start_address) / 4 + 1; + return instruction_count * 8; + } + + private: + Header* header_; +}; + +} // namespace debug +} // namespace xe + +#endif // XENIA_DEBUG_TRACE_DATA_H_