--trace_functions and --trace_function_coverage
This commit is contained in:
parent
ade5388728
commit
94c62b91d0
|
@ -48,18 +48,23 @@ class ChunkedMappedMemoryWriter {
|
||||||
virtual ~ChunkedMappedMemoryWriter() = default;
|
virtual ~ChunkedMappedMemoryWriter() = default;
|
||||||
|
|
||||||
static std::unique_ptr<ChunkedMappedMemoryWriter> Open(
|
static 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 = false);
|
||||||
|
|
||||||
virtual uint8_t* Allocate(size_t length) = 0;
|
virtual uint8_t* Allocate(size_t length) = 0;
|
||||||
virtual void Flush() = 0;
|
virtual void Flush() = 0;
|
||||||
virtual void FlushNew() = 0;
|
virtual void FlushNew() = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ChunkedMappedMemoryWriter(const std::wstring& path, size_t chunk_size)
|
ChunkedMappedMemoryWriter(const std::wstring& path, size_t chunk_size,
|
||||||
: path_(path), chunk_size_(chunk_size) {}
|
bool low_address_space)
|
||||||
|
: path_(path),
|
||||||
|
chunk_size_(chunk_size),
|
||||||
|
low_address_space_(low_address_space) {}
|
||||||
|
|
||||||
std::wstring path_;
|
std::wstring path_;
|
||||||
size_t chunk_size_;
|
size_t chunk_size_;
|
||||||
|
bool low_address_space_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -38,9 +39,7 @@ class Win32MappedMemory : public MappedMemory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flush() override {
|
void Flush() override { FlushViewOfFile(data(), size()); }
|
||||||
FlushViewOfFile(data(), size());
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLE file_handle;
|
HANDLE file_handle;
|
||||||
HANDLE mapping_handle;
|
HANDLE mapping_handle;
|
||||||
|
@ -114,8 +113,9 @@ std::unique_ptr<MappedMemory> MappedMemory::Open(const std::wstring& path,
|
||||||
|
|
||||||
class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
||||||
public:
|
public:
|
||||||
Win32ChunkedMappedMemoryWriter(const std::wstring& path, size_t chunk_size)
|
Win32ChunkedMappedMemoryWriter(const std::wstring& path, size_t chunk_size,
|
||||||
: ChunkedMappedMemoryWriter(path, chunk_size) {}
|
bool low_address_space)
|
||||||
|
: ChunkedMappedMemoryWriter(path, chunk_size, low_address_space) {}
|
||||||
|
|
||||||
~Win32ChunkedMappedMemoryWriter() override {
|
~Win32ChunkedMappedMemoryWriter() override {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
@ -132,7 +132,7 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
||||||
}
|
}
|
||||||
auto chunk = std::make_unique<Chunk>(chunk_size_);
|
auto chunk = std::make_unique<Chunk>(chunk_size_);
|
||||||
auto chunk_path = path_ + L"." + std::to_wstring(chunks_.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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
uint8_t* result = chunk->Allocate(length);
|
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_access = GENERIC_READ | GENERIC_WRITE;
|
||||||
DWORD file_share = 0;
|
DWORD file_share = 0;
|
||||||
DWORD create_mode = OPEN_EXISTING;
|
DWORD create_mode = CREATE_ALWAYS;
|
||||||
DWORD mapping_protect = PAGE_READWRITE;
|
DWORD mapping_protect = PAGE_READWRITE;
|
||||||
DWORD view_access = FILE_MAP_READ | FILE_MAP_WRITE;
|
DWORD view_access = FILE_MAP_READ | FILE_MAP_WRITE;
|
||||||
|
|
||||||
|
@ -197,8 +197,28 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
data_ = reinterpret_cast<uint8_t*>(
|
// If specified, ensure the allocation occurs in the lower 32-bit address
|
||||||
MapViewOfFile(mapping_handle_, view_access, 0, 0, capacity_));
|
// space.
|
||||||
|
if (low_address_space) {
|
||||||
|
bool successful = false;
|
||||||
|
data_ = reinterpret_cast<uint8_t*>(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<uint8_t*>(
|
||||||
|
MapViewOfFile(mapping_handle_, view_access, 0, 0, capacity_));
|
||||||
|
}
|
||||||
if (!data_) {
|
if (!data_) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -215,9 +235,7 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Flush() {
|
void Flush() { FlushViewOfFile(data_, offset_); }
|
||||||
FlushViewOfFile(data_, offset_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlushNew() {
|
void FlushNew() {
|
||||||
FlushViewOfFile(data_ + last_flush_offset_, offset_ - last_flush_offset_);
|
FlushViewOfFile(data_ + last_flush_offset_, offset_ - last_flush_offset_);
|
||||||
|
@ -238,13 +256,13 @@ class Win32ChunkedMappedMemoryWriter : public ChunkedMappedMemoryWriter {
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<ChunkedMappedMemoryWriter> ChunkedMappedMemoryWriter::Open(
|
std::unique_ptr<ChunkedMappedMemoryWriter> 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;
|
SYSTEM_INFO system_info;
|
||||||
GetSystemInfo(&system_info);
|
GetSystemInfo(&system_info);
|
||||||
size_t aligned_chunk_size =
|
size_t aligned_chunk_size =
|
||||||
xe::round_up(chunk_size, system_info.dwAllocationGranularity);
|
xe::round_up(chunk_size, system_info.dwAllocationGranularity);
|
||||||
return std::make_unique<Win32ChunkedMappedMemoryWriter>(path,
|
return std::make_unique<Win32ChunkedMappedMemoryWriter>(
|
||||||
aligned_chunk_size);
|
path, aligned_chunk_size, low_address_space);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -29,6 +29,10 @@ size_t hash_combine(size_t seed, const T& v, const Ts&... vs) {
|
||||||
|
|
||||||
size_t page_size();
|
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,
|
void copy_and_swap_16_aligned(uint16_t* dest, const uint16_t* src,
|
||||||
size_t count);
|
size_t count);
|
||||||
void copy_and_swap_16_unaligned(uint16_t* dest, const uint16_t* src,
|
void copy_and_swap_16_unaligned(uint16_t* dest, const uint16_t* src,
|
||||||
|
|
|
@ -41,7 +41,7 @@ class Assembler {
|
||||||
virtual bool Assemble(FunctionInfo* symbol_info, hir::HIRBuilder* builder,
|
virtual bool Assemble(FunctionInfo* symbol_info, hir::HIRBuilder* builder,
|
||||||
uint32_t debug_info_flags,
|
uint32_t debug_info_flags,
|
||||||
std::unique_ptr<DebugInfo> debug_info,
|
std::unique_ptr<DebugInfo> debug_info,
|
||||||
uint32_t trace_flags, Function** out_function) = 0;
|
Function** out_function) = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Backend* backend_;
|
Backend* backend_;
|
||||||
|
|
|
@ -60,22 +60,22 @@ void X64Assembler::Reset() {
|
||||||
bool X64Assembler::Assemble(FunctionInfo* symbol_info, HIRBuilder* builder,
|
bool X64Assembler::Assemble(FunctionInfo* symbol_info, HIRBuilder* builder,
|
||||||
uint32_t debug_info_flags,
|
uint32_t debug_info_flags,
|
||||||
std::unique_ptr<DebugInfo> debug_info,
|
std::unique_ptr<DebugInfo> debug_info,
|
||||||
uint32_t trace_flags, Function** out_function) {
|
Function** out_function) {
|
||||||
SCOPE_profile_cpu_f("cpu");
|
SCOPE_profile_cpu_f("cpu");
|
||||||
|
|
||||||
// Reset when we leave.
|
// Reset when we leave.
|
||||||
xe::make_reset_scope(this);
|
xe::make_reset_scope(this);
|
||||||
|
|
||||||
// Lower HIR -> x64.
|
// Lower HIR -> x64.
|
||||||
void* machine_code = 0;
|
void* machine_code = nullptr;
|
||||||
size_t code_size = 0;
|
size_t code_size = 0;
|
||||||
if (!emitter_->Emit(builder, debug_info_flags, debug_info.get(), trace_flags,
|
if (!emitter_->Emit(builder, debug_info_flags, debug_info.get(), machine_code,
|
||||||
machine_code, code_size)) {
|
code_size)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stash generated machine code.
|
// 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_);
|
DumpMachineCode(debug_info.get(), machine_code, code_size, &string_buffer_);
|
||||||
debug_info->set_machine_code_disasm(string_buffer_.ToString());
|
debug_info->set_machine_code_disasm(string_buffer_.ToString());
|
||||||
string_buffer_.Reset();
|
string_buffer_.Reset();
|
||||||
|
|
|
@ -35,7 +35,7 @@ class X64Assembler : public Assembler {
|
||||||
|
|
||||||
bool Assemble(FunctionInfo* symbol_info, hir::HIRBuilder* builder,
|
bool Assemble(FunctionInfo* symbol_info, hir::HIRBuilder* builder,
|
||||||
uint32_t debug_info_flags,
|
uint32_t debug_info_flags,
|
||||||
std::unique_ptr<DebugInfo> debug_info, uint32_t trace_flags,
|
std::unique_ptr<DebugInfo> debug_info,
|
||||||
Function** out_function) override;
|
Function** out_function) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "xenia/base/atomic.h"
|
#include "xenia/base/atomic.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
|
#include "xenia/base/memory.h"
|
||||||
#include "xenia/base/vec128.h"
|
#include "xenia/base/vec128.h"
|
||||||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||||
#include "xenia/cpu/backend/x64/x64_code_cache.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()),
|
code_cache_(backend->code_cache()),
|
||||||
allocator_(allocator),
|
allocator_(allocator),
|
||||||
current_instr_(0),
|
current_instr_(0),
|
||||||
|
debug_info_(nullptr),
|
||||||
|
debug_info_flags_(0),
|
||||||
source_map_count_(0),
|
source_map_count_(0),
|
||||||
stack_size_(0),
|
stack_size_(0) {}
|
||||||
trace_flags_(0),
|
|
||||||
cpu_() {}
|
|
||||||
|
|
||||||
X64Emitter::~X64Emitter() = default;
|
X64Emitter::~X64Emitter() = default;
|
||||||
|
|
||||||
bool X64Emitter::Emit(HIRBuilder* builder, uint32_t debug_info_flags,
|
bool X64Emitter::Emit(HIRBuilder* builder, uint32_t debug_info_flags,
|
||||||
DebugInfo* debug_info, uint32_t trace_flags,
|
DebugInfo* debug_info, void*& out_code_address,
|
||||||
void*& out_code_address, size_t& out_code_size) {
|
size_t& out_code_size) {
|
||||||
SCOPE_profile_cpu_f("cpu");
|
SCOPE_profile_cpu_f("cpu");
|
||||||
|
|
||||||
// Reset.
|
// 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_count_ = 0;
|
||||||
source_map_arena_.Reset();
|
source_map_arena_.Reset();
|
||||||
}
|
}
|
||||||
trace_flags_ = trace_flags;
|
|
||||||
|
|
||||||
// Fill the generator with code.
|
// Fill the generator with code.
|
||||||
size_t stack_size = 0;
|
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);
|
out_code_address = Emplace(stack_size);
|
||||||
|
|
||||||
// Stash source map.
|
// Stash source map.
|
||||||
if (debug_info_flags & DEBUG_INFO_SOURCE_MAP) {
|
if (debug_info_flags_ & DebugInfoFlags::kDebugInfoSourceMap) {
|
||||||
debug_info->InitializeSourceMap(
|
debug_info->InitializeSourceMap(
|
||||||
source_map_count_, (SourceMapEntry*)source_map_arena_.CloneContents());
|
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
|
// IMPORTANT: any changes to the prolog must be kept in sync with
|
||||||
// X64CodeCache, which dynamically generates exception information.
|
// X64CodeCache, which dynamically generates exception information.
|
||||||
// Adding or changing anything here must be matched!
|
// Adding or changing anything here must be matched!
|
||||||
const bool emit_prolog = true;
|
|
||||||
const size_t stack_size = StackLayout::GUEST_STACK_SIZE + stack_offset;
|
const size_t stack_size = StackLayout::GUEST_STACK_SIZE + stack_offset;
|
||||||
assert_true((stack_size + 8) % 16 == 0);
|
assert_true((stack_size + 8) % 16 == 0);
|
||||||
out_stack_size = stack_size;
|
out_stack_size = stack_size;
|
||||||
stack_size_ = stack_size;
|
stack_size_ = stack_size;
|
||||||
if (emit_prolog) {
|
sub(rsp, (uint32_t)stack_size);
|
||||||
sub(rsp, (uint32_t)stack_size);
|
mov(qword[rsp + StackLayout::GUEST_RCX_HOME], rcx);
|
||||||
mov(qword[rsp + StackLayout::GUEST_RCX_HOME], rcx);
|
mov(qword[rsp + StackLayout::GUEST_RET_ADDR], rdx);
|
||||||
mov(qword[rsp + StackLayout::GUEST_RET_ADDR], rdx);
|
mov(qword[rsp + StackLayout::GUEST_CALL_RET_ADDR], 0);
|
||||||
mov(qword[rsp + StackLayout::GUEST_CALL_RET_ADDR], 0);
|
|
||||||
mov(rdx, qword[rcx + 8]); // membase
|
// 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.
|
// Body.
|
||||||
auto block = builder->first_block();
|
auto block = builder->first_block();
|
||||||
while (block) {
|
while (block) {
|
||||||
|
@ -187,10 +216,8 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t& out_stack_size) {
|
||||||
// Function epilog.
|
// Function epilog.
|
||||||
L("epilog");
|
L("epilog");
|
||||||
EmitTraceUserCallReturn();
|
EmitTraceUserCallReturn();
|
||||||
if (emit_prolog) {
|
mov(rcx, qword[rsp + StackLayout::GUEST_RCX_HOME]);
|
||||||
mov(rcx, qword[rsp + StackLayout::GUEST_RCX_HOME]);
|
add(rsp, (uint32_t)stack_size);
|
||||||
add(rsp, (uint32_t)stack_size);
|
|
||||||
}
|
|
||||||
ret();
|
ret();
|
||||||
|
|
||||||
#if XE_DEBUG
|
#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->hir_offset = uint32_t(i->block->ordinal << 16) | i->ordinal;
|
||||||
entry->code_offset = static_cast<uint32_t>(getSize());
|
entry->code_offset = static_cast<uint32_t>(getSize());
|
||||||
source_map_count_++;
|
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() {
|
void X64Emitter::EmitGetCurrentThreadId() {
|
||||||
|
@ -234,6 +278,7 @@ uint64_t TrapDebugPrint(void* raw_context, uint64_t address) {
|
||||||
XELOGD("(DebugPrint) %s", str);
|
XELOGD("(DebugPrint) %s", str);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void X64Emitter::Trap(uint16_t trap_type) {
|
void X64Emitter::Trap(uint16_t trap_type) {
|
||||||
switch (trap_type) {
|
switch (trap_type) {
|
||||||
case 20:
|
case 20:
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
#include "xenia/base/arena.h"
|
#include "xenia/base/arena.h"
|
||||||
#include "xenia/cpu/hir/value.h"
|
#include "xenia/cpu/hir/value.h"
|
||||||
|
#include "xenia/debug/trace_data.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
|
@ -106,8 +107,8 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||||
const Xbyak::util::Cpu* cpu() const { return &cpu_; }
|
const Xbyak::util::Cpu* cpu() const { return &cpu_; }
|
||||||
|
|
||||||
bool Emit(hir::HIRBuilder* builder, uint32_t debug_info_flags,
|
bool Emit(hir::HIRBuilder* builder, uint32_t debug_info_flags,
|
||||||
DebugInfo* debug_info, uint32_t trace_flags,
|
DebugInfo* debug_info, void*& out_code_address,
|
||||||
void*& out_code_address, size_t& out_code_size);
|
size_t& out_code_size);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Reserved: rsp
|
// Reserved: rsp
|
||||||
|
@ -176,6 +177,8 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||||
void LoadConstantXmm(Xbyak::Xmm dest, const vec128_t& v);
|
void LoadConstantXmm(Xbyak::Xmm dest, const vec128_t& v);
|
||||||
Xbyak::Address StashXmm(int index, const Xbyak::Xmm& r);
|
Xbyak::Address StashXmm(int index, const Xbyak::Xmm& r);
|
||||||
|
|
||||||
|
DebugInfo* debug_info() const { return debug_info_; }
|
||||||
|
|
||||||
size_t stack_size() const { return stack_size_; }
|
size_t stack_size() const { return stack_size_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -193,13 +196,13 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||||
|
|
||||||
hir::Instr* current_instr_;
|
hir::Instr* current_instr_;
|
||||||
|
|
||||||
|
DebugInfo* debug_info_;
|
||||||
|
uint32_t debug_info_flags_;
|
||||||
size_t source_map_count_;
|
size_t source_map_count_;
|
||||||
Arena source_map_arena_;
|
Arena source_map_arena_;
|
||||||
|
|
||||||
size_t stack_size_;
|
size_t stack_size_;
|
||||||
|
|
||||||
uint32_t trace_flags_;
|
|
||||||
|
|
||||||
static const uint32_t gpr_reg_map_[GPR_COUNT];
|
static const uint32_t gpr_reg_map_[GPR_COUNT];
|
||||||
static const uint32_t xmm_reg_map_[XMM_COUNT];
|
static const uint32_t xmm_reg_map_[XMM_COUNT];
|
||||||
};
|
};
|
||||||
|
|
|
@ -95,13 +95,6 @@ EMITTER_OPCODE_TABLE(
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
EMITTER(SOURCE_OFFSET, MATCH(I<OPCODE_SOURCE_OFFSET, VoidOp, OffsetOp>)) {
|
EMITTER(SOURCE_OFFSET, MATCH(I<OPCODE_SOURCE_OFFSET, VoidOp, OffsetOp>)) {
|
||||||
static void Emit(X64Emitter& e, const EmitArgType& 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);
|
e.MarkSourceOffset(i.instr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,6 +22,11 @@ DECLARE_bool(dump_module_map);
|
||||||
DECLARE_bool(debug);
|
DECLARE_bool(debug);
|
||||||
DECLARE_bool(always_disasm);
|
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_bool(validate_hir);
|
||||||
|
|
||||||
DECLARE_uint64(break_on_instruction);
|
DECLARE_uint64(break_on_instruction);
|
||||||
|
|
|
@ -36,6 +36,15 @@ DEFINE_bool(
|
||||||
always_disasm, false,
|
always_disasm, false,
|
||||||
"Always add debug info to functions, even when no debugger is attached.");
|
"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,
|
DEFINE_bool(validate_hir, false,
|
||||||
"Perform validation checks on the HIR during compilation.");
|
"Perform validation checks on the HIR during compilation.");
|
||||||
|
|
||||||
|
|
|
@ -13,18 +13,25 @@
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "xenia/debug/trace_data.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace cpu {
|
namespace cpu {
|
||||||
|
|
||||||
enum DebugInfoFlags {
|
enum DebugInfoFlags {
|
||||||
DEBUG_INFO_NONE = 0,
|
kDebugInfoNone = 0,
|
||||||
DEBUG_INFO_SOURCE_DISASM = (1 << 1),
|
kDebugInfoDisasmSource = (1 << 1),
|
||||||
DEBUG_INFO_RAW_HIR_DISASM = (1 << 2),
|
kDebugInfoDisasmRawHir = (1 << 2),
|
||||||
DEBUG_INFO_HIR_DISASM = (1 << 3),
|
kDebugInfoDisasmHir = (1 << 3),
|
||||||
DEBUG_INFO_MACHINE_CODE_DISASM = (1 << 4),
|
kDebugInfoDisasmMachineCode = (1 << 4),
|
||||||
DEBUG_INFO_SOURCE_MAP = (1 << 5),
|
kDebugInfoAllDisasm = kDebugInfoDisasmSource | kDebugInfoDisasmRawHir |
|
||||||
DEBUG_INFO_DEFAULT = DEBUG_INFO_SOURCE_MAP,
|
kDebugInfoDisasmHir | kDebugInfoDisasmMachineCode,
|
||||||
DEBUG_INFO_ALL_DISASM = 0xFFFF,
|
kDebugInfoSourceMap = (1 << 5),
|
||||||
|
kDebugInfoTraceFunctions = (1 << 6),
|
||||||
|
kDebugInfoTraceFunctionCoverage = (1 << 7) | kDebugInfoTraceFunctions,
|
||||||
|
kDebugInfoTraceFunctionReferences = (1 << 8) | kDebugInfoTraceFunctions,
|
||||||
|
kDebugInfoTraceFunctionData = (1 << 9) | kDebugInfoTraceFunctions,
|
||||||
|
kDebugInfoAll = 0xFFFFFFFF,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct SourceMapEntry_s {
|
typedef struct SourceMapEntry_s {
|
||||||
|
@ -38,6 +45,19 @@ class DebugInfo {
|
||||||
DebugInfo();
|
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_; }
|
const char* source_disasm() const { return source_disasm_; }
|
||||||
void set_source_disasm(char* value) { source_disasm_ = value; }
|
void set_source_disasm(char* value) { source_disasm_ = value; }
|
||||||
const char* raw_hir_disasm() const { return raw_hir_disasm_; }
|
const char* raw_hir_disasm() const { return raw_hir_disasm_; }
|
||||||
|
@ -53,6 +73,11 @@ class DebugInfo {
|
||||||
SourceMapEntry* LookupCodeOffset(uint32_t offset);
|
SourceMapEntry* LookupCodeOffset(uint32_t offset);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
uint32_t address_reference_count_;
|
||||||
|
uint32_t instruction_result_count_;
|
||||||
|
|
||||||
|
debug::FunctionTraceData trace_data_;
|
||||||
|
|
||||||
char* source_disasm_;
|
char* source_disasm_;
|
||||||
char* raw_hir_disasm_;
|
char* raw_hir_disasm_;
|
||||||
char* hir_disasm_;
|
char* hir_disasm_;
|
||||||
|
|
|
@ -97,11 +97,10 @@ bool PPCFrontend::DeclareFunction(FunctionInfo* symbol_info) {
|
||||||
|
|
||||||
bool PPCFrontend::DefineFunction(FunctionInfo* symbol_info,
|
bool PPCFrontend::DefineFunction(FunctionInfo* symbol_info,
|
||||||
uint32_t debug_info_flags,
|
uint32_t debug_info_flags,
|
||||||
uint32_t trace_flags,
|
|
||||||
Function** out_function) {
|
Function** out_function) {
|
||||||
PPCTranslator* translator = translator_pool_.Allocate(this);
|
PPCTranslator* translator = translator_pool_.Allocate(this);
|
||||||
bool result = translator->Translate(symbol_info, debug_info_flags,
|
bool result =
|
||||||
trace_flags, out_function);
|
translator->Translate(symbol_info, debug_info_flags, out_function);
|
||||||
translator_pool_.Release(translator);
|
translator_pool_.Release(translator);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ class PPCFrontend {
|
||||||
|
|
||||||
bool DeclareFunction(FunctionInfo* symbol_info);
|
bool DeclareFunction(FunctionInfo* symbol_info);
|
||||||
bool DefineFunction(FunctionInfo* symbol_info, uint32_t debug_info_flags,
|
bool DefineFunction(FunctionInfo* symbol_info, uint32_t debug_info_flags,
|
||||||
uint32_t trace_flags, Function** out_function);
|
Function** out_function);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Processor* processor_;
|
Processor* processor_;
|
||||||
|
|
|
@ -53,6 +53,10 @@ bool PPCScanner::Scan(FunctionInfo* symbol_info, DebugInfo* debug_info) {
|
||||||
|
|
||||||
LOGPPC("Analyzing function %.8X...", symbol_info->address());
|
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<uint32_t>(symbol_info->address());
|
uint32_t start_address = static_cast<uint32_t>(symbol_info->address());
|
||||||
uint32_t end_address = static_cast<uint32_t>(symbol_info->end_address());
|
uint32_t end_address = static_cast<uint32_t>(symbol_info->end_address());
|
||||||
uint32_t address = start_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.
|
// This lookup is *expensive* and should be avoided when scanning.
|
||||||
i.type = GetInstrType(i.code);
|
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
|
// 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.
|
// of whether or not this is a normal function with a prolog/epilog.
|
||||||
// Some valid leaf functions won't have this, but most will.
|
// 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
|
// - if present, flag function as needing a stack
|
||||||
// - record prolog/epilog lengths/stack size/etc
|
// - 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);
|
LOGPPC("Finished analyzing %.8X", start_address);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "xenia/cpu/frontend/ppc_instr.h"
|
#include "xenia/cpu/frontend/ppc_instr.h"
|
||||||
#include "xenia/cpu/frontend/ppc_scanner.h"
|
#include "xenia/cpu/frontend/ppc_scanner.h"
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/cpu/processor.h"
|
||||||
|
#include "xenia/debug/debugger.h"
|
||||||
#include "xenia/profiling.h"
|
#include "xenia/profiling.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -87,7 +88,7 @@ PPCTranslator::PPCTranslator(PPCFrontend* frontend) : frontend_(frontend) {
|
||||||
PPCTranslator::~PPCTranslator() = default;
|
PPCTranslator::~PPCTranslator() = default;
|
||||||
|
|
||||||
bool PPCTranslator::Translate(FunctionInfo* symbol_info,
|
bool PPCTranslator::Translate(FunctionInfo* symbol_info,
|
||||||
uint32_t debug_info_flags, uint32_t trace_flags,
|
uint32_t debug_info_flags,
|
||||||
Function** out_function) {
|
Function** out_function) {
|
||||||
SCOPE_profile_cpu_f("cpu");
|
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.
|
// NOTE: we only want to do this when required, as it's expensive to build.
|
||||||
if (FLAGS_always_disasm) {
|
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<DebugInfo> debug_info;
|
std::unique_ptr<DebugInfo> debug_info;
|
||||||
if (debug_info_flags) {
|
if (debug_info_flags) {
|
||||||
|
@ -111,8 +124,25 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info,
|
||||||
return false;
|
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.
|
// Stash source.
|
||||||
if (debug_info_flags & DEBUG_INFO_SOURCE_DISASM) {
|
if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmSource) {
|
||||||
DumpSource(symbol_info, &string_buffer_);
|
DumpSource(symbol_info, &string_buffer_);
|
||||||
debug_info->set_source_disasm(string_buffer_.ToString());
|
debug_info->set_source_disasm(string_buffer_.ToString());
|
||||||
string_buffer_.Reset();
|
string_buffer_.Reset();
|
||||||
|
@ -132,7 +162,7 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stash raw HIR.
|
// Stash raw HIR.
|
||||||
if (debug_info_flags & DEBUG_INFO_RAW_HIR_DISASM) {
|
if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmRawHir) {
|
||||||
builder_->Dump(&string_buffer_);
|
builder_->Dump(&string_buffer_);
|
||||||
debug_info->set_raw_hir_disasm(string_buffer_.ToString());
|
debug_info->set_raw_hir_disasm(string_buffer_.ToString());
|
||||||
string_buffer_.Reset();
|
string_buffer_.Reset();
|
||||||
|
@ -144,7 +174,7 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stash optimized HIR.
|
// Stash optimized HIR.
|
||||||
if (debug_info_flags & DEBUG_INFO_HIR_DISASM) {
|
if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmHir) {
|
||||||
builder_->Dump(&string_buffer_);
|
builder_->Dump(&string_buffer_);
|
||||||
debug_info->set_hir_disasm(string_buffer_.ToString());
|
debug_info->set_hir_disasm(string_buffer_.ToString());
|
||||||
string_buffer_.Reset();
|
string_buffer_.Reset();
|
||||||
|
@ -152,7 +182,7 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info,
|
||||||
|
|
||||||
// Assemble to backend machine code.
|
// Assemble to backend machine code.
|
||||||
if (!assembler_->Assemble(symbol_info, builder_.get(), debug_info_flags,
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ class PPCTranslator {
|
||||||
~PPCTranslator();
|
~PPCTranslator();
|
||||||
|
|
||||||
bool Translate(FunctionInfo* symbol_info, uint32_t debug_info_flags,
|
bool Translate(FunctionInfo* symbol_info, uint32_t debug_info_flags,
|
||||||
uint32_t trace_flags, Function** out_function);
|
Function** out_function);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void DumpSource(FunctionInfo* symbol_info, StringBuffer* string_buffer);
|
void DumpSource(FunctionInfo* symbol_info, StringBuffer* string_buffer);
|
||||||
|
|
|
@ -73,7 +73,6 @@ class BuiltinModule : public Module {
|
||||||
Processor::Processor(xe::Memory* memory, ExportResolver* export_resolver)
|
Processor::Processor(xe::Memory* memory, ExportResolver* export_resolver)
|
||||||
: memory_(memory),
|
: memory_(memory),
|
||||||
debug_info_flags_(0),
|
debug_info_flags_(0),
|
||||||
trace_flags_(0),
|
|
||||||
builtin_module_(nullptr),
|
builtin_module_(nullptr),
|
||||||
next_builtin_address_(0xFFFF0000ul),
|
next_builtin_address_(0xFFFF0000ul),
|
||||||
export_resolver_(export_resolver),
|
export_resolver_(export_resolver),
|
||||||
|
@ -99,8 +98,8 @@ Processor::~Processor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Processor::Setup() {
|
bool Processor::Setup() {
|
||||||
debug_info_flags_ = DEBUG_INFO_DEFAULT;
|
// TODO(benvanik): query mode from debugger?
|
||||||
trace_flags_ = 0;
|
debug_info_flags_ = DebugInfoFlags::kDebugInfoSourceMap;
|
||||||
|
|
||||||
auto frontend = std::make_unique<xe::cpu::frontend::PPCFrontend>(this);
|
auto frontend = std::make_unique<xe::cpu::frontend::PPCFrontend>(this);
|
||||||
// TODO(benvanik): set options/etc.
|
// TODO(benvanik): set options/etc.
|
||||||
|
@ -110,6 +109,7 @@ bool Processor::Setup() {
|
||||||
|
|
||||||
// Create debugger first. Other types hook up to it.
|
// Create debugger first. Other types hook up to it.
|
||||||
debugger_.reset(new xe::debug::Debugger(this));
|
debugger_.reset(new xe::debug::Debugger(this));
|
||||||
|
debugger_->StartSession();
|
||||||
|
|
||||||
std::unique_ptr<Module> builtin_module(new BuiltinModule(this));
|
std::unique_ptr<Module> builtin_module(new BuiltinModule(this));
|
||||||
builtin_module_ = builtin_module.get();
|
builtin_module_ = builtin_module.get();
|
||||||
|
@ -289,8 +289,7 @@ bool Processor::DemandFunction(FunctionInfo* symbol_info,
|
||||||
if (symbol_status == SymbolInfo::STATUS_NEW) {
|
if (symbol_status == SymbolInfo::STATUS_NEW) {
|
||||||
// Symbol is undefined, so define now.
|
// Symbol is undefined, so define now.
|
||||||
Function* function = nullptr;
|
Function* function = nullptr;
|
||||||
if (!frontend_->DefineFunction(symbol_info, debug_info_flags_, trace_flags_,
|
if (!frontend_->DefineFunction(symbol_info, debug_info_flags_, &function)) {
|
||||||
&function)) {
|
|
||||||
symbol_info->set_status(SymbolInfo::STATUS_FAILED);
|
symbol_info->set_status(SymbolInfo::STATUS_FAILED);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,6 @@ class Processor {
|
||||||
Memory* memory_;
|
Memory* memory_;
|
||||||
|
|
||||||
uint32_t debug_info_flags_;
|
uint32_t debug_info_flags_;
|
||||||
uint32_t trace_flags_;
|
|
||||||
|
|
||||||
std::unique_ptr<debug::Debugger> debugger_;
|
std::unique_ptr<debug::Debugger> debugger_;
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ SymbolInfo::Status TestModule::DeclareFunction(uint32_t address,
|
||||||
compiler_->Compile(builder_.get());
|
compiler_->Compile(builder_.get());
|
||||||
|
|
||||||
Function* fn = nullptr;
|
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);
|
symbol_info->set_function(fn);
|
||||||
status = SymbolInfo::STATUS_DEFINED;
|
status = SymbolInfo::STATUS_DEFINED;
|
||||||
|
|
|
@ -9,11 +9,16 @@
|
||||||
|
|
||||||
#include "xenia/debug/debugger.h"
|
#include "xenia/debug/debugger.h"
|
||||||
|
|
||||||
|
#include <gflags/gflags.h>
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "xenia/base/string.h"
|
||||||
#include "xenia/cpu/function.h"
|
#include "xenia/cpu/function.h"
|
||||||
#include "xenia/cpu/processor.h"
|
#include "xenia/cpu/processor.h"
|
||||||
|
|
||||||
|
DEFINE_string(debug_session_path, "", "Debug output path.");
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace debug {
|
namespace debug {
|
||||||
|
|
||||||
|
@ -28,6 +33,34 @@ Debugger::Debugger(cpu::Processor* processor) : processor_(processor) {}
|
||||||
|
|
||||||
Debugger::~Debugger() = default;
|
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) {
|
int Debugger::SuspendAllThreads(uint32_t timeout_ms) {
|
||||||
std::lock_guard<std::mutex> guard(threads_lock_);
|
std::lock_guard<std::mutex> guard(threads_lock_);
|
||||||
|
|
||||||
|
|
|
@ -11,11 +11,13 @@
|
||||||
#define XENIA_DEBUG_DEBUGGER_H_
|
#define XENIA_DEBUG_DEBUGGER_H_
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "xenia/base/delegate.h"
|
#include "xenia/base/delegate.h"
|
||||||
|
#include "xenia/base/mapped_memory.h"
|
||||||
#include "xenia/cpu/thread_state.h"
|
#include "xenia/cpu/thread_state.h"
|
||||||
#include "xenia/debug/breakpoint.h"
|
#include "xenia/debug/breakpoint.h"
|
||||||
|
|
||||||
|
@ -65,6 +67,12 @@ class Debugger {
|
||||||
|
|
||||||
cpu::Processor* processor() const { return processor_; }
|
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 SuspendAllThreads(uint32_t timeout_ms = UINT_MAX);
|
||||||
int ResumeThread(uint32_t thread_id);
|
int ResumeThread(uint32_t thread_id);
|
||||||
int ResumeAllThreads(bool force = false);
|
int ResumeAllThreads(bool force = false);
|
||||||
|
@ -92,6 +100,8 @@ class Debugger {
|
||||||
private:
|
private:
|
||||||
cpu::Processor* processor_;
|
cpu::Processor* processor_;
|
||||||
|
|
||||||
|
std::unique_ptr<ChunkedMappedMemoryWriter> trace_functions_;
|
||||||
|
|
||||||
std::mutex threads_lock_;
|
std::mutex threads_lock_;
|
||||||
std::unordered_map<uint32_t, cpu::ThreadState*> threads_;
|
std::unordered_map<uint32_t, cpu::ThreadState*> threads_;
|
||||||
|
|
||||||
|
|
|
@ -5,5 +5,6 @@
|
||||||
'debug_server.h',
|
'debug_server.h',
|
||||||
'debugger.cc',
|
'debugger.cc',
|
||||||
'debugger.h',
|
'debugger.h',
|
||||||
|
'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 <cstdint>
|
||||||
|
|
||||||
|
#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<Header*>(trace_data);
|
||||||
|
header_->data_size = uint32_t(trace_data_size);
|
||||||
|
header_->start_address = start_address;
|
||||||
|
header_->end_address = end_address;
|
||||||
|
header_->type = 0;
|
||||||
|
header_->function_thread_use = 0;
|
||||||
|
header_->function_call_count = 0;
|
||||||
|
for (int i = 0; i < kFunctionCallerHistoryCount; ++i) {
|
||||||
|
header_->function_caller_history[i] = 0;
|
||||||
|
}
|
||||||
|
// Clear any remaining.
|
||||||
|
std::memset(trace_data + sizeof(Header), 0,
|
||||||
|
trace_data_size - sizeof(Header));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_valid() const { return header_ != nullptr; }
|
||||||
|
|
||||||
|
uint32_t start_address() const { return header_->start_address; }
|
||||||
|
uint32_t end_address() const { return header_->end_address; }
|
||||||
|
uint32_t instruction_count() const {
|
||||||
|
return (header_->end_address - header_->start_address) / 4 + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Header* header() const { return header_; }
|
||||||
|
|
||||||
|
uint8_t* instruction_execute_counts() const {
|
||||||
|
return reinterpret_cast<uint8_t*>(header_) + sizeof(Header);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t SizeOfHeader() { return sizeof(Header); }
|
||||||
|
|
||||||
|
static size_t SizeOfInstructionCounts(uint32_t start_address,
|
||||||
|
uint32_t end_address) {
|
||||||
|
uint32_t instruction_count = (end_address - start_address) / 4 + 1;
|
||||||
|
return instruction_count * 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Header* header_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace debug
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_DEBUG_TRACE_DATA_H_
|
Loading…
Reference in New Issue