Refactoring SymbolInfo/FunctionInfo/Function into Symbol/Function.
This commit is contained in:
parent
48d6e6becf
commit
eaa1a8ee3a
|
@ -15,8 +15,7 @@
|
|||
namespace xe {
|
||||
namespace cpu {
|
||||
class DebugInfo;
|
||||
class Function;
|
||||
class FunctionInfo;
|
||||
class GuestFunction;
|
||||
namespace hir {
|
||||
class HIRBuilder;
|
||||
} // namespace hir
|
||||
|
@ -38,10 +37,9 @@ class Assembler {
|
|||
|
||||
virtual void Reset();
|
||||
|
||||
virtual bool Assemble(FunctionInfo* symbol_info, hir::HIRBuilder* builder,
|
||||
virtual bool Assemble(GuestFunction* function, hir::HIRBuilder* builder,
|
||||
uint32_t debug_info_flags,
|
||||
std::unique_ptr<DebugInfo> debug_info,
|
||||
Function** out_function) = 0;
|
||||
std::unique_ptr<DebugInfo> debug_info) = 0;
|
||||
|
||||
protected:
|
||||
Backend* backend_;
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
class GuestFunction;
|
||||
class Module;
|
||||
class Processor;
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
@ -46,6 +48,9 @@ class Backend {
|
|||
|
||||
virtual std::unique_ptr<Assembler> CreateAssembler() = 0;
|
||||
|
||||
virtual std::unique_ptr<GuestFunction> CreateGuestFunction(
|
||||
Module* module, uint32_t address) = 0;
|
||||
|
||||
protected:
|
||||
Processor* processor_;
|
||||
MachineInfo machine_info_;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
#include <string>
|
||||
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
@ -29,7 +29,7 @@ class CodeCache {
|
|||
|
||||
// Finds a function based on the given host PC (that may be within a
|
||||
// function).
|
||||
virtual FunctionInfo* LookupFunction(uint64_t host_pc) = 0;
|
||||
virtual GuestFunction* LookupFunction(uint64_t host_pc) = 0;
|
||||
|
||||
// Finds platform-specific function unwind info for the given host PC.
|
||||
virtual void* LookupUnwindInfo(uint64_t host_pc) = 0;
|
||||
|
|
|
@ -66,30 +66,26 @@ void X64Assembler::Reset() {
|
|||
Assembler::Reset();
|
||||
}
|
||||
|
||||
bool X64Assembler::Assemble(FunctionInfo* symbol_info, HIRBuilder* builder,
|
||||
bool X64Assembler::Assemble(GuestFunction* function, HIRBuilder* builder,
|
||||
uint32_t debug_info_flags,
|
||||
std::unique_ptr<DebugInfo> debug_info,
|
||||
Function** out_function) {
|
||||
std::unique_ptr<DebugInfo> debug_info) {
|
||||
SCOPE_profile_cpu_f("cpu");
|
||||
|
||||
// Reset when we leave.
|
||||
xe::make_reset_scope(this);
|
||||
|
||||
// Create now, and populate as we go.
|
||||
// We may throw it away if we fail.
|
||||
auto fn = std::make_unique<X64Function>(symbol_info);
|
||||
|
||||
// Lower HIR -> x64.
|
||||
void* machine_code = nullptr;
|
||||
size_t code_size = 0;
|
||||
if (!emitter_->Emit(symbol_info, builder, debug_info_flags, debug_info.get(),
|
||||
machine_code, code_size, fn->source_map())) {
|
||||
if (!emitter_->Emit(function, builder, debug_info_flags, debug_info.get(),
|
||||
machine_code, code_size, function->source_map())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Stash generated machine code.
|
||||
if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmMachineCode) {
|
||||
DumpMachineCode(machine_code, code_size, fn->source_map(), &string_buffer_);
|
||||
DumpMachineCode(machine_code, code_size, function->source_map(),
|
||||
&string_buffer_);
|
||||
debug_info->set_machine_code_disasm(string_buffer_.ToString());
|
||||
string_buffer_.Reset();
|
||||
}
|
||||
|
@ -102,11 +98,10 @@ bool X64Assembler::Assemble(FunctionInfo* symbol_info, HIRBuilder* builder,
|
|||
}
|
||||
}
|
||||
|
||||
fn->set_debug_info(std::move(debug_info));
|
||||
fn->Setup(reinterpret_cast<uint8_t*>(machine_code), code_size);
|
||||
function->set_debug_info(std::move(debug_info));
|
||||
static_cast<X64Function*>(function)
|
||||
->Setup(reinterpret_cast<uint8_t*>(machine_code), code_size);
|
||||
|
||||
// Pass back ownership.
|
||||
*out_function = fn.release();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,9 @@ class X64Assembler : public Assembler {
|
|||
|
||||
void Reset() override;
|
||||
|
||||
bool Assemble(FunctionInfo* symbol_info, hir::HIRBuilder* builder,
|
||||
bool Assemble(GuestFunction* function, hir::HIRBuilder* builder,
|
||||
uint32_t debug_info_flags,
|
||||
std::unique_ptr<DebugInfo> debug_info,
|
||||
Function** out_function) override;
|
||||
std::unique_ptr<DebugInfo> debug_info) override;
|
||||
|
||||
private:
|
||||
void DumpMachineCode(void* machine_code, size_t code_size,
|
||||
|
|
|
@ -10,8 +10,9 @@
|
|||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||
|
||||
#include "xenia/cpu/backend/x64/x64_assembler.h"
|
||||
#include "xenia/cpu/backend/x64/x64_emitter.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/backend/x64/x64_sequences.h"
|
||||
#include "xenia/cpu/backend/x64/x64_stack_layout.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
|
@ -106,6 +107,11 @@ std::unique_ptr<Assembler> X64Backend::CreateAssembler() {
|
|||
return std::make_unique<X64Assembler>(this);
|
||||
}
|
||||
|
||||
std::unique_ptr<GuestFunction> X64Backend::CreateGuestFunction(
|
||||
Module* module, uint32_t address) {
|
||||
return std::make_unique<X64Function>(module, address);
|
||||
}
|
||||
|
||||
using namespace Xbyak;
|
||||
|
||||
X64ThunkEmitter::X64ThunkEmitter(X64Backend* backend, XbyakAllocator* allocator)
|
||||
|
|
|
@ -56,6 +56,9 @@ class X64Backend : public Backend {
|
|||
|
||||
std::unique_ptr<Assembler> CreateAssembler() override;
|
||||
|
||||
std::unique_ptr<GuestFunction> CreateGuestFunction(Module* module,
|
||||
uint32_t address) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<X64CodeCache> code_cache_;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
@ -120,7 +121,7 @@ void* X64CodeCache::PlaceHostCode(uint32_t guest_address, void* machine_code,
|
|||
|
||||
void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
||||
size_t code_size, size_t stack_size,
|
||||
FunctionInfo* function_info) {
|
||||
GuestFunction* function_info) {
|
||||
// Hold a lock while we bump the pointers up. This is important as the
|
||||
// unwind table requires entries AND code to be sorted in order.
|
||||
size_t low_mark;
|
||||
|
@ -220,15 +221,15 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
|
|||
return uint32_t(uintptr_t(data_address));
|
||||
}
|
||||
|
||||
FunctionInfo* X64CodeCache::LookupFunction(uint64_t host_pc) {
|
||||
GuestFunction* X64CodeCache::LookupFunction(uint64_t host_pc) {
|
||||
uint32_t key = uint32_t(host_pc - kGeneratedCodeBase);
|
||||
void* fn_entry = std::bsearch(
|
||||
&key, generated_code_map_.data(), generated_code_map_.size() + 1,
|
||||
sizeof(std::pair<uint32_t, FunctionInfo*>),
|
||||
sizeof(std::pair<uint32_t, Function*>),
|
||||
[](const void* key_ptr, const void* element_ptr) {
|
||||
auto key = *reinterpret_cast<const uint32_t*>(key_ptr);
|
||||
auto element =
|
||||
reinterpret_cast<const std::pair<uint64_t, FunctionInfo*>*>(
|
||||
reinterpret_cast<const std::pair<uint64_t, GuestFunction*>*>(
|
||||
element_ptr);
|
||||
if (key < (element->first >> 32)) {
|
||||
return -1;
|
||||
|
@ -239,7 +240,8 @@ FunctionInfo* X64CodeCache::LookupFunction(uint64_t host_pc) {
|
|||
}
|
||||
});
|
||||
if (fn_entry) {
|
||||
return reinterpret_cast<const std::pair<uint64_t, FunctionInfo*>*>(fn_entry)
|
||||
return reinterpret_cast<const std::pair<uint64_t, GuestFunction*>*>(
|
||||
fn_entry)
|
||||
->second;
|
||||
} else {
|
||||
return nullptr;
|
||||
|
|
|
@ -50,10 +50,10 @@ class X64CodeCache : public CodeCache {
|
|||
size_t code_size, size_t stack_size);
|
||||
void* PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
||||
size_t code_size, size_t stack_size,
|
||||
FunctionInfo* function_info);
|
||||
GuestFunction* function_info);
|
||||
uint32_t PlaceData(const void* data, size_t length);
|
||||
|
||||
FunctionInfo* LookupFunction(uint64_t host_pc) override;
|
||||
GuestFunction* LookupFunction(uint64_t host_pc) override;
|
||||
|
||||
protected:
|
||||
// All executable code falls within 0x80000000 to 0x9FFFFFFF, so we can
|
||||
|
@ -111,7 +111,7 @@ class X64CodeCache : public CodeCache {
|
|||
// Sorted map by host PC base offsets to source function info.
|
||||
// This can be used to bsearch on host PC to find the guest function.
|
||||
// The key is [start address | end address].
|
||||
std::vector<std::pair<uint64_t, FunctionInfo*>> generated_code_map_;
|
||||
std::vector<std::pair<uint64_t, GuestFunction*>> generated_code_map_;
|
||||
};
|
||||
|
||||
} // namespace x64
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/platform_win.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
|
||||
// When enabled, this will use Windows 8 APIs to get unwind info.
|
||||
// TODO(benvanik): figure out why the callback variant doesn't work.
|
||||
|
|
|
@ -27,8 +27,9 @@
|
|||
#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/processor.h"
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
#include "xenia/cpu/symbol.h"
|
||||
#include "xenia/cpu/thread_state.h"
|
||||
#include "xenia/debug/debugger.h"
|
||||
#include "xenia/profiling.h"
|
||||
|
@ -87,7 +88,7 @@ X64Emitter::X64Emitter(X64Backend* backend, XbyakAllocator* allocator)
|
|||
|
||||
X64Emitter::~X64Emitter() = default;
|
||||
|
||||
bool X64Emitter::Emit(FunctionInfo* function_info, HIRBuilder* builder,
|
||||
bool X64Emitter::Emit(GuestFunction* function, HIRBuilder* builder,
|
||||
uint32_t debug_info_flags, DebugInfo* debug_info,
|
||||
void*& out_code_address, size_t& out_code_size,
|
||||
std::vector<SourceMapEntry>& out_source_map) {
|
||||
|
@ -96,6 +97,7 @@ bool X64Emitter::Emit(FunctionInfo* function_info, HIRBuilder* builder,
|
|||
// Reset.
|
||||
debug_info_ = debug_info;
|
||||
debug_info_flags_ = debug_info_flags;
|
||||
trace_data_ = &function->trace_data();
|
||||
source_map_arena_.Reset();
|
||||
|
||||
// Fill the generator with code.
|
||||
|
@ -106,7 +108,7 @@ bool X64Emitter::Emit(FunctionInfo* function_info, HIRBuilder* builder,
|
|||
|
||||
// Copy the final code to the cache and relocate it.
|
||||
out_code_size = getSize();
|
||||
out_code_address = Emplace(stack_size, function_info);
|
||||
out_code_address = Emplace(stack_size, function);
|
||||
|
||||
// Stash source map.
|
||||
source_map_arena_.CloneContents(out_source_map);
|
||||
|
@ -114,16 +116,16 @@ bool X64Emitter::Emit(FunctionInfo* function_info, HIRBuilder* builder,
|
|||
return true;
|
||||
}
|
||||
|
||||
void* X64Emitter::Emplace(size_t stack_size, FunctionInfo* function_info) {
|
||||
void* X64Emitter::Emplace(size_t stack_size, GuestFunction* function) {
|
||||
// To avoid changing xbyak, we do a switcharoo here.
|
||||
// top_ points to the Xbyak buffer, and since we are in AutoGrow mode
|
||||
// it has pending relocations. We copy the top_ to our buffer, swap the
|
||||
// pointer, relocate, then return the original scratch pointer for use.
|
||||
uint8_t* old_address = top_;
|
||||
void* new_address;
|
||||
if (function_info) {
|
||||
new_address = code_cache_->PlaceGuestCode(function_info->address(), top_,
|
||||
size_, stack_size, function_info);
|
||||
if (function) {
|
||||
new_address = code_cache_->PlaceGuestCode(function->address(), top_, size_,
|
||||
stack_size, function);
|
||||
} else {
|
||||
new_address = code_cache_->PlaceHostCode(0, top_, size_, stack_size);
|
||||
}
|
||||
|
@ -177,8 +179,8 @@ bool X64Emitter::Emit(HIRBuilder* builder, size_t& out_stack_size) {
|
|||
// 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();
|
||||
assert_true(uint64_t(trace_data_->header()) < UINT_MAX);
|
||||
auto trace_header = trace_data_->header();
|
||||
|
||||
// Call count.
|
||||
lock();
|
||||
|
@ -265,11 +267,10 @@ void X64Emitter::MarkSourceOffset(const Instr* i) {
|
|||
}
|
||||
|
||||
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;
|
||||
(entry->source_offset - trace_data_->start_address()) / 4;
|
||||
lock();
|
||||
inc(qword[low_address(trace_data.instruction_execute_counts() +
|
||||
inc(qword[low_address(trace_data_->instruction_execute_counts() +
|
||||
instruction_index * 8)]);
|
||||
}
|
||||
}
|
||||
|
@ -341,8 +342,7 @@ extern "C" uint64_t ResolveFunction(void* raw_context,
|
|||
// TODO(benvanik): required?
|
||||
assert_not_zero(target_address);
|
||||
|
||||
Function* fn = NULL;
|
||||
thread_state->processor()->ResolveFunction(target_address, &fn);
|
||||
auto fn = thread_state->processor()->ResolveFunction(target_address);
|
||||
assert_not_null(fn);
|
||||
auto x64_fn = static_cast<X64Function*>(fn);
|
||||
uint64_t addr = reinterpret_cast<uint64_t>(x64_fn->machine_code());
|
||||
|
@ -350,11 +350,11 @@ extern "C" uint64_t ResolveFunction(void* raw_context,
|
|||
return addr;
|
||||
}
|
||||
|
||||
void X64Emitter::Call(const hir::Instr* instr, FunctionInfo* symbol_info) {
|
||||
assert_not_null(symbol_info);
|
||||
auto fn = reinterpret_cast<X64Function*>(symbol_info->function());
|
||||
void X64Emitter::Call(const hir::Instr* instr, GuestFunction* function) {
|
||||
assert_not_null(function);
|
||||
auto fn = static_cast<X64Function*>(function);
|
||||
// Resolve address to the function to call and store in rax.
|
||||
if (fn) {
|
||||
if (fn->machine_code()) {
|
||||
// TODO(benvanik): is it worth it to do this? It removes the need for
|
||||
// a ResolveFunction call, but makes the table less useful.
|
||||
assert_zero(uint64_t(fn->machine_code()) & 0xFFFFFFFF00000000);
|
||||
|
@ -363,7 +363,7 @@ void X64Emitter::Call(const hir::Instr* instr, FunctionInfo* symbol_info) {
|
|||
// Load the pointer to the indirection table maintained in X64CodeCache.
|
||||
// The target dword will either contain the address of the generated code
|
||||
// or a thunk to ResolveAddress.
|
||||
mov(ebx, symbol_info->address());
|
||||
mov(ebx, function->address());
|
||||
mov(eax, dword[ebx]);
|
||||
}
|
||||
|
||||
|
@ -418,43 +418,50 @@ void X64Emitter::CallIndirect(const hir::Instr* instr, const Reg64& reg) {
|
|||
}
|
||||
}
|
||||
|
||||
uint64_t UndefinedCallExtern(void* raw_context, uint64_t symbol_info_ptr) {
|
||||
auto symbol_info = reinterpret_cast<FunctionInfo*>(symbol_info_ptr);
|
||||
XELOGW("undefined extern call to %.8X %s", symbol_info->address(),
|
||||
symbol_info->name().c_str());
|
||||
uint64_t UndefinedCallExtern(void* raw_context, uint64_t function_ptr) {
|
||||
auto function = reinterpret_cast<Function*>(function_ptr);
|
||||
XELOGW("undefined extern call to %.8X %s", function->address(),
|
||||
function->name().c_str());
|
||||
return 0;
|
||||
}
|
||||
void X64Emitter::CallExtern(const hir::Instr* instr,
|
||||
const FunctionInfo* symbol_info) {
|
||||
if (symbol_info->behavior() == FunctionBehavior::kBuiltin &&
|
||||
symbol_info->builtin_handler()) {
|
||||
// rcx = context
|
||||
// rdx = target host function
|
||||
// r8 = arg0
|
||||
// r9 = arg1
|
||||
mov(rdx, reinterpret_cast<uint64_t>(symbol_info->builtin_handler()));
|
||||
mov(r8, reinterpret_cast<uint64_t>(symbol_info->builtin_arg0()));
|
||||
mov(r9, reinterpret_cast<uint64_t>(symbol_info->builtin_arg1()));
|
||||
auto thunk = backend()->guest_to_host_thunk();
|
||||
mov(rax, reinterpret_cast<uint64_t>(thunk));
|
||||
call(rax);
|
||||
ReloadECX();
|
||||
ReloadEDX();
|
||||
// rax = host return
|
||||
} else if (symbol_info->behavior() == FunctionBehavior::kExtern &&
|
||||
symbol_info->extern_handler()) {
|
||||
// rcx = context
|
||||
// rdx = target host function
|
||||
mov(rdx, reinterpret_cast<uint64_t>(symbol_info->extern_handler()));
|
||||
mov(r8, qword[rcx + offsetof(cpu::frontend::PPCContext, kernel_state)]);
|
||||
auto thunk = backend()->guest_to_host_thunk();
|
||||
mov(rax, reinterpret_cast<uint64_t>(thunk));
|
||||
call(rax);
|
||||
ReloadECX();
|
||||
ReloadEDX();
|
||||
// rax = host return
|
||||
} else {
|
||||
CallNative(UndefinedCallExtern, reinterpret_cast<uint64_t>(symbol_info));
|
||||
void X64Emitter::CallExtern(const hir::Instr* instr, const Function* function) {
|
||||
bool undefined = true;
|
||||
if (function->behavior() == Function::Behavior::kBuiltin) {
|
||||
auto builtin_function = static_cast<const BuiltinFunction*>(function);
|
||||
if (builtin_function->handler()) {
|
||||
undefined = false;
|
||||
// rcx = context
|
||||
// rdx = target host function
|
||||
// r8 = arg0
|
||||
// r9 = arg1
|
||||
mov(rdx, reinterpret_cast<uint64_t>(builtin_function->handler()));
|
||||
mov(r8, reinterpret_cast<uint64_t>(builtin_function->arg0()));
|
||||
mov(r9, reinterpret_cast<uint64_t>(builtin_function->arg1()));
|
||||
auto thunk = backend()->guest_to_host_thunk();
|
||||
mov(rax, reinterpret_cast<uint64_t>(thunk));
|
||||
call(rax);
|
||||
ReloadECX();
|
||||
ReloadEDX();
|
||||
// rax = host return
|
||||
}
|
||||
} else if (function->behavior() == Function::Behavior::kExtern) {
|
||||
auto extern_function = static_cast<const GuestFunction*>(function);
|
||||
if (extern_function->extern_handler()) {
|
||||
undefined = false;
|
||||
// rcx = context
|
||||
// rdx = target host function
|
||||
mov(rdx, reinterpret_cast<uint64_t>(extern_function->extern_handler()));
|
||||
mov(r8, qword[rcx + offsetof(cpu::frontend::PPCContext, kernel_state)]);
|
||||
auto thunk = backend()->guest_to_host_thunk();
|
||||
mov(rax, reinterpret_cast<uint64_t>(thunk));
|
||||
call(rax);
|
||||
ReloadECX();
|
||||
ReloadEDX();
|
||||
// rax = host return
|
||||
}
|
||||
}
|
||||
if (undefined) {
|
||||
CallNative(UndefinedCallExtern, reinterpret_cast<uint64_t>(function));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
#include "xenia/cpu/hir/hir_builder.h"
|
||||
#include "xenia/cpu/hir/instr.h"
|
||||
#include "xenia/cpu/hir/value.h"
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
#include "xenia/debug/function_trace_data.h"
|
||||
#include "xenia/memory.h"
|
||||
|
||||
|
@ -27,10 +26,7 @@
|
|||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
class DebugInfo;
|
||||
class FunctionInfo;
|
||||
class Processor;
|
||||
class SymbolInfo;
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
|
@ -118,7 +114,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
|||
Processor* processor() const { return processor_; }
|
||||
X64Backend* backend() const { return backend_; }
|
||||
|
||||
bool Emit(FunctionInfo* function_info, hir::HIRBuilder* builder,
|
||||
bool Emit(GuestFunction* function, hir::HIRBuilder* builder,
|
||||
uint32_t debug_info_flags, DebugInfo* debug_info,
|
||||
void*& out_code_address, size_t& out_code_size,
|
||||
std::vector<SourceMapEntry>& out_source_map);
|
||||
|
@ -163,9 +159,9 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
|||
void Trap(uint16_t trap_type = 0);
|
||||
void UnimplementedInstr(const hir::Instr* i);
|
||||
|
||||
void Call(const hir::Instr* instr, FunctionInfo* symbol_info);
|
||||
void Call(const hir::Instr* instr, GuestFunction* function);
|
||||
void CallIndirect(const hir::Instr* instr, const Xbyak::Reg64& reg);
|
||||
void CallExtern(const hir::Instr* instr, const FunctionInfo* symbol_info);
|
||||
void CallExtern(const hir::Instr* instr, const Function* function);
|
||||
void CallNative(void* fn);
|
||||
void CallNative(uint64_t (*fn)(void* raw_context));
|
||||
void CallNative(uint64_t (*fn)(void* raw_context, uint64_t arg0));
|
||||
|
@ -199,7 +195,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
|||
size_t stack_size() const { return stack_size_; }
|
||||
|
||||
protected:
|
||||
void* Emplace(size_t stack_size, FunctionInfo* function_info = nullptr);
|
||||
void* Emplace(size_t stack_size, GuestFunction* function = nullptr);
|
||||
bool Emit(hir::HIRBuilder* builder, size_t& out_stack_size);
|
||||
void EmitGetCurrentThreadId();
|
||||
void EmitTraceUserCallReturn();
|
||||
|
@ -218,6 +214,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
|||
|
||||
DebugInfo* debug_info_ = nullptr;
|
||||
uint32_t debug_info_flags_ = 0;
|
||||
debug::FunctionTraceData* trace_data_ = nullptr;
|
||||
Arena source_map_arena_;
|
||||
|
||||
size_t stack_size_ = 0;
|
||||
|
|
|
@ -18,8 +18,8 @@ namespace cpu {
|
|||
namespace backend {
|
||||
namespace x64 {
|
||||
|
||||
X64Function::X64Function(FunctionInfo* symbol_info)
|
||||
: Function(symbol_info), machine_code_(nullptr), machine_code_length_(0) {}
|
||||
X64Function::X64Function(Module* module, uint32_t address)
|
||||
: GuestFunction(module, address) {}
|
||||
|
||||
X64Function::~X64Function() {
|
||||
// machine_code_ is freed by code cache.
|
||||
|
@ -30,14 +30,6 @@ void X64Function::Setup(uint8_t* machine_code, size_t machine_code_length) {
|
|||
machine_code_length_ = machine_code_length;
|
||||
}
|
||||
|
||||
bool X64Function::AddBreakpointImpl(debug::Breakpoint* breakpoint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool X64Function::RemoveBreakpointImpl(debug::Breakpoint* breakpoint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool X64Function::CallImpl(ThreadState* thread_state, uint32_t return_address) {
|
||||
auto backend =
|
||||
reinterpret_cast<X64Backend*>(thread_state->processor()->backend());
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
#define XENIA_BACKEND_X64_X64_FUNCTION_H_
|
||||
|
||||
#include "xenia/cpu/function.h"
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
#include "xenia/cpu/thread_state.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -19,10 +18,10 @@ namespace cpu {
|
|||
namespace backend {
|
||||
namespace x64 {
|
||||
|
||||
class X64Function : public Function {
|
||||
class X64Function : public GuestFunction {
|
||||
public:
|
||||
X64Function(FunctionInfo* symbol_info);
|
||||
virtual ~X64Function();
|
||||
X64Function(Module* module, uint32_t address);
|
||||
~X64Function() override;
|
||||
|
||||
uint8_t* machine_code() const override { return machine_code_; }
|
||||
size_t machine_code_length() const override { return machine_code_length_; }
|
||||
|
@ -30,13 +29,11 @@ class X64Function : public Function {
|
|||
void Setup(uint8_t* machine_code, size_t machine_code_length);
|
||||
|
||||
protected:
|
||||
bool AddBreakpointImpl(debug::Breakpoint* breakpoint) override;
|
||||
bool RemoveBreakpointImpl(debug::Breakpoint* breakpoint) override;
|
||||
bool CallImpl(ThreadState* thread_state, uint32_t return_address) override;
|
||||
|
||||
private:
|
||||
uint8_t* machine_code_;
|
||||
size_t machine_code_length_;
|
||||
uint8_t* machine_code_ = nullptr;
|
||||
size_t machine_code_length_ = 0;
|
||||
};
|
||||
|
||||
} // namespace x64
|
||||
|
|
|
@ -154,7 +154,7 @@ struct OffsetOp : Op<OffsetOp, KEY_TYPE_O> {
|
|||
};
|
||||
|
||||
struct SymbolOp : Op<SymbolOp, KEY_TYPE_S> {
|
||||
FunctionInfo* value;
|
||||
Function* value;
|
||||
|
||||
protected:
|
||||
template <typename T, KeyType KEY_TYPE>
|
||||
|
@ -162,7 +162,7 @@ struct SymbolOp : Op<SymbolOp, KEY_TYPE_S> {
|
|||
template <hir::Opcode OPCODE, typename... Ts>
|
||||
friend struct I;
|
||||
bool Load(const Instr::Op& op) {
|
||||
this->value = op.symbol_info;
|
||||
this->value = op.symbol;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
@ -856,7 +856,8 @@ EMITTER_OPCODE_TABLE(OPCODE_TRAP_TRUE, TRAP_TRUE_I8, TRAP_TRUE_I16,
|
|||
// ============================================================================
|
||||
struct CALL : Sequence<CALL, I<OPCODE_CALL, VoidOp, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
e.Call(i.instr, i.src1.value);
|
||||
assert_true(i.src1.value->is_guest());
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src1.value));
|
||||
}
|
||||
};
|
||||
EMITTER_OPCODE_TABLE(OPCODE_CALL, CALL);
|
||||
|
@ -867,60 +868,66 @@ EMITTER_OPCODE_TABLE(OPCODE_CALL, CALL);
|
|||
struct CALL_TRUE_I8
|
||||
: Sequence<CALL_TRUE_I8, I<OPCODE_CALL_TRUE, VoidOp, I8Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, i.src2.value);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_TRUE_I16
|
||||
: Sequence<CALL_TRUE_I16, I<OPCODE_CALL_TRUE, VoidOp, I16Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, i.src2.value);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_TRUE_I32
|
||||
: Sequence<CALL_TRUE_I32, I<OPCODE_CALL_TRUE, VoidOp, I32Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, i.src2.value);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_TRUE_I64
|
||||
: Sequence<CALL_TRUE_I64, I<OPCODE_CALL_TRUE, VoidOp, I64Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.test(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, i.src2.value);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_TRUE_F32
|
||||
: Sequence<CALL_TRUE_F32, I<OPCODE_CALL_TRUE, VoidOp, F32Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, i.src2.value);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
struct CALL_TRUE_F64
|
||||
: Sequence<CALL_TRUE_F64, I<OPCODE_CALL_TRUE, VoidOp, F64Op, SymbolOp>> {
|
||||
static void Emit(X64Emitter& e, const EmitArgType& i) {
|
||||
assert_true(i.src2.value->is_guest());
|
||||
e.vptest(i.src1, i.src1);
|
||||
Xbyak::Label skip;
|
||||
e.jz(skip);
|
||||
e.Call(i.instr, i.src2.value);
|
||||
e.Call(i.instr, static_cast<GuestFunction*>(i.src2.value));
|
||||
e.L(skip);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -91,9 +91,9 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
|||
case OPCODE_CALL_TRUE:
|
||||
if (i->src1.value->IsConstant()) {
|
||||
if (i->src1.value->IsConstantTrue()) {
|
||||
auto symbol_info = i->src2.symbol_info;
|
||||
auto symbol = i->src2.symbol;
|
||||
i->Replace(&OPCODE_CALL_info, i->flags);
|
||||
i->src1.symbol_info = symbol_info;
|
||||
i->src1.symbol = symbol;
|
||||
} else {
|
||||
i->Remove();
|
||||
}
|
||||
|
@ -101,13 +101,13 @@ bool ConstantPropagationPass::Run(HIRBuilder* builder) {
|
|||
break;
|
||||
case OPCODE_CALL_INDIRECT:
|
||||
if (i->src1.value->IsConstant()) {
|
||||
FunctionInfo* symbol_info;
|
||||
if (!processor_->LookupFunctionInfo(
|
||||
(uint32_t)i->src1.value->constant.i32, &symbol_info)) {
|
||||
auto function = processor_->LookupFunction(
|
||||
uint32_t(i->src1.value->constant.i32));
|
||||
if (!function) {
|
||||
break;
|
||||
}
|
||||
i->Replace(&OPCODE_CALL_info, i->flags);
|
||||
i->src1.symbol_info = symbol_info;
|
||||
i->src1.symbol = function;
|
||||
}
|
||||
break;
|
||||
case OPCODE_CALL_INDIRECT_TRUE:
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "xenia/cpu/debug_info.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace xe {
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "xenia/debug/function_trace_data.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
|
@ -37,6 +35,9 @@ enum DebugInfoFlags : uint32_t {
|
|||
kDebugInfoAll = 0xFFFFFFFF,
|
||||
};
|
||||
|
||||
// DEPRECATED
|
||||
// This will be getting removed or refactored to contain only on-demand
|
||||
// disassembly data.
|
||||
class DebugInfo {
|
||||
public:
|
||||
DebugInfo();
|
||||
|
@ -53,8 +54,6 @@ class DebugInfo {
|
|||
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_; }
|
||||
|
@ -70,8 +69,6 @@ class DebugInfo {
|
|||
uint32_t address_reference_count_;
|
||||
uint32_t instruction_result_count_;
|
||||
|
||||
debug::FunctionTraceData trace_data_;
|
||||
|
||||
char* source_disasm_;
|
||||
char* raw_hir_disasm_;
|
||||
char* hir_disasm_;
|
||||
|
|
|
@ -53,7 +53,7 @@ int InstrEmit_branch(PPCHIRBuilder& f, const char* src, uint64_t cia,
|
|||
// recursion.
|
||||
uint32_t nia_value = nia->AsUint64() & 0xFFFFFFFF;
|
||||
bool is_recursion = false;
|
||||
if (nia_value == f.symbol_info()->address() && lk) {
|
||||
if (nia_value == f.function()->address() && lk) {
|
||||
is_recursion = true;
|
||||
}
|
||||
Label* label = is_recursion ? NULL : f.LookupLabel(nia_value);
|
||||
|
@ -71,14 +71,14 @@ int InstrEmit_branch(PPCHIRBuilder& f, const char* src, uint64_t cia,
|
|||
}
|
||||
} else {
|
||||
// Call function.
|
||||
auto symbol_info = f.LookupFunction(nia_value);
|
||||
auto function = f.LookupFunction(nia_value);
|
||||
if (cond) {
|
||||
if (!expect_true) {
|
||||
cond = f.IsFalse(cond);
|
||||
}
|
||||
f.CallTrue(cond, symbol_info, call_flags);
|
||||
f.CallTrue(cond, function, call_flags);
|
||||
} else {
|
||||
f.Call(symbol_info, call_flags);
|
||||
f.Call(function, call_flags);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -419,7 +419,7 @@ XEEMITTER(mcrf, 0x4C000000, XL)(PPCHIRBuilder& f, InstrData& i) {
|
|||
// System linkage (A-24)
|
||||
|
||||
XEEMITTER(sc, 0x44000002, SC)(PPCHIRBuilder& f, InstrData& i) {
|
||||
f.CallExtern(f.symbol_info());
|
||||
f.CallExtern(f.function());
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ bool PPCFrontend::Initialize() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool PPCFrontend::DeclareFunction(FunctionInfo* symbol_info) {
|
||||
bool PPCFrontend::DeclareFunction(GuestFunction* function) {
|
||||
// Could scan or something here.
|
||||
// Could also check to see if it's a well-known function type and classify
|
||||
// for later.
|
||||
|
@ -95,12 +95,10 @@ bool PPCFrontend::DeclareFunction(FunctionInfo* symbol_info) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool PPCFrontend::DefineFunction(FunctionInfo* symbol_info,
|
||||
uint32_t debug_info_flags,
|
||||
Function** out_function) {
|
||||
PPCTranslator* translator = translator_pool_.Allocate(this);
|
||||
bool result =
|
||||
translator->Translate(symbol_info, debug_info_flags, out_function);
|
||||
bool PPCFrontend::DefineFunction(GuestFunction* function,
|
||||
uint32_t debug_info_flags) {
|
||||
auto translator = translator_pool_.Allocate(this);
|
||||
bool result = translator->Translate(function, debug_info_flags);
|
||||
translator_pool_.Release(translator);
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
#include "xenia/base/type_pool.h"
|
||||
#include "xenia/cpu/frontend/context_info.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
#include "xenia/memory.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -35,8 +34,8 @@ class PPCTranslator;
|
|||
struct PPCBuiltins {
|
||||
xe::mutex global_lock;
|
||||
bool global_lock_taken;
|
||||
FunctionInfo* check_global_lock;
|
||||
FunctionInfo* handle_global_lock;
|
||||
Function* check_global_lock;
|
||||
Function* handle_global_lock;
|
||||
};
|
||||
|
||||
class PPCFrontend {
|
||||
|
@ -51,9 +50,8 @@ class PPCFrontend {
|
|||
ContextInfo* context_info() const { return context_info_.get(); }
|
||||
PPCBuiltins* builtins() { return &builtins_; }
|
||||
|
||||
bool DeclareFunction(FunctionInfo* symbol_info);
|
||||
bool DefineFunction(FunctionInfo* symbol_info, uint32_t debug_info_flags,
|
||||
Function** out_function);
|
||||
bool DeclareFunction(GuestFunction* function);
|
||||
bool DefineFunction(GuestFunction* function, uint32_t debug_info_flags);
|
||||
|
||||
private:
|
||||
Processor* processor_;
|
||||
|
|
|
@ -40,27 +40,29 @@ PPCHIRBuilder::PPCHIRBuilder(PPCFrontend* frontend)
|
|||
PPCHIRBuilder::~PPCHIRBuilder() = default;
|
||||
|
||||
void PPCHIRBuilder::Reset() {
|
||||
function_ = nullptr;
|
||||
start_address_ = 0;
|
||||
instr_count_ = 0;
|
||||
instr_offset_list_ = NULL;
|
||||
label_list_ = NULL;
|
||||
with_debug_info_ = false;
|
||||
HIRBuilder::Reset();
|
||||
}
|
||||
|
||||
bool PPCHIRBuilder::Emit(FunctionInfo* symbol_info, uint32_t flags) {
|
||||
bool PPCHIRBuilder::Emit(GuestFunction* function, uint32_t flags) {
|
||||
SCOPE_profile_cpu_f("cpu");
|
||||
|
||||
Memory* memory = frontend_->memory();
|
||||
|
||||
symbol_info_ = symbol_info;
|
||||
start_address_ = symbol_info->address();
|
||||
instr_count_ = (symbol_info->end_address() - symbol_info->address()) / 4 + 1;
|
||||
function_ = function;
|
||||
start_address_ = function_->address();
|
||||
instr_count_ = (function_->end_address() - function_->address()) / 4 + 1;
|
||||
|
||||
with_debug_info_ = (flags & EMIT_DEBUG_COMMENTS) == EMIT_DEBUG_COMMENTS;
|
||||
if (with_debug_info_) {
|
||||
CommentFormat("%s fn %.8X-%.8X %s", symbol_info->module()->name().c_str(),
|
||||
symbol_info->address(), symbol_info->end_address(),
|
||||
symbol_info->name().c_str());
|
||||
CommentFormat("%s fn %.8X-%.8X %s", function_->module()->name().c_str(),
|
||||
function_->address(), function_->end_address(),
|
||||
function_->name().c_str());
|
||||
}
|
||||
|
||||
// Allocate offset list.
|
||||
|
@ -78,8 +80,8 @@ bool PPCHIRBuilder::Emit(FunctionInfo* symbol_info, uint32_t flags) {
|
|||
// Always mark entry with label.
|
||||
label_list_[0] = NewLabel();
|
||||
|
||||
uint32_t start_address = symbol_info->address();
|
||||
uint32_t end_address = symbol_info->end_address();
|
||||
uint32_t start_address = function_->address();
|
||||
uint32_t end_address = function_->end_address();
|
||||
InstrData i;
|
||||
for (uint32_t address = start_address, offset = 0; address <= end_address;
|
||||
address += 4, offset++) {
|
||||
|
@ -165,13 +167,8 @@ void PPCHIRBuilder::AnnotateLabel(uint32_t address, Label* label) {
|
|||
memcpy(label->name, name_buffer, sizeof(name_buffer));
|
||||
}
|
||||
|
||||
FunctionInfo* PPCHIRBuilder::LookupFunction(uint32_t address) {
|
||||
Processor* processor = frontend_->processor();
|
||||
FunctionInfo* symbol_info;
|
||||
if (!processor->LookupFunctionInfo(address, &symbol_info)) {
|
||||
return nullptr;
|
||||
}
|
||||
return symbol_info;
|
||||
Function* PPCHIRBuilder::LookupFunction(uint32_t address) {
|
||||
return frontend_->processor()->LookupFunction(address);
|
||||
}
|
||||
|
||||
Label* PPCHIRBuilder::LookupLabel(uint32_t address) {
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "xenia/base/string_buffer.h"
|
||||
#include "xenia/cpu/hir/hir_builder.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
@ -28,18 +27,18 @@ class PPCHIRBuilder : public hir::HIRBuilder {
|
|||
|
||||
public:
|
||||
PPCHIRBuilder(PPCFrontend* frontend);
|
||||
virtual ~PPCHIRBuilder();
|
||||
~PPCHIRBuilder() override;
|
||||
|
||||
virtual void Reset();
|
||||
void Reset() override;
|
||||
|
||||
enum EmitFlags {
|
||||
// Emit comment nodes.
|
||||
EMIT_DEBUG_COMMENTS = 1 << 0,
|
||||
};
|
||||
bool Emit(FunctionInfo* symbol_info, uint32_t flags);
|
||||
bool Emit(GuestFunction* function, uint32_t flags);
|
||||
|
||||
FunctionInfo* symbol_info() const { return symbol_info_; }
|
||||
FunctionInfo* LookupFunction(uint32_t address);
|
||||
GuestFunction* function() const { return function_; }
|
||||
Function* LookupFunction(uint32_t address);
|
||||
Label* LookupLabel(uint32_t address);
|
||||
|
||||
Value* LoadLR();
|
||||
|
@ -83,7 +82,6 @@ class PPCHIRBuilder : public hir::HIRBuilder {
|
|||
private:
|
||||
void AnnotateLabel(uint32_t address, Label* label);
|
||||
|
||||
private:
|
||||
PPCFrontend* frontend_;
|
||||
|
||||
// Reset whenever needed:
|
||||
|
@ -91,7 +89,7 @@ class PPCHIRBuilder : public hir::HIRBuilder {
|
|||
|
||||
// Reset each Emit:
|
||||
bool with_debug_info_;
|
||||
FunctionInfo* symbol_info_;
|
||||
GuestFunction* function_;
|
||||
uint64_t start_address_;
|
||||
uint64_t instr_count_;
|
||||
Instr** instr_offset_list_;
|
||||
|
|
|
@ -35,11 +35,10 @@ PPCScanner::~PPCScanner() {}
|
|||
|
||||
bool PPCScanner::IsRestGprLr(uint32_t address) {
|
||||
auto function = frontend_->processor()->QueryFunction(address);
|
||||
return function &&
|
||||
function->symbol_info()->behavior() == FunctionBehavior::kEpilogReturn;
|
||||
return function && function->behavior() == Function::Behavior::kEpilogReturn;
|
||||
}
|
||||
|
||||
bool PPCScanner::Scan(FunctionInfo* symbol_info, DebugInfo* debug_info) {
|
||||
bool PPCScanner::Scan(GuestFunction* function, DebugInfo* 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
|
||||
|
@ -49,14 +48,14 @@ bool PPCScanner::Scan(FunctionInfo* symbol_info, DebugInfo* debug_info) {
|
|||
|
||||
Memory* memory = frontend_->memory();
|
||||
|
||||
LOGPPC("Analyzing function %.8X...", symbol_info->address());
|
||||
LOGPPC("Analyzing function %.8X...", function->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 end_address = static_cast<uint32_t>(symbol_info->end_address());
|
||||
uint32_t start_address = static_cast<uint32_t>(function->address());
|
||||
uint32_t end_address = static_cast<uint32_t>(function->end_address());
|
||||
uint32_t address = start_address;
|
||||
uint32_t furthest_target = start_address;
|
||||
size_t blocks_found = 0;
|
||||
|
@ -270,7 +269,7 @@ bool PPCScanner::Scan(FunctionInfo* symbol_info, DebugInfo* debug_info) {
|
|||
LOGPPC("Function ran under: %.8X-%.8X ended at %.8X", start_address,
|
||||
end_address, address + 4);
|
||||
}
|
||||
symbol_info->set_end_address(address);
|
||||
function->set_end_address(address);
|
||||
|
||||
// If there's spare bits at the end, split the function.
|
||||
// TODO(benvanik): splitting?
|
||||
|
@ -289,13 +288,13 @@ bool PPCScanner::Scan(FunctionInfo* symbol_info, DebugInfo* debug_info) {
|
|||
return true;
|
||||
}
|
||||
|
||||
std::vector<BlockInfo> PPCScanner::FindBlocks(FunctionInfo* symbol_info) {
|
||||
std::vector<BlockInfo> PPCScanner::FindBlocks(GuestFunction* function) {
|
||||
Memory* memory = frontend_->memory();
|
||||
|
||||
std::map<uint32_t, BlockInfo> block_map;
|
||||
|
||||
uint32_t start_address = symbol_info->address();
|
||||
uint32_t end_address = symbol_info->end_address();
|
||||
uint32_t start_address = function->address();
|
||||
uint32_t end_address = function->end_address();
|
||||
bool in_block = false;
|
||||
uint32_t block_start = 0;
|
||||
InstrData i;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "xenia/cpu/debug_info.h"
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
@ -21,25 +21,24 @@ namespace frontend {
|
|||
|
||||
class PPCFrontend;
|
||||
|
||||
typedef struct BlockInfo_t {
|
||||
struct BlockInfo {
|
||||
uint32_t start_address;
|
||||
uint32_t end_address;
|
||||
} BlockInfo;
|
||||
};
|
||||
|
||||
class PPCScanner {
|
||||
public:
|
||||
PPCScanner(PPCFrontend* frontend);
|
||||
~PPCScanner();
|
||||
|
||||
bool Scan(FunctionInfo* symbol_info, DebugInfo* debug_info);
|
||||
bool Scan(GuestFunction* function, DebugInfo* debug_info);
|
||||
|
||||
std::vector<BlockInfo> FindBlocks(FunctionInfo* symbol_info);
|
||||
std::vector<BlockInfo> FindBlocks(GuestFunction* function);
|
||||
|
||||
private:
|
||||
bool IsRestGprLr(uint32_t address);
|
||||
|
||||
private:
|
||||
PPCFrontend* frontend_;
|
||||
PPCFrontend* frontend_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace frontend
|
||||
|
|
|
@ -99,9 +99,8 @@ PPCTranslator::PPCTranslator(PPCFrontend* frontend) : frontend_(frontend) {
|
|||
|
||||
PPCTranslator::~PPCTranslator() = default;
|
||||
|
||||
bool PPCTranslator::Translate(FunctionInfo* symbol_info,
|
||||
uint32_t debug_info_flags,
|
||||
Function** out_function) {
|
||||
bool PPCTranslator::Translate(GuestFunction* function,
|
||||
uint32_t debug_info_flags) {
|
||||
SCOPE_profile_cpu_f("cpu");
|
||||
|
||||
// Reset() all caching when we leave.
|
||||
|
@ -137,7 +136,7 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info,
|
|||
}
|
||||
|
||||
// Scan the function to find its extents and gather debug data.
|
||||
if (!scanner_->Scan(symbol_info, debug_info.get())) {
|
||||
if (!scanner_->Scan(function, debug_info.get())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -153,19 +152,19 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info,
|
|||
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());
|
||||
function->address(), function->end_address());
|
||||
}
|
||||
uint8_t* trace_data = debugger->AllocateFunctionTraceData(trace_data_size);
|
||||
if (trace_data) {
|
||||
debug_info->trace_data().Reset(trace_data, trace_data_size,
|
||||
symbol_info->address(),
|
||||
symbol_info->end_address());
|
||||
function->trace_data().Reset(trace_data, trace_data_size,
|
||||
function->address(),
|
||||
function->end_address());
|
||||
}
|
||||
}
|
||||
|
||||
// Stash source.
|
||||
if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmSource) {
|
||||
DumpSource(symbol_info, &string_buffer_);
|
||||
DumpSource(function, &string_buffer_);
|
||||
debug_info->set_source_disasm(string_buffer_.ToString());
|
||||
string_buffer_.Reset();
|
||||
}
|
||||
|
@ -179,7 +178,7 @@ bool PPCTranslator::Translate(FunctionInfo* symbol_info,
|
|||
if (debug_info) {
|
||||
emit_flags |= PPCHIRBuilder::EMIT_DEBUG_COMMENTS;
|
||||
}
|
||||
if (!builder_->Emit(symbol_info, emit_flags)) {
|
||||
if (!builder_->Emit(function, emit_flags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -203,27 +202,26 @@ 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), out_function)) {
|
||||
if (!assembler_->Assemble(function, builder_.get(), debug_info_flags,
|
||||
std::move(debug_info))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
void PPCTranslator::DumpSource(FunctionInfo* symbol_info,
|
||||
void PPCTranslator::DumpSource(GuestFunction* function,
|
||||
StringBuffer* string_buffer) {
|
||||
Memory* memory = frontend_->memory();
|
||||
|
||||
string_buffer->AppendFormat(
|
||||
"%s fn %.8X-%.8X %s\n", symbol_info->module()->name().c_str(),
|
||||
symbol_info->address(), symbol_info->end_address(),
|
||||
symbol_info->name().c_str());
|
||||
"%s fn %.8X-%.8X %s\n", function->module()->name().c_str(),
|
||||
function->address(), function->end_address(), function->name().c_str());
|
||||
|
||||
auto blocks = scanner_->FindBlocks(symbol_info);
|
||||
auto blocks = scanner_->FindBlocks(function);
|
||||
|
||||
uint32_t start_address = symbol_info->address();
|
||||
uint32_t end_address = symbol_info->end_address();
|
||||
uint32_t start_address = function->address();
|
||||
uint32_t end_address = function->end_address();
|
||||
InstrData i;
|
||||
auto block_it = blocks.begin();
|
||||
for (uint32_t address = start_address, offset = 0; address <= end_address;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include "xenia/base/string_buffer.h"
|
||||
#include "xenia/cpu/backend/assembler.h"
|
||||
#include "xenia/cpu/compiler/compiler.h"
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
@ -30,13 +30,11 @@ class PPCTranslator {
|
|||
PPCTranslator(PPCFrontend* frontend);
|
||||
~PPCTranslator();
|
||||
|
||||
bool Translate(FunctionInfo* symbol_info, uint32_t debug_info_flags,
|
||||
Function** out_function);
|
||||
bool Translate(GuestFunction* function, uint32_t debug_info_flags);
|
||||
|
||||
private:
|
||||
void DumpSource(FunctionInfo* symbol_info, StringBuffer* string_buffer);
|
||||
void DumpSource(GuestFunction* function, StringBuffer* string_buffer);
|
||||
|
||||
private:
|
||||
PPCFrontend* frontend_;
|
||||
std::unique_ptr<PPCScanner> scanner_;
|
||||
std::unique_ptr<PPCHIRBuilder> builder_;
|
||||
|
|
|
@ -224,8 +224,8 @@ class TestRunner {
|
|||
}
|
||||
|
||||
// Execute test.
|
||||
xe::cpu::Function* fn = nullptr;
|
||||
if (!processor->ResolveFunction(test_case.address, &fn)) {
|
||||
auto fn = processor->ResolveFunction(test_case.address);
|
||||
if (!fn) {
|
||||
XELOGE("Entry function not found");
|
||||
return false;
|
||||
}
|
||||
|
@ -238,7 +238,9 @@ class TestRunner {
|
|||
bool result = CheckTestResults(test_case);
|
||||
if (!result) {
|
||||
// Also dump all disasm/etc.
|
||||
fn->debug_info()->Dump();
|
||||
if (fn->is_guest()) {
|
||||
static_cast<xe::cpu::GuestFunction*>(fn)->debug_info()->Dump();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "xenia/cpu/function.h"
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
#include "xenia/cpu/symbol.h"
|
||||
#include "xenia/cpu/thread_state.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -18,12 +18,56 @@ namespace cpu {
|
|||
|
||||
using xe::debug::Breakpoint;
|
||||
|
||||
Function::Function(FunctionInfo* symbol_info)
|
||||
: address_(symbol_info->address()), symbol_info_(symbol_info) {}
|
||||
Function::Function(Module* module, uint32_t address)
|
||||
: Symbol(Symbol::Type::kFunction, module, address) {}
|
||||
|
||||
Function::~Function() = default;
|
||||
|
||||
const SourceMapEntry* Function::LookupSourceOffset(uint32_t offset) const {
|
||||
BuiltinFunction::BuiltinFunction(Module* module, uint32_t address)
|
||||
: Function(module, address) {
|
||||
behavior_ = Behavior::kBuiltin;
|
||||
}
|
||||
|
||||
BuiltinFunction::~BuiltinFunction() = default;
|
||||
|
||||
void BuiltinFunction::SetupBuiltin(Handler handler, void* arg0, void* arg1) {
|
||||
behavior_ = Behavior::kBuiltin;
|
||||
handler_ = handler;
|
||||
arg0_ = arg0;
|
||||
arg1_ = arg1;
|
||||
}
|
||||
|
||||
bool BuiltinFunction::Call(ThreadState* thread_state, uint32_t return_address) {
|
||||
// SCOPE_profile_cpu_f("cpu");
|
||||
|
||||
ThreadState* original_thread_state = ThreadState::Get();
|
||||
if (original_thread_state != thread_state) {
|
||||
ThreadState::Bind(thread_state);
|
||||
}
|
||||
|
||||
assert_not_null(handler_);
|
||||
handler_(thread_state->context(), arg0_, arg1_);
|
||||
|
||||
if (original_thread_state != thread_state) {
|
||||
ThreadState::Bind(original_thread_state);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GuestFunction::GuestFunction(Module* module, uint32_t address)
|
||||
: Function(module, address) {
|
||||
behavior_ = Behavior::kDefault;
|
||||
}
|
||||
|
||||
GuestFunction::~GuestFunction() = default;
|
||||
|
||||
void GuestFunction::SetupExtern(ExternHandler handler) {
|
||||
behavior_ = Behavior::kExtern;
|
||||
extern_handler_ = handler;
|
||||
}
|
||||
|
||||
const SourceMapEntry* GuestFunction::LookupSourceOffset(uint32_t offset) const {
|
||||
// TODO(benvanik): binary search? We know the list is sorted by code order.
|
||||
for (size_t i = 0; i < source_map_.size(); ++i) {
|
||||
const auto& entry = source_map_[i];
|
||||
|
@ -34,7 +78,7 @@ const SourceMapEntry* Function::LookupSourceOffset(uint32_t offset) const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
const SourceMapEntry* Function::LookupHIROffset(uint32_t offset) const {
|
||||
const SourceMapEntry* GuestFunction::LookupHIROffset(uint32_t offset) const {
|
||||
// TODO(benvanik): binary search? We know the list is sorted by code order.
|
||||
for (size_t i = 0; i < source_map_.size(); ++i) {
|
||||
const auto& entry = source_map_[i];
|
||||
|
@ -45,7 +89,7 @@ const SourceMapEntry* Function::LookupHIROffset(uint32_t offset) const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
const SourceMapEntry* Function::LookupCodeOffset(uint32_t offset) const {
|
||||
const SourceMapEntry* GuestFunction::LookupCodeOffset(uint32_t offset) const {
|
||||
// TODO(benvanik): binary search? We know the list is sorted by code order.
|
||||
for (int64_t i = source_map_.size() - 1; i >= 0; --i) {
|
||||
const auto& entry = source_map_[i];
|
||||
|
@ -56,49 +100,7 @@ const SourceMapEntry* Function::LookupCodeOffset(uint32_t offset) const {
|
|||
return source_map_.empty() ? nullptr : &source_map_[0];
|
||||
}
|
||||
|
||||
bool Function::AddBreakpoint(Breakpoint* breakpoint) {
|
||||
std::lock_guard<xe::mutex> guard(lock_);
|
||||
bool found = false;
|
||||
for (auto other : breakpoints_) {
|
||||
if (other == breakpoint) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
return true;
|
||||
} else {
|
||||
breakpoints_.push_back(breakpoint);
|
||||
return AddBreakpointImpl(breakpoint);
|
||||
}
|
||||
}
|
||||
|
||||
bool Function::RemoveBreakpoint(Breakpoint* breakpoint) {
|
||||
std::lock_guard<xe::mutex> guard(lock_);
|
||||
for (auto it = breakpoints_.begin(); it != breakpoints_.end(); ++it) {
|
||||
if (*it == breakpoint) {
|
||||
if (!RemoveBreakpointImpl(breakpoint)) {
|
||||
return false;
|
||||
}
|
||||
breakpoints_.erase(it);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Breakpoint* Function::FindBreakpoint(uint32_t address) {
|
||||
std::lock_guard<xe::mutex> guard(lock_);
|
||||
Breakpoint* result = nullptr;
|
||||
for (auto breakpoint : breakpoints_) {
|
||||
if (breakpoint->address() == address) {
|
||||
result = breakpoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Function::Call(ThreadState* thread_state, uint32_t return_address) {
|
||||
bool GuestFunction::Call(ThreadState* thread_state, uint32_t return_address) {
|
||||
// SCOPE_profile_cpu_f("cpu");
|
||||
|
||||
ThreadState* original_thread_state = ThreadState::Get();
|
||||
|
@ -106,29 +108,25 @@ bool Function::Call(ThreadState* thread_state, uint32_t return_address) {
|
|||
ThreadState::Bind(thread_state);
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
|
||||
if (symbol_info_->behavior() == FunctionBehavior::kBuiltin) {
|
||||
auto handler = symbol_info_->builtin_handler();
|
||||
assert_not_null(handler);
|
||||
handler(thread_state->context(), symbol_info_->builtin_arg0(),
|
||||
symbol_info_->builtin_arg1());
|
||||
} else if (symbol_info_->behavior() == FunctionBehavior::kExtern) {
|
||||
auto handler = symbol_info_->extern_handler();
|
||||
if (handler) {
|
||||
handler(thread_state->context(), thread_state->context()->kernel_state);
|
||||
bool result = false;
|
||||
if (behavior_ == Behavior::kExtern) {
|
||||
// Special handling for extern functions to speed things up (we don't
|
||||
// trampoline into guest code only to trampoline back out).
|
||||
if (extern_handler_) {
|
||||
extern_handler_(thread_state->context(),
|
||||
thread_state->context()->kernel_state);
|
||||
} else {
|
||||
XELOGW("undefined extern call to %.8X %s", symbol_info_->address(),
|
||||
symbol_info_->name().c_str());
|
||||
XELOGW("undefined extern call to %.8X %s", address(), name().c_str());
|
||||
result = false;
|
||||
}
|
||||
} else {
|
||||
CallImpl(thread_state, return_address);
|
||||
result = CallImpl(thread_state, return_address);
|
||||
}
|
||||
|
||||
if (original_thread_state != thread_state) {
|
||||
ThreadState::Bind(original_thread_state);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,32 +11,89 @@
|
|||
#define XENIA_CPU_FUNCTION_H_
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/mutex.h"
|
||||
#include "xenia/cpu/debug_info.h"
|
||||
#include "xenia/cpu/frontend/ppc_context.h"
|
||||
#include "xenia/cpu/symbol.h"
|
||||
#include "xenia/cpu/thread_state.h"
|
||||
#include "xenia/debug/breakpoint.h"
|
||||
#include "xenia/debug/function_trace_data.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
class FunctionInfo;
|
||||
|
||||
struct SourceMapEntry {
|
||||
uint32_t source_offset; // Original source address/offset.
|
||||
uint32_t hir_offset; // Block ordinal (16b) | Instr ordinal (16b)
|
||||
uint32_t code_offset; // Offset from emitted code start.
|
||||
};
|
||||
|
||||
class Function {
|
||||
class Function : public Symbol {
|
||||
public:
|
||||
Function(FunctionInfo* symbol_info);
|
||||
virtual ~Function();
|
||||
enum class Behavior {
|
||||
kDefault = 0,
|
||||
kProlog,
|
||||
kEpilog,
|
||||
kEpilogReturn,
|
||||
kBuiltin,
|
||||
kExtern,
|
||||
};
|
||||
|
||||
~Function() override;
|
||||
|
||||
uint32_t address() const { return address_; }
|
||||
FunctionInfo* symbol_info() const { return symbol_info_; }
|
||||
bool has_end_address() const { return end_address_ > 0; }
|
||||
uint32_t end_address() const { return end_address_; }
|
||||
void set_end_address(uint32_t value) { end_address_ = value; }
|
||||
Behavior behavior() const { return behavior_; }
|
||||
void set_behavior(Behavior value) { behavior_ = value; }
|
||||
bool is_guest() const { return behavior_ != Behavior::kBuiltin; }
|
||||
|
||||
virtual bool Call(ThreadState* thread_state, uint32_t return_address) = 0;
|
||||
|
||||
protected:
|
||||
Function(Module* module, uint32_t address);
|
||||
|
||||
uint32_t end_address_ = 0;
|
||||
Behavior behavior_ = Behavior::kDefault;
|
||||
};
|
||||
|
||||
class BuiltinFunction : public Function {
|
||||
public:
|
||||
typedef void (*Handler)(frontend::PPCContext* ppc_context, void* arg0,
|
||||
void* arg1);
|
||||
|
||||
BuiltinFunction(Module* module, uint32_t address);
|
||||
~BuiltinFunction() override;
|
||||
|
||||
void SetupBuiltin(Handler handler, void* arg0, void* arg1);
|
||||
|
||||
Handler handler() const { return handler_; }
|
||||
void* arg0() const { return arg0_; }
|
||||
void* arg1() const { return arg1_; }
|
||||
|
||||
bool Call(ThreadState* thread_state, uint32_t return_address) override;
|
||||
|
||||
protected:
|
||||
Handler handler_ = nullptr;
|
||||
void* arg0_ = nullptr;
|
||||
void* arg1_ = nullptr;
|
||||
};
|
||||
|
||||
class GuestFunction : public Function {
|
||||
public:
|
||||
typedef void (*ExternHandler)(frontend::PPCContext* ppc_context,
|
||||
kernel::KernelState* kernel_state);
|
||||
|
||||
GuestFunction(Module* module, uint32_t address);
|
||||
~GuestFunction() override;
|
||||
|
||||
uint32_t address() const { return address_; }
|
||||
bool has_end_address() const { return end_address_ > 0; }
|
||||
uint32_t end_address() const { return end_address_; }
|
||||
void set_end_address(uint32_t value) { end_address_ = value; }
|
||||
|
||||
virtual uint8_t* machine_code() const = 0;
|
||||
virtual size_t machine_code_length() const = 0;
|
||||
|
@ -45,32 +102,26 @@ class Function {
|
|||
void set_debug_info(std::unique_ptr<DebugInfo> debug_info) {
|
||||
debug_info_ = std::move(debug_info);
|
||||
}
|
||||
debug::FunctionTraceData& trace_data() { return trace_data_; }
|
||||
std::vector<SourceMapEntry>& source_map() { return source_map_; }
|
||||
|
||||
ExternHandler extern_handler() const { return extern_handler_; }
|
||||
void SetupExtern(ExternHandler handler);
|
||||
|
||||
const SourceMapEntry* LookupSourceOffset(uint32_t offset) const;
|
||||
const SourceMapEntry* LookupHIROffset(uint32_t offset) const;
|
||||
const SourceMapEntry* LookupCodeOffset(uint32_t offset) const;
|
||||
|
||||
bool AddBreakpoint(debug::Breakpoint* breakpoint);
|
||||
bool RemoveBreakpoint(debug::Breakpoint* breakpoint);
|
||||
|
||||
bool Call(ThreadState* thread_state, uint32_t return_address);
|
||||
bool Call(ThreadState* thread_state, uint32_t return_address) override;
|
||||
|
||||
protected:
|
||||
debug::Breakpoint* FindBreakpoint(uint32_t address);
|
||||
virtual bool AddBreakpointImpl(debug::Breakpoint* breakpoint) { return 0; }
|
||||
virtual bool RemoveBreakpointImpl(debug::Breakpoint* breakpoint) { return 0; }
|
||||
virtual bool CallImpl(ThreadState* thread_state, uint32_t return_address) = 0;
|
||||
|
||||
protected:
|
||||
uint32_t address_;
|
||||
FunctionInfo* symbol_info_;
|
||||
std::unique_ptr<DebugInfo> debug_info_;
|
||||
debug::FunctionTraceData trace_data_;
|
||||
std::vector<SourceMapEntry> source_map_;
|
||||
|
||||
// TODO(benvanik): move elsewhere? DebugData?
|
||||
xe::mutex lock_;
|
||||
std::vector<debug::Breakpoint*> breakpoints_;
|
||||
ExternHandler extern_handler_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
|
|
|
@ -13,10 +13,11 @@
|
|||
#include <cstring>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
#include "xenia/cpu/hir/block.h"
|
||||
#include "xenia/cpu/hir/instr.h"
|
||||
#include "xenia/cpu/hir/label.h"
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
#include "xenia/cpu/symbol.h"
|
||||
#include "xenia/profiling.h"
|
||||
|
||||
// Will scribble arena memory to hopefully find use before clears.
|
||||
|
@ -162,7 +163,7 @@ void HIRBuilder::DumpOp(StringBuffer* str, OpcodeSignatureType sig_type,
|
|||
break;
|
||||
case OPCODE_SIG_TYPE_S:
|
||||
if (true) {
|
||||
auto target = op->symbol_info;
|
||||
auto target = op->symbol;
|
||||
str->Append(!target->name().empty() ? target->name() : "<fn>");
|
||||
}
|
||||
break;
|
||||
|
@ -820,25 +821,24 @@ void HIRBuilder::TrapTrue(Value* cond, uint16_t trap_code) {
|
|||
EndBlock();
|
||||
}
|
||||
|
||||
void HIRBuilder::Call(FunctionInfo* symbol_info, uint16_t call_flags) {
|
||||
void HIRBuilder::Call(Function* symbol, uint16_t call_flags) {
|
||||
Instr* i = AppendInstr(OPCODE_CALL_info, call_flags);
|
||||
i->src1.symbol_info = symbol_info;
|
||||
i->src1.symbol = symbol;
|
||||
i->src2.value = i->src3.value = NULL;
|
||||
EndBlock();
|
||||
}
|
||||
|
||||
void HIRBuilder::CallTrue(Value* cond, FunctionInfo* symbol_info,
|
||||
uint16_t call_flags) {
|
||||
void HIRBuilder::CallTrue(Value* cond, Function* symbol, uint16_t call_flags) {
|
||||
if (cond->IsConstant()) {
|
||||
if (cond->IsConstantTrue()) {
|
||||
Call(symbol_info, call_flags);
|
||||
Call(symbol, call_flags);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Instr* i = AppendInstr(OPCODE_CALL_TRUE_info, call_flags);
|
||||
i->set_src1(cond);
|
||||
i->src2.symbol_info = symbol_info;
|
||||
i->src2.symbol = symbol;
|
||||
i->src3.value = NULL;
|
||||
EndBlock();
|
||||
}
|
||||
|
@ -868,9 +868,9 @@ void HIRBuilder::CallIndirectTrue(Value* cond, Value* value,
|
|||
EndBlock();
|
||||
}
|
||||
|
||||
void HIRBuilder::CallExtern(FunctionInfo* symbol_info) {
|
||||
void HIRBuilder::CallExtern(Function* symbol) {
|
||||
Instr* i = AppendInstr(OPCODE_CALL_EXTERN_info, 0);
|
||||
i->src1.symbol_info = symbol_info;
|
||||
i->src1.symbol = symbol;
|
||||
i->src2.value = i->src3.value = NULL;
|
||||
EndBlock();
|
||||
}
|
||||
|
|
|
@ -83,12 +83,11 @@ class HIRBuilder {
|
|||
void Trap(uint16_t trap_code = 0);
|
||||
void TrapTrue(Value* cond, uint16_t trap_code = 0);
|
||||
|
||||
void Call(FunctionInfo* symbol_info, uint16_t call_flags = 0);
|
||||
void CallTrue(Value* cond, FunctionInfo* symbol_info,
|
||||
uint16_t call_flags = 0);
|
||||
void Call(Function* symbol, uint16_t call_flags = 0);
|
||||
void CallTrue(Value* cond, Function* symbol, uint16_t call_flags = 0);
|
||||
void CallIndirect(Value* value, uint16_t call_flags = 0);
|
||||
void CallIndirectTrue(Value* cond, Value* value, uint16_t call_flags = 0);
|
||||
void CallExtern(FunctionInfo* symbol_info);
|
||||
void CallExtern(Function* symbol);
|
||||
void Return();
|
||||
void ReturnTrue(Value* cond);
|
||||
void SetReturnAddress(Value* value);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
class FunctionInfo;
|
||||
class Function;
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
|
@ -37,7 +37,7 @@ class Instr {
|
|||
uint32_t ordinal;
|
||||
|
||||
typedef union {
|
||||
FunctionInfo* symbol_info;
|
||||
Function* symbol;
|
||||
Label* label;
|
||||
Value* value;
|
||||
uint64_t offset;
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/cpu/instrument.h"
|
||||
|
||||
#include "xenia/memory.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
Instrument::Instrument(Processor* processor)
|
||||
: processor_(processor),
|
||||
memory_(processor->memory()),
|
||||
is_attached_(false) {}
|
||||
|
||||
Instrument::~Instrument() {
|
||||
if (is_attached_) {
|
||||
Detach();
|
||||
}
|
||||
}
|
||||
|
||||
bool Instrument::Attach() {
|
||||
if (is_attached_) {
|
||||
return false;
|
||||
}
|
||||
// runtime->AttachInstrument(this);
|
||||
is_attached_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Instrument::Detach() {
|
||||
if (!is_attached_) {
|
||||
return false;
|
||||
}
|
||||
is_attached_ = false;
|
||||
// runtime->DetachInstrument(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
FunctionInstrument::FunctionInstrument(Processor* processor, Function* function)
|
||||
: Instrument(processor), target_(function) {}
|
||||
|
||||
bool FunctionInstrument::Attach() {
|
||||
if (!Instrument::Attach()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Function impl attach:
|
||||
// - add instrument to list
|
||||
// JIT: transition to instrumented state
|
||||
// - rewrite enter/exit to jump to instrumentation thunk
|
||||
// - some sort of locking required?
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionInstrument::Detach() {
|
||||
if (!Instrument::Detach()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FunctionInstrument::Enter(ThreadState* thread_state) {
|
||||
// ? Placement new? How to get instance type?
|
||||
}
|
||||
|
||||
void FunctionInstrument::Exit(ThreadState* thread_state) {
|
||||
//
|
||||
}
|
||||
|
||||
MemoryInstrument::MemoryInstrument(Processor* processor, uint32_t address,
|
||||
uint32_t end_address)
|
||||
: Instrument(processor), address_(address), end_address_(end_address) {}
|
||||
|
||||
bool MemoryInstrument::Attach() {
|
||||
if (!Instrument::Attach()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(benvanik): protect memory page, catch exception
|
||||
// https://github.com/frida/frida-gum/blob/master/gum/gummemoryaccessmonitor.c
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemoryInstrument::Detach() {
|
||||
if (!Instrument::Detach()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MemoryInstrument::Access(ThreadState* thread_state, uint32_t address,
|
||||
AccessType type) {
|
||||
// TODO(benvanik): get thread local instance
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
|
@ -1,142 +0,0 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_CPU_INSTRUMENT_H_
|
||||
#define XENIA_CPU_INSTRUMENT_H_
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
class Function;
|
||||
class Memory;
|
||||
class Processor;
|
||||
class ThreadState;
|
||||
|
||||
class Instrument {
|
||||
public:
|
||||
Instrument(Processor* processor);
|
||||
virtual ~Instrument();
|
||||
|
||||
Processor* processor() const { return processor_; }
|
||||
Memory* memory() const { return memory_; }
|
||||
bool is_attached() const { return is_attached_; }
|
||||
|
||||
virtual bool Attach();
|
||||
virtual bool Detach();
|
||||
|
||||
private:
|
||||
Processor* processor_;
|
||||
Memory* memory_;
|
||||
bool is_attached_;
|
||||
};
|
||||
|
||||
class FunctionInstrument : public Instrument {
|
||||
public:
|
||||
FunctionInstrument(Processor* processor, Function* function);
|
||||
virtual ~FunctionInstrument() {}
|
||||
|
||||
Function* target() const { return target_; }
|
||||
|
||||
virtual bool Attach();
|
||||
virtual bool Detach();
|
||||
|
||||
public:
|
||||
void Enter(ThreadState* thread_state);
|
||||
void Exit(ThreadState* thread_state);
|
||||
|
||||
protected:
|
||||
class Instance {
|
||||
public:
|
||||
Instance(FunctionInstrument* instrument) : instrument_(instrument) {}
|
||||
virtual ~Instance() {}
|
||||
|
||||
FunctionInstrument* instrument() const { return instrument_; }
|
||||
|
||||
virtual void OnEnter(ThreadState* thread_state) = 0;
|
||||
virtual void OnExit(ThreadState* thread_state) = 0;
|
||||
|
||||
// TODO(benvanik): utilities:
|
||||
// Log(...)
|
||||
// DebugBreak(type)
|
||||
// GetArg(N)/GetReturn()/SetReturn(V) ?
|
||||
// CallSelf()
|
||||
// Call(target_fn/address)
|
||||
// Return(opt_value)
|
||||
|
||||
private:
|
||||
FunctionInstrument* instrument_;
|
||||
};
|
||||
|
||||
private:
|
||||
Function* target_;
|
||||
};
|
||||
|
||||
class MemoryInstrument : public Instrument {
|
||||
public:
|
||||
MemoryInstrument(Processor* processor, uint32_t address,
|
||||
uint32_t end_address);
|
||||
virtual ~MemoryInstrument() {}
|
||||
|
||||
uint64_t address() const { return address_; }
|
||||
uint64_t end_address() const { return end_address_; }
|
||||
|
||||
virtual bool Attach();
|
||||
virtual bool Detach();
|
||||
|
||||
public:
|
||||
enum AccessType {
|
||||
ACCESS_READ = (1 << 1),
|
||||
ACCESS_WRITE = (1 << 2),
|
||||
};
|
||||
void Access(ThreadState* thread_state, uint32_t address, AccessType type);
|
||||
|
||||
protected:
|
||||
class Instance {
|
||||
public:
|
||||
Instance(MemoryInstrument* instrument) : instrument_(instrument) {}
|
||||
virtual ~Instance() {}
|
||||
|
||||
MemoryInstrument* instrument() const { return instrument_; }
|
||||
|
||||
virtual void OnAccess(ThreadState* thread_state, uint32_t address,
|
||||
AccessType type) = 0;
|
||||
|
||||
private:
|
||||
MemoryInstrument* instrument_;
|
||||
};
|
||||
|
||||
private:
|
||||
uint32_t address_;
|
||||
uint32_t end_address_;
|
||||
};
|
||||
|
||||
// ThreadInstrument
|
||||
// (v Detach())
|
||||
// ThreadInstrumentInstance:
|
||||
// instrument()
|
||||
// v OnCreate(context, state_ptr)
|
||||
// v OnStart(context, state_ptr)
|
||||
// v OnResume(context, state_ptr)
|
||||
// v OnSuspend(context, state_ptr)
|
||||
// v OnExit(context, state_ptr)
|
||||
|
||||
// ModuleInstrument
|
||||
// (v Detach())
|
||||
// ModuleInstrumentInstance:
|
||||
// instrument()
|
||||
// v OnLoad(context)
|
||||
// v OnUnload(context)
|
||||
// // get proc address?
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_INSTRUMENT_H_
|
|
@ -26,12 +26,12 @@ Module::~Module() = default;
|
|||
|
||||
bool Module::ContainsAddress(uint32_t address) { return true; }
|
||||
|
||||
SymbolInfo* Module::LookupSymbol(uint32_t address, bool wait) {
|
||||
Symbol* Module::LookupSymbol(uint32_t address, bool wait) {
|
||||
lock_.lock();
|
||||
const auto it = map_.find(address);
|
||||
SymbolInfo* symbol_info = it != map_.end() ? it->second : nullptr;
|
||||
if (symbol_info) {
|
||||
if (symbol_info->status() == SymbolStatus::kDeclaring) {
|
||||
Symbol* symbol = it != map_.end() ? it->second : nullptr;
|
||||
if (symbol) {
|
||||
if (symbol->status() == Symbol::Status::kDeclaring) {
|
||||
// Some other thread is declaring the symbol - wait.
|
||||
if (wait) {
|
||||
do {
|
||||
|
@ -39,133 +39,130 @@ SymbolInfo* Module::LookupSymbol(uint32_t address, bool wait) {
|
|||
// TODO(benvanik): sleep for less time?
|
||||
xe::threading::Sleep(std::chrono::microseconds(100));
|
||||
lock_.lock();
|
||||
} while (symbol_info->status() == SymbolStatus::kDeclaring);
|
||||
} while (symbol->status() == Symbol::Status::kDeclaring);
|
||||
} else {
|
||||
// Immediate request, just return.
|
||||
symbol_info = nullptr;
|
||||
symbol = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
lock_.unlock();
|
||||
return symbol_info;
|
||||
return symbol;
|
||||
}
|
||||
|
||||
SymbolStatus Module::DeclareSymbol(SymbolType type, uint32_t address,
|
||||
SymbolInfo** out_symbol_info) {
|
||||
*out_symbol_info = nullptr;
|
||||
Symbol::Status Module::DeclareSymbol(Symbol::Type type, uint32_t address,
|
||||
Symbol** out_symbol) {
|
||||
*out_symbol = nullptr;
|
||||
lock_.lock();
|
||||
auto it = map_.find(address);
|
||||
SymbolInfo* symbol_info = it != map_.end() ? it->second : nullptr;
|
||||
SymbolStatus status;
|
||||
if (symbol_info) {
|
||||
Symbol* symbol = it != map_.end() ? it->second : nullptr;
|
||||
Symbol::Status status;
|
||||
if (symbol) {
|
||||
// If we exist but are the wrong type, die.
|
||||
if (symbol_info->type() != type) {
|
||||
if (symbol->type() != type) {
|
||||
lock_.unlock();
|
||||
return SymbolStatus::kFailed;
|
||||
return Symbol::Status::kFailed;
|
||||
}
|
||||
// If we aren't ready yet spin and wait.
|
||||
if (symbol_info->status() == SymbolStatus::kDeclaring) {
|
||||
if (symbol->status() == Symbol::Status::kDeclaring) {
|
||||
// Still declaring, so spin.
|
||||
do {
|
||||
lock_.unlock();
|
||||
// TODO(benvanik): sleep for less time?
|
||||
xe::threading::Sleep(std::chrono::microseconds(100));
|
||||
lock_.lock();
|
||||
} while (symbol_info->status() == SymbolStatus::kDeclaring);
|
||||
} while (symbol->status() == Symbol::Status::kDeclaring);
|
||||
}
|
||||
status = symbol_info->status();
|
||||
status = symbol->status();
|
||||
} else {
|
||||
// Create and return for initialization.
|
||||
switch (type) {
|
||||
case SymbolType::kFunction:
|
||||
symbol_info = new FunctionInfo(this, address);
|
||||
case Symbol::Type::kFunction:
|
||||
symbol = CreateFunction(address).release();
|
||||
break;
|
||||
case SymbolType::kVariable:
|
||||
symbol_info = new VariableInfo(this, address);
|
||||
case Symbol::Type::kVariable:
|
||||
symbol = new Symbol(Symbol::Type::kVariable, this, address);
|
||||
break;
|
||||
}
|
||||
map_[address] = symbol_info;
|
||||
list_.emplace_back(symbol_info);
|
||||
status = SymbolStatus::kNew;
|
||||
map_[address] = symbol;
|
||||
list_.emplace_back(symbol);
|
||||
status = Symbol::Status::kNew;
|
||||
}
|
||||
lock_.unlock();
|
||||
*out_symbol_info = symbol_info;
|
||||
*out_symbol = symbol;
|
||||
|
||||
// Get debug info from providers, if this is new.
|
||||
if (status == SymbolStatus::kNew) {
|
||||
if (status == Symbol::Status::kNew) {
|
||||
// TODO(benvanik): lookup in map data/dwarf/etc?
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
SymbolStatus Module::DeclareFunction(uint32_t address,
|
||||
FunctionInfo** out_symbol_info) {
|
||||
SymbolInfo* symbol_info;
|
||||
SymbolStatus status =
|
||||
DeclareSymbol(SymbolType::kFunction, address, &symbol_info);
|
||||
*out_symbol_info = (FunctionInfo*)symbol_info;
|
||||
Symbol::Status Module::DeclareFunction(uint32_t address,
|
||||
Function** out_function) {
|
||||
Symbol* symbol;
|
||||
Symbol::Status status =
|
||||
DeclareSymbol(Symbol::Type::kFunction, address, &symbol);
|
||||
*out_function = static_cast<Function*>(symbol);
|
||||
return status;
|
||||
}
|
||||
|
||||
SymbolStatus Module::DeclareVariable(uint32_t address,
|
||||
VariableInfo** out_symbol_info) {
|
||||
SymbolInfo* symbol_info;
|
||||
SymbolStatus status =
|
||||
DeclareSymbol(SymbolType::kVariable, address, &symbol_info);
|
||||
*out_symbol_info = (VariableInfo*)symbol_info;
|
||||
Symbol::Status Module::DeclareVariable(uint32_t address, Symbol** out_symbol) {
|
||||
Symbol::Status status =
|
||||
DeclareSymbol(Symbol::Type::kVariable, address, out_symbol);
|
||||
return status;
|
||||
}
|
||||
|
||||
SymbolStatus Module::DefineSymbol(SymbolInfo* symbol_info) {
|
||||
Symbol::Status Module::DefineSymbol(Symbol* symbol) {
|
||||
lock_.lock();
|
||||
SymbolStatus status;
|
||||
if (symbol_info->status() == SymbolStatus::kDeclared) {
|
||||
Symbol::Status status;
|
||||
if (symbol->status() == Symbol::Status::kDeclared) {
|
||||
// Declared but undefined, so request caller define it.
|
||||
symbol_info->set_status(SymbolStatus::kDefining);
|
||||
status = SymbolStatus::kNew;
|
||||
} else if (symbol_info->status() == SymbolStatus::kDefining) {
|
||||
symbol->set_status(Symbol::Status::kDefining);
|
||||
status = Symbol::Status::kNew;
|
||||
} else if (symbol->status() == Symbol::Status::kDefining) {
|
||||
// Still defining, so spin.
|
||||
do {
|
||||
lock_.unlock();
|
||||
// TODO(benvanik): sleep for less time?
|
||||
xe::threading::Sleep(std::chrono::microseconds(100));
|
||||
lock_.lock();
|
||||
} while (symbol_info->status() == SymbolStatus::kDefining);
|
||||
status = symbol_info->status();
|
||||
} while (symbol->status() == Symbol::Status::kDefining);
|
||||
status = symbol->status();
|
||||
} else {
|
||||
status = symbol_info->status();
|
||||
status = symbol->status();
|
||||
}
|
||||
lock_.unlock();
|
||||
return status;
|
||||
}
|
||||
|
||||
SymbolStatus Module::DefineFunction(FunctionInfo* symbol_info) {
|
||||
return DefineSymbol((SymbolInfo*)symbol_info);
|
||||
Symbol::Status Module::DefineFunction(Function* symbol) {
|
||||
return DefineSymbol((Symbol*)symbol);
|
||||
}
|
||||
|
||||
SymbolStatus Module::DefineVariable(VariableInfo* symbol_info) {
|
||||
return DefineSymbol((SymbolInfo*)symbol_info);
|
||||
Symbol::Status Module::DefineVariable(Symbol* symbol) {
|
||||
return DefineSymbol((Symbol*)symbol);
|
||||
}
|
||||
|
||||
void Module::ForEachFunction(std::function<void(FunctionInfo*)> callback) {
|
||||
void Module::ForEachFunction(std::function<void(Function*)> callback) {
|
||||
std::lock_guard<xe::mutex> guard(lock_);
|
||||
for (auto& symbol_info : list_) {
|
||||
if (symbol_info->type() == SymbolType::kFunction) {
|
||||
FunctionInfo* info = static_cast<FunctionInfo*>(symbol_info.get());
|
||||
for (auto& symbol : list_) {
|
||||
if (symbol->type() == Symbol::Type::kFunction) {
|
||||
Function* info = static_cast<Function*>(symbol.get());
|
||||
callback(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Module::ForEachSymbol(size_t start_index, size_t end_index,
|
||||
std::function<void(SymbolInfo*)> callback) {
|
||||
std::function<void(Symbol*)> callback) {
|
||||
std::lock_guard<xe::mutex> guard(lock_);
|
||||
start_index = std::min(start_index, list_.size());
|
||||
end_index = std::min(end_index, list_.size());
|
||||
for (size_t i = start_index; i <= end_index; ++i) {
|
||||
auto& symbol_info = list_[i];
|
||||
callback(symbol_info.get());
|
||||
auto& symbol = list_[i];
|
||||
callback(symbol.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,19 +220,19 @@ bool Module::ReadMap(const char* file_name) {
|
|||
|
||||
if (type_str == "f") {
|
||||
// Function.
|
||||
FunctionInfo* fn_info;
|
||||
if (!processor_->LookupFunctionInfo(this, address, &fn_info)) {
|
||||
auto function = processor_->LookupFunction(this, address);
|
||||
if (!function) {
|
||||
continue;
|
||||
}
|
||||
// Don't overwrite names we've set elsewhere.
|
||||
if (fn_info->name().empty()) {
|
||||
if (function->name().empty()) {
|
||||
// If it's a mangled C++ name (?name@...) just use the name.
|
||||
// TODO(benvanik): better demangling, or leave it to clients.
|
||||
/*if (name[0] == '?') {
|
||||
size_t at = name.find('@');
|
||||
name = name.substr(1, at - 1);
|
||||
}*/
|
||||
fn_info->set_name(name.c_str());
|
||||
function->set_name(name.c_str());
|
||||
}
|
||||
} else {
|
||||
// Variable.
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
#include <vector>
|
||||
|
||||
#include "xenia/base/mutex.h"
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
#include "xenia/cpu/symbol.h"
|
||||
#include "xenia/memory.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
class Function;
|
||||
class Processor;
|
||||
|
||||
class Module {
|
||||
|
@ -37,36 +37,36 @@ class Module {
|
|||
|
||||
virtual bool ContainsAddress(uint32_t address);
|
||||
|
||||
SymbolInfo* LookupSymbol(uint32_t address, bool wait = true);
|
||||
virtual SymbolStatus DeclareFunction(uint32_t address,
|
||||
FunctionInfo** out_symbol_info);
|
||||
virtual SymbolStatus DeclareVariable(uint32_t address,
|
||||
VariableInfo** out_symbol_info);
|
||||
Symbol* LookupSymbol(uint32_t address, bool wait = true);
|
||||
virtual Symbol::Status DeclareFunction(uint32_t address,
|
||||
Function** out_function);
|
||||
virtual Symbol::Status DeclareVariable(uint32_t address, Symbol** out_symbol);
|
||||
|
||||
SymbolStatus DefineFunction(FunctionInfo* symbol_info);
|
||||
SymbolStatus DefineVariable(VariableInfo* symbol_info);
|
||||
Symbol::Status DefineFunction(Function* symbol);
|
||||
Symbol::Status DefineVariable(Symbol* symbol);
|
||||
|
||||
void ForEachFunction(std::function<void(FunctionInfo*)> callback);
|
||||
void ForEachFunction(std::function<void(Function*)> callback);
|
||||
void ForEachSymbol(size_t start_index, size_t end_index,
|
||||
std::function<void(SymbolInfo*)> callback);
|
||||
std::function<void(Symbol*)> callback);
|
||||
size_t QuerySymbolCount();
|
||||
|
||||
bool ReadMap(const char* file_name);
|
||||
|
||||
private:
|
||||
SymbolStatus DeclareSymbol(SymbolType type, uint32_t address,
|
||||
SymbolInfo** out_symbol_info);
|
||||
SymbolStatus DefineSymbol(SymbolInfo* symbol_info);
|
||||
|
||||
protected:
|
||||
Processor* processor_;
|
||||
Memory* memory_;
|
||||
virtual std::unique_ptr<Function> CreateFunction(uint32_t address) = 0;
|
||||
|
||||
Processor* processor_ = nullptr;
|
||||
Memory* memory_ = nullptr;
|
||||
|
||||
private:
|
||||
Symbol::Status DeclareSymbol(Symbol::Type type, uint32_t address,
|
||||
Symbol** out_symbol);
|
||||
Symbol::Status DefineSymbol(Symbol* symbol);
|
||||
|
||||
// TODO(benvanik): replace with a better data structure.
|
||||
xe::mutex lock_;
|
||||
std::unordered_map<uint32_t, SymbolInfo*> map_;
|
||||
std::vector<std::unique_ptr<SymbolInfo>> list_;
|
||||
std::unordered_map<uint32_t, Symbol*> map_;
|
||||
std::vector<std::unique_ptr<Symbol>> list_;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
|
|
|
@ -42,6 +42,11 @@ class BuiltinModule : public Module {
|
|||
return (address & 0xFFFFFFF0) == 0xFFFFFFF0;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Function> CreateFunction(uint32_t address) override {
|
||||
return std::unique_ptr<Function>(new BuiltinFunction(this, address));
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
};
|
||||
|
@ -142,20 +147,22 @@ std::vector<Module*> Processor::GetModules() {
|
|||
return clone;
|
||||
}
|
||||
|
||||
FunctionInfo* Processor::DefineBuiltin(const std::string& name,
|
||||
FunctionInfo::BuiltinHandler handler,
|
||||
void* arg0, void* arg1) {
|
||||
Function* Processor::DefineBuiltin(const std::string& name,
|
||||
BuiltinFunction::Handler handler, void* arg0,
|
||||
void* arg1) {
|
||||
uint32_t address = next_builtin_address_;
|
||||
next_builtin_address_ += 4;
|
||||
|
||||
FunctionInfo* fn_info;
|
||||
builtin_module_->DeclareFunction(address, &fn_info);
|
||||
fn_info->set_end_address(address + 4);
|
||||
fn_info->set_name(name);
|
||||
fn_info->SetupBuiltin(handler, arg0, arg1);
|
||||
fn_info->set_status(SymbolStatus::kDeclared);
|
||||
Function* function;
|
||||
builtin_module_->DeclareFunction(address, &function);
|
||||
function->set_end_address(address + 4);
|
||||
function->set_name(name);
|
||||
|
||||
return fn_info;
|
||||
auto builtin_function = static_cast<BuiltinFunction*>(function);
|
||||
builtin_function->SetupBuiltin(handler, arg0, arg1);
|
||||
|
||||
function->set_status(Symbol::Status::kDeclared);
|
||||
return function;
|
||||
}
|
||||
|
||||
Function* Processor::QueryFunction(uint32_t address) {
|
||||
|
@ -170,40 +177,36 @@ std::vector<Function*> Processor::FindFunctionsWithAddress(uint32_t address) {
|
|||
return entry_table_.FindWithAddress(address);
|
||||
}
|
||||
|
||||
bool Processor::ResolveFunction(uint32_t address, Function** out_function) {
|
||||
*out_function = nullptr;
|
||||
Function* Processor::ResolveFunction(uint32_t address) {
|
||||
Entry* entry;
|
||||
Entry::Status status = entry_table_.GetOrCreate(address, &entry);
|
||||
if (status == Entry::STATUS_NEW) {
|
||||
// Needs to be generated. We have the 'lock' on it and must do so now.
|
||||
|
||||
// Grab symbol declaration.
|
||||
FunctionInfo* symbol_info;
|
||||
if (!LookupFunctionInfo(address, &symbol_info)) {
|
||||
return false;
|
||||
auto function = LookupFunction(address);
|
||||
if (!function) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!DemandFunction(symbol_info, &entry->function)) {
|
||||
if (!DemandFunction(function)) {
|
||||
entry->status = Entry::STATUS_FAILED;
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
entry->end_address = symbol_info->end_address();
|
||||
entry->function = function;
|
||||
entry->end_address = function->end_address();
|
||||
status = entry->status = Entry::STATUS_READY;
|
||||
}
|
||||
if (status == Entry::STATUS_READY) {
|
||||
// Ready to use.
|
||||
*out_function = entry->function;
|
||||
return true;
|
||||
return entry->function;
|
||||
} else {
|
||||
// Failed or bad state.
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool Processor::LookupFunctionInfo(uint32_t address,
|
||||
FunctionInfo** out_symbol_info) {
|
||||
*out_symbol_info = nullptr;
|
||||
|
||||
Function* Processor::LookupFunction(uint32_t address) {
|
||||
// TODO(benvanik): fast reject invalid addresses/log errors.
|
||||
|
||||
// Find the module that contains the address.
|
||||
|
@ -221,64 +224,57 @@ bool Processor::LookupFunctionInfo(uint32_t address,
|
|||
}
|
||||
if (!code_module) {
|
||||
// No module found that could contain the address.
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return LookupFunctionInfo(code_module, address, out_symbol_info);
|
||||
return LookupFunction(code_module, address);
|
||||
}
|
||||
|
||||
bool Processor::LookupFunctionInfo(Module* module, uint32_t address,
|
||||
FunctionInfo** out_symbol_info) {
|
||||
Function* Processor::LookupFunction(Module* module, uint32_t address) {
|
||||
// Atomic create/lookup symbol in module.
|
||||
// If we get back the NEW flag we must declare it now.
|
||||
FunctionInfo* symbol_info = nullptr;
|
||||
SymbolStatus symbol_status = module->DeclareFunction(address, &symbol_info);
|
||||
if (symbol_status == SymbolStatus::kNew) {
|
||||
Function* function = nullptr;
|
||||
auto symbol_status = module->DeclareFunction(address, &function);
|
||||
if (symbol_status == Symbol::Status::kNew) {
|
||||
// Symbol is undeclared, so declare now.
|
||||
if (!frontend_->DeclareFunction(symbol_info)) {
|
||||
symbol_info->set_status(SymbolStatus::kFailed);
|
||||
return false;
|
||||
assert_true(function->is_guest());
|
||||
if (!frontend_->DeclareFunction(static_cast<GuestFunction*>(function))) {
|
||||
function->set_status(Symbol::Status::kFailed);
|
||||
return nullptr;
|
||||
}
|
||||
symbol_info->set_status(SymbolStatus::kDeclared);
|
||||
function->set_status(Symbol::Status::kDeclared);
|
||||
}
|
||||
|
||||
*out_symbol_info = symbol_info;
|
||||
return true;
|
||||
return function;
|
||||
}
|
||||
|
||||
bool Processor::DemandFunction(FunctionInfo* symbol_info,
|
||||
Function** out_function) {
|
||||
*out_function = nullptr;
|
||||
|
||||
bool Processor::DemandFunction(Function* function) {
|
||||
// Lock function for generation. If it's already being generated
|
||||
// by another thread this will block and return DECLARED.
|
||||
Module* module = symbol_info->module();
|
||||
SymbolStatus symbol_status = module->DefineFunction(symbol_info);
|
||||
if (symbol_status == SymbolStatus::kNew) {
|
||||
auto module = function->module();
|
||||
auto symbol_status = module->DefineFunction(function);
|
||||
if (symbol_status == Symbol::Status::kNew) {
|
||||
// Symbol is undefined, so define now.
|
||||
Function* function = nullptr;
|
||||
if (!frontend_->DefineFunction(symbol_info, debug_info_flags_, &function)) {
|
||||
symbol_info->set_status(SymbolStatus::kFailed);
|
||||
assert_true(function->is_guest());
|
||||
if (!frontend_->DefineFunction(static_cast<GuestFunction*>(function),
|
||||
debug_info_flags_)) {
|
||||
function->set_status(Symbol::Status::kFailed);
|
||||
return false;
|
||||
}
|
||||
symbol_info->set_function(function);
|
||||
|
||||
// Before we give the symbol back to the rest, let the debugger know.
|
||||
if (debugger_) {
|
||||
debugger_->OnFunctionDefined(symbol_info, function);
|
||||
debugger_->OnFunctionDefined(function);
|
||||
}
|
||||
|
||||
symbol_info->set_status(SymbolStatus::kDefined);
|
||||
symbol_status = symbol_info->status();
|
||||
function->set_status(Symbol::Status::kDefined);
|
||||
symbol_status = function->status();
|
||||
}
|
||||
|
||||
if (symbol_status == SymbolStatus::kFailed) {
|
||||
if (symbol_status == Symbol::Status::kFailed) {
|
||||
// Symbol likely failed.
|
||||
return false;
|
||||
}
|
||||
|
||||
*out_function = symbol_info->function();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -286,8 +282,8 @@ bool Processor::Execute(ThreadState* thread_state, uint32_t address) {
|
|||
SCOPE_profile_cpu_f("cpu");
|
||||
|
||||
// Attempt to get the function.
|
||||
Function* fn;
|
||||
if (!ResolveFunction(address, &fn)) {
|
||||
auto function = ResolveFunction(address);
|
||||
if (!function) {
|
||||
// Symbol not found in any module.
|
||||
XELOGCPU("Execute(%.8X): failed to find function", address);
|
||||
return false;
|
||||
|
@ -305,7 +301,7 @@ bool Processor::Execute(ThreadState* thread_state, uint32_t address) {
|
|||
context->lr = 0xBCBCBCBC;
|
||||
|
||||
// Execute the function.
|
||||
auto result = fn->Call(thread_state, uint32_t(context->lr));
|
||||
auto result = function->Call(thread_state, uint32_t(context->lr));
|
||||
|
||||
context->lr = previous_lr;
|
||||
context->r[1] += 64 + 112;
|
||||
|
|
|
@ -69,17 +69,16 @@ class Processor {
|
|||
std::vector<Module*> GetModules();
|
||||
|
||||
Module* builtin_module() const { return builtin_module_; }
|
||||
FunctionInfo* DefineBuiltin(const std::string& name,
|
||||
FunctionInfo::BuiltinHandler handler, void* arg0,
|
||||
void* arg1);
|
||||
Function* DefineBuiltin(const std::string& name,
|
||||
BuiltinFunction::Handler handler, void* arg0,
|
||||
void* arg1);
|
||||
|
||||
Function* QueryFunction(uint32_t address);
|
||||
std::vector<Function*> FindFunctionsWithAddress(uint32_t address);
|
||||
|
||||
bool LookupFunctionInfo(uint32_t address, FunctionInfo** out_symbol_info);
|
||||
bool LookupFunctionInfo(Module* module, uint32_t address,
|
||||
FunctionInfo** out_symbol_info);
|
||||
bool ResolveFunction(uint32_t address, Function** out_function);
|
||||
Function* LookupFunction(uint32_t address);
|
||||
Function* LookupFunction(Module* module, uint32_t address);
|
||||
Function* ResolveFunction(uint32_t address);
|
||||
|
||||
bool Execute(ThreadState* thread_state, uint32_t address);
|
||||
uint64_t Execute(ThreadState* thread_state, uint32_t address, uint64_t args[],
|
||||
|
@ -89,7 +88,7 @@ class Processor {
|
|||
void LowerIrql(Irql old_value);
|
||||
|
||||
private:
|
||||
bool DemandFunction(FunctionInfo* symbol_info, Function** out_function);
|
||||
bool DemandFunction(Function* function);
|
||||
|
||||
Memory* memory_ = nullptr;
|
||||
debug::Debugger* debugger_ = nullptr;
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
@ -60,5 +62,10 @@ bool RawModule::ContainsAddress(uint32_t address) {
|
|||
return address >= low_address_ && address < high_address_;
|
||||
}
|
||||
|
||||
std::unique_ptr<Function> RawModule::CreateFunction(uint32_t address) {
|
||||
return std::unique_ptr<Function>(
|
||||
processor_->backend()->CreateGuestFunction(this, address));
|
||||
}
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
|
|
@ -28,6 +28,9 @@ class RawModule : public Module {
|
|||
|
||||
bool ContainsAddress(uint32_t address) override;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Function> CreateFunction(uint32_t address) override;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
uint32_t base_address_;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
#include "xenia/cpu/function.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
@ -49,7 +49,7 @@ struct StackFrame {
|
|||
} host_symbol;
|
||||
// Contains symbol information for kGuest frames.
|
||||
struct {
|
||||
FunctionInfo* function_info;
|
||||
Function* function;
|
||||
} guest_symbol;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -205,19 +205,21 @@ class Win32StackWalker : public StackWalker {
|
|||
if (frame.host_pc >= code_cache_min_ && frame.host_pc < code_cache_max_) {
|
||||
// Guest symbol, so we can look it up quickly in the code cache.
|
||||
frame.type = StackFrame::Type::kGuest;
|
||||
auto function_info = code_cache_->LookupFunction(frame.host_pc);
|
||||
if (function_info) {
|
||||
frame.guest_symbol.function_info = function_info;
|
||||
auto function = code_cache_->LookupFunction(frame.host_pc);
|
||||
if (function) {
|
||||
frame.guest_symbol.function = function;
|
||||
// Figure out where in guest code we are by looking up the
|
||||
// displacement in x64 from the JIT'ed code start to the PC.
|
||||
uint32_t host_displacement =
|
||||
uint32_t(frame.host_pc) -
|
||||
uint32_t(uint64_t(function_info->function()->machine_code()));
|
||||
auto entry =
|
||||
function_info->function()->LookupCodeOffset(host_displacement);
|
||||
frame.guest_pc = entry->source_offset;
|
||||
if (function->is_guest()) {
|
||||
auto guest_function = static_cast<GuestFunction*>(function);
|
||||
uint32_t host_displacement =
|
||||
uint32_t(frame.host_pc) -
|
||||
uint32_t(uint64_t(guest_function->machine_code()));
|
||||
auto entry = guest_function->LookupCodeOffset(host_displacement);
|
||||
frame.guest_pc = entry->source_offset;
|
||||
}
|
||||
} else {
|
||||
frame.guest_symbol.function_info = nullptr;
|
||||
frame.guest_symbol.function = nullptr;
|
||||
}
|
||||
} else {
|
||||
// Host symbol, which means either emulator or system.
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* 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_CPU_SYMBOL_INFO_H_
|
||||
#define XENIA_CPU_SYMBOL_INFO_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
class Module;
|
||||
|
||||
class Symbol {
|
||||
public:
|
||||
enum class Type {
|
||||
kFunction,
|
||||
kVariable,
|
||||
};
|
||||
|
||||
enum class Status {
|
||||
kNew,
|
||||
kDeclaring,
|
||||
kDeclared,
|
||||
kDefining,
|
||||
kDefined,
|
||||
kFailed,
|
||||
};
|
||||
|
||||
Symbol(Type type, Module* module, uint32_t address)
|
||||
: type_(type), module_(module), address_(address) {}
|
||||
virtual ~Symbol() = default;
|
||||
|
||||
Type type() const { return type_; }
|
||||
Module* module() const { return module_; }
|
||||
Status status() const { return status_; }
|
||||
void set_status(Status value) { status_ = value; }
|
||||
uint32_t address() const { return address_; }
|
||||
|
||||
const std::string& name() const { return name_; }
|
||||
void set_name(const std::string& value) { name_ = value; }
|
||||
|
||||
protected:
|
||||
Type type_ = Type::kVariable;
|
||||
Module* module_ = nullptr;
|
||||
Status status_ = Status::kDefining;
|
||||
uint32_t address_ = 0;
|
||||
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_SYMBOL_INFO_H_
|
|
@ -1,55 +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/cpu/symbol_info.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
SymbolInfo::SymbolInfo(SymbolType type, Module* module, uint32_t address)
|
||||
: type_(type),
|
||||
module_(module),
|
||||
status_(SymbolStatus::kDefining),
|
||||
address_(address),
|
||||
name_("") {}
|
||||
|
||||
SymbolInfo::~SymbolInfo() = default;
|
||||
|
||||
FunctionInfo::FunctionInfo(Module* module, uint32_t address)
|
||||
: SymbolInfo(SymbolType::kFunction, module, address),
|
||||
end_address_(0),
|
||||
behavior_(FunctionBehavior::kDefault),
|
||||
function_(nullptr) {
|
||||
std::memset(&extern_info_, 0, sizeof(extern_info_));
|
||||
}
|
||||
|
||||
FunctionInfo::~FunctionInfo() = default;
|
||||
|
||||
void FunctionInfo::SetupBuiltin(BuiltinHandler handler, void* arg0,
|
||||
void* arg1) {
|
||||
behavior_ = FunctionBehavior::kBuiltin;
|
||||
builtin_info_.handler = handler;
|
||||
builtin_info_.arg0 = arg0;
|
||||
builtin_info_.arg1 = arg1;
|
||||
}
|
||||
|
||||
void FunctionInfo::SetupExtern(ExternHandler handler) {
|
||||
behavior_ = FunctionBehavior::kExtern;
|
||||
extern_info_.handler = handler;
|
||||
}
|
||||
|
||||
VariableInfo::VariableInfo(Module* module, uint32_t address)
|
||||
: SymbolInfo(SymbolType::kVariable, module, address) {}
|
||||
|
||||
VariableInfo::~VariableInfo() = default;
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
|
@ -1,122 +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_CPU_SYMBOL_INFO_H_
|
||||
#define XENIA_CPU_SYMBOL_INFO_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/cpu/frontend/ppc_context.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
class Function;
|
||||
class Module;
|
||||
|
||||
enum class SymbolType {
|
||||
kFunction,
|
||||
kVariable,
|
||||
};
|
||||
|
||||
enum class SymbolStatus {
|
||||
kNew,
|
||||
kDeclaring,
|
||||
kDeclared,
|
||||
kDefining,
|
||||
kDefined,
|
||||
kFailed,
|
||||
};
|
||||
|
||||
class SymbolInfo {
|
||||
public:
|
||||
SymbolInfo(SymbolType type, Module* module, uint32_t address);
|
||||
virtual ~SymbolInfo();
|
||||
|
||||
SymbolType type() const { return type_; }
|
||||
Module* module() const { return module_; }
|
||||
SymbolStatus status() const { return status_; }
|
||||
void set_status(SymbolStatus value) { status_ = value; }
|
||||
uint32_t address() const { return address_; }
|
||||
|
||||
const std::string& name() const { return name_; }
|
||||
void set_name(const std::string& value) { name_ = value; }
|
||||
|
||||
protected:
|
||||
SymbolType type_;
|
||||
Module* module_;
|
||||
SymbolStatus status_;
|
||||
uint32_t address_;
|
||||
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
enum class FunctionBehavior {
|
||||
kDefault = 0,
|
||||
kProlog,
|
||||
kEpilog,
|
||||
kEpilogReturn,
|
||||
kBuiltin,
|
||||
kExtern,
|
||||
};
|
||||
|
||||
class FunctionInfo : public SymbolInfo {
|
||||
public:
|
||||
FunctionInfo(Module* module, uint32_t address);
|
||||
~FunctionInfo() override;
|
||||
|
||||
bool has_end_address() const { return end_address_ > 0; }
|
||||
uint32_t end_address() const { return end_address_; }
|
||||
void set_end_address(uint32_t value) { end_address_ = value; }
|
||||
|
||||
FunctionBehavior behavior() const { return behavior_; }
|
||||
void set_behavior(FunctionBehavior value) { behavior_ = value; }
|
||||
|
||||
Function* function() const { return function_; }
|
||||
void set_function(Function* value) { function_ = value; }
|
||||
|
||||
typedef void (*BuiltinHandler)(frontend::PPCContext* ppc_context, void* arg0,
|
||||
void* arg1);
|
||||
void SetupBuiltin(BuiltinHandler handler, void* arg0, void* arg1);
|
||||
BuiltinHandler builtin_handler() const { return builtin_info_.handler; }
|
||||
void* builtin_arg0() const { return builtin_info_.arg0; }
|
||||
void* builtin_arg1() const { return builtin_info_.arg1; }
|
||||
|
||||
typedef void (*ExternHandler)(frontend::PPCContext* ppc_context,
|
||||
kernel::KernelState* kernel_state);
|
||||
void SetupExtern(ExternHandler handler);
|
||||
ExternHandler extern_handler() const { return extern_info_.handler; }
|
||||
|
||||
private:
|
||||
uint32_t end_address_;
|
||||
FunctionBehavior behavior_;
|
||||
Function* function_;
|
||||
union {
|
||||
struct {
|
||||
ExternHandler handler;
|
||||
} extern_info_;
|
||||
struct {
|
||||
BuiltinHandler handler;
|
||||
void* arg0;
|
||||
void* arg1;
|
||||
} builtin_info_;
|
||||
};
|
||||
};
|
||||
|
||||
class VariableInfo : public SymbolInfo {
|
||||
public:
|
||||
VariableInfo(Module* module, uint32_t address);
|
||||
~VariableInfo() override;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_CPU_SYMBOL_INFO_H_
|
|
@ -71,29 +71,34 @@ bool TestModule::ContainsAddress(uint32_t address) {
|
|||
return contains_address_(address);
|
||||
}
|
||||
|
||||
SymbolStatus TestModule::DeclareFunction(uint32_t address,
|
||||
FunctionInfo** out_symbol_info) {
|
||||
SymbolStatus status = Module::DeclareFunction(address, out_symbol_info);
|
||||
if (status == SymbolStatus::kNew) {
|
||||
auto symbol_info = *out_symbol_info;
|
||||
std::unique_ptr<Function> TestModule::CreateFunction(uint32_t address) {
|
||||
return std::unique_ptr<Function>(
|
||||
processor_->backend()->CreateGuestFunction(this, address));
|
||||
}
|
||||
|
||||
Symbol::Status TestModule::DeclareFunction(uint32_t address,
|
||||
Function** out_function) {
|
||||
Symbol::Status status = Module::DeclareFunction(address, out_function);
|
||||
if (status == Symbol::Status::kNew) {
|
||||
auto function = static_cast<GuestFunction*>(*out_function);
|
||||
|
||||
// Reset() all caching when we leave.
|
||||
xe::make_reset_scope(compiler_);
|
||||
xe::make_reset_scope(assembler_);
|
||||
|
||||
if (!generate_(*builder_.get())) {
|
||||
symbol_info->set_status(SymbolStatus::kFailed);
|
||||
return SymbolStatus::kFailed;
|
||||
function->set_status(Symbol::Status::kFailed);
|
||||
return Symbol::Status::kFailed;
|
||||
}
|
||||
|
||||
// Run optimization passes.
|
||||
compiler_->Compile(builder_.get());
|
||||
|
||||
Function* fn = nullptr;
|
||||
assembler_->Assemble(symbol_info, builder_.get(), 0, nullptr, &fn);
|
||||
// Assemble the function.
|
||||
assembler_->Assemble(function, builder_.get(), 0, nullptr);
|
||||
|
||||
symbol_info->set_function(fn);
|
||||
status = SymbolStatus::kDefined;
|
||||
symbol_info->set_status(status);
|
||||
status = Symbol::Status::kDefined;
|
||||
function->set_status(status);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
|
|
@ -33,8 +33,11 @@ class TestModule : public Module {
|
|||
|
||||
bool ContainsAddress(uint32_t address) override;
|
||||
|
||||
SymbolStatus DeclareFunction(uint32_t address,
|
||||
FunctionInfo** out_symbol_info) override;
|
||||
Symbol::Status DeclareFunction(uint32_t address,
|
||||
Function** out_symbol) override;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Function> CreateFunction(uint32_t address) override;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
|
|
|
@ -65,8 +65,7 @@ class TestFunction {
|
|||
void Run(std::function<void(PPCContext*)> pre_call,
|
||||
std::function<void(PPCContext*)> post_call) {
|
||||
for (auto& processor : processors) {
|
||||
xe::cpu::Function* fn;
|
||||
processor->ResolveFunction(0x80000000, &fn);
|
||||
auto fn = processor->ResolveFunction(0x80000000);
|
||||
|
||||
uint32_t stack_size = 64 * 1024;
|
||||
uint32_t stack_address = memory_size - stack_size;
|
||||
|
|
|
@ -375,12 +375,12 @@ bool XexModule::SetupLibraryImports(const char* name,
|
|||
}
|
||||
|
||||
// Setup a variable and define it.
|
||||
VariableInfo* var_info;
|
||||
Symbol* var_info;
|
||||
DeclareVariable(record_addr, &var_info);
|
||||
var_info->set_name(import_name.GetString());
|
||||
var_info->set_status(SymbolStatus::kDeclared);
|
||||
var_info->set_status(Symbol::Status::kDeclared);
|
||||
DefineVariable(var_info);
|
||||
var_info->set_status(SymbolStatus::kDefined);
|
||||
var_info->set_status(Symbol::Status::kDefined);
|
||||
} else if (record_type == 1) {
|
||||
// Thunk.
|
||||
if (kernel_export) {
|
||||
|
@ -389,10 +389,10 @@ bool XexModule::SetupLibraryImports(const char* name,
|
|||
import_name.AppendFormat("__%s_%.3X", libbasename.c_str(), ordinal);
|
||||
}
|
||||
|
||||
FunctionInfo* fn_info;
|
||||
DeclareFunction(record_addr, &fn_info);
|
||||
fn_info->set_end_address(record_addr + 16 - 4);
|
||||
fn_info->set_name(import_name.GetString());
|
||||
Function* function;
|
||||
DeclareFunction(record_addr, &function);
|
||||
function->set_end_address(record_addr + 16 - 4);
|
||||
function->set_name(import_name.GetString());
|
||||
|
||||
if (kernel_export) {
|
||||
// On load we have something like this in memory:
|
||||
|
@ -415,14 +415,14 @@ bool XexModule::SetupLibraryImports(const char* name,
|
|||
xe::store_and_swap<uint32_t>(p + 0x8, 0x60000000);
|
||||
xe::store_and_swap<uint32_t>(p + 0xC, 0x60000000);
|
||||
|
||||
FunctionInfo::ExternHandler handler = 0;
|
||||
GuestFunction::ExternHandler handler = nullptr;
|
||||
if (kernel_export) {
|
||||
if (kernel_export->function_data.trampoline) {
|
||||
handler = (FunctionInfo::ExternHandler)
|
||||
handler = (GuestFunction::ExternHandler)
|
||||
kernel_export->function_data.trampoline;
|
||||
} else {
|
||||
handler =
|
||||
(FunctionInfo::ExternHandler)kernel_export->function_data.shim;
|
||||
(GuestFunction::ExternHandler)kernel_export->function_data.shim;
|
||||
}
|
||||
} else {
|
||||
XELOGW("WARNING: Imported kernel function %s is unimplemented!",
|
||||
|
@ -430,7 +430,7 @@ bool XexModule::SetupLibraryImports(const char* name,
|
|||
handler = UndefinedImport;
|
||||
}
|
||||
|
||||
fn_info->SetupExtern(handler);
|
||||
static_cast<GuestFunction*>(function)->SetupExtern(handler);
|
||||
} else if (user_export_addr) {
|
||||
// Rewrite PPC code to set r11 to the target address
|
||||
// So we'll have:
|
||||
|
@ -458,7 +458,7 @@ bool XexModule::SetupLibraryImports(const char* name,
|
|||
xe::store_and_swap<uint32_t>(p + 0xC, 0x60000000);
|
||||
}
|
||||
|
||||
fn_info->set_status(SymbolStatus::kDeclared);
|
||||
function->set_status(Symbol::Status::kDeclared);
|
||||
} else {
|
||||
// Bad.
|
||||
assert_always();
|
||||
|
@ -472,6 +472,11 @@ bool XexModule::ContainsAddress(uint32_t address) {
|
|||
return address >= low_address_ && address < high_address_;
|
||||
}
|
||||
|
||||
std::unique_ptr<Function> XexModule::CreateFunction(uint32_t address) {
|
||||
return std::unique_ptr<Function>(
|
||||
processor_->backend()->CreateGuestFunction(this, address));
|
||||
}
|
||||
|
||||
bool XexModule::FindSaveRest() {
|
||||
// Special stack save/restore functions.
|
||||
// http://research.microsoft.com/en-us/um/redmond/projects/invisible/src/crt/md/ppc/xxx.s.htm
|
||||
|
@ -677,27 +682,27 @@ bool XexModule::FindSaveRest() {
|
|||
uint32_t address = gplr_start;
|
||||
for (int n = 14; n <= 31; n++) {
|
||||
snprintf(name, xe::countof(name), "__savegprlr_%d", n);
|
||||
FunctionInfo* symbol_info;
|
||||
DeclareFunction(address, &symbol_info);
|
||||
symbol_info->set_end_address(address + (31 - n) * 4 + 2 * 4);
|
||||
symbol_info->set_name(name);
|
||||
Function* function;
|
||||
DeclareFunction(address, &function);
|
||||
function->set_end_address(address + (31 - n) * 4 + 2 * 4);
|
||||
function->set_name(name);
|
||||
// TODO(benvanik): set type fn->type = FunctionSymbol::User;
|
||||
// TODO(benvanik): set flags fn->flags |= FunctionSymbol::kFlagSaveGprLr;
|
||||
symbol_info->set_behavior(FunctionBehavior::kProlog);
|
||||
symbol_info->set_status(SymbolStatus::kDeclared);
|
||||
function->set_behavior(Function::Behavior::kProlog);
|
||||
function->set_status(Symbol::Status::kDeclared);
|
||||
address += 4;
|
||||
}
|
||||
address = gplr_start + 20 * 4;
|
||||
for (int n = 14; n <= 31; n++) {
|
||||
snprintf(name, xe::countof(name), "__restgprlr_%d", n);
|
||||
FunctionInfo* symbol_info;
|
||||
DeclareFunction(address, &symbol_info);
|
||||
symbol_info->set_end_address(address + (31 - n) * 4 + 3 * 4);
|
||||
symbol_info->set_name(name);
|
||||
Function* function;
|
||||
DeclareFunction(address, &function);
|
||||
function->set_end_address(address + (31 - n) * 4 + 3 * 4);
|
||||
function->set_name(name);
|
||||
// TODO(benvanik): set type fn->type = FunctionSymbol::User;
|
||||
// TODO(benvanik): set flags fn->flags |= FunctionSymbol::kFlagRestGprLr;
|
||||
symbol_info->set_behavior(FunctionBehavior::kEpilogReturn);
|
||||
symbol_info->set_status(SymbolStatus::kDeclared);
|
||||
function->set_behavior(Function::Behavior::kEpilogReturn);
|
||||
function->set_status(Symbol::Status::kDeclared);
|
||||
address += 4;
|
||||
}
|
||||
}
|
||||
|
@ -705,27 +710,27 @@ bool XexModule::FindSaveRest() {
|
|||
uint32_t address = fpr_start;
|
||||
for (int n = 14; n <= 31; n++) {
|
||||
snprintf(name, xe::countof(name), "__savefpr_%d", n);
|
||||
FunctionInfo* symbol_info;
|
||||
DeclareFunction(address, &symbol_info);
|
||||
symbol_info->set_end_address(address + (31 - n) * 4 + 1 * 4);
|
||||
symbol_info->set_name(name);
|
||||
Function* function;
|
||||
DeclareFunction(address, &function);
|
||||
function->set_end_address(address + (31 - n) * 4 + 1 * 4);
|
||||
function->set_name(name);
|
||||
// TODO(benvanik): set type fn->type = FunctionSymbol::User;
|
||||
// TODO(benvanik): set flags fn->flags |= FunctionSymbol::kFlagSaveFpr;
|
||||
symbol_info->set_behavior(FunctionBehavior::kProlog);
|
||||
symbol_info->set_status(SymbolStatus::kDeclared);
|
||||
function->set_behavior(Function::Behavior::kProlog);
|
||||
function->set_status(Symbol::Status::kDeclared);
|
||||
address += 4;
|
||||
}
|
||||
address = fpr_start + (18 * 4) + (1 * 4);
|
||||
for (int n = 14; n <= 31; n++) {
|
||||
snprintf(name, xe::countof(name), "__restfpr_%d", n);
|
||||
FunctionInfo* symbol_info;
|
||||
DeclareFunction(address, &symbol_info);
|
||||
symbol_info->set_end_address(address + (31 - n) * 4 + 1 * 4);
|
||||
symbol_info->set_name(name);
|
||||
Function* function;
|
||||
DeclareFunction(address, &function);
|
||||
function->set_end_address(address + (31 - n) * 4 + 1 * 4);
|
||||
function->set_name(name);
|
||||
// TODO(benvanik): set type fn->type = FunctionSymbol::User;
|
||||
// TODO(benvanik): set flags fn->flags |= FunctionSymbol::kFlagRestFpr;
|
||||
symbol_info->set_behavior(FunctionBehavior::kEpilog);
|
||||
symbol_info->set_status(SymbolStatus::kDeclared);
|
||||
function->set_behavior(Function::Behavior::kEpilog);
|
||||
function->set_status(Symbol::Status::kDeclared);
|
||||
address += 4;
|
||||
}
|
||||
}
|
||||
|
@ -738,49 +743,49 @@ bool XexModule::FindSaveRest() {
|
|||
uint32_t address = vmx_start;
|
||||
for (int n = 14; n <= 31; n++) {
|
||||
snprintf(name, xe::countof(name), "__savevmx_%d", n);
|
||||
FunctionInfo* symbol_info;
|
||||
DeclareFunction(address, &symbol_info);
|
||||
symbol_info->set_name(name);
|
||||
Function* function;
|
||||
DeclareFunction(address, &function);
|
||||
function->set_name(name);
|
||||
// TODO(benvanik): set type fn->type = FunctionSymbol::User;
|
||||
// TODO(benvanik): set flags fn->flags |= FunctionSymbol::kFlagSaveVmx;
|
||||
symbol_info->set_behavior(FunctionBehavior::kProlog);
|
||||
symbol_info->set_status(SymbolStatus::kDeclared);
|
||||
function->set_behavior(Function::Behavior::kProlog);
|
||||
function->set_status(Symbol::Status::kDeclared);
|
||||
address += 2 * 4;
|
||||
}
|
||||
address += 4;
|
||||
for (int n = 64; n <= 127; n++) {
|
||||
snprintf(name, xe::countof(name), "__savevmx_%d", n);
|
||||
FunctionInfo* symbol_info;
|
||||
DeclareFunction(address, &symbol_info);
|
||||
symbol_info->set_name(name);
|
||||
Function* function;
|
||||
DeclareFunction(address, &function);
|
||||
function->set_name(name);
|
||||
// TODO(benvanik): set type fn->type = FunctionSymbol::User;
|
||||
// TODO(benvanik): set flags fn->flags |= FunctionSymbol::kFlagSaveVmx;
|
||||
symbol_info->set_behavior(FunctionBehavior::kProlog);
|
||||
symbol_info->set_status(SymbolStatus::kDeclared);
|
||||
function->set_behavior(Function::Behavior::kProlog);
|
||||
function->set_status(Symbol::Status::kDeclared);
|
||||
address += 2 * 4;
|
||||
}
|
||||
address = vmx_start + (18 * 2 * 4) + (1 * 4) + (64 * 2 * 4) + (1 * 4);
|
||||
for (int n = 14; n <= 31; n++) {
|
||||
snprintf(name, xe::countof(name), "__restvmx_%d", n);
|
||||
FunctionInfo* symbol_info;
|
||||
DeclareFunction(address, &symbol_info);
|
||||
symbol_info->set_name(name);
|
||||
Function* function;
|
||||
DeclareFunction(address, &function);
|
||||
function->set_name(name);
|
||||
// TODO(benvanik): set type fn->type = FunctionSymbol::User;
|
||||
// TODO(benvanik): set flags fn->flags |= FunctionSymbol::kFlagRestVmx;
|
||||
symbol_info->set_behavior(FunctionBehavior::kEpilog);
|
||||
symbol_info->set_status(SymbolStatus::kDeclared);
|
||||
function->set_behavior(Function::Behavior::kEpilog);
|
||||
function->set_status(Symbol::Status::kDeclared);
|
||||
address += 2 * 4;
|
||||
}
|
||||
address += 4;
|
||||
for (int n = 64; n <= 127; n++) {
|
||||
snprintf(name, xe::countof(name), "__restvmx_%d", n);
|
||||
FunctionInfo* symbol_info;
|
||||
DeclareFunction(address, &symbol_info);
|
||||
symbol_info->set_name(name);
|
||||
Function* function;
|
||||
DeclareFunction(address, &function);
|
||||
function->set_name(name);
|
||||
// TODO(benvanik): set type fn->type = FunctionSymbol::User;
|
||||
// TODO(benvanik): set flags fn->flags |= FunctionSymbol::kFlagRestVmx;
|
||||
symbol_info->set_behavior(FunctionBehavior::kEpilog);
|
||||
symbol_info->set_status(SymbolStatus::kDeclared);
|
||||
function->set_behavior(Function::Behavior::kEpilog);
|
||||
function->set_status(Symbol::Status::kDeclared);
|
||||
address += 2 * 4;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,12 +77,14 @@ class XexModule : public xe::cpu::Module {
|
|||
|
||||
bool ContainsAddress(uint32_t address) override;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<Function> CreateFunction(uint32_t address) override;
|
||||
|
||||
private:
|
||||
bool SetupLibraryImports(const char* name,
|
||||
const xex2_import_library* library);
|
||||
bool FindSaveRest();
|
||||
|
||||
private:
|
||||
Processor* processor_ = nullptr;
|
||||
kernel::KernelState* kernel_state_ = nullptr;
|
||||
std::string name_;
|
||||
|
|
|
@ -308,10 +308,10 @@ bool DebugServer::ProcessPacket(const proto::Packet* packet) {
|
|||
frame_body->host_function_address = frame.host_symbol.address;
|
||||
frame_body->guest_pc = frame.guest_pc;
|
||||
frame_body->guest_function_address = 0;
|
||||
auto function_info = frame.guest_symbol.function_info;
|
||||
if (frame.type == cpu::StackFrame::Type::kGuest && function_info) {
|
||||
frame_body->guest_function_address = function_info->address();
|
||||
std::strncpy(frame_body->name, function_info->name().c_str(),
|
||||
auto function = frame.guest_symbol.function;
|
||||
if (frame.type == cpu::StackFrame::Type::kGuest && function) {
|
||||
frame_body->guest_function_address = function->address();
|
||||
std::strncpy(frame_body->name, function->name().c_str(),
|
||||
xe::countof(frame_body->name));
|
||||
} else {
|
||||
std::strncpy(frame_body->name, frame.host_symbol.name,
|
||||
|
|
|
@ -169,10 +169,9 @@ void Debugger::DumpThreadStacks() {
|
|||
XELOGI(" %.2lld %.16llX %s", count - i - 1, frame.host_pc,
|
||||
frame.host_symbol.name);
|
||||
} else {
|
||||
auto function_info = frame.guest_symbol.function_info;
|
||||
auto function = frame.guest_symbol.function;
|
||||
XELOGI(" %.2lld %.16llX %.8X %s", count - i - 1, frame.host_pc,
|
||||
frame.guest_pc,
|
||||
function_info ? function_info->name().c_str() : "?");
|
||||
frame.guest_pc, function ? function->name().c_str() : "?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -190,12 +189,13 @@ int Debugger::AddBreakpoint(Breakpoint* breakpoint) {
|
|||
auto fns =
|
||||
emulator_->processor()->FindFunctionsWithAddress(breakpoint->address());
|
||||
|
||||
// TODO(benvanik): breakpoints.
|
||||
// Add.
|
||||
for (auto fn : fns) {
|
||||
if (fn->AddBreakpoint(breakpoint)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// for (auto fn : fns) {
|
||||
// if (fn->AddBreakpoint(breakpoint)) {
|
||||
// return 1;
|
||||
// }
|
||||
//}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -226,9 +226,10 @@ int Debugger::RemoveBreakpoint(Breakpoint* breakpoint) {
|
|||
emulator_->processor()->FindFunctionsWithAddress(breakpoint->address());
|
||||
|
||||
// Remove.
|
||||
for (auto fn : fns) {
|
||||
// TODO(benvanik): breakpoints.
|
||||
/*for (auto fn : fns) {
|
||||
fn->RemoveBreakpoint(breakpoint);
|
||||
}
|
||||
}*/
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -322,31 +323,30 @@ void Debugger::OnThreadDestroyed(xe::kernel::XThread* thread) {
|
|||
// TODO(benvanik): notify transports.
|
||||
}
|
||||
|
||||
void Debugger::OnFunctionDefined(cpu::FunctionInfo* symbol_info,
|
||||
cpu::Function* function) {
|
||||
void Debugger::OnFunctionDefined(cpu::Function* function) {
|
||||
// TODO(benvanik): breakpoints?
|
||||
// Man, I'd love not to take this lock.
|
||||
std::vector<Breakpoint*> breakpoints;
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
for (uint32_t address = symbol_info->address();
|
||||
address <= symbol_info->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);
|
||||
}
|
||||
}
|
||||
// std::vector<Breakpoint*> breakpoints;
|
||||
//{
|
||||
// std::lock_guard<std::recursive_mutex> lock(mutex_);
|
||||
// for (uint32_t address = function->address();
|
||||
// address <= function->end_address(); address += 4) {
|
||||
// auto range = breakpoints_.equal_range(address);
|
||||
// if (range.first == range.second) {
|
||||
// continue;
|
||||
// }
|
||||
// for (auto it = range.first; it != range.second; ++it) {
|
||||
// Breakpoint* breakpoint = it->second;
|
||||
// breakpoints.push_back(breakpoint);
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
// if (breakpoints.size()) {
|
||||
// // Breakpoints to add!
|
||||
// for (auto breakpoint : breakpoints) {
|
||||
// function->AddBreakpoint(breakpoint);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
void Debugger::OnBreakpointHit(xe::kernel::XThread* thread,
|
||||
|
|
|
@ -83,8 +83,7 @@ class Debugger {
|
|||
void OnThreadExit(xe::kernel::XThread* thread);
|
||||
void OnThreadDestroyed(xe::kernel::XThread* thread);
|
||||
|
||||
void OnFunctionDefined(cpu::FunctionInfo* symbol_info,
|
||||
cpu::Function* function);
|
||||
void OnFunctionDefined(cpu::Function* function);
|
||||
|
||||
void OnBreakpointHit(xe::kernel::XThread* thread, Breakpoint* breakpoint);
|
||||
|
||||
|
|
Loading…
Reference in New Issue