Mostly working stack walking (besides issue #372).
This commit is contained in:
@@ -12,6 +12,8 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "xenia/cpu/symbol_info.h"
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
namespace backend {
|
||||
@@ -24,6 +26,13 @@ class CodeCache {
|
||||
virtual std::wstring file_name() const = 0;
|
||||
virtual uint32_t base_address() const = 0;
|
||||
virtual uint32_t total_size() const = 0;
|
||||
|
||||
// Finds a function based on the given host PC (that may be within a
|
||||
// function).
|
||||
virtual FunctionInfo* 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;
|
||||
};
|
||||
|
||||
} // namespace backend
|
||||
|
||||
@@ -93,8 +93,8 @@ bool X64Assembler::Assemble(FunctionInfo* symbol_info, HIRBuilder* builder,
|
||||
// Lower HIR -> x64.
|
||||
void* machine_code = nullptr;
|
||||
size_t code_size = 0;
|
||||
if (!emitter_->Emit(symbol_info->address(), builder, debug_info_flags,
|
||||
debug_info.get(), machine_code, code_size)) {
|
||||
if (!emitter_->Emit(symbol_info, builder, debug_info_flags, debug_info.get(),
|
||||
machine_code, code_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ HostToGuestThunk X64ThunkEmitter::EmitHostToGuestThunk() {
|
||||
mov(r8, qword[rsp + 8 * 3]);
|
||||
ret();
|
||||
|
||||
void* fn = Emplace(0, stack_size);
|
||||
void* fn = Emplace(stack_size);
|
||||
return (HostToGuestThunk)fn;
|
||||
}
|
||||
|
||||
@@ -228,7 +228,7 @@ GuestToHostThunk X64ThunkEmitter::EmitGuestToHostThunk() {
|
||||
mov(rdx, qword[rsp + 8 * 2]);
|
||||
ret();
|
||||
|
||||
void* fn = Emplace(0, stack_size);
|
||||
void* fn = Emplace(stack_size);
|
||||
return (HostToGuestThunk)fn;
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ ResolveFunctionThunk X64ThunkEmitter::EmitResolveFunctionThunk() {
|
||||
mov(rdx, qword[rsp + 8 * 2]);
|
||||
jmp(rax);
|
||||
|
||||
void* fn = Emplace(0, stack_size);
|
||||
void* fn = Emplace(stack_size);
|
||||
return (ResolveFunctionThunk)fn;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,9 @@ bool X64CodeCache::Initialize() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Preallocate the function map to a large, reasonable size.
|
||||
generated_code_map_.reserve(kMaximumFunctionCount);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -107,8 +110,17 @@ void X64CodeCache::CommitExecutableRange(uint32_t guest_low,
|
||||
}
|
||||
}
|
||||
|
||||
void* X64CodeCache::PlaceCode(uint32_t guest_address, void* machine_code,
|
||||
size_t code_size, size_t stack_size) {
|
||||
void* X64CodeCache::PlaceHostCode(uint32_t guest_address, void* machine_code,
|
||||
size_t code_size, size_t stack_size) {
|
||||
// Same for now. We may use different pools or whatnot later on, like when
|
||||
// we only want to place guest code in a serialized cache on disk.
|
||||
return PlaceGuestCode(guest_address, machine_code, code_size, stack_size,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void* X64CodeCache::PlaceGuestCode(uint32_t guest_address, void* machine_code,
|
||||
size_t code_size, size_t stack_size,
|
||||
FunctionInfo* 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;
|
||||
@@ -133,6 +145,13 @@ void* X64CodeCache::PlaceCode(uint32_t guest_address, void* machine_code,
|
||||
generated_code_offset_ += unwind_reservation.data_size;
|
||||
|
||||
high_mark = generated_code_offset_;
|
||||
|
||||
// Store in map. It is maintained in sorted order of host PC dependent on
|
||||
// us also being append-only.
|
||||
generated_code_map_.emplace_back(
|
||||
(uint64_t(code_address - generated_code_base_) << 32) |
|
||||
generated_code_offset_,
|
||||
function_info);
|
||||
}
|
||||
|
||||
// If we are going above the high water mark of committed memory, commit some
|
||||
@@ -201,6 +220,32 @@ uint32_t X64CodeCache::PlaceData(const void* data, size_t length) {
|
||||
return uint32_t(uintptr_t(data_address));
|
||||
}
|
||||
|
||||
FunctionInfo* 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*>),
|
||||
[](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*>*>(
|
||||
element_ptr);
|
||||
if (key < (element->first >> 32)) {
|
||||
return -1;
|
||||
} else if (key > uint32_t(element->first)) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
if (fn_entry) {
|
||||
return reinterpret_cast<const std::pair<uint64_t, FunctionInfo*>*>(fn_entry)
|
||||
->second;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace x64
|
||||
} // namespace backend
|
||||
} // namespace cpu
|
||||
|
||||
@@ -46,17 +46,31 @@ class X64CodeCache : public CodeCache {
|
||||
|
||||
void CommitExecutableRange(uint32_t guest_low, uint32_t guest_high);
|
||||
|
||||
void* PlaceCode(uint32_t guest_address, void* machine_code, size_t code_size,
|
||||
size_t stack_size);
|
||||
|
||||
void* PlaceHostCode(uint32_t guest_address, void* machine_code,
|
||||
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);
|
||||
uint32_t PlaceData(const void* data, size_t length);
|
||||
|
||||
FunctionInfo* LookupFunction(uint64_t host_pc) override;
|
||||
|
||||
protected:
|
||||
// All executable code falls within 0x80000000 to 0x9FFFFFFF, so we can
|
||||
// only map enough for lookups within that range.
|
||||
const static uint64_t kIndirectionTableBase = 0x80000000;
|
||||
const static uint64_t kIndirectionTableSize = 0x1FFFFFFF;
|
||||
// The code range is 512MB, but we know the total code games will have is
|
||||
// pretty small (dozens of mb at most) and our expansion is reasonablish
|
||||
// so 256MB should be more than enough.
|
||||
const static uint64_t kGeneratedCodeBase = 0xA0000000;
|
||||
const static uint64_t kGeneratedCodeSize = 0x0FFFFFFF;
|
||||
|
||||
// This is picked to be high enough to cover whatever we can reasonably
|
||||
// expect. If we hit issues with this it probably means some corner case
|
||||
// in analysis triggering.
|
||||
const static size_t kMaximumFunctionCount = 30000;
|
||||
|
||||
struct UnwindReservation {
|
||||
size_t data_size = 0;
|
||||
size_t table_slot = 0;
|
||||
@@ -94,6 +108,10 @@ class X64CodeCache : public CodeCache {
|
||||
size_t generated_code_offset_ = 0;
|
||||
// Current high water mark of COMMITTED code.
|
||||
std::atomic<size_t> generated_code_commit_mark_ = {0};
|
||||
// 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_;
|
||||
};
|
||||
|
||||
} // namespace x64
|
||||
|
||||
@@ -39,6 +39,8 @@ class Win32X64CodeCache : public X64CodeCache {
|
||||
|
||||
bool Initialize() override;
|
||||
|
||||
void* LookupUnwindInfo(uint64_t host_pc) override;
|
||||
|
||||
private:
|
||||
UnwindReservation RequestUnwindReservation(uint8_t* entry_address) override;
|
||||
void PlaceCode(uint32_t guest_address, void* machine_code, size_t code_size,
|
||||
@@ -48,7 +50,6 @@ class Win32X64CodeCache : public X64CodeCache {
|
||||
void InitializeUnwindEntry(uint8_t* unwind_entry_address,
|
||||
size_t unwind_table_slot, void* code_address,
|
||||
size_t code_size, size_t stack_size);
|
||||
void* LookupUnwindEntry(uintptr_t host_address);
|
||||
|
||||
// Growable function table system handle.
|
||||
void* unwind_table_handle_ = nullptr;
|
||||
@@ -84,7 +85,7 @@ bool Win32X64CodeCache::Initialize() {
|
||||
|
||||
// Compute total number of unwind entries we should allocate.
|
||||
// We don't support reallocing right now, so this should be high.
|
||||
unwind_table_.resize(30000);
|
||||
unwind_table_.resize(kMaximumFunctionCount);
|
||||
|
||||
#ifdef USE_GROWABLE_FUNCTION_TABLE
|
||||
// Create table and register with the system. It's empty now, but we'll grow
|
||||
@@ -268,9 +269,9 @@ void Win32X64CodeCache::InitializeUnwindEntry(uint8_t* unwind_entry_address,
|
||||
fn_entry.UnwindData = (DWORD)(unwind_entry_address - generated_code_base_);
|
||||
}
|
||||
|
||||
void* Win32X64CodeCache::LookupUnwindEntry(uintptr_t host_address) {
|
||||
void* fn_entry = std::bsearch(
|
||||
&host_address, unwind_table_.data(), unwind_table_count_ + 1,
|
||||
void* Win32X64CodeCache::LookupUnwindInfo(uint64_t host_pc) {
|
||||
return std::bsearch(
|
||||
&host_pc, unwind_table_.data(), unwind_table_count_ + 1,
|
||||
sizeof(RUNTIME_FUNCTION),
|
||||
[](const void* key_ptr, const void* element_ptr) {
|
||||
auto key =
|
||||
@@ -284,7 +285,6 @@ void* Win32X64CodeCache::LookupUnwindEntry(uintptr_t host_address) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
return reinterpret_cast<RUNTIME_FUNCTION*>(fn_entry);
|
||||
}
|
||||
|
||||
} // namespace x64
|
||||
|
||||
@@ -93,7 +93,7 @@ X64Emitter::X64Emitter(X64Backend* backend, XbyakAllocator* allocator)
|
||||
|
||||
X64Emitter::~X64Emitter() = default;
|
||||
|
||||
bool X64Emitter::Emit(uint32_t guest_address, HIRBuilder* builder,
|
||||
bool X64Emitter::Emit(FunctionInfo* function_info, HIRBuilder* builder,
|
||||
uint32_t debug_info_flags, DebugInfo* debug_info,
|
||||
void*& out_code_address, size_t& out_code_size) {
|
||||
SCOPE_profile_cpu_f("cpu");
|
||||
@@ -114,7 +114,7 @@ bool X64Emitter::Emit(uint32_t guest_address, HIRBuilder* builder,
|
||||
|
||||
// Copy the final code to the cache and relocate it.
|
||||
out_code_size = getSize();
|
||||
out_code_address = Emplace(guest_address, stack_size);
|
||||
out_code_address = Emplace(stack_size, function_info);
|
||||
|
||||
// Stash source map.
|
||||
if (debug_info_flags_ & DebugInfoFlags::kDebugInfoSourceMap) {
|
||||
@@ -125,14 +125,19 @@ bool X64Emitter::Emit(uint32_t guest_address, HIRBuilder* builder,
|
||||
return true;
|
||||
}
|
||||
|
||||
void* X64Emitter::Emplace(uint32_t guest_address, size_t stack_size) {
|
||||
void* X64Emitter::Emplace(size_t stack_size, FunctionInfo* function_info) {
|
||||
// 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 =
|
||||
code_cache_->PlaceCode(guest_address, top_, size_, stack_size);
|
||||
void* new_address;
|
||||
if (function_info) {
|
||||
new_address = code_cache_->PlaceGuestCode(function_info->address(), top_,
|
||||
size_, stack_size, function_info);
|
||||
} else {
|
||||
new_address = code_cache_->PlaceHostCode(0, top_, size_, stack_size);
|
||||
}
|
||||
top_ = (uint8_t*)new_address;
|
||||
ready();
|
||||
top_ = old_address;
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#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"
|
||||
|
||||
@@ -114,7 +115,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||
Processor* processor() const { return processor_; }
|
||||
X64Backend* backend() const { return backend_; }
|
||||
|
||||
bool Emit(uint32_t guest_address, hir::HIRBuilder* builder,
|
||||
bool Emit(FunctionInfo* function_info, hir::HIRBuilder* builder,
|
||||
uint32_t debug_info_flags, DebugInfo* debug_info,
|
||||
void*& out_code_address, size_t& out_code_size);
|
||||
|
||||
@@ -192,7 +193,7 @@ class X64Emitter : public Xbyak::CodeGenerator {
|
||||
size_t stack_size() const { return stack_size_; }
|
||||
|
||||
protected:
|
||||
void* Emplace(uint32_t guest_address, size_t stack_size);
|
||||
void* Emplace(size_t stack_size, FunctionInfo* function_info = nullptr);
|
||||
bool Emit(hir::HIRBuilder* builder, size_t& out_stack_size);
|
||||
void EmitGetCurrentThreadId();
|
||||
void EmitTraceUserCallReturn();
|
||||
|
||||
Reference in New Issue
Block a user