Mostly working stack walking (besides issue #372).

This commit is contained in:
Ben Vanik
2015-07-29 00:15:52 -07:00
parent e01c2ac98d
commit e657276996
14 changed files with 531 additions and 23 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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();